diff --git a/src/test/kotlin/kestrel/WindowFunctionTest.kt b/src/test/kotlin/kestrel/WindowFunctionTest.kt index 60a8253..1145bbb 100644 --- a/src/test/kotlin/kestrel/WindowFunctionTest.kt +++ b/src/test/kotlin/kestrel/WindowFunctionTest.kt @@ -304,3 +304,161 @@ class WindowFunctionTest { ) } + @Test + fun lagPastThePartitionEdgeFallsBackToTheDefaultExpressionInsteadOfNull() { + seed() + val result = select( + "SELECT id, LAG(salary, 1, 0) OVER (PARTITION BY deptId ORDER BY salary DESC) AS prev " + + "FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf(0L, 400L, 400L, 0L, 200L, 200L), + result.rows.map { (it[1] as Value.IntegerValue).value } + ) + } + + @Test + fun lagOffsetMustNotBeNegative() { + seed() + val error = assertFailsWith { + run("SELECT LAG(salary, -1) OVER (ORDER BY id) FROM employees") + } + assertTrue(error.message!!.contains("negative"), error.message!!) + } + + @Test + fun lagOffsetMustBeAnInteger() { + seed() + val error = assertFailsWith { + run("SELECT LAG(salary, 1.5) OVER (ORDER BY id) FROM employees") + } + assertTrue(error.message!!.contains("integer"), error.message!!) + } + + @Test + fun firstValueReturnsTheFirstRowsValueForEveryRowInThePartition() { + seed() + val result = select( + "SELECT id, FIRST_VALUE(salary) OVER (PARTITION BY deptId ORDER BY salary DESC) AS top " + + "FROM employees ORDER BY id ASC" + ) + assertEquals(listOf(400L, 400L, 400L, 200L, 200L, 200L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun lastValueSharesOneValuePerPeerGroupRatherThanTheWholePartition() { + seed() + val result = select( + "SELECT id, LAST_VALUE(salary) OVER (PARTITION BY deptId ORDER BY salary DESC) AS lv " + + "FROM employees ORDER BY id ASC" + ) + assertEquals(listOf(400L, 400L, 300L, 200L, 200L, 100L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun lastValueWithoutOverOrderByUsesTheWholePartitionsLastRow() { + seed() + val result = select( + "SELECT id, LAST_VALUE(salary) OVER (PARTITION BY deptId) AS lv FROM employees ORDER BY id ASC" + ) + assertEquals(listOf(300L, 300L, 300L, 100L, 100L, 100L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun ntileSplitsEachPartitionIntoAsEvenBucketsAsPossible() { + seed() + val result = select( + "SELECT id, NTILE(2) OVER (PARTITION BY deptId ORDER BY salary DESC) AS bucket " + + "FROM employees ORDER BY id ASC" + ) + assertEquals(listOf(1L, 1L, 2L, 1L, 1L, 2L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun ntileWithMoreBucketsThanRowsLeavesTheHighestBucketsUnused() { + seed() + val result = select( + "SELECT id, NTILE(5) OVER (PARTITION BY deptId ORDER BY salary DESC) AS bucket " + + "FROM employees ORDER BY id ASC" + ) + assertEquals(listOf(1L, 2L, 3L, 1L, 2L, 3L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun ntileBucketCountMustBePositive() { + seed() + val error = assertFailsWith { + run("SELECT NTILE(0) OVER (ORDER BY id) FROM employees") + } + assertTrue(error.message!!.contains("positive"), error.message!!) + } + + @Test + fun ntileBucketCountMustBeAnInteger() { + seed() + val error = assertFailsWith { + run("SELECT NTILE(1.5) OVER (ORDER BY id) FROM employees") + } + assertTrue(error.message!!.contains("integer"), error.message!!) + } + + @Test + fun windowFunctionNestedInsideLagsOffsetIsRejected() { + seed() + val error = assertFailsWith { + run("SELECT LAG(salary, ROW_NUMBER() OVER (ORDER BY id)) OVER (ORDER BY id) FROM employees") + } + assertTrue(error.message!!.contains("nested"), error.message!!) + } + + @Test + fun explicitRowsFrameProducesAMovingAverageInsteadOfARunningTotal() { + seed() + val result = select( + "SELECT id, SUM(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS s " + + "FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf(400L, 800L, 700L, 200L, 400L, 300L), + result.rows.map { (it[1] as Value.IntegerValue).value } + ) + } + + @Test + fun rowsBetweenCurrentRowAndFollowingLooksForward() { + seed() + val result = select( + "SELECT id, SUM(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS s " + + "FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf(800L, 700L, 300L, 400L, 300L, 100L), + result.rows.map { (it[1] as Value.IntegerValue).value } + ) + } + + @Test + fun rowsBetweenUnboundedPrecedingAndCurrentRowMatchesTheImplicitDefaultOnAUniqueOrderKey() { + seed() + val explicit = select( + "SELECT id, SUM(salary) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s " + + "FROM employees ORDER BY id ASC" + ) + val implicit = select("SELECT id, SUM(salary) OVER (ORDER BY id) AS s FROM employees ORDER BY id ASC") + assertEquals(implicit.rows, explicit.rows) + } + + @Test + fun frameEntirelyPastThePartitionEdgeAggregatesToNullAndCountsZero() { + seed() + val result = select( + "SELECT id, SUM(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN 5 FOLLOWING AND 9 FOLLOWING) AS s, " + + "COUNT(*) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN 5 FOLLOWING AND 9 FOLLOWING) AS c " + + "FROM employees WHERE deptId = 1 ORDER BY id ASC" + ) + assertEquals( + listOf(listOf(int(1), Value.NullValue, int(0)), listOf(int(2), Value.NullValue, int(0)), listOf(int(3), Value.NullValue, int(0))), + result.rows + ) + } +