feat(catalog): add DependencyAnalysis and referencesRelation
This commit is contained in:
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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user