feat(catalog): extend Catalog with collationsFor

This commit is contained in:
dualfroz committed 2026-09-01 13:52:45 +02:00
1 parent db58d7127a
commit af2a4708cf
2 files changed
+113 -3

No files matched your search

+16 -3
View File
@@ -810,13 +810,20 @@ class Catalog {
fun restore(snapshot: CatalogSnapshot) {
tables.clear()
indexes.clear()
views.clear()
triggers.clear()
sequences.clear()
views.putAll(snapshot.views)
triggers.putAll(snapshot.triggers)
sequences.putAll(snapshot.sequences)
for ((key, image) in snapshot.tables) {
val table = Table(image.first)
table.rows.addAll(image.second)
tables[key] = table
}
for (definition in snapshot.indexDefinitions) {
val index = SortedIndex(definition)
val schema = tables.getValue(definition.tableName.lowercase()).schema
val index = SortedIndex(definition, collationsFor(schema, definition.columnNames))
indexes[definition.name.lowercase()] = index
reindexOne(tables.getValue(definition.tableName.lowercase()), index)
}
@@ -824,7 +831,13 @@ class Catalog {
private fun reindexOne(table: Table, index: SortedIndex) {
index.clear()
val columnIndex = table.schema.columnIndex(index.definition.columnName)!!
table.rows.forEachIndexed { rowIndex, row -> index.add(row.values[columnIndex], rowIndex) }
val columnIndices = index.definition.columnNames.map { table.schema.columnIndex(it)!! }
table.rows.forEachIndexed { rowIndex, row ->
index.add(columnIndices.map { row.values[it] }, rowIndex)
}
}
/** One [Collation] per entry of [columnNames], in order - [Collation.Binary] for any name [schema] cannot resolve. */
private fun collationsFor(schema: TableSchema, columnNames: List<String>): List<Collation> =
columnNames.map { schema.column(it)?.collation ?: Collation.Binary }
}
@@ -0,0 +1,97 @@
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.CurrentValueExpression
import kestrel.sql.ast.ExistsExpression
import kestrel.sql.ast.Expression
import kestrel.sql.ast.FunctionExpression
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.ScalarSubqueryExpression
import kestrel.sql.ast.UnaryExpression
import kestrel.sql.ast.WindowExpression
/**
* Walks a CHECK constraint's expression looking for (or renaming) a plain column reference by
* name. Used by [Catalog] to decide whether dropping a column must also drop a dependent CHECK
* (`ALTER TABLE ... DROP COLUMN`) and to keep a CHECK in sync when a column is renamed
* (`ALTER TABLE ... RENAME COLUMN`). The subquery and window-function variants are handled only to
* keep the `when` exhaustive: `Executor.requireNoSubquery` already rejects any CHECK expression
* containing a subquery, an aggregate, or a window function at CREATE TABLE time, so those branches
* are unreachable in practice.
*/
internal object CheckExpressionColumns {
fun references(expression: Expression, columnName: String): Boolean = when (expression) {
is ColumnExpression -> expression.name.equals(columnName, ignoreCase = true)
is LiteralExpression -> false
is BinaryExpression -> references(expression.left, columnName) || references(expression.right, columnName)
is UnaryExpression -> references(expression.operand, columnName)
is CallExpression -> expression.arguments.any { references(it, columnName) }
is FunctionExpression -> expression.argument?.let { references(it, columnName) } ?: false
is IsNullExpression -> references(expression.operand, columnName)
is InExpression -> references(expression.operand, columnName) || expression.values.any { references(it, columnName) }
is BetweenExpression ->
references(expression.operand, columnName) ||
references(expression.low, columnName) ||
references(expression.high, columnName)
is LikeExpression -> references(expression.operand, columnName) || references(expression.pattern, columnName)
is CaseExpression ->
(expression.operand?.let { references(it, columnName) } ?: false) ||
expression.branches.any { references(it.condition, columnName) || references(it.result, columnName) } ||
(expression.elseExpression?.let { references(it, columnName) } ?: false)
is CastExpression -> references(expression.operand, columnName)
is CollateExpression -> references(expression.operand, columnName)
is ScalarSubqueryExpression, is ExistsExpression, is InSubqueryExpression, is WindowExpression,
is NextValueExpression, is CurrentValueExpression, is MergeActionExpression, is ParameterExpression -> false
}
fun rename(expression: Expression, oldName: String, newName: String): Expression = when (expression) {
is ColumnExpression ->
if (expression.name.equals(oldName, ignoreCase = true)) expression.copy(name = newName) else expression
is LiteralExpression -> expression
is BinaryExpression -> expression.copy(
left = rename(expression.left, oldName, newName),
right = rename(expression.right, oldName, newName)
)
is UnaryExpression -> expression.copy(operand = rename(expression.operand, oldName, newName))
is CallExpression -> expression.copy(arguments = expression.arguments.map { rename(it, oldName, newName) })
is FunctionExpression -> expression.copy(argument = expression.argument?.let { rename(it, oldName, newName) })
is IsNullExpression -> expression.copy(operand = rename(expression.operand, oldName, newName))
is InExpression -> expression.copy(
operand = rename(expression.operand, oldName, newName),
values = expression.values.map { rename(it, oldName, newName) }
)
is BetweenExpression -> expression.copy(
operand = rename(expression.operand, oldName, newName),
low = rename(expression.low, oldName, newName),
high = rename(expression.high, oldName, newName)
)
is LikeExpression -> expression.copy(
operand = rename(expression.operand, oldName, newName),
pattern = rename(expression.pattern, oldName, newName)
)
is CaseExpression -> expression.copy(
operand = expression.operand?.let { rename(it, oldName, newName) },
branches = expression.branches.map {
it.copy(condition = rename(it.condition, oldName, newName), result = rename(it.result, oldName, newName))
},
elseExpression = expression.elseExpression?.let { rename(it, oldName, newName) }
)
is CastExpression -> expression.copy(operand = rename(expression.operand, oldName, newName))
is CollateExpression -> expression.copy(operand = rename(expression.operand, oldName, newName))
is ScalarSubqueryExpression, is ExistsExpression, is InSubqueryExpression, is WindowExpression,
is NextValueExpression, is CurrentValueExpression, is MergeActionExpression, is ParameterExpression -> expression
}
}