feat(persistence): extend SqlWriter with expressionText and triggerText
This commit is contained in:
1 parent
16da420d79
commit
6ff6e4e91e
1 file changed
+195
-5
@@ -2,46 +2,236 @@ package kestrel.persistence
|
|||||||
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import kestrel.catalog.Catalog
|
import kestrel.catalog.Catalog
|
||||||
|
import kestrel.catalog.CheckConstraint
|
||||||
import kestrel.catalog.Column
|
import kestrel.catalog.Column
|
||||||
|
import kestrel.catalog.ForeignKeyAction
|
||||||
|
import kestrel.catalog.ForeignKeyConstraint
|
||||||
|
import kestrel.catalog.Sequence
|
||||||
|
import kestrel.catalog.SequenceDefinition
|
||||||
|
import kestrel.catalog.UniqueConstraint
|
||||||
|
import kestrel.catalog.TriggerDefinition
|
||||||
|
import kestrel.catalog.sqlKeyword
|
||||||
|
import kestrel.sql.ast.AddColumn
|
||||||
|
import kestrel.sql.ast.AddConstraint
|
||||||
|
import kestrel.sql.ast.AddedCheck
|
||||||
|
import kestrel.sql.ast.AddedConstraint
|
||||||
|
import kestrel.sql.ast.AddedForeignKey
|
||||||
|
import kestrel.sql.ast.AddedUnique
|
||||||
|
import kestrel.sql.ast.AlterTableStatement
|
||||||
import kestrel.sql.ast.Assignment
|
import kestrel.sql.ast.Assignment
|
||||||
|
import kestrel.sql.ast.BetweenExpression
|
||||||
import kestrel.sql.ast.BinaryExpression
|
import kestrel.sql.ast.BinaryExpression
|
||||||
|
import kestrel.sql.ast.CallExpression
|
||||||
|
import kestrel.sql.ast.CaseExpression
|
||||||
|
import kestrel.sql.ast.CastExpression
|
||||||
|
import kestrel.sql.ast.ChangeColumnType
|
||||||
|
import kestrel.sql.ast.CollateExpression
|
||||||
import kestrel.sql.ast.ColumnExpression
|
import kestrel.sql.ast.ColumnExpression
|
||||||
|
import kestrel.sql.ast.ConflictDoNothing
|
||||||
|
import kestrel.sql.ast.ConflictDoUpdate
|
||||||
|
import kestrel.sql.ast.AlterSequenceStatement
|
||||||
import kestrel.sql.ast.CreateIndexStatement
|
import kestrel.sql.ast.CreateIndexStatement
|
||||||
|
import kestrel.sql.ast.CreateSequenceStatement
|
||||||
import kestrel.sql.ast.CreateTableStatement
|
import kestrel.sql.ast.CreateTableStatement
|
||||||
|
import kestrel.sql.ast.CreateTriggerStatement
|
||||||
|
import kestrel.sql.ast.CreateViewStatement
|
||||||
|
import kestrel.sql.ast.CubeSpec
|
||||||
|
import kestrel.sql.ast.CurrentValueExpression
|
||||||
import kestrel.sql.ast.DeleteStatement
|
import kestrel.sql.ast.DeleteStatement
|
||||||
|
import kestrel.sql.ast.DropColumn
|
||||||
|
import kestrel.sql.ast.DropConstraint
|
||||||
import kestrel.sql.ast.DropIndexStatement
|
import kestrel.sql.ast.DropIndexStatement
|
||||||
|
import kestrel.sql.ast.DropMode
|
||||||
|
import kestrel.sql.ast.DropSequenceStatement
|
||||||
import kestrel.sql.ast.DropTableStatement
|
import kestrel.sql.ast.DropTableStatement
|
||||||
|
import kestrel.sql.ast.DropTriggerStatement
|
||||||
|
import kestrel.sql.ast.DropViewStatement
|
||||||
|
import kestrel.sql.ast.ExistsExpression
|
||||||
import kestrel.sql.ast.Expression
|
import kestrel.sql.ast.Expression
|
||||||
|
import kestrel.sql.ast.ExpressionProjection
|
||||||
|
import kestrel.sql.ast.FrameBound
|
||||||
|
import kestrel.sql.ast.FrameBoundType
|
||||||
|
import kestrel.sql.ast.FrameExclude
|
||||||
|
import kestrel.sql.ast.FrameUnit
|
||||||
|
import kestrel.sql.ast.FromItem
|
||||||
import kestrel.sql.ast.FunctionExpression
|
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.InsertStatement
|
import kestrel.sql.ast.InsertStatement
|
||||||
|
import kestrel.sql.ast.IsNullExpression
|
||||||
|
import kestrel.sql.ast.JoinType
|
||||||
|
import kestrel.sql.ast.LikeExpression
|
||||||
import kestrel.sql.ast.LiteralExpression
|
import kestrel.sql.ast.LiteralExpression
|
||||||
|
import kestrel.sql.ast.MergeActionExpression
|
||||||
|
import kestrel.sql.ast.ParameterExpression
|
||||||
|
import kestrel.sql.ast.MergeMatchedDelete
|
||||||
|
import kestrel.sql.ast.MergeMatchedUpdate
|
||||||
|
import kestrel.sql.ast.MergeNotMatchedInsert
|
||||||
|
import kestrel.sql.ast.MergeStatement
|
||||||
|
import kestrel.sql.ast.MergeWhenClause
|
||||||
|
import kestrel.sql.ast.NextValueExpression
|
||||||
|
import kestrel.sql.ast.OnConflictClause
|
||||||
|
import kestrel.sql.ast.OrderBy
|
||||||
|
import kestrel.sql.ast.OrderKey
|
||||||
|
import kestrel.sql.ast.Projection
|
||||||
|
import kestrel.sql.ast.QueryStatement
|
||||||
|
import kestrel.sql.ast.RenameColumn
|
||||||
|
import kestrel.sql.ast.RenameTable
|
||||||
|
import kestrel.sql.ast.RollupSpec
|
||||||
|
import kestrel.sql.ast.ScalarSubqueryExpression
|
||||||
|
import kestrel.sql.ast.SelectStatement
|
||||||
|
import kestrel.sql.ast.SetOperationStatement
|
||||||
|
import kestrel.sql.ast.SetOperator
|
||||||
|
import kestrel.sql.ast.StarProjection
|
||||||
import kestrel.sql.ast.Statement
|
import kestrel.sql.ast.Statement
|
||||||
|
import kestrel.sql.ast.SubquerySource
|
||||||
|
import kestrel.sql.ast.TableSource
|
||||||
|
import kestrel.sql.ast.TriggerEvent
|
||||||
|
import kestrel.sql.ast.TriggerTiming
|
||||||
|
import kestrel.sql.ast.TruncateTableStatement
|
||||||
import kestrel.sql.ast.UnaryExpression
|
import kestrel.sql.ast.UnaryExpression
|
||||||
import kestrel.sql.ast.UpdateStatement
|
import kestrel.sql.ast.UpdateStatement
|
||||||
|
import kestrel.sql.ast.WindowExpression
|
||||||
|
import kestrel.sql.ast.WithQuery
|
||||||
|
import kestrel.storage.Collation
|
||||||
import kestrel.storage.Value
|
import kestrel.storage.Value
|
||||||
|
import kestrel.storage.displayWith
|
||||||
|
|
||||||
object SqlWriter {
|
object SqlWriter {
|
||||||
|
fun expressionText(expression: Expression): String = expression(expression)
|
||||||
|
|
||||||
fun canPersist(statement: Statement): Boolean = statement is CreateTableStatement ||
|
fun canPersist(statement: Statement): Boolean = statement is CreateTableStatement ||
|
||||||
statement is DropTableStatement ||
|
statement is DropTableStatement ||
|
||||||
|
statement is TruncateTableStatement ||
|
||||||
|
statement is AlterTableStatement ||
|
||||||
statement is CreateIndexStatement ||
|
statement is CreateIndexStatement ||
|
||||||
statement is DropIndexStatement ||
|
statement is DropIndexStatement ||
|
||||||
|
statement is CreateViewStatement ||
|
||||||
|
statement is DropViewStatement ||
|
||||||
|
statement is CreateTriggerStatement ||
|
||||||
|
statement is DropTriggerStatement ||
|
||||||
|
statement is CreateSequenceStatement ||
|
||||||
|
statement is DropSequenceStatement ||
|
||||||
|
statement is AlterSequenceStatement ||
|
||||||
statement is InsertStatement ||
|
statement is InsertStatement ||
|
||||||
statement is UpdateStatement ||
|
statement is UpdateStatement ||
|
||||||
statement is DeleteStatement
|
statement is DeleteStatement ||
|
||||||
|
statement is MergeStatement
|
||||||
|
|
||||||
fun write(statement: Statement): String = when (statement) {
|
fun write(statement: Statement): String = when (statement) {
|
||||||
is CreateTableStatement -> "CREATE TABLE ${statement.tableName} (" +
|
is CreateTableStatement -> "CREATE ${if (statement.temporary) "TEMPORARY " else ""}TABLE ${statement.tableName} (" +
|
||||||
statement.columns.joinToString(", ") { columnDefinition(it) } + ");"
|
createTableBodyText(
|
||||||
is DropTableStatement -> "DROP TABLE ${statement.tableName};"
|
statement.columns,
|
||||||
|
statement.foreignKeys,
|
||||||
|
statement.checks,
|
||||||
|
statement.uniqueConstraints,
|
||||||
|
statement.compositePrimaryKey
|
||||||
|
) + ");"
|
||||||
|
is DropTableStatement -> "DROP TABLE ${statement.tableName}${dropModeText(statement.mode)};"
|
||||||
|
is TruncateTableStatement -> writeTruncate(statement)
|
||||||
|
is AlterTableStatement -> alterTableText(statement)
|
||||||
is CreateIndexStatement ->
|
is CreateIndexStatement ->
|
||||||
"CREATE INDEX ${statement.indexName} ON ${statement.tableName} (${statement.columnName});"
|
"CREATE ${if (statement.unique) "UNIQUE " else ""}INDEX ${statement.indexName} ON " +
|
||||||
|
"${statement.tableName} (${statement.columnNames.joinToString(", ")});"
|
||||||
is DropIndexStatement -> "DROP INDEX ${statement.indexName};"
|
is DropIndexStatement -> "DROP INDEX ${statement.indexName};"
|
||||||
|
is CreateViewStatement ->
|
||||||
|
"CREATE ${if (statement.orReplace) "OR REPLACE " else ""}VIEW ${statement.viewName} AS " +
|
||||||
|
"${queryText(statement.query)};"
|
||||||
|
is DropViewStatement -> "DROP VIEW ${statement.viewName}${dropModeText(statement.mode)};"
|
||||||
|
is CreateTriggerStatement -> triggerText(
|
||||||
|
statement.triggerName, statement.timing, statement.event, statement.tableName, statement.whenClause, statement.body
|
||||||
|
)
|
||||||
|
is DropTriggerStatement -> "DROP TRIGGER ${statement.triggerName};"
|
||||||
|
is CreateSequenceStatement -> createSequenceText(statement)
|
||||||
|
is DropSequenceStatement -> "DROP SEQUENCE ${statement.sequenceName};"
|
||||||
|
is AlterSequenceStatement -> alterSequenceText(statement)
|
||||||
is InsertStatement -> writeInsert(statement)
|
is InsertStatement -> writeInsert(statement)
|
||||||
is UpdateStatement -> writeUpdate(statement)
|
is UpdateStatement -> writeUpdate(statement)
|
||||||
is DeleteStatement -> writeDelete(statement)
|
is DeleteStatement -> writeDelete(statement)
|
||||||
|
is MergeStatement -> writeMerge(statement)
|
||||||
else -> throw IllegalArgumentException("statement is not persistable")
|
else -> throw IllegalArgumentException("statement is not persistable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun triggerText(definition: TriggerDefinition): String =
|
||||||
|
triggerText(definition.name, definition.timing, definition.event, definition.tableName, definition.whenClause, definition.body)
|
||||||
|
|
||||||
|
private fun triggerText(
|
||||||
|
name: String,
|
||||||
|
timing: TriggerTiming,
|
||||||
|
event: TriggerEvent,
|
||||||
|
tableName: String,
|
||||||
|
whenClause: Expression?,
|
||||||
|
body: List<Statement>
|
||||||
|
): String = buildString {
|
||||||
|
append("CREATE TRIGGER $name ")
|
||||||
|
append(if (timing == TriggerTiming.Before) "BEFORE" else "AFTER")
|
||||||
|
append(" ")
|
||||||
|
append(
|
||||||
|
when (event) {
|
||||||
|
TriggerEvent.Insert -> "INSERT"
|
||||||
|
TriggerEvent.Update -> "UPDATE"
|
||||||
|
TriggerEvent.Delete -> "DELETE"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
append(" ON $tableName FOR EACH ROW")
|
||||||
|
whenClause?.let { append(" WHEN ${expression(it)}") }
|
||||||
|
append(" BEGIN ")
|
||||||
|
for (statement in body) {
|
||||||
|
append(write(statement))
|
||||||
|
append(" ")
|
||||||
|
}
|
||||||
|
append("END;")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSequenceText(statement: CreateSequenceStatement): String = buildString {
|
||||||
|
append("CREATE SEQUENCE ${statement.sequenceName}")
|
||||||
|
statement.incrementBy?.let { append(" INCREMENT BY $it") }
|
||||||
|
statement.minValue?.let { append(" MINVALUE $it") }
|
||||||
|
statement.maxValue?.let { append(" MAXVALUE $it") }
|
||||||
|
statement.startValue?.let { append(" START WITH $it") }
|
||||||
|
if (statement.cycle) append(" CYCLE")
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun alterSequenceText(statement: AlterSequenceStatement): String = buildString {
|
||||||
|
append("ALTER SEQUENCE ${statement.sequenceName}")
|
||||||
|
statement.incrementBy?.let { append(" INCREMENT BY $it") }
|
||||||
|
when {
|
||||||
|
statement.clearMinValue -> append(" NO MINVALUE")
|
||||||
|
statement.minValue != null -> append(" MINVALUE ${statement.minValue}")
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
statement.clearMaxValue -> append(" NO MAXVALUE")
|
||||||
|
statement.maxValue != null -> append(" MAXVALUE ${statement.maxValue}")
|
||||||
|
}
|
||||||
|
if (statement.restart) {
|
||||||
|
append(" RESTART")
|
||||||
|
statement.restartWith?.let { append(" WITH $it") }
|
||||||
|
}
|
||||||
|
when (statement.cycle) {
|
||||||
|
true -> append(" CYCLE")
|
||||||
|
false -> append(" NO CYCLE")
|
||||||
|
null -> Unit
|
||||||
|
}
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bakes the sequence's CURRENT position into `START WITH`, rather than dumping
|
||||||
|
* [SequenceDefinition.startValue] as written at CREATE time, so a reload resumes NEXTVAL
|
||||||
|
* exactly where this session left off instead of reissuing already-handed-out values - see
|
||||||
|
* [Sequence]'s own doc on why the counter (unlike the CREATE/DROP/ALTER SEQUENCE structure
|
||||||
|
* every other part of [dump] is free to just replay literally) needs this special treatment.
|
||||||
|
*/
|
||||||
|
private fun sequenceDumpText(sequence: Sequence): String {
|
||||||
|
val definition = sequence.definition
|
||||||
|
val cycle = if (definition.cycle) " CYCLE" else ""
|
||||||
|
return "CREATE SEQUENCE ${definition.name} INCREMENT BY ${definition.incrementBy} " +
|
||||||
|
"MINVALUE ${definition.minValue} MAXVALUE ${definition.maxValue} START WITH ${sequence.peekNext()}$cycle;"
|
||||||
|
}
|
||||||
|
|
||||||
fun dump(catalog: Catalog): String {
|
fun dump(catalog: Catalog): String {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
for (tableName in catalog.tableNames) {
|
for (tableName in catalog.tableNames) {
|
||||||
|
|||||||
Reference in new issue
Block a user