feat(engine): add substituteSelect and substituteGroupingSpec

This commit is contained in:
dualfroz committed 2026-09-02 11:37:10 +02:00
1 parent 020a3c09ed
commit d6b9462677
1 file changed
+213
@@ -156,3 +156,216 @@ internal object ParameterSubstitution {
)
}
private fun substituteSelect(select: SelectStatement, boundValues: List<Value>): SelectStatement = select.copy(
from = substituteFromItem(select.from, boundValues),
joins = select.joins.map { join ->
join.copy(
from = substituteFromItem(join.from, boundValues),
on = join.on?.let { substituteExpression(it, boundValues) }
)
},
where = select.where?.let { substituteExpression(it, boundValues) },
groupBy = select.groupBy.map { substituteExpression(it, boundValues) },
groupingSpec = select.groupingSpec?.let { substituteGroupingSpec(it, boundValues) },
having = select.having?.let { substituteExpression(it, boundValues) },
projection = substituteProjection(select.projection, boundValues),
orderBy = select.orderBy?.let { substituteOrderBy(it, boundValues) },
distinctOn = select.distinctOn.map { substituteExpression(it, boundValues) }
)
private fun substituteGroupingSpec(spec: GroupingSpec, boundValues: List<Value>): GroupingSpec = when (spec) {
is RollupSpec -> spec.copy(expressions = spec.expressions.map { substituteExpression(it, boundValues) })
is CubeSpec -> spec.copy(expressions = spec.expressions.map { substituteExpression(it, boundValues) })
is GroupingSetsSpec -> spec.copy(sets = spec.sets.map { set -> set.map { substituteExpression(it, boundValues) } })
}
private fun substituteFromItem(item: FromItem, boundValues: List<Value>): FromItem = when (item) {
is TableSource -> item
is SubquerySource -> item.copy(select = substituteQuery(item.select, boundValues))
}
private fun substituteProjection(projection: Projection, boundValues: List<Value>): Projection = when (projection) {
StarProjection -> projection
is ExpressionProjection ->
projection.copy(items = projection.items.map { it.copy(expression = substituteExpression(it.expression, boundValues)) })
}
private fun substituteOrderBy(orderBy: OrderBy, boundValues: List<Value>): OrderBy =
orderBy.copy(keys = orderBy.keys.map { it.copy(expression = substituteExpression(it.expression, boundValues)) })
private fun substituteExpression(expression: Expression, boundValues: List<Value>): Expression = when (expression) {
is ParameterExpression -> LiteralExpression(boundValueAt(expression.index, boundValues))
is ScalarSubqueryExpression -> expression.copy(select = substituteSelect(expression.select, boundValues))
is ExistsExpression -> expression.copy(select = substituteSelect(expression.select, boundValues))
is InSubqueryExpression -> expression.copy(
operand = substituteExpression(expression.operand, boundValues),
select = substituteSelect(expression.select, boundValues)
)
is BinaryExpression -> expression.copy(
left = substituteExpression(expression.left, boundValues),
right = substituteExpression(expression.right, boundValues)
)
is UnaryExpression -> expression.copy(operand = substituteExpression(expression.operand, boundValues))
is CallExpression -> expression.copy(arguments = expression.arguments.map { substituteExpression(it, boundValues) })
is IsNullExpression -> expression.copy(operand = substituteExpression(expression.operand, boundValues))
is InExpression -> expression.copy(
operand = substituteExpression(expression.operand, boundValues),
values = expression.values.map { substituteExpression(it, boundValues) }
)
is BetweenExpression -> expression.copy(
operand = substituteExpression(expression.operand, boundValues),
low = substituteExpression(expression.low, boundValues),
high = substituteExpression(expression.high, boundValues)
)
is LikeExpression -> expression.copy(
operand = substituteExpression(expression.operand, boundValues),
pattern = substituteExpression(expression.pattern, boundValues)
)
is CaseExpression -> expression.copy(
operand = expression.operand?.let { substituteExpression(it, boundValues) },
branches = expression.branches.map {
it.copy(condition = substituteExpression(it.condition, boundValues), result = substituteExpression(it.result, boundValues))
},
elseExpression = expression.elseExpression?.let { substituteExpression(it, boundValues) }
)
is CastExpression -> expression.copy(operand = substituteExpression(expression.operand, boundValues))
is CollateExpression -> expression.copy(operand = substituteExpression(expression.operand, boundValues))
is FunctionExpression -> expression.copy(
argument = expression.argument?.let { substituteExpression(it, boundValues) },
separator = expression.separator?.let { substituteExpression(it, boundValues) },
orderBy = expression.orderBy.map { it.copy(expression = substituteExpression(it.expression, boundValues)) },
filter = expression.filter?.let { substituteExpression(it, boundValues) }
)
is WindowExpression -> expression.copy(
argument = expression.argument?.let { substituteExpression(it, boundValues) },
partitionBy = expression.partitionBy.map { substituteExpression(it, boundValues) },
orderBy = expression.orderBy.map { it.copy(expression = substituteExpression(it.expression, boundValues)) },
offset = expression.offset?.let { substituteExpression(it, boundValues) },
defaultValue = expression.defaultValue?.let { substituteExpression(it, boundValues) },
frame = expression.frame?.let { frame ->
frame.copy(
start = substituteFrameBound(frame.start, boundValues),
end = substituteFrameBound(frame.end, boundValues)
)
}
)
is ColumnExpression, is LiteralExpression, is NextValueExpression, is CurrentValueExpression,
is MergeActionExpression -> expression
}
private fun substituteFrameBound(bound: FrameBound, boundValues: List<Value>): FrameBound =
bound.copy(offset = bound.offset?.let { substituteExpression(it, boundValues) })
private fun boundValueAt(index: Int, boundValues: List<Value>): Value =
boundValues.getOrNull(index - 1) ?: throw KestrelException("no value supplied for parameter \$$index")
// --- parameterCount's twin walk: same shape as substitute above, but only tracks the max index seen. ---
private fun maxIndexInAssignments(assignments: List<Assignment>): Int =
assignments.maxOfOrNull { it.value?.let(::maxIndexInExpression) ?: 0 } ?: 0
private fun maxIndexInOnConflict(clause: OnConflictClause): Int = when (val action = clause.action) {
ConflictDoNothing -> 0
is ConflictDoUpdate -> maxOf(maxIndexInAssignments(action.assignments), action.where?.let(::maxIndexInExpression) ?: 0)
}
private fun maxIndexInWhenClause(clause: MergeWhenClause): Int = when (clause) {
is MergeMatchedUpdate -> maxOf(
clause.condition?.let(::maxIndexInExpression) ?: 0,
maxIndexInAssignments(clause.assignments)
)
is MergeMatchedDelete -> clause.condition?.let(::maxIndexInExpression) ?: 0
is MergeNotMatchedInsert -> maxOf(
clause.condition?.let(::maxIndexInExpression) ?: 0,
clause.values.maxOfOrNull { it?.let(::maxIndexInExpression) ?: 0 } ?: 0
)
}
private fun maxIndexInQuery(query: QueryStatement): Int = when (query) {
is SelectStatement -> maxIndexInSelect(query)
is SetOperationStatement -> maxOf(
maxIndexInQuery(query.left),
maxIndexInQuery(query.right),
query.orderBy?.let(::maxIndexInOrderBy) ?: 0
)
is WithQuery -> maxOf(
query.ctes.maxOfOrNull { maxIndexInQuery(it.query) } ?: 0,
maxIndexInQuery(query.body)
)
}
private fun maxIndexInSelect(select: SelectStatement): Int = maxOf(
maxIndexInFromItem(select.from),
select.joins.maxOfOrNull { join -> maxOf(maxIndexInFromItem(join.from), join.on?.let(::maxIndexInExpression) ?: 0) } ?: 0,
select.where?.let(::maxIndexInExpression) ?: 0,
select.groupBy.maxOfOrNull(::maxIndexInExpression) ?: 0,
select.groupingSpec?.let(::maxIndexInGroupingSpec) ?: 0,
select.having?.let(::maxIndexInExpression) ?: 0,
maxIndexInProjection(select.projection),
select.orderBy?.let(::maxIndexInOrderBy) ?: 0,
select.distinctOn.maxOfOrNull(::maxIndexInExpression) ?: 0
)
private fun maxIndexInGroupingSpec(spec: GroupingSpec): Int = when (spec) {
is RollupSpec -> spec.expressions.maxOfOrNull(::maxIndexInExpression) ?: 0
is CubeSpec -> spec.expressions.maxOfOrNull(::maxIndexInExpression) ?: 0
is GroupingSetsSpec -> spec.sets.maxOfOrNull { set -> set.maxOfOrNull(::maxIndexInExpression) ?: 0 } ?: 0
}
private fun maxIndexInFromItem(item: FromItem): Int = when (item) {
is TableSource -> 0
is SubquerySource -> maxIndexInQuery(item.select)
}
private fun maxIndexInProjection(projection: Projection): Int = when (projection) {
StarProjection -> 0
is ExpressionProjection -> projection.items.maxOfOrNull { maxIndexInExpression(it.expression) } ?: 0
}
private fun maxIndexInOrderBy(orderBy: OrderBy): Int = orderBy.keys.maxOfOrNull { maxIndexInExpression(it.expression) } ?: 0
private fun maxIndexInExpression(expression: Expression): Int = when (expression) {
is ParameterExpression -> expression.index
is ScalarSubqueryExpression -> maxIndexInSelect(expression.select)
is ExistsExpression -> maxIndexInSelect(expression.select)
is InSubqueryExpression -> maxOf(maxIndexInExpression(expression.operand), maxIndexInSelect(expression.select))
is BinaryExpression -> maxOf(maxIndexInExpression(expression.left), maxIndexInExpression(expression.right))
is UnaryExpression -> maxIndexInExpression(expression.operand)
is CallExpression -> expression.arguments.maxOfOrNull(::maxIndexInExpression) ?: 0
is IsNullExpression -> maxIndexInExpression(expression.operand)
is InExpression -> maxOf(
maxIndexInExpression(expression.operand),
expression.values.maxOfOrNull(::maxIndexInExpression) ?: 0
)
is BetweenExpression -> maxOf(
maxIndexInExpression(expression.operand),
maxIndexInExpression(expression.low),
maxIndexInExpression(expression.high)
)
is LikeExpression -> maxOf(maxIndexInExpression(expression.operand), maxIndexInExpression(expression.pattern))
is CaseExpression -> maxOf(
expression.operand?.let(::maxIndexInExpression) ?: 0,
expression.branches.maxOfOrNull { maxOf(maxIndexInExpression(it.condition), maxIndexInExpression(it.result)) } ?: 0,
expression.elseExpression?.let(::maxIndexInExpression) ?: 0
)
is CastExpression -> maxIndexInExpression(expression.operand)
is CollateExpression -> maxIndexInExpression(expression.operand)
is FunctionExpression -> maxOf(
expression.argument?.let(::maxIndexInExpression) ?: 0,
expression.separator?.let(::maxIndexInExpression) ?: 0,
expression.orderBy.maxOfOrNull { maxIndexInExpression(it.expression) } ?: 0,
expression.filter?.let(::maxIndexInExpression) ?: 0
)
is WindowExpression -> maxOf(
expression.argument?.let(::maxIndexInExpression) ?: 0,
expression.partitionBy.maxOfOrNull(::maxIndexInExpression) ?: 0,
expression.orderBy.maxOfOrNull { maxIndexInExpression(it.expression) } ?: 0,
expression.offset?.let(::maxIndexInExpression) ?: 0,
expression.defaultValue?.let(::maxIndexInExpression) ?: 0,
expression.frame?.start?.offset?.let(::maxIndexInExpression) ?: 0,
expression.frame?.end?.offset?.let(::maxIndexInExpression) ?: 0
)
is ColumnExpression, is LiteralExpression, is NextValueExpression, is CurrentValueExpression,
is MergeActionExpression -> 0
}
}