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 kestrel.catalog.Catalog
|
||||
import kestrel.catalog.CheckConstraint
|
||||
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.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.ChangeColumnType
|
||||
import kestrel.sql.ast.CollateExpression
|
||||
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.CreateSequenceStatement
|
||||
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.DropColumn
|
||||
import kestrel.sql.ast.DropConstraint
|
||||
import kestrel.sql.ast.DropIndexStatement
|
||||
import kestrel.sql.ast.DropMode
|
||||
import kestrel.sql.ast.DropSequenceStatement
|
||||
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.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.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.IsNullExpression
|
||||
import kestrel.sql.ast.JoinType
|
||||
import kestrel.sql.ast.LikeExpression
|
||||
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.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.UpdateStatement
|
||||
import kestrel.sql.ast.WindowExpression
|
||||
import kestrel.sql.ast.WithQuery
|
||||
import kestrel.storage.Collation
|
||||
import kestrel.storage.Value
|
||||
import kestrel.storage.displayWith
|
||||
|
||||
object SqlWriter {
|
||||
fun expressionText(expression: Expression): String = expression(expression)
|
||||
|
||||
fun canPersist(statement: Statement): Boolean = statement is CreateTableStatement ||
|
||||
statement is DropTableStatement ||
|
||||
statement is TruncateTableStatement ||
|
||||
statement is AlterTableStatement ||
|
||||
statement is CreateIndexStatement ||
|
||||
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 UpdateStatement ||
|
||||
statement is DeleteStatement
|
||||
statement is DeleteStatement ||
|
||||
statement is MergeStatement
|
||||
|
||||
fun write(statement: Statement): String = when (statement) {
|
||||
is CreateTableStatement -> "CREATE TABLE ${statement.tableName} (" +
|
||||
statement.columns.joinToString(", ") { columnDefinition(it) } + ");"
|
||||
is DropTableStatement -> "DROP TABLE ${statement.tableName};"
|
||||
is CreateTableStatement -> "CREATE ${if (statement.temporary) "TEMPORARY " else ""}TABLE ${statement.tableName} (" +
|
||||
createTableBodyText(
|
||||
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 ->
|
||||
"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 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 UpdateStatement -> writeUpdate(statement)
|
||||
is DeleteStatement -> writeDelete(statement)
|
||||
is MergeStatement -> writeMerge(statement)
|
||||
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 {
|
||||
val builder = StringBuilder()
|
||||
for (tableName in catalog.tableNames) {
|
||||
|
||||
Reference in new issue
Block a user