diff --git a/src/test/kotlin/kestrel/ParserTest.kt b/src/test/kotlin/kestrel/ParserTest.kt index 0476269..a417fad 100644 --- a/src/test/kotlin/kestrel/ParserTest.kt +++ b/src/test/kotlin/kestrel/ParserTest.kt @@ -1111,4 +1111,177 @@ class ParserTest { fun columnLevelCollateBeforeDefaultAttachesToTheColumnNotTheDefaultExpression() { val statement = parseOne("CREATE TABLE t (name TEXT COLLATE NOCASE DEFAULT 'x')") as CreateTableStatement assertEquals(Collation.NoCase, statement.columns[0].collation) + assertEquals(LiteralExpression(Value.TextValue("x")), statement.columns[0].default) + } + + @Test + fun columnLevelCollateWithAnUnknownSequenceNameFailsToParse() { + assertFailsWith { parse("CREATE TABLE t (name TEXT COLLATE BOGUS)") } + } + + @Test + fun columnLevelCollateWithoutAFollowingNameFailsToParse() { + assertFailsWith { parse("CREATE TABLE t (name TEXT COLLATE)") } + } + + @Test + fun parsesColumnLevelCollateInAlterTableAddColumn() { + val statement = parseOne("ALTER TABLE t ADD COLUMN name TEXT COLLATE NOCASE") as AlterTableStatement + val action = statement.action as AddColumn + assertEquals(Collation.NoCase, action.column.collation) + } + + @Test + fun parsesDerivedTableInFromWithRequiredAlias() { + val statement = parseOne("SELECT s.id FROM (SELECT id FROM users) AS s") as SelectStatement + val from = statement.from as SubquerySource + assertEquals("s", from.alias) + assertEquals("users", tableName(from.select)) + } + + @Test + fun parsesDerivedTableWithImplicitAlias() { + val statement = parseOne("SELECT s.id FROM (SELECT id FROM users) s") as SelectStatement + assertEquals("s", (statement.from as SubquerySource).alias) + } + + @Test + fun derivedTableWithoutAliasFails() { + assertFailsWith { parseOne("SELECT id FROM (SELECT id FROM users)") } + } + + @Test + fun parsesDerivedTableAsJoinTarget() { + val statement = parseOne( + "SELECT * FROM users JOIN (SELECT dept, COUNT(*) AS n FROM users GROUP BY dept) AS d ON users.dept = d.dept" + ) as SelectStatement + val joinFrom = statement.joins.single().from as SubquerySource + assertEquals("d", joinFrom.alias) + } + + @Test + fun parsesLateralJoinMarksTheSubquerySource() { + val statement = parseOne( + "SELECT * FROM users u JOIN LATERAL (SELECT id FROM orders WHERE orders.userId = u.id) AS o ON TRUE" + ) as SelectStatement + val joinFrom = statement.joins.single().from as SubquerySource + assertEquals("o", joinFrom.alias) + assertTrue(joinFrom.lateral) + } + + @Test + fun nonLateralDerivedTableJoinIsNotMarkedLateral() { + val statement = parseOne("SELECT * FROM users JOIN (SELECT id FROM orders) AS o ON TRUE") as SelectStatement + assertFalse((statement.joins.single().from as SubquerySource).lateral) + } + + @Test + fun lateralBeforeAPlainTableFails() { + assertFailsWith { parseOne("SELECT * FROM users JOIN LATERAL orders ON TRUE") } + } + + @Test + fun parsesRankingWindowFunctionWithPartitionAndOrder() { + val statement = parseOne( + "SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM users" + ) as SelectStatement + val item = (statement.projection as ExpressionProjection).items.single() + val window = item.expression as WindowExpression + assertEquals("ROW_NUMBER", window.name) + assertEquals(WindowFunctionKind.RowNumber, window.kind) + assertNull(window.argument) + assertFalse(window.star) + assertEquals(listOf(ColumnExpression("dept")), window.partitionBy) + assertEquals(1, window.orderBy.size) + assertEquals(ColumnExpression("salary"), window.orderBy.single().expression) + assertTrue(window.orderBy.single().descending) + assertEquals("rn", item.alias) + } + + @Test + fun parsesAggregateWindowFunctionWithStarAndNoPartition() { + val statement = parseOne("SELECT COUNT(*) OVER () FROM users") as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals(WindowFunctionKind.Aggregate, window.kind) + assertTrue(window.star) + assertNull(window.argument) + assertTrue(window.partitionBy.isEmpty()) + assertTrue(window.orderBy.isEmpty()) + } + + @Test + fun aggregateWindowFunctionWithSingleArgumentAndOrderBy() { + val statement = parseOne("SELECT SUM(salary) OVER (ORDER BY id) FROM users") as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals("SUM", window.name) + assertEquals(ColumnExpression("salary"), window.argument) + assertFalse(window.star) + assertEquals(listOf(ColumnExpression("id")), window.orderBy.map { it.expression }) + } + + @Test + fun windowOverOrderByParsesAnExplicitNullsFirstClause() { + val statement = parseOne("SELECT SUM(salary) OVER (ORDER BY bonus NULLS FIRST) FROM users") as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val key = window.orderBy.single() + assertEquals(ColumnExpression("bonus"), key.expression) + assertEquals(false, key.descending) + assertEquals(true, key.nullsFirst) + } + + @Test + fun functionNameWithoutOverIsNotTreatedAsAWindowFunction() { + val statement = parseOne("SELECT ROW_NUMBER() FROM users") as SelectStatement + val expression = (statement.projection as ExpressionProjection).items.single().expression + assertTrue(expression is CallExpression) + assertEquals("ROW_NUMBER", (expression as CallExpression).name) + } + + @Test + fun rowNumberWithArgumentsFailsToParse() { + assertFailsWith { parseOne("SELECT ROW_NUMBER(id) OVER () FROM users") } + } + + @Test + fun rankWithAnArgumentFailsToParse() { + assertFailsWith { parseOne("SELECT RANK(id) OVER (ORDER BY id) FROM users") } + } + + @Test + fun nonWindowableFunctionBeforeOverFailsToParse() { + assertFailsWith { parseOne("SELECT UPPER(name) OVER () FROM users") } + } + + @Test + fun lagWithOffsetAndDefaultParsesIntoTheirOwnFields() { + val statement = parseOne( + "SELECT LAG(salary, 2, 0) OVER (PARTITION BY dept ORDER BY id) AS prev FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals("LAG", window.name) + assertEquals(WindowFunctionKind.Lag, window.kind) + assertEquals(ColumnExpression("salary"), window.argument) + assertEquals(LiteralExpression(Value.IntegerValue(2)), window.offset) + assertEquals(LiteralExpression(Value.IntegerValue(0)), window.defaultValue) + assertFalse(window.star) + } + + @Test + fun leadWithASingleArgumentLeavesOffsetAndDefaultNull() { + val statement = parseOne("SELECT LEAD(salary) OVER (ORDER BY id) FROM users") as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals(WindowFunctionKind.Lead, window.kind) + assertEquals(ColumnExpression("salary"), window.argument) + assertNull(window.offset) + assertNull(window.defaultValue) + } + + @Test + fun parsesFirstValueLastValueAndNtile() { + val statement = parseOne( + "SELECT FIRST_VALUE(salary) OVER (ORDER BY id), LAST_VALUE(salary) OVER (ORDER BY id), " + + "NTILE(4) OVER (ORDER BY id) FROM users" + ) as SelectStatement + val windows = (statement.projection as ExpressionProjection).items.map { it.expression as WindowExpression } + assertEquals(WindowFunctionKind.FirstValue, windows[0].kind) }