diff --git a/src/test/kotlin/kestrel/ParserTest.kt b/src/test/kotlin/kestrel/ParserTest.kt index 4ffe6c5..e31f0f6 100644 --- a/src/test/kotlin/kestrel/ParserTest.kt +++ b/src/test/kotlin/kestrel/ParserTest.kt @@ -1803,4 +1803,177 @@ class ParserTest { @Test fun parsesGeneratedAlwaysAsStoredInAlterTableAddColumn() { val statement = parseOne("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED") as AlterTableStatement + val column = (statement.action as AddColumn).column + assertEquals( + BinaryExpression(BinaryOperator.Multiply, ColumnExpression("a"), LiteralExpression(Value.IntegerValue(2))), + column.generated + ) + assertEquals(null, column.default) + } + + @Test + fun alterTableAddColumnGeneratedRejectsCombiningWithDefault() { + assertFailsWith { + parse("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT 1 GENERATED ALWAYS AS (a) STORED") + } + } + + @Test + fun alterTableAddColumnGeneratedStillRejectsPrimaryKey() { + assertFailsWith { + parse("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a) STORED PRIMARY KEY") + } + } + + @Test + fun parsesCreateTriggerWithTimingEventAndBody() { + val statement = parseOne( + "CREATE TRIGGER log AFTER INSERT ON orders FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END" + ) as CreateTriggerStatement + assertEquals("log", statement.triggerName) + assertEquals(TriggerTiming.After, statement.timing) + assertEquals(TriggerEvent.Insert, statement.event) + assertEquals("orders", statement.tableName) + assertEquals(null, statement.whenClause) + assertEquals(1, statement.body.size) + assertTrue(statement.body.single() is InsertStatement) + } + + @Test + fun parsesCreateTriggerWithWhenClauseAndMultipleBodyStatements() { + val statement = parseOne( + "CREATE TRIGGER guard BEFORE UPDATE ON accounts FOR EACH ROW WHEN NEW.balance < 0 " + + "BEGIN UPDATE accounts SET balance = 0 WHERE id = NEW.id; DELETE FROM overdrafts WHERE account_id = NEW.id; END" + ) as CreateTriggerStatement + assertEquals(TriggerTiming.Before, statement.timing) + assertEquals(TriggerEvent.Update, statement.event) + assertTrue(statement.whenClause is BinaryExpression) + assertEquals(2, statement.body.size) + assertTrue(statement.body[0] is UpdateStatement) + assertTrue(statement.body[1] is DeleteStatement) + } + + @Test + fun parsesDropTrigger() { + val statement = parseOne("DROP TRIGGER log") as DropTriggerStatement + assertEquals("log", statement.triggerName) + } + + @Test + fun createTriggerRejectsNonDmlBodyStatement() { + assertFailsWith { parse("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN SELECT 1; END") } + } + + @Test + fun createTriggerRequiresForEachRow() { + assertFailsWith { parse("CREATE TRIGGER log AFTER INSERT ON t BEGIN DELETE FROM t WHERE 1 = 0; END") } + } + + @Test + fun createTriggerRequiresMatchingEnd() { + assertFailsWith { parse("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0;") } + } + + @Test + fun createTriggerRequiresTimingAndEventKeywords() { + assertFailsWith { parse("CREATE TRIGGER log ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END") } + assertFailsWith { parse("CREATE TRIGGER log AFTER ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END") } + } + + @Test + fun parsesCreateSequenceWithNoOptions() { + val statement = parseOne("CREATE SEQUENCE seq") as CreateSequenceStatement + assertEquals("seq", statement.sequenceName) + assertEquals(null, statement.incrementBy) + assertEquals(null, statement.minValue) + assertEquals(null, statement.maxValue) + assertEquals(null, statement.startValue) + assertEquals(false, statement.cycle) + } + + @Test + fun parsesCreateSequenceWithEveryOptionInAnyOrder() { + val statement = parseOne("CREATE SEQUENCE seq CYCLE START WITH 10 MAXVALUE 100 MINVALUE 1 INCREMENT BY 2") as CreateSequenceStatement + assertEquals(2L, statement.incrementBy) + assertEquals(1L, statement.minValue) + assertEquals(100L, statement.maxValue) + assertEquals(10L, statement.startValue) + assertEquals(true, statement.cycle) + } + + @Test + fun parsesCreateSequenceWithNegativeBounds() { + val statement = parseOne("CREATE SEQUENCE seq INCREMENT BY -1 MINVALUE -100 START WITH -1") as CreateSequenceStatement + assertEquals(-1L, statement.incrementBy) + assertEquals(-100L, statement.minValue) + assertEquals(-1L, statement.startValue) + } + + @Test + fun parsesDropSequence() { + val statement = parseOne("DROP SEQUENCE seq") as DropSequenceStatement + assertEquals("seq", statement.sequenceName) + } + + @Test + fun parsesAlterSequenceRestartWithAndIncrementBy() { + val statement = parseOne("ALTER SEQUENCE seq INCREMENT BY 5 RESTART WITH 1") as AlterSequenceStatement + assertEquals("seq", statement.sequenceName) + assertEquals(5L, statement.incrementBy) + assertEquals(true, statement.restart) + assertEquals(1L, statement.restartWith) + } + + @Test + fun parsesAlterSequenceBareRestart() { + val statement = parseOne("ALTER SEQUENCE seq RESTART") as AlterSequenceStatement + assertEquals(true, statement.restart) + assertEquals(null, statement.restartWith) + } + + @Test + fun parsesAlterSequenceNoMinvalueNoMaxvalueNoCycle() { + val statement = parseOne("ALTER SEQUENCE seq NO MINVALUE NO MAXVALUE NO CYCLE") as AlterSequenceStatement + assertEquals(true, statement.clearMinValue) + assertEquals(true, statement.clearMaxValue) + assertEquals(false, statement.cycle) + } + + @Test + fun alterDispatchesToTableOrSequenceByFollowingKeyword() { + assertTrue(parseOne("ALTER TABLE t ADD COLUMN x INTEGER") is AlterTableStatement) + assertTrue(parseOne("ALTER SEQUENCE seq INCREMENT BY 1") is AlterSequenceStatement) + assertFailsWith { parse("ALTER VIEW v RENAME TO v2") } + } + + @Test + fun parsesNextvalAndCurrvalAsDedicatedExpressions() { + val select = parseOne("SELECT NEXTVAL('seq'), CURRVAL('seq') FROM t") as SelectStatement + val items = (select.projection as ExpressionProjection).items + assertEquals(NextValueExpression("seq"), items[0].expression) + assertEquals(CurrentValueExpression("seq"), items[1].expression) + } + + @Test + fun nextvalRequiresASingleStringLiteralArgument() { + assertFailsWith { parse("SELECT NEXTVAL(seq_column)") } + assertFailsWith { parse("SELECT NEXTVAL('a', 'b')") } + assertFailsWith { parse("SELECT NEXTVAL()") } + } + + @Test + fun parsesGroupByRollup() { + val statement = parseOne("SELECT dept, region, SUM(amount) FROM sales GROUP BY ROLLUP (dept, region)") as SelectStatement + val spec = statement.groupingSpec as RollupSpec + assertEquals(listOf("dept", "region"), spec.expressions.map { (it as ColumnExpression).name }) + assertEquals(listOf("dept", "region"), statement.groupBy.map { (it as ColumnExpression).name }) + } + + @Test + fun parsesGroupByCube() { + val statement = parseOne("SELECT dept, region, SUM(amount) FROM sales GROUP BY CUBE (dept, region)") as SelectStatement + val spec = statement.groupingSpec as CubeSpec + assertEquals(listOf("dept", "region"), spec.expressions.map { (it as ColumnExpression).name }) + } + }