diff --git a/src/test/kotlin/kestrel/ParserTest.kt b/src/test/kotlin/kestrel/ParserTest.kt index a417fad..0ccccf2 100644 --- a/src/test/kotlin/kestrel/ParserTest.kt +++ b/src/test/kotlin/kestrel/ParserTest.kt @@ -1284,4 +1284,177 @@ class ParserTest { ) as SelectStatement val windows = (statement.projection as ExpressionProjection).items.map { it.expression as WindowExpression } assertEquals(WindowFunctionKind.FirstValue, windows[0].kind) + assertEquals(WindowFunctionKind.LastValue, windows[1].kind) + assertEquals(WindowFunctionKind.Ntile, windows[2].kind) + assertEquals(LiteralExpression(Value.IntegerValue(4)), windows[2].argument) + } + + @Test + fun lagWithNoArgumentsFailsToParse() { + assertFailsWith { parseOne("SELECT LAG() OVER (ORDER BY id) FROM users") } + } + + @Test + fun lagWithFourArgumentsFailsToParse() { + assertFailsWith { parseOne("SELECT LAG(salary, 1, 0, 9) OVER (ORDER BY id) FROM users") } + } + + @Test + fun firstValueWithTwoArgumentsFailsToParse() { + assertFailsWith { parseOne("SELECT FIRST_VALUE(salary, id) OVER (ORDER BY id) FROM users") } + } + + @Test + fun ntileWithNoArgumentsFailsToParse() { + assertFailsWith { parseOne("SELECT NTILE() OVER (ORDER BY id) FROM users") } + } + + @Test + fun parsesExplicitRowsBetweenFrameWithLiteralOffsets() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameBoundType.Preceding, frame.start.type) + assertEquals(LiteralExpression(Value.IntegerValue(1)), frame.start.offset) + assertEquals(FrameBoundType.Following, frame.end.type) + assertEquals(LiteralExpression(Value.IntegerValue(2)), frame.end.offset) + } + + @Test + fun parsesUnboundedAndCurrentRowFrameBounds() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameBoundType.UnboundedPreceding, frame.start.type) + assertNull(frame.start.offset) + assertEquals(FrameBoundType.CurrentRow, frame.end.type) + assertNull(frame.end.offset) + } + + @Test + fun rowsShorthandWithoutBetweenEndsAtCurrentRow() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY id ROWS 2 PRECEDING) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameBoundType.Preceding, frame.start.type) + assertEquals(LiteralExpression(Value.IntegerValue(2)), frame.start.offset) + assertEquals(FrameBoundType.CurrentRow, frame.end.type) + } + + @Test + fun rowsShorthandWithUnboundedFollowingFailsBecauseItWouldStartAfterCurrentRow() { + assertFailsWith { + parseOne("SELECT SUM(salary) OVER (ORDER BY id ROWS UNBOUNDED FOLLOWING) FROM users") + } + } + + @Test + fun frameStartAfterFrameEndFailsToParse() { + assertFailsWith { + parseOne("SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN CURRENT ROW AND 1 PRECEDING) FROM users") + } + } + + @Test + fun frameWithoutBetweenOrBoundFailsToParse() { + assertFailsWith { + parseOne("SELECT SUM(salary) OVER (ORDER BY id ROWS) FROM users") + } + } + + @Test + fun windowWithNoFrameLeavesFrameNull() { + 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 + assertNull(window.frame) + } + + @Test + fun parsesExplicitRangeBetweenFrameWithLiteralOffsets() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameUnit.Range, frame.unit) + assertEquals(FrameBoundType.Preceding, frame.start.type) + assertEquals(LiteralExpression(Value.IntegerValue(5)), frame.start.offset) + assertEquals(FrameBoundType.Following, frame.end.type) + assertEquals(LiteralExpression(Value.IntegerValue(10)), frame.end.offset) + } + + @Test + fun rangeShorthandWithoutBetweenEndsAtCurrentRowAndDefaultsRowsUnitOtherwise() { + val rangeStatement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount RANGE 3 PRECEDING) FROM users" + ) as SelectStatement + val rangeWindow = (rangeStatement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals(FrameUnit.Range, rangeWindow.frame!!.unit) + assertEquals(FrameBoundType.CurrentRow, rangeWindow.frame!!.end.type) + + val rowsStatement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount ROWS 3 PRECEDING) FROM users" + ) as SelectStatement + val rowsWindow = (rowsStatement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals(FrameUnit.Rows, rowsWindow.frame!!.unit) + } + + @Test + fun parsesExplicitGroupsBetweenFrameWithLiteralOffsets() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount GROUPS BETWEEN 1 PRECEDING AND 2 FOLLOWING) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameUnit.Groups, frame.unit) + assertEquals(FrameBoundType.Preceding, frame.start.type) + assertEquals(LiteralExpression(Value.IntegerValue(1)), frame.start.offset) + assertEquals(FrameBoundType.Following, frame.end.type) + assertEquals(LiteralExpression(Value.IntegerValue(2)), frame.end.offset) + } + + @Test + fun groupsShorthandWithoutBetweenEndsAtCurrentRow() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount GROUPS 3 PRECEDING) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + assertEquals(FrameUnit.Groups, window.frame!!.unit) + assertEquals(FrameBoundType.CurrentRow, window.frame!!.end.type) + } + + @Test + fun groupsFrameStartAfterFrameEndFailsToParse() { + assertFailsWith { + parseOne("SELECT SUM(salary) OVER (ORDER BY amount GROUPS BETWEEN CURRENT ROW AND 1 PRECEDING) FROM users") + } + } + + @Test + fun parsesRangeBetweenUnboundedAndCurrentRow() { + val statement = parseOne( + "SELECT SUM(salary) OVER (ORDER BY amount RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM users" + ) as SelectStatement + val window = (statement.projection as ExpressionProjection).items.single().expression as WindowExpression + val frame = window.frame!! + assertEquals(FrameUnit.Range, frame.unit) + assertEquals(FrameBoundType.UnboundedPreceding, frame.start.type) + assertEquals(FrameBoundType.CurrentRow, frame.end.type) + } + + @Test + fun rangeFrameStartAfterFrameEndFailsToParse() { + assertFailsWith { + parseOne("SELECT SUM(salary) OVER (ORDER BY amount RANGE BETWEEN CURRENT ROW AND 1 PRECEDING) FROM users") + } + } + + @Test + fun frameWithNoExcludeClauseDefaultsToNoOthers() { }