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 parseOr(): Expression {
|
||||
|
||||
Reference in new issue
Block a user