feat(catalog): add RelationRename and rename
This commit is contained in:
1 parent
8a595f4f02
commit
2665166513
1 file changed
+182
@@ -0,0 +1,182 @@
|
|||||||
|
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.CubeSpec
|
||||||
|
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.GroupingSetsSpec
|
||||||
|
import kestrel.sql.ast.GroupingSpec
|
||||||
|
import kestrel.sql.ast.InExpression
|
||||||
|
import kestrel.sql.ast.InSubqueryExpression
|
||||||
|
import kestrel.sql.ast.IsNullExpression
|
||||||
|
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.RollupSpec
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites every [TableSource] named [oldName] to [newName] throughout a view's defining query -
|
||||||
|
* FROM/JOIN targets, derived tables, CTE bodies, set-operation branches, and every subquery
|
||||||
|
* expression (scalar/EXISTS/IN) nested inside WHERE/HAVING/projection/GROUP BY/ORDER BY. Used by
|
||||||
|
* [Catalog] to keep views working after `ALTER TABLE ... RENAME TO`.
|
||||||
|
*
|
||||||
|
* A CTE whose own name equals [oldName] shadows the real table for the rest of that WITH clause
|
||||||
|
* (same rule SQL itself uses to resolve the name): once such a CTE is seen, later CTEs and the
|
||||||
|
* body are left untouched, because a bare reference to that name from there on means the CTE, not
|
||||||
|
* the table being renamed - renaming it there would silently change what the query means.
|
||||||
|
*/
|
||||||
|
internal object RelationRename {
|
||||||
|
fun rename(query: QueryStatement, oldName: String, newName: String): QueryStatement = when (query) {
|
||||||
|
is SelectStatement -> renameInSelect(query, oldName, newName)
|
||||||
|
is SetOperationStatement -> query.copy(
|
||||||
|
left = rename(query.left, oldName, newName),
|
||||||
|
right = rename(query.right, oldName, newName)
|
||||||
|
)
|
||||||
|
is WithQuery -> renameInWith(query, oldName, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameInWith(query: WithQuery, oldName: String, newName: String): WithQuery {
|
||||||
|
val newCtes = mutableListOf<CommonTableExpression>()
|
||||||
|
var shadowed = false
|
||||||
|
for (cte in query.ctes) {
|
||||||
|
newCtes.add(if (shadowed) cte else cte.copy(query = rename(cte.query, oldName, newName)))
|
||||||
|
if (cte.name.equals(oldName, ignoreCase = true)) shadowed = true
|
||||||
|
}
|
||||||
|
val newBody = if (shadowed) query.body else rename(query.body, oldName, newName)
|
||||||
|
return query.copy(ctes = newCtes, body = newBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameInSelect(select: SelectStatement, oldName: String, newName: String): SelectStatement =
|
||||||
|
select.copy(
|
||||||
|
from = renameFromItem(select.from, oldName, newName),
|
||||||
|
joins = select.joins.map { join ->
|
||||||
|
join.copy(
|
||||||
|
from = renameFromItem(join.from, oldName, newName),
|
||||||
|
on = join.on?.let { renameInExpression(it, oldName, newName) }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
where = select.where?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
groupBy = select.groupBy.map { renameInExpression(it, oldName, newName) },
|
||||||
|
groupingSpec = select.groupingSpec?.let { renameInGroupingSpec(it, oldName, newName) },
|
||||||
|
having = select.having?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
projection = renameInProjection(select.projection, oldName, newName),
|
||||||
|
orderBy = select.orderBy?.let { orderBy ->
|
||||||
|
orderBy.copy(keys = orderBy.keys.map { it.copy(expression = renameInExpression(it.expression, oldName, newName)) })
|
||||||
|
},
|
||||||
|
distinctOn = select.distinctOn.map { renameInExpression(it, oldName, newName) }
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `groupBy` itself only ever carries the union of a [GroupingSpec]'s expressions (see
|
||||||
|
* [SelectStatement.groupBy]'s doc), never the spec's own per-set structure - so renaming that
|
||||||
|
* union alone would leave a stale table name buried inside a subquery nested in
|
||||||
|
* [RollupSpec]/[CubeSpec]/[GroupingSetsSpec], the one place [GroupAggregation] actually reads for
|
||||||
|
* execution. Kept in exact lockstep with `groupBy`'s own rewrite above.
|
||||||
|
*/
|
||||||
|
private fun renameInGroupingSpec(spec: GroupingSpec, oldName: String, newName: String): GroupingSpec = when (spec) {
|
||||||
|
is RollupSpec -> spec.copy(expressions = spec.expressions.map { renameInExpression(it, oldName, newName) })
|
||||||
|
is CubeSpec -> spec.copy(expressions = spec.expressions.map { renameInExpression(it, oldName, newName) })
|
||||||
|
is GroupingSetsSpec -> spec.copy(sets = spec.sets.map { set -> set.map { renameInExpression(it, oldName, newName) } })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameFromItem(item: FromItem, oldName: String, newName: String): FromItem = when (item) {
|
||||||
|
is TableSource -> if (item.name.equals(oldName, ignoreCase = true)) item.copy(name = newName) else item
|
||||||
|
is SubquerySource -> item.copy(select = rename(item.select, oldName, newName))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameInProjection(projection: Projection, oldName: String, newName: String): Projection = when (projection) {
|
||||||
|
is StarProjection -> projection
|
||||||
|
is ExpressionProjection ->
|
||||||
|
projection.copy(items = projection.items.map { it.copy(expression = renameInExpression(it.expression, oldName, newName)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameInExpression(expression: Expression, oldName: String, newName: String): Expression = when (expression) {
|
||||||
|
is ScalarSubqueryExpression -> expression.copy(select = renameInSelect(expression.select, oldName, newName))
|
||||||
|
is ExistsExpression -> expression.copy(select = renameInSelect(expression.select, oldName, newName))
|
||||||
|
is InSubqueryExpression -> expression.copy(
|
||||||
|
operand = renameInExpression(expression.operand, oldName, newName),
|
||||||
|
select = renameInSelect(expression.select, oldName, newName)
|
||||||
|
)
|
||||||
|
is BinaryExpression -> expression.copy(
|
||||||
|
left = renameInExpression(expression.left, oldName, newName),
|
||||||
|
right = renameInExpression(expression.right, oldName, newName)
|
||||||
|
)
|
||||||
|
is UnaryExpression -> expression.copy(operand = renameInExpression(expression.operand, oldName, newName))
|
||||||
|
is CallExpression -> expression.copy(arguments = expression.arguments.map { renameInExpression(it, oldName, newName) })
|
||||||
|
is IsNullExpression -> expression.copy(operand = renameInExpression(expression.operand, oldName, newName))
|
||||||
|
is InExpression -> expression.copy(
|
||||||
|
operand = renameInExpression(expression.operand, oldName, newName),
|
||||||
|
values = expression.values.map { renameInExpression(it, oldName, newName) }
|
||||||
|
)
|
||||||
|
is BetweenExpression -> expression.copy(
|
||||||
|
operand = renameInExpression(expression.operand, oldName, newName),
|
||||||
|
low = renameInExpression(expression.low, oldName, newName),
|
||||||
|
high = renameInExpression(expression.high, oldName, newName)
|
||||||
|
)
|
||||||
|
is LikeExpression -> expression.copy(
|
||||||
|
operand = renameInExpression(expression.operand, oldName, newName),
|
||||||
|
pattern = renameInExpression(expression.pattern, oldName, newName)
|
||||||
|
)
|
||||||
|
is CaseExpression -> expression.copy(
|
||||||
|
operand = expression.operand?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
branches = expression.branches.map {
|
||||||
|
it.copy(
|
||||||
|
condition = renameInExpression(it.condition, oldName, newName),
|
||||||
|
result = renameInExpression(it.result, oldName, newName)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
elseExpression = expression.elseExpression?.let { renameInExpression(it, oldName, newName) }
|
||||||
|
)
|
||||||
|
is CastExpression -> expression.copy(operand = renameInExpression(expression.operand, oldName, newName))
|
||||||
|
is CollateExpression -> expression.copy(operand = renameInExpression(expression.operand, oldName, newName))
|
||||||
|
is FunctionExpression -> expression.copy(
|
||||||
|
argument = expression.argument?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
separator = expression.separator?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
orderBy = expression.orderBy.map { it.copy(expression = renameInExpression(it.expression, oldName, newName)) },
|
||||||
|
filter = expression.filter?.let { renameInExpression(it, oldName, newName) }
|
||||||
|
)
|
||||||
|
is WindowExpression -> expression.copy(
|
||||||
|
argument = expression.argument?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
partitionBy = expression.partitionBy.map { renameInExpression(it, oldName, newName) },
|
||||||
|
orderBy = expression.orderBy.map { it.copy(expression = renameInExpression(it.expression, oldName, newName)) },
|
||||||
|
offset = expression.offset?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
defaultValue = expression.defaultValue?.let { renameInExpression(it, oldName, newName) },
|
||||||
|
frame = expression.frame?.let { frame ->
|
||||||
|
frame.copy(
|
||||||
|
start = renameInFrameBound(frame.start, oldName, newName),
|
||||||
|
end = renameInFrameBound(frame.end, oldName, newName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is ColumnExpression, is LiteralExpression, is NextValueExpression, is CurrentValueExpression,
|
||||||
|
is MergeActionExpression, is ParameterExpression -> expression
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameInFrameBound(bound: FrameBound, oldName: String, newName: String): FrameBound =
|
||||||
|
bound.copy(offset = bound.offset?.let { renameInExpression(it, oldName, newName) })
|
||||||
|
}
|
||||||
Reference in new issue
Block a user