feat(engine): extend Executor with applyGeneratedColumns
This commit is contained in:
1 parent
5ee00ad3c2
commit
5b69c50fdf
1 file changed
+154
-1
@@ -473,8 +473,161 @@ class Executor(private val catalog: Catalog) : SequenceAccess {
|
|||||||
is CollateExpression -> requireNoGeneratedColumnReference(expression.operand, generatedNames, ownerColumn)
|
is CollateExpression -> requireNoGeneratedColumnReference(expression.operand, generatedNames, ownerColumn)
|
||||||
// Already rejected earlier in validateGeneratedDefinitions by checkColumns/requireNoSubquery -
|
// Already rejected earlier in validateGeneratedDefinitions by checkColumns/requireNoSubquery -
|
||||||
// kept here only so this `when` stays exhaustive.
|
// kept here only so this `when` stays exhaustive.
|
||||||
|
is FunctionExpression, is WindowExpression, is ScalarSubqueryExpression, is ExistsExpression,
|
||||||
|
is InSubqueryExpression, is NextValueExpression, is CurrentValueExpression, is MergeActionExpression,
|
||||||
|
is ParameterExpression -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recomputes every GENERATED column in [values] from its expression, in place - safe to call
|
||||||
|
* any time after every non-GENERATED column has its final value for this row (explicit
|
||||||
|
* assignment, DEFAULT, or otherwise), because [validateGeneratedDefinitions] guarantees a
|
||||||
|
* GENERATED expression only ever reads a non-GENERATED column of the same row, never another
|
||||||
|
* GENERATED one - so evaluation order among multiple GENERATED columns never matters.
|
||||||
|
*/
|
||||||
|
private fun applyGeneratedColumns(schema: TableSchema, values: MutableList<Value>) {
|
||||||
|
if (schema.columns.none { it.generated != null }) return
|
||||||
|
val evaluator = Evaluator(schema, Row(values), subqueryRunner)
|
||||||
|
for ((index, column) in schema.columns.withIndex()) {
|
||||||
|
val generated = column.generated ?: continue
|
||||||
|
values[index] = coerceForColumn(evaluator.evaluate(generated), column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Standard SQL forbids assigning a GENERATED column's value directly - it is always derived, never set. */
|
||||||
|
private fun rejectGeneratedAssignment(column: Column, explicitValueGiven: Boolean) {
|
||||||
|
if (explicitValueGiven && column.generated != null) {
|
||||||
|
throw KestrelException("cannot assign an explicit value to GENERATED column ${column.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [NextValueExpression] skips the generic "evaluate once to see what type comes back" check
|
||||||
|
* [validateDefaultDefinitions] otherwise uses, because evaluating it for real would mean a bare
|
||||||
|
* `CREATE TABLE` actually consumes a sequence value before a single row exists - instead this
|
||||||
|
* checks the two things that actually matter statically: the sequence is real, and the column it
|
||||||
|
* defaults can hold what NEXTVAL returns.
|
||||||
|
*/
|
||||||
|
private fun validateNextValueDefault(default: NextValueExpression, column: Column) {
|
||||||
|
catalog.sequence(default.sequenceName)
|
||||||
|
if (column.type != ColumnType.Integer) {
|
||||||
|
throw KestrelException("NEXTVAL can only be used as a DEFAULT for an INTEGER column, but ${column.name} is ${column.type.displayName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DEFAULT must be a constant expression: no column reference (there is no row to read one
|
||||||
|
* from when a value is missing at INSERT time, or none at all yet when backfilling ALTER
|
||||||
|
* TABLE ADD COLUMN), no subquery, and no aggregate. [NextValueExpression] is the one exception -
|
||||||
|
* see [validateNextValueDefault] - because unlike everything else here it names an external,
|
||||||
|
* mutable counter rather than something computable from the expression alone.
|
||||||
|
* [CurrentValueExpression] is rejected outright: CURRVAL only ever means something after a prior
|
||||||
|
* NEXTVAL in the same session, which can never be true yet at CREATE TABLE/ADD COLUMN time.
|
||||||
|
*/
|
||||||
|
private fun validateConstantDefault(expression: Expression) {
|
||||||
|
when (expression) {
|
||||||
|
is ColumnExpression -> throw KestrelException("DEFAULT expression cannot reference a column")
|
||||||
|
is ScalarSubqueryExpression, is ExistsExpression, is InSubqueryExpression ->
|
||||||
|
throw KestrelException("DEFAULT expression cannot contain a subquery")
|
||||||
|
is FunctionExpression -> throw KestrelException("DEFAULT expression cannot contain an aggregate function")
|
||||||
|
is BinaryExpression -> {
|
||||||
|
validateConstantDefault(expression.left)
|
||||||
|
validateConstantDefault(expression.right)
|
||||||
|
}
|
||||||
|
is UnaryExpression -> validateConstantDefault(expression.operand)
|
||||||
|
is CallExpression -> expression.arguments.forEach { validateConstantDefault(it) }
|
||||||
|
is IsNullExpression -> validateConstantDefault(expression.operand)
|
||||||
|
is InExpression -> {
|
||||||
|
validateConstantDefault(expression.operand)
|
||||||
|
expression.values.forEach { validateConstantDefault(it) }
|
||||||
|
}
|
||||||
|
is BetweenExpression -> {
|
||||||
|
validateConstantDefault(expression.operand)
|
||||||
|
validateConstantDefault(expression.low)
|
||||||
|
validateConstantDefault(expression.high)
|
||||||
|
}
|
||||||
|
is LikeExpression -> {
|
||||||
|
validateConstantDefault(expression.operand)
|
||||||
|
validateConstantDefault(expression.pattern)
|
||||||
|
}
|
||||||
|
is CaseExpression -> {
|
||||||
|
expression.operand?.let { validateConstantDefault(it) }
|
||||||
|
expression.branches.forEach {
|
||||||
|
validateConstantDefault(it.condition)
|
||||||
|
validateConstantDefault(it.result)
|
||||||
|
}
|
||||||
|
expression.elseExpression?.let { validateConstantDefault(it) }
|
||||||
|
}
|
||||||
|
is CastExpression -> validateConstantDefault(expression.operand)
|
||||||
|
is CollateExpression -> validateConstantDefault(expression.operand)
|
||||||
|
is WindowExpression -> throw KestrelException("DEFAULT expression cannot contain a window function")
|
||||||
|
is NextValueExpression -> Unit
|
||||||
|
is CurrentValueExpression -> throw KestrelException("DEFAULT expression cannot contain CURRVAL")
|
||||||
|
is MergeActionExpression -> throw KestrelException("DEFAULT expression cannot contain MERGE_ACTION()")
|
||||||
|
is ParameterExpression -> throw KestrelException("DEFAULT expression cannot contain a parameter reference")
|
||||||
|
is LiteralExpression -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireNoSubquery(expression: Expression) {
|
||||||
|
when (expression) {
|
||||||
|
is ScalarSubqueryExpression, is ExistsExpression, is InSubqueryExpression ->
|
||||||
|
throw KestrelException("CHECK constraints and GENERATED column expressions cannot contain subqueries")
|
||||||
|
is FunctionExpression -> throw KestrelException("aggregate functions are not allowed in CHECK constraints")
|
||||||
|
is BinaryExpression -> {
|
||||||
|
requireNoSubquery(expression.left)
|
||||||
|
requireNoSubquery(expression.right)
|
||||||
|
}
|
||||||
|
is UnaryExpression -> requireNoSubquery(expression.operand)
|
||||||
|
is CallExpression -> expression.arguments.forEach { requireNoSubquery(it) }
|
||||||
|
is IsNullExpression -> requireNoSubquery(expression.operand)
|
||||||
|
is InExpression -> {
|
||||||
|
requireNoSubquery(expression.operand)
|
||||||
|
expression.values.forEach { requireNoSubquery(it) }
|
||||||
|
}
|
||||||
|
is BetweenExpression -> {
|
||||||
|
requireNoSubquery(expression.operand)
|
||||||
|
requireNoSubquery(expression.low)
|
||||||
|
requireNoSubquery(expression.high)
|
||||||
|
}
|
||||||
|
is LikeExpression -> {
|
||||||
|
requireNoSubquery(expression.operand)
|
||||||
|
requireNoSubquery(expression.pattern)
|
||||||
|
}
|
||||||
|
is CaseExpression -> {
|
||||||
|
expression.operand?.let { requireNoSubquery(it) }
|
||||||
|
expression.branches.forEach {
|
||||||
|
requireNoSubquery(it.condition)
|
||||||
|
requireNoSubquery(it.result)
|
||||||
|
}
|
||||||
|
expression.elseExpression?.let { requireNoSubquery(it) }
|
||||||
|
}
|
||||||
|
is CastExpression -> requireNoSubquery(expression.operand)
|
||||||
|
is CollateExpression -> requireNoSubquery(expression.operand)
|
||||||
|
is WindowExpression -> throw KestrelException("window functions are not allowed in CHECK constraints")
|
||||||
|
is NextValueExpression, is CurrentValueExpression ->
|
||||||
|
throw KestrelException("NEXTVAL/CURRVAL are not allowed in a CHECK constraint, trigger WHEN clause, or GENERATED column expression")
|
||||||
|
is MergeActionExpression ->
|
||||||
|
throw KestrelException("MERGE_ACTION() is not allowed in a CHECK constraint, trigger WHEN clause, or GENERATED column expression")
|
||||||
|
is ParameterExpression ->
|
||||||
|
throw KestrelException("a parameter reference is not allowed in a CHECK constraint, trigger WHEN clause, or GENERATED column expression")
|
||||||
|
is ColumnExpression, is LiteralExpression -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enforceChecks(schema: TableSchema, row: Row) {
|
||||||
|
for (check in schema.checks) {
|
||||||
|
val result = Evaluator(schema, row, subqueryRunner).evaluate(check.expression)
|
||||||
|
if (result is Value.BooleanValue && !result.value) {
|
||||||
|
val label = check.name?.let { " $it" } ?: ""
|
||||||
|
throw KestrelException("CHECK constraint$label failed: ${schema.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun executeDropTable(statement: DropTableStatement): StatementResult {
|
private fun executeDropTable(statement: DropTableStatement): StatementResult {
|
||||||
catalog.dropTable(statement.tableName)
|
catalog.dropTable(statement.tableName, cascade = statement.mode == DropMode.Cascade)
|
||||||
return DropTableResult(statement.tableName)
|
return DropTableResult(statement.tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user