feat(parser): extend Parser with parseCreateSequence

This commit is contained in:
dualfroz committed 2026-09-02 21:41:00 +02:00
1 parent 63bc1c01ba
commit 48fa9202a4
1 file changed
+195 -7
+195 -7
View File
@@ -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 {