feat(engine): extend Evaluator with evaluateNextValue

This commit is contained in:
dualfroz committed 2026-09-01 17:19:44 +02:00
1 parent e42f3ed09b
commit b165621a2a
1 file changed
+173
+173
View File
@@ -111,6 +111,179 @@ class Evaluator(
is FunctionExpression -> throw KestrelException(
"aggregate function ${expression.name} is not allowed here"
)
is WindowExpression -> throw KestrelException(
"window function ${expression.name} is not allowed here"
)
is CallExpression -> evaluateCall(expression)
is IsNullExpression -> evaluateIsNull(expression)
is InExpression -> evaluateIn(expression)
is BetweenExpression -> evaluateBetween(expression)
is LikeExpression -> evaluateLike(expression)
is CaseExpression -> evaluateCase(expression)
is ScalarSubqueryExpression -> evaluateScalarSubquery(expression)
is ExistsExpression -> evaluateExists(expression)
is InSubqueryExpression -> evaluateInSubquery(expression)
is CastExpression -> evaluateCast(expression)
is NextValueExpression -> evaluateNextValue(expression)
is CurrentValueExpression -> evaluateCurrentValue(expression)
is MergeActionExpression -> Value.TextValue(mergeAction ?: throw KestrelException("MERGE_ACTION() is not allowed here"))
is ParameterExpression -> throw KestrelException(
"parameter reference \$${expression.index} is only valid inside a PREPARE-d statement, resolved by EXECUTE"
)
}
private fun evaluateNextValue(expression: NextValueExpression): Value = Value.IntegerValue(
(sequences ?: throw KestrelException("NEXTVAL is not allowed here")).nextValue(expression.sequenceName)
)
private fun evaluateCurrentValue(expression: CurrentValueExpression): Value = Value.IntegerValue(
(sequences ?: throw KestrelException("CURRVAL is not allowed here")).currentValue(expression.sequenceName)
)
private fun runSubquery(select: SelectStatement): SelectResult =
(subqueries ?: throw KestrelException("subqueries are not allowed here")).run(select, resolver)
private fun evaluateScalarSubquery(expression: ScalarSubqueryExpression): Value {
val result = runSubquery(expression.select)
if (result.columns.size != 1) {
throw KestrelException("scalar subquery must return exactly one column but returned ${result.columns.size}")
}
return when {
result.rows.isEmpty() -> Value.NullValue
result.rows.size > 1 -> throw KestrelException("scalar subquery returned more than one row")
else -> result.rows.single().single()
}
}
private fun evaluateExists(expression: ExistsExpression): Value =
Value.BooleanValue(runSubquery(expression.select).rows.isNotEmpty())
private fun evaluateInSubquery(expression: InSubqueryExpression): Value {
val operand = evaluate(expression.operand)
if (operand is Value.NullValue) return Value.NullValue
val collation = collationOf(expression.operand) ?: Collation.Binary
val result = runSubquery(expression.select)
if (result.columns.size != 1) {
throw KestrelException("IN subquery must return exactly one column but returned ${result.columns.size}")
}
var sawNull = false
for (row in result.rows) {
val candidate = row.single()
if (candidate is Value.NullValue) {
sawNull = true
continue
}
if (compareOperands(operand, candidate, collation) == 0) return Value.BooleanValue(!expression.negated)
}
return if (sawNull) Value.NullValue else Value.BooleanValue(expression.negated)
}
private fun evaluateCast(expression: CastExpression): Value {
val operand = evaluate(expression.operand)
if (operand is Value.NullValue) return Value.NullValue
return when (expression.targetType) {
ColumnType.Integer -> castToInteger(operand)
ColumnType.Real -> castToReal(operand)
ColumnType.Text -> Value.TextValue(castToText(operand))
ColumnType.Boolean -> castToBoolean(operand)
ColumnType.Decimal -> castToDecimal(operand, expression.precision, expression.scale)
ColumnType.Date -> castToDate(operand)
}
}
private fun castToInteger(value: Value): Value = Value.IntegerValue(
when (value) {
is Value.IntegerValue -> value.value
is Value.RealValue -> value.value.toLong()
is Value.DecimalValue -> value.value.toLong()
is Value.DateValue -> value.value.toEpochDay()
is Value.TextValue -> value.value.trim().toLongOrNull()
?: throw KestrelException("cannot cast '${value.value}' to INTEGER")
else -> throw KestrelException("cannot cast ${value.typeName} to INTEGER")
}
)
private fun castToReal(value: Value): Value = Value.RealValue(
when (value) {
is Value.IntegerValue -> value.value.toDouble()
is Value.RealValue -> value.value
is Value.DecimalValue -> value.value.toDouble()
is Value.TextValue -> value.value.trim().toDoubleOrNull()
?: throw KestrelException("cannot cast '${value.value}' to REAL")
else -> throw KestrelException("cannot cast ${value.typeName} to REAL")
}
)
private fun castToText(value: Value): String = when (value) {
is Value.IntegerValue -> value.value.toString()
is Value.RealValue -> value.value.toString()
is Value.DecimalValue -> value.value.toPlainString()
is Value.BooleanValue -> value.value.toString()
is Value.DateValue -> value.value.toString()
is Value.TextValue -> value.value
else -> throw KestrelException("cannot cast ${value.typeName} to TEXT")
}
private fun castToBoolean(value: Value): Value = Value.BooleanValue(
when (value) {
is Value.BooleanValue -> value.value
is Value.IntegerValue -> value.value != 0L
is Value.TextValue -> when (value.value.trim().lowercase()) {
"true" -> true
"false" -> false
else -> throw KestrelException("cannot cast '${value.value}' to BOOLEAN")
}
else -> throw KestrelException("cannot cast ${value.typeName} to BOOLEAN")
}
)
private fun castToDecimal(value: Value, precision: Int?, scale: Int?): Value {
val raw = when (value) {
is Value.DecimalValue -> value.value
is Value.IntegerValue -> BigDecimal(value.value)
is Value.RealValue -> BigDecimal.valueOf(value.value)
is Value.TextValue -> value.value.trim().toBigDecimalOrNull()
?: throw KestrelException("cannot cast '${value.value}' to DECIMAL")
else -> throw KestrelException("cannot cast ${value.typeName} to DECIMAL")
}
return Value.DecimalValue(DecimalColumns.normalize(raw, precision, scale, "CAST"))
}
private fun castToDate(value: Value): Value = Value.DateValue(
when (value) {
is Value.DateValue -> value.value
is Value.IntegerValue -> LocalDate.ofEpochDay(value.value)
is Value.TextValue -> runCatching { LocalDate.parse(value.value.trim()) }.getOrElse {
throw KestrelException("cannot cast '${value.value}' to DATE")
}
else -> throw KestrelException("cannot cast ${value.typeName} to DATE")
}
)
private fun evaluateCall(expression: CallExpression): Value {
if (expression.name.equals("GROUPING", ignoreCase = true)) {
throw KestrelException("GROUPING() can only be used in the SELECT list, HAVING, or ORDER BY of a query with GROUP BY")
}
val function = ScalarFunctions.lookup(expression.name)
?: throw KestrelException("unknown function: ${expression.name}")
return function.apply(expression.arguments.map { evaluate(it) })
}
private fun evaluateIsNull(expression: IsNullExpression): Value {
val isNull = evaluate(expression.operand).isNull
return Value.BooleanValue(if (expression.negated) !isNull else isNull)
}
private fun evaluateIn(expression: InExpression): Value {
val operand = evaluate(expression.operand)
if (operand is Value.NullValue) return Value.NullValue
val collation = collationOf(expression.operand) ?: Collation.Binary
var sawNull = false
for (candidateExpression in expression.values) {
val candidate = evaluate(candidateExpression)
if (candidate is Value.NullValue) {
sawNull = true
continue
}
private fun evaluateColumn(expression: ColumnExpression): Value {