feat(engine): extend Evaluator with evaluateNextValue
This commit is contained in:
1 parent
e42f3ed09b
commit
b165621a2a
1 file changed
+173
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user