feat(catalog): add DependencyAnalysis and referencesRelation

This commit is contained in:
dualfroz committed 2026-09-01 14:10:29 +02:00
1 parent af2a4708cf
commit 8a595f4f02
1 file changed
+123
@@ -0,0 +1,123 @@
package kestrel.catalog
import kestrel.sql.ast.BetweenExpression
import kestrel.sql.ast.BinaryExpression
import kestrel.sql.ast.CallExpression
import kestrel.sql.ast.CaseExpression
import kestrel.sql.ast.CastExpression
import kestrel.sql.ast.CollateExpression
import kestrel.sql.ast.ColumnExpression
import kestrel.sql.ast.CommonTableExpression
import kestrel.sql.ast.CurrentValueExpression
import kestrel.sql.ast.ExistsExpression
import kestrel.sql.ast.Expression
import kestrel.sql.ast.ExpressionProjection
import kestrel.sql.ast.FrameBound
import kestrel.sql.ast.FromItem
import kestrel.sql.ast.FunctionExpression
import kestrel.sql.ast.InExpression
import kestrel.sql.ast.InSubqueryExpression
import kestrel.sql.ast.IsNullExpression
import kestrel.sql.ast.JoinClause
import kestrel.sql.ast.LikeExpression
import kestrel.sql.ast.LiteralExpression
import kestrel.sql.ast.MergeActionExpression
import kestrel.sql.ast.NextValueExpression
import kestrel.sql.ast.ParameterExpression
import kestrel.sql.ast.Projection
import kestrel.sql.ast.QueryStatement
import kestrel.sql.ast.ScalarSubqueryExpression
import kestrel.sql.ast.SelectStatement
import kestrel.sql.ast.SetOperationStatement
import kestrel.sql.ast.StarProjection
import kestrel.sql.ast.SubquerySource
import kestrel.sql.ast.TableSource
import kestrel.sql.ast.UnaryExpression
import kestrel.sql.ast.WindowExpression
import kestrel.sql.ast.WithQuery
/**
* Walks a view's defining query looking for a relation name anywhere it could appear:
* FROM/JOIN targets, derived tables, CTE bodies, set-operation branches, and every
* subquery expression (scalar/EXISTS/IN) nested inside WHERE/HAVING/projection/GROUP BY.
* Used by [Catalog] to find what would break (RESTRICT) or need to cascade (CASCADE)
* when a table or view is dropped.
*/
internal object DependencyAnalysis {
fun referencesRelation(query: QueryStatement, name: String): Boolean = when (query) {
is SelectStatement -> referencesInSelect(query, name)
is SetOperationStatement -> referencesRelation(query.left, name) || referencesRelation(query.right, name)
is WithQuery -> query.ctes.any { referencesInCte(it, name) } || referencesRelation(query.body, name)
}
private fun referencesInCte(cte: CommonTableExpression, name: String): Boolean =
referencesRelation(cte.query, name)
private fun referencesInSelect(select: SelectStatement, name: String): Boolean {
if (referencesFromItem(select.from, name)) return true
if (select.joins.any { referencesJoin(it, name) }) return true
if (select.where?.let { referencesInExpression(it, name) } == true) return true
if (select.groupBy.any { referencesInExpression(it, name) }) return true
if (select.having?.let { referencesInExpression(it, name) } == true) return true
if (select.distinctOn.any { referencesInExpression(it, name) }) return true
return referencesInProjection(select.projection, name)
}
private fun referencesJoin(join: JoinClause, name: String): Boolean =
referencesFromItem(join.from, name) || (join.on?.let { referencesInExpression(it, name) } == true)
private fun referencesFromItem(item: FromItem, name: String): Boolean = when (item) {
is TableSource -> item.name.equals(name, ignoreCase = true)
is SubquerySource -> referencesRelation(item.select, name)
}
private fun referencesInProjection(projection: Projection, name: String): Boolean = when (projection) {
is StarProjection -> false
is ExpressionProjection -> projection.items.any { referencesInExpression(it.expression, name) }
}
private fun referencesInExpression(expression: Expression, name: String): Boolean = when (expression) {
is ScalarSubqueryExpression -> referencesRelation(expression.select, name)
is ExistsExpression -> referencesRelation(expression.select, name)
is InSubqueryExpression ->
referencesRelation(expression.select, name) || referencesInExpression(expression.operand, name)
is BinaryExpression -> referencesInExpression(expression.left, name) || referencesInExpression(expression.right, name)
is UnaryExpression -> referencesInExpression(expression.operand, name)
is CallExpression -> expression.arguments.any { referencesInExpression(it, name) }
is IsNullExpression -> referencesInExpression(expression.operand, name)
is InExpression ->
referencesInExpression(expression.operand, name) || expression.values.any { referencesInExpression(it, name) }
is BetweenExpression ->
referencesInExpression(expression.operand, name) ||
referencesInExpression(expression.low, name) ||
referencesInExpression(expression.high, name)
is LikeExpression ->
referencesInExpression(expression.operand, name) || referencesInExpression(expression.pattern, name)
is CaseExpression ->
(expression.operand?.let { referencesInExpression(it, name) } ?: false) ||
expression.branches.any {
referencesInExpression(it.condition, name) || referencesInExpression(it.result, name)
} ||
(expression.elseExpression?.let { referencesInExpression(it, name) } ?: false)
is CastExpression -> referencesInExpression(expression.operand, name)
is CollateExpression -> referencesInExpression(expression.operand, name)
is FunctionExpression ->
(expression.argument?.let { referencesInExpression(it, name) } ?: false) ||
(expression.separator?.let { referencesInExpression(it, name) } ?: false) ||
expression.orderBy.any { referencesInExpression(it.expression, name) } ||
(expression.filter?.let { referencesInExpression(it, name) } ?: false)
is WindowExpression ->
(expression.argument?.let { referencesInExpression(it, name) } ?: false) ||
(expression.offset?.let { referencesInExpression(it, name) } ?: false) ||
(expression.defaultValue?.let { referencesInExpression(it, name) } ?: false) ||
(expression.frame?.start?.let { referencesInFrameBound(it, name) } ?: false) ||
(expression.frame?.end?.let { referencesInFrameBound(it, name) } ?: false) ||
expression.partitionBy.any { referencesInExpression(it, name) } ||
expression.orderBy.any { referencesInExpression(it.expression, name) }
is ColumnExpression, is LiteralExpression, is NextValueExpression, is CurrentValueExpression,
is MergeActionExpression, is ParameterExpression -> false
}
private fun referencesInFrameBound(bound: FrameBound, name: String): Boolean =
bound.offset?.let { referencesInExpression(it, name) } ?: false
}