feat(parser): extend Parser with parseTruncate and parseDropMode
This commit is contained in:
1 parent
81653b5d5a
commit
89c9c7e6ca
1 file changed
+191
@@ -1129,6 +1129,197 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `TRUNCATE [TABLE] t1 [, t2, ...] [RESTART IDENTITY | CONTINUE IDENTITY] [CASCADE | RESTRICT]`.
|
||||||
|
* `TABLE` and the identity clause are both optional, matching Postgres (which also defaults to
|
||||||
|
* CONTINUE IDENTITY when neither RESTART nor CONTINUE is written).
|
||||||
|
*/
|
||||||
|
private fun parseTruncate(): TruncateTableStatement {
|
||||||
|
expect(TokenType.Truncate, "expected TRUNCATE")
|
||||||
|
match(TokenType.Table)
|
||||||
|
val tableNames = mutableListOf(expectIdentifier("expected table name"))
|
||||||
|
while (match(TokenType.Comma)) {
|
||||||
|
tableNames.add(expectIdentifier("expected table name"))
|
||||||
|
}
|
||||||
|
val restartIdentity = when {
|
||||||
|
match(TokenType.Restart) -> {
|
||||||
|
expect(TokenType.Identity, "expected IDENTITY after RESTART")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
match(TokenType.Continue) -> {
|
||||||
|
expect(TokenType.Identity, "expected IDENTITY after CONTINUE")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
return TruncateTableStatement(tableNames, restartIdentity, parseDropMode())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDropMode(): DropMode = when {
|
||||||
|
match(TokenType.Cascade) -> DropMode.Cascade
|
||||||
|
match(TokenType.Restrict) -> DropMode.Restrict
|
||||||
|
else -> DropMode.Restrict
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseAlter(): Statement {
|
||||||
|
expect(TokenType.Alter, "expected ALTER")
|
||||||
|
return when {
|
||||||
|
check(TokenType.Table) -> parseAlterTable()
|
||||||
|
check(TokenType.Sequence) -> parseAlterSequence()
|
||||||
|
else -> throw error("expected TABLE or SEQUENCE after ALTER but found '${describe(peek())}'", peek())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every option may appear at most once and in any order, same leniency as
|
||||||
|
* [parseCreateSequence]. `NO MINVALUE`/`NO MAXVALUE` here are NOT equivalent to leaving the
|
||||||
|
* option out entirely - see [AlterSequenceStatement]'s own doc.
|
||||||
|
*/
|
||||||
|
private fun parseAlterSequence(): AlterSequenceStatement {
|
||||||
|
expect(TokenType.Sequence, "expected SEQUENCE")
|
||||||
|
val name = expectIdentifier("expected sequence name")
|
||||||
|
var incrementBy: Long? = null
|
||||||
|
var minValue: Long? = null
|
||||||
|
var clearMinValue = false
|
||||||
|
var maxValue: Long? = null
|
||||||
|
var clearMaxValue = false
|
||||||
|
var restart = false
|
||||||
|
var restartWith: Long? = null
|
||||||
|
var cycle: Boolean? = null
|
||||||
|
while (true) {
|
||||||
|
when {
|
||||||
|
match(TokenType.Increment) -> {
|
||||||
|
match(TokenType.By)
|
||||||
|
incrementBy = expectLongLiteral("expected a value after INCREMENT BY")
|
||||||
|
}
|
||||||
|
match(TokenType.Minvalue) -> minValue = expectLongLiteral("expected a value after MINVALUE")
|
||||||
|
match(TokenType.Maxvalue) -> maxValue = expectLongLiteral("expected a value after MAXVALUE")
|
||||||
|
match(TokenType.Cycle) -> cycle = true
|
||||||
|
match(TokenType.Restart) -> {
|
||||||
|
restart = true
|
||||||
|
if (match(TokenType.With)) restartWith = expectLongLiteral("expected a value after RESTART WITH")
|
||||||
|
}
|
||||||
|
check(TokenType.No) && peekIsSequenceNoClause() -> {
|
||||||
|
advance()
|
||||||
|
when {
|
||||||
|
match(TokenType.Minvalue) -> clearMinValue = true
|
||||||
|
match(TokenType.Maxvalue) -> clearMaxValue = true
|
||||||
|
match(TokenType.Cycle) -> cycle = false
|
||||||
|
else -> error("unreachable: guarded by peekIsSequenceNoClause")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AlterSequenceStatement(name, incrementBy, minValue, clearMinValue, maxValue, clearMaxValue, restart, restartWith, cycle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseAlterTable(): AlterTableStatement {
|
||||||
|
expect(TokenType.Table, "expected TABLE after ALTER")
|
||||||
|
val tableName = expectIdentifier("expected table name")
|
||||||
|
val action: AlterTableAction = when {
|
||||||
|
match(TokenType.Add) -> {
|
||||||
|
if (match(TokenType.Column)) {
|
||||||
|
AddColumn(parseAlterAddColumnDefinition())
|
||||||
|
} else {
|
||||||
|
expect(TokenType.Constraint, "expected COLUMN or CONSTRAINT after ADD")
|
||||||
|
val name = expectIdentifier("expected constraint name after CONSTRAINT")
|
||||||
|
val constraint: AddedConstraint = when {
|
||||||
|
check(TokenType.Foreign) -> AddedForeignKey(parseTableForeignKey().copy(name = name))
|
||||||
|
check(TokenType.Check) -> AddedCheck(parseTableCheck().copy(name = name))
|
||||||
|
check(TokenType.Unique) -> AddedUnique(parseTableUnique().copy(name = name))
|
||||||
|
else -> throw error(
|
||||||
|
"expected FOREIGN KEY, CHECK, or UNIQUE after CONSTRAINT $name but found '${describe(peek())}'",
|
||||||
|
peek()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AddConstraint(constraint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match(TokenType.Drop) -> {
|
||||||
|
if (match(TokenType.Column)) {
|
||||||
|
val columnName = expectIdentifier("expected column name")
|
||||||
|
DropColumn(columnName, parseDropMode())
|
||||||
|
} else {
|
||||||
|
expect(TokenType.Constraint, "expected COLUMN or CONSTRAINT after DROP")
|
||||||
|
val name = expectIdentifier("expected constraint name")
|
||||||
|
DropConstraint(name, parseDropMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match(TokenType.Rename) -> {
|
||||||
|
if (match(TokenType.To)) {
|
||||||
|
val newName = expectIdentifier("expected new table name")
|
||||||
|
RenameTable(newName)
|
||||||
|
} else {
|
||||||
|
expect(TokenType.Column, "expected COLUMN or TO after RENAME")
|
||||||
|
val oldName = expectIdentifier("expected column name")
|
||||||
|
expect(TokenType.To, "expected TO after column name")
|
||||||
|
val newName = expectIdentifier("expected new column name")
|
||||||
|
RenameColumn(oldName, newName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match(TokenType.Alter) -> {
|
||||||
|
match(TokenType.Column)
|
||||||
|
val columnName = expectIdentifier("expected column name")
|
||||||
|
if (!isContextualKeyword("TYPE")) {
|
||||||
|
throw error("expected TYPE after column name in ALTER TABLE ... ALTER COLUMN", peek())
|
||||||
|
}
|
||||||
|
advance()
|
||||||
|
val spec = parseTypeSpec()
|
||||||
|
val using = if (match(TokenType.Using)) parseExpression() else null
|
||||||
|
ChangeColumnType(columnName, spec.type, spec.precision, spec.scale, using)
|
||||||
|
}
|
||||||
|
else -> throw error(
|
||||||
|
"expected ADD, DROP, RENAME, or ALTER after table name in ALTER TABLE but found '${describe(peek())}'",
|
||||||
|
peek()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return AlterTableStatement(tableName, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deliberately restricted column definition: no PRIMARY KEY/REFERENCES/UNIQUE/CHECK, since
|
||||||
|
* enforcing those against a table that may already hold rows (composite-key interactions,
|
||||||
|
* FK target validation, CASCADE bookkeeping) is out of scope for a single ADD COLUMN - CREATE
|
||||||
|
* UNIQUE INDEX already covers UNIQUE on an existing table, and CREATE TABLE covers the rest.
|
||||||
|
* GENERATED ALWAYS AS is allowed, unlike those - see `Executor.executeAddColumn`/
|
||||||
|
* `DiskExecutor.executeAddColumn` for why it does not share that "out of scope" reasoning
|
||||||
|
* (it backfills by evaluating the expression per existing row, not by writing one constant).
|
||||||
|
*/
|
||||||
|
private fun parseAlterAddColumnDefinition(): Column {
|
||||||
|
val columnName = expectIdentifier("expected column name")
|
||||||
|
val spec = parseTypeSpec()
|
||||||
|
var notNull = false
|
||||||
|
var default: Expression? = null
|
||||||
|
var generated: Expression? = null
|
||||||
|
var collation = Collation.Binary
|
||||||
|
while (true) {
|
||||||
|
when {
|
||||||
|
check(TokenType.Not) -> {
|
||||||
|
advance()
|
||||||
|
expect(TokenType.Null, "expected NULL after NOT")
|
||||||
|
notNull = true
|
||||||
|
}
|
||||||
|
check(TokenType.Default) -> {
|
||||||
|
advance()
|
||||||
|
default = parseExpression()
|
||||||
|
}
|
||||||
|
isContextualKeyword("GENERATED") -> generated = parseGeneratedClause()
|
||||||
|
check(TokenType.Collate) -> collation = parseColumnCollation()
|
||||||
|
check(TokenType.Primary) -> throw error("ALTER TABLE ADD COLUMN cannot declare PRIMARY KEY", peek())
|
||||||
|
check(TokenType.References) -> throw error("ALTER TABLE ADD COLUMN cannot declare REFERENCES", peek())
|
||||||
|
check(TokenType.Unique) ->
|
||||||
|
throw error("ALTER TABLE ADD COLUMN cannot declare UNIQUE; use CREATE UNIQUE INDEX instead", peek())
|
||||||
|
check(TokenType.Check) -> throw error("ALTER TABLE ADD COLUMN cannot declare CHECK", peek())
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (generated != null && default != null) {
|
||||||
|
throw error("a column cannot declare both DEFAULT and GENERATED ALWAYS AS", peek())
|
||||||
|
}
|
||||||
|
return Column(columnName, spec.type, notNull, primaryKey = false, spec.precision, spec.scale, default, generated, collation)
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseExpression(): Expression = parseOr()
|
private fun parseExpression(): Expression = parseOr()
|
||||||
|
|
||||||
private fun parseOr(): Expression {
|
private fun parseOr(): Expression {
|
||||||
|
|||||||
Reference in new issue
Block a user