feat(ast): extend Ast with CreateTableStatement and DropMode
This commit is contained in:
1 parent
260d649276
commit
3eb5cd7fca
1 file changed
+192
-10
@@ -1,41 +1,223 @@
|
||||
package kestrel.sql.ast
|
||||
|
||||
import kestrel.catalog.CheckConstraint
|
||||
import kestrel.catalog.Column
|
||||
import kestrel.catalog.ForeignKeyConstraint
|
||||
import kestrel.catalog.UniqueConstraint
|
||||
import kestrel.storage.Collation
|
||||
import kestrel.storage.ColumnType
|
||||
import kestrel.storage.Value
|
||||
|
||||
sealed interface Statement
|
||||
|
||||
data class CreateTableStatement(val tableName: String, val columns: List<Column>) : Statement
|
||||
data class CreateTableStatement(
|
||||
val tableName: String,
|
||||
val columns: List<Column>,
|
||||
val foreignKeys: List<ForeignKeyConstraint> = emptyList(),
|
||||
val checks: List<CheckConstraint> = emptyList(),
|
||||
val uniqueConstraints: List<UniqueConstraint> = emptyList(),
|
||||
val compositePrimaryKey: List<String> = emptyList(),
|
||||
/** `CREATE TEMP TABLE`/`CREATE TEMPORARY TABLE` - see [kestrel.catalog.TableSchema.temporary]. */
|
||||
val temporary: Boolean = false
|
||||
) : Statement
|
||||
|
||||
data class DropTableStatement(val tableName: String) : Statement
|
||||
enum class DropMode { Restrict, Cascade }
|
||||
|
||||
data class DropTableStatement(val tableName: String, val mode: DropMode = DropMode.Restrict) : Statement
|
||||
|
||||
/**
|
||||
* `TRUNCATE TABLE t1 [, t2, ...] [RESTART IDENTITY | CONTINUE IDENTITY] [CASCADE | RESTRICT]`.
|
||||
* Unlike a WHERE-less DELETE, TRUNCATE never fires row-level triggers and never runs a FOREIGN KEY's
|
||||
* own ON DELETE action row by row - see [kestrel.catalog.Catalog.truncateTables]/
|
||||
* [kestrel.store.sql.DiskCatalog.truncateTables] for why [mode] means something structurally
|
||||
* different here than it does for [DropTableStatement]/[DropViewStatement].
|
||||
*/
|
||||
data class TruncateTableStatement(
|
||||
val tableNames: List<String>,
|
||||
val restartIdentity: Boolean = false,
|
||||
val mode: DropMode = DropMode.Restrict
|
||||
) : Statement
|
||||
|
||||
data class CreateIndexStatement(
|
||||
val indexName: String,
|
||||
val tableName: String,
|
||||
val columnName: String
|
||||
val columnNames: List<String>,
|
||||
val unique: Boolean = false
|
||||
) : Statement
|
||||
|
||||
data class DropIndexStatement(val indexName: String) : Statement
|
||||
|
||||
sealed interface AlterTableAction
|
||||
|
||||
data class AddColumn(val column: Column) : AlterTableAction
|
||||
|
||||
data class DropColumn(val columnName: String, val mode: DropMode = DropMode.Restrict) : AlterTableAction
|
||||
|
||||
data class RenameColumn(val oldName: String, val newName: String) : AlterTableAction
|
||||
|
||||
data class RenameTable(val newName: String) : AlterTableAction
|
||||
|
||||
/**
|
||||
* `ALTER TABLE t ALTER [COLUMN] col TYPE newtype [USING expression]` (Postgres syntax). [using],
|
||||
* when given, is evaluated per existing row against that row's OLD values (exactly like an UPDATE
|
||||
* SET assignment) and the result coerced into [newType]/[precision]/[scale]; when omitted it
|
||||
* defaults to a bare reference to [columnName] itself - "recompute this column as its own old
|
||||
* value, coerced to the new type" - so a widening change (INTEGER -> DECIMAL, say) needs no
|
||||
* explicit `USING` at all, matching Postgres' own default behavior.
|
||||
*/
|
||||
data class ChangeColumnType(
|
||||
val columnName: String,
|
||||
val newType: ColumnType,
|
||||
val precision: Int? = null,
|
||||
val scale: Int? = null,
|
||||
val using: Expression? = null
|
||||
) : AlterTableAction
|
||||
|
||||
/**
|
||||
* `ALTER TABLE t ADD CONSTRAINT name CHECK (...) | UNIQUE (...) | FOREIGN KEY (...) REFERENCES ...` -
|
||||
* the constraint's own name lives inside [AddedCheck.check]/[AddedUnique.unique]/[AddedForeignKey.foreignKey]
|
||||
* (each already carries a `name: String?`, shared with the identical table-level `CONSTRAINT name` clause
|
||||
* a `CREATE TABLE` can declare inline), not as a separate field here.
|
||||
*/
|
||||
sealed interface AddedConstraint
|
||||
data class AddedCheck(val check: CheckConstraint) : AddedConstraint
|
||||
data class AddedUnique(val unique: UniqueConstraint) : AddedConstraint
|
||||
data class AddedForeignKey(val foreignKey: ForeignKeyConstraint) : AddedConstraint
|
||||
|
||||
data class AddConstraint(val constraint: AddedConstraint) : AlterTableAction
|
||||
|
||||
/** `ALTER TABLE t DROP CONSTRAINT name [RESTRICT | CASCADE]` - see [kestrel.catalog.Catalog.dropConstraint]. */
|
||||
data class DropConstraint(val name: String, val mode: DropMode = DropMode.Restrict) : AlterTableAction
|
||||
|
||||
data class AlterTableStatement(val tableName: String, val action: AlterTableAction) : Statement
|
||||
|
||||
data class CreateViewStatement(
|
||||
val viewName: String,
|
||||
val query: QueryStatement,
|
||||
val orReplace: Boolean
|
||||
) : Statement
|
||||
|
||||
data class DropViewStatement(val viewName: String, val mode: DropMode = DropMode.Restrict) : Statement
|
||||
|
||||
data class InsertStatement(
|
||||
val tableName: String,
|
||||
val columns: List<String>?,
|
||||
val rows: List<List<Expression>>
|
||||
) : Statement
|
||||
val rows: List<List<Expression?>>,
|
||||
val onConflict: OnConflictClause? = null,
|
||||
val returning: Projection? = null
|
||||
) : PreparableStatement
|
||||
|
||||
/** Empty [targetColumns] means "any unique or primary key violation" (bare `ON CONFLICT DO ...`), matching Postgres. */
|
||||
data class OnConflictClause(val targetColumns: List<String>, val action: ConflictAction)
|
||||
|
||||
sealed interface ConflictAction
|
||||
|
||||
data object ConflictDoNothing : ConflictAction
|
||||
|
||||
data class ConflictDoUpdate(val assignments: List<Assignment>, val where: Expression?) : ConflictAction
|
||||
|
||||
/**
|
||||
* The statement kinds [PrepareStatement] can wrap: a SELECT/set-operation/CTE ([QueryStatement])
|
||||
* or a data-mutating [InsertStatement]/[UpdateStatement]/[DeleteStatement]/[MergeStatement] - the
|
||||
* same "any DML or query, never DDL/transaction control" boundary Postgres' own PREPARE draws.
|
||||
* Declared as its own sealed interface (rather than checking `is QueryStatement || is InsertStatement
|
||||
* || ...` by hand at every call site) so the compiler enforces [ParameterSubstitution]'s `when` stays
|
||||
* exhaustive as new statement kinds are added.
|
||||
*/
|
||||
sealed interface PreparableStatement : Statement
|
||||
|
||||
sealed interface QueryStatement : Statement, PreparableStatement
|
||||
|
||||
data class SelectStatement(
|
||||
val projection: Projection,
|
||||
val tableName: String,
|
||||
val tableAlias: String? = null,
|
||||
val from: FromItem,
|
||||
val joins: List<JoinClause> = emptyList(),
|
||||
val where: Expression? = null,
|
||||
/** For [RollupSpec]/[CubeSpec]/[GroupingSetsSpec], this is the deduplicated union of every expression across every one of [groupingSpec]'s grouping sets (order = first appearance) - NOT the flat comma list SQL text would suggest. Every existing consumer that only needs "which expressions are legal grouping columns" (validation, dependency analysis, rename propagation, the plain-`GROUP BY a, b` write-back path) reads this field and needs no awareness of [groupingSpec] at all; only [kestrel.engine.GroupAggregation], which needs the actual per-set breakdown, reads [groupingSpec]. */
|
||||
val groupBy: List<Expression> = emptyList(),
|
||||
val groupingSpec: GroupingSpec? = null,
|
||||
val having: Expression? = null,
|
||||
val orderBy: OrderBy? = null,
|
||||
val limit: Int? = null
|
||||
) : Statement
|
||||
val limit: Int? = null,
|
||||
/** `LIMIT`/`OFFSET` may be written in either order (matching Postgres) - applied together, after ORDER BY/DISTINCT dedup and before projection, skipping this many rows before [limit] (if any) caps what remains. */
|
||||
val offset: Int? = null,
|
||||
/**
|
||||
* `true` for both `SELECT DISTINCT ...` and `SELECT DISTINCT ON (...) ...` - `false` (the
|
||||
* default, also written explicitly as `SELECT ALL ...`) means every source row that survives
|
||||
* WHERE/GROUP BY/HAVING produces its own output row. See [distinctOn] for what distinguishes
|
||||
* the two `true` forms from each other.
|
||||
*/
|
||||
val distinct: Boolean = false,
|
||||
/**
|
||||
* Empty for plain `SELECT DISTINCT` (dedup by the *entire* output row, evaluated after
|
||||
* projection - see [kestrel.engine.SelectRunner]'s `Plan.projectRows`). Non-empty for
|
||||
* `SELECT DISTINCT ON (expr, ...) ...`: keeps only the first source row per distinct
|
||||
* combination of these expressions, evaluated the same way `GROUP BY` expressions are
|
||||
* (pre-projection, against the joined/filtered row) - never resolved against a `SELECT`-list
|
||||
* alias, matching Postgres' own grammar where `DISTINCT ON (...)` is parsed before the select
|
||||
* list even exists. "First" means first in `ORDER BY` order when one is given (which is why the
|
||||
* parser requires `ORDER BY`, if present at all, to start with exactly these expressions - the
|
||||
* whole point of `DISTINCT ON` is picking a deterministic row per group), otherwise first in
|
||||
* scan/join order. Deliberately rejected outright when combined with `GROUP BY` or aggregates -
|
||||
* unlike plain [distinct], there is no unsurprising way to define "first row of a group" once
|
||||
* the underlying rows have already been collapsed into aggregate groups.
|
||||
*/
|
||||
val distinctOn: List<Expression> = emptyList()
|
||||
) : QueryStatement
|
||||
|
||||
data class DeleteStatement(val tableName: String, val where: Expression?) : Statement
|
||||
/**
|
||||
* Distinguishes `GROUP BY ROLLUP (...)` / `CUBE (...)` / `GROUPING SETS (...)` from an ordinary
|
||||
* `GROUP BY a, b, ...` (represented by `groupingSpec == null`, the only case before this existed).
|
||||
* [kestrel.engine.GroupingSetExpansion] turns any of these into the concrete list of grouping sets
|
||||
* [kestrel.engine.GroupAggregation] actually groups by - one full pass over the source rows per set,
|
||||
* unioned together, so a single query reports subtotals (and, for [Rollup]/[Cube]/an explicit `()`
|
||||
* element, a grand total) alongside whatever detail rows survive. Mixing plain columns with
|
||||
* ROLLUP/CUBE in the same `GROUP BY` list (SQL allows this, producing a cross product of sets) is a
|
||||
* scope cut - the parser only accepts one of these forms standing alone as the entire clause.
|
||||
*/
|
||||
sealed interface GroupingSpec
|
||||
|
||||
data class RollupSpec(val expressions: List<Expression>) : GroupingSpec
|
||||
|
||||
data class CubeSpec(val expressions: List<Expression>) : GroupingSpec
|
||||
|
||||
/** Each element of [sets] is one grouping set as written; an empty inner list is the `()` grand-total set. */
|
||||
data class GroupingSetsSpec(val sets: List<List<Expression>>) : GroupingSpec
|
||||
|
||||
enum class SetOperator { Union, UnionAll, Intersect, IntersectAll, Except, ExceptAll }
|
||||
|
||||
data class SetOperationStatement(
|
||||
val operator: SetOperator,
|
||||
val left: QueryStatement,
|
||||
val right: QueryStatement,
|
||||
val orderBy: OrderBy? = null,
|
||||
val limit: Int? = null,
|
||||
val offset: Int? = null
|
||||
) : QueryStatement
|
||||
|
||||
data class CommonTableExpression(
|
||||
val name: String,
|
||||
val columns: List<String>?,
|
||||
val query: QueryStatement
|
||||
)
|
||||
|
||||
data class WithQuery(
|
||||
val recursive: Boolean,
|
||||
val ctes: List<CommonTableExpression>,
|
||||
val body: QueryStatement
|
||||
) : QueryStatement
|
||||
|
||||
sealed interface FromItem
|
||||
|
||||
data class TableSource(val name: String, val alias: String? = null) : FromItem
|
||||
|
||||
data class SubquerySource(val select: QueryStatement, val alias: String, val lateral: Boolean = false) : FromItem
|
||||
|
||||
data class DeleteStatement(
|
||||
val tableName: String,
|
||||
val where: Expression?,
|
||||
val returning: Projection? = null
|
||||
) : PreparableStatement
|
||||
|
||||
data class UpdateStatement(
|
||||
val tableName: String,
|
||||
|
||||
Reference in new issue
Block a user