feat(parser): extend Parser with parseCreateSequence
This commit is contained in:
1 parent
63bc1c01ba
commit
48fa9202a4
1 file changed
+195
-7
@@ -186,20 +186,133 @@ class Parser(private val tokens: List<Token>) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCreateIndex(): CreateIndexStatement {
|
||||
/**
|
||||
* Every numeric option (`INCREMENT [BY]`/`MINVALUE`/`MAXVALUE`/`START [WITH]`) may appear at
|
||||
* most once and in any order, matching Postgres; a repeated option silently keeps the last one
|
||||
* written rather than erroring, the same "last one wins" leniency [parseAlterTable] already
|
||||
* gives `ADD`/`DROP`/`RENAME` if written more than once in spirit (though that one can only ever
|
||||
* carry a single action). `NO MINVALUE`/`NO MAXVALUE` here are never distinguished from "not
|
||||
* written at all" - see [CreateSequenceStatement]'s own doc for why that's fine for CREATE
|
||||
* specifically (unlike [AlterSequenceStatement], there is no earlier bound to preserve).
|
||||
*/
|
||||
private fun parseCreateSequence(): CreateSequenceStatement {
|
||||
expect(TokenType.Sequence, "expected SEQUENCE")
|
||||
val name = expectIdentifier("expected sequence name")
|
||||
var incrementBy: Long? = null
|
||||
var minValue: Long? = null
|
||||
var maxValue: Long? = null
|
||||
var startValue: Long? = null
|
||||
var cycle = false
|
||||
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.Start) -> {
|
||||
match(TokenType.With)
|
||||
startValue = expectLongLiteral("expected a value after START WITH")
|
||||
}
|
||||
match(TokenType.Cycle) -> cycle = true
|
||||
check(TokenType.No) && peekIsSequenceNoClause() -> {
|
||||
advance()
|
||||
when {
|
||||
match(TokenType.Minvalue) -> minValue = null
|
||||
match(TokenType.Maxvalue) -> maxValue = null
|
||||
match(TokenType.Cycle) -> cycle = false
|
||||
else -> error("unreachable: guarded by peekIsSequenceNoClause")
|
||||
}
|
||||
}
|
||||
else -> break
|
||||
}
|
||||
}
|
||||
return CreateSequenceStatement(name, incrementBy, minValue, maxValue, startValue, cycle)
|
||||
}
|
||||
|
||||
/** Distinguishes a sequence option's `NO MINVALUE|MAXVALUE|CYCLE` from `ON DELETE NO ACTION`'s unrelated `NO` - both start with the same token. */
|
||||
private fun peekIsSequenceNoClause(): Boolean =
|
||||
peekAt(1)?.type in setOf(TokenType.Minvalue, TokenType.Maxvalue, TokenType.Cycle)
|
||||
|
||||
private fun peekAt(offset: Int): Token? = tokens.getOrNull(position + offset)
|
||||
|
||||
/**
|
||||
* `body` is deliberately restricted to INSERT/UPDATE/DELETE ([parseTriggerBodyStatement]) -
|
||||
* the only statement kinds with a side effect worth firing, matching SQLite's own trigger-body
|
||||
* grammar. The closing `END` here is the trigger body's own terminator, distinct from the `;`
|
||||
* `parseProgram` still expects after the whole CREATE TRIGGER statement.
|
||||
*/
|
||||
private fun parseCreateTrigger(): CreateTriggerStatement {
|
||||
expect(TokenType.Trigger, "expected TRIGGER")
|
||||
val triggerName = expectIdentifier("expected trigger name")
|
||||
val timing = when {
|
||||
match(TokenType.Before) -> TriggerTiming.Before
|
||||
match(TokenType.After) -> TriggerTiming.After
|
||||
else -> throw error("expected BEFORE or AFTER after trigger name but found '${describe(peek())}'", peek())
|
||||
}
|
||||
val event = when {
|
||||
match(TokenType.Insert) -> TriggerEvent.Insert
|
||||
match(TokenType.Update) -> TriggerEvent.Update
|
||||
match(TokenType.Delete) -> TriggerEvent.Delete
|
||||
else -> throw error("expected INSERT, UPDATE, or DELETE after ${timing.name.uppercase()} but found '${describe(peek())}'", peek())
|
||||
}
|
||||
expect(TokenType.On, "expected ON after trigger event")
|
||||
val tableName = expectIdentifier("expected table name")
|
||||
expect(TokenType.For, "expected FOR after table name")
|
||||
expect(TokenType.Each, "expected EACH after FOR")
|
||||
expect(TokenType.Row, "expected ROW after EACH")
|
||||
val whenClause = if (match(TokenType.When)) parseExpression() else null
|
||||
expect(TokenType.Begin, "expected BEGIN to start trigger body")
|
||||
val body = mutableListOf<Statement>()
|
||||
while (!check(TokenType.End)) {
|
||||
body.add(parseTriggerBodyStatement())
|
||||
expect(TokenType.Semicolon, "expected ';' after trigger body statement")
|
||||
}
|
||||
expect(TokenType.End, "expected END to close trigger body")
|
||||
return CreateTriggerStatement(triggerName, timing, event, tableName, whenClause, body)
|
||||
}
|
||||
|
||||
private fun parseTriggerBodyStatement(): Statement = when (peek().type) {
|
||||
TokenType.Insert -> parseInsert()
|
||||
TokenType.Update -> parseUpdate()
|
||||
TokenType.Delete -> parseDelete()
|
||||
else -> throw error(
|
||||
"trigger body only supports INSERT, UPDATE, or DELETE statements but found '${describe(peek())}'",
|
||||
peek()
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseCreateView(orReplace: Boolean): CreateViewStatement {
|
||||
expect(TokenType.View, "expected VIEW")
|
||||
val viewName = expectIdentifier("expected view name")
|
||||
expect(TokenType.As, "expected AS after view name")
|
||||
val query = parseQueryStatement()
|
||||
return CreateViewStatement(viewName, query, orReplace)
|
||||
}
|
||||
|
||||
private fun parseCreateUniqueIndex(): CreateIndexStatement {
|
||||
expect(TokenType.Unique, "expected UNIQUE")
|
||||
return parseCreateIndex(unique = true)
|
||||
}
|
||||
|
||||
private fun parseCreateIndex(unique: Boolean): CreateIndexStatement {
|
||||
expect(TokenType.Index, "expected INDEX")
|
||||
val indexName = expectIdentifier("expected index name")
|
||||
expect(TokenType.On, "expected ON after index name")
|
||||
val tableName = expectIdentifier("expected table name")
|
||||
expect(TokenType.LeftParen, "expected '(' before indexed column")
|
||||
val columnName = expectIdentifier("expected column name")
|
||||
val columnNames = mutableListOf(expectIdentifier("expected column name"))
|
||||
while (match(TokenType.Comma)) {
|
||||
columnNames.add(expectIdentifier("expected column name"))
|
||||
}
|
||||
expect(TokenType.RightParen, "expected ')' after indexed column")
|
||||
return CreateIndexStatement(indexName, tableName, columnName)
|
||||
return CreateIndexStatement(indexName, tableName, columnNames, unique)
|
||||
}
|
||||
|
||||
private fun parseExplain(): ExplainStatement {
|
||||
expect(TokenType.Explain, "expected EXPLAIN")
|
||||
return ExplainStatement(parseSelect())
|
||||
return ExplainStatement(parseQueryStatement())
|
||||
}
|
||||
|
||||
private fun parseBegin(): Statement {
|
||||
@@ -216,20 +329,95 @@ class Parser(private val tokens: List<Token>) {
|
||||
|
||||
private fun parseRollback(): Statement {
|
||||
expect(TokenType.Rollback, "expected ROLLBACK")
|
||||
if (match(TokenType.To)) {
|
||||
match(TokenType.Savepoint)
|
||||
val name = expectIdentifier("expected savepoint name after ROLLBACK TO")
|
||||
return RollbackToSavepointStatement(name)
|
||||
}
|
||||
match(TokenType.Transaction)
|
||||
return RollbackStatement
|
||||
}
|
||||
|
||||
private fun parseCreateTable(): CreateTableStatement {
|
||||
private fun parseSavepoint(): Statement {
|
||||
expect(TokenType.Savepoint, "expected SAVEPOINT")
|
||||
val name = expectIdentifier("expected savepoint name after SAVEPOINT")
|
||||
return SavepointStatement(name)
|
||||
}
|
||||
|
||||
private fun parseReleaseSavepoint(): Statement {
|
||||
expect(TokenType.Release, "expected RELEASE")
|
||||
match(TokenType.Savepoint)
|
||||
val name = expectIdentifier("expected savepoint name after RELEASE")
|
||||
return ReleaseSavepointStatement(name)
|
||||
}
|
||||
|
||||
private fun parsePrepare(): Statement {
|
||||
expect(TokenType.Prepare, "expected PREPARE")
|
||||
val name = expectIdentifier("expected prepared statement name")
|
||||
expect(TokenType.As, "expected AS after prepared statement name")
|
||||
return PrepareStatement(name, parsePreparableStatement())
|
||||
}
|
||||
|
||||
private fun parsePreparableStatement(): PreparableStatement = when (peek().type) {
|
||||
TokenType.Select, TokenType.With -> parseQueryStatement()
|
||||
TokenType.Insert -> parseInsert()
|
||||
TokenType.Update -> parseUpdate()
|
||||
TokenType.Delete -> parseDelete()
|
||||
TokenType.Merge -> parseMerge()
|
||||
else -> throw error(
|
||||
"PREPARE only supports SELECT, INSERT, UPDATE, DELETE, or MERGE but found '${describe(peek())}'",
|
||||
peek()
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseExecute(): Statement {
|
||||
expect(TokenType.Execute, "expected EXECUTE")
|
||||
val name = expectIdentifier("expected prepared statement name")
|
||||
val arguments = mutableListOf<Expression>()
|
||||
if (match(TokenType.LeftParen)) {
|
||||
if (!check(TokenType.RightParen)) {
|
||||
arguments.add(parseExpression())
|
||||
while (match(TokenType.Comma)) arguments.add(parseExpression())
|
||||
}
|
||||
expect(TokenType.RightParen, "expected ')' after EXECUTE arguments")
|
||||
}
|
||||
return ExecuteStatement(name, arguments)
|
||||
}
|
||||
|
||||
private fun parseDeallocate(): Statement {
|
||||
expect(TokenType.Deallocate, "expected DEALLOCATE")
|
||||
match(TokenType.Prepare)
|
||||
if (match(TokenType.All)) return DeallocateStatement(null, all = true)
|
||||
val name = expectIdentifier("expected prepared statement name or ALL after DEALLOCATE")
|
||||
return DeallocateStatement(name)
|
||||
}
|
||||
|
||||
private fun parseCreateTable(temporary: Boolean = false): CreateTableStatement {
|
||||
expect(TokenType.Table, "expected TABLE after CREATE")
|
||||
val tableName = expectIdentifier("expected table name")
|
||||
expect(TokenType.LeftParen, "expected '(' after table name")
|
||||
val columns = mutableListOf<Column>()
|
||||
val foreignKeys = mutableListOf<ForeignKeyConstraint>()
|
||||
val checks = mutableListOf<CheckConstraint>()
|
||||
val uniqueConstraints = mutableListOf<UniqueConstraint>()
|
||||
var compositePrimaryKey: List<String> = emptyList()
|
||||
do {
|
||||
columns.add(parseColumnDefinition())
|
||||
when {
|
||||
check(TokenType.Constraint) -> parseNamedTableConstraint(foreignKeys, checks, uniqueConstraints)
|
||||
check(TokenType.Foreign) -> foreignKeys.add(parseTableForeignKey())
|
||||
check(TokenType.Check) -> checks.add(parseTableCheck())
|
||||
check(TokenType.Unique) -> uniqueConstraints.add(parseTableUnique())
|
||||
check(TokenType.Primary) -> {
|
||||
if (compositePrimaryKey.isNotEmpty()) {
|
||||
throw error("table declares more than one PRIMARY KEY clause", peek())
|
||||
}
|
||||
compositePrimaryKey = parseTablePrimaryKey()
|
||||
}
|
||||
else -> columns.add(parseColumnDefinition(foreignKeys, checks, uniqueConstraints))
|
||||
}
|
||||
} while (match(TokenType.Comma))
|
||||
expect(TokenType.RightParen, "expected ')' after column definitions")
|
||||
return CreateTableStatement(tableName, columns)
|
||||
return CreateTableStatement(tableName, columns, foreignKeys, checks, uniqueConstraints, compositePrimaryKey, temporary)
|
||||
}
|
||||
|
||||
private fun parseColumnDefinition(): Column {
|
||||
|
||||
Reference in new issue
Block a user