From 613cb1ecef25fd933a26a4f69eeee39e17602830 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 12:50:15 +0200 Subject: [PATCH] test(sql): cover frameEntirelyPastThePartitionEdgeAggregatesToNullAnd... --- .../kestrel/store/sql/DiskExecutorTest.kt | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt index 9923374..ab53f08 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -2373,3 +2373,164 @@ class DiskExecutorTest { assertEquals(implicit.rows, explicit.rows) } + @Test + fun frameEntirelyPastThePartitionEdgeAggregatesToNullAndCountsZero() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.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 + ) + } + + @Test + fun firstValueAndLastValueRespectAnExplicitFrameInsteadOfThePeerGroupDefault() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select( + "SELECT id, " + + "FIRST_VALUE(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS f, " + + "LAST_VALUE(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS l " + + "FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf( + listOf(int(1), int(400), int(400)), + listOf(int(2), int(400), int(300)), + listOf(int(3), int(400), int(300)), + listOf(int(4), int(200), int(200)), + listOf(int(5), int(200), int(100)), + listOf(int(6), int(200), int(100)) + ), + result.rows + ) + } + + @Test + fun rowsFrameShorthandWithoutBetweenEndsAtCurrentRow() { + val db = newDatabase() + db.seedWindowEmployees() + val explicit = db.select( + "SELECT id, SUM(salary) OVER (PARTITION BY deptId ORDER BY id ROWS 1 PRECEDING) AS s FROM employees ORDER BY id ASC" + ) + val spelledOut = db.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(spelledOut.rows, explicit.rows) + } + + @Test + fun rowsFrameOffsetMustNotBeNegative() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN -1 PRECEDING AND CURRENT ROW) FROM employees") + } + assertTrue(error.message!!.contains("negative"), error.message!!) + } + + @Test + fun rowsFrameOffsetMustBeAnInteger() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN 1.5 PRECEDING AND CURRENT ROW) FROM employees") + } + assertTrue(error.message!!.contains("integer"), error.message!!) + } + + @Test + fun rowsFrameIsRejectedOnRankingAndOffsetWindowFunctions() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT ROW_NUMBER() OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM employees") + } + assertTrue(error.message!!.contains("frame"), error.message!!) + } + + @Test + fun windowFunctionNestedInsideAFrameOffsetIsRejected() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run( + "SELECT SUM(salary) OVER (ORDER BY id ROWS BETWEEN ROW_NUMBER() OVER (ORDER BY id) PRECEDING AND CURRENT ROW) " + + "FROM employees" + ) + } + assertTrue(error.message!!.contains("nested"), error.message!!) + } + + /** amount is monotonic in id (10, 20, 20, 30, 50) so `ORDER BY id` and `ORDER BY amount` agree, with a tie at 20. */ + private fun Database.seedWindowSales() { + run("CREATE TABLE sales (id INTEGER PRIMARY KEY, amount INTEGER)") + run("INSERT INTO sales VALUES (1, 10), (2, 20), (3, 20), (4, 30), (5, 50)") + } + + @Test + fun rangeBetweenPrecedingAndFollowingIncludesRowsWithinValueDistanceRegardlessOfPhysicalRowCount() { + val db = newDatabase() + db.seedWindowSales() + val result = db.select( + "SELECT id, SUM(amount) OVER (ORDER BY amount RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS s FROM sales ORDER BY id ASC" + ) + assertEquals(listOf(50L, 80L, 80L, 70L, 50L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun rangeBetweenPrecedingAndFollowingOnADescendingOrderByFlipsWhichDirectionIsPreceding() { + val db = newDatabase() + db.seedWindowSales() + val result = db.select( + "SELECT id, SUM(amount) OVER (ORDER BY amount DESC RANGE BETWEEN 15 PRECEDING AND 5 FOLLOWING) AS s FROM sales ORDER BY id ASC" + ) + assertEquals(listOf(50L, 70L, 70L, 30L, 50L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun rangeCurrentRowExpandsToTheWholePeerGroupUnlikeRowsCurrentRow() { + val db = newDatabase() + db.seedWindowSales() + val range = db.select( + "SELECT id, SUM(amount) OVER (ORDER BY amount RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s FROM sales ORDER BY id ASC" + ) + val rows = db.select( + "SELECT id, SUM(amount) OVER (ORDER BY amount ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s FROM sales ORDER BY id ASC" + ) + assertEquals(listOf(10L, 50L, 50L, 80L, 130L), range.rows.map { (it[1] as Value.IntegerValue).value }) + assertEquals(listOf(10L, 30L, 50L, 80L, 130L), rows.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun rangeFrameMeasuresDistanceOnADecimalColumn() { + val db = newDatabase() + db.run("CREATE TABLE prices (id INTEGER PRIMARY KEY, price DECIMAL(10, 2))") + db.run("INSERT INTO prices VALUES (1, 1.00), (2, 1.40), (3, 1.50), (4, 2.00)") + val result = db.select( + "SELECT id, COUNT(*) OVER (ORDER BY price RANGE BETWEEN 0.5 PRECEDING AND 0.5 FOLLOWING) AS c FROM prices ORDER BY id ASC" + ) + assertEquals(listOf(3L, 3L, 4L, 2L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } + + @Test + fun rangeFrameMeasuresDistanceInDaysOnADateColumn() { + val db = newDatabase() + db.run("CREATE TABLE events (id INTEGER PRIMARY KEY, day DATE)") + db.run( + "INSERT INTO events VALUES " + + "(1, CAST('2024-01-01' AS DATE)), (2, CAST('2024-01-02' AS DATE)), " + + "(3, CAST('2024-01-05' AS DATE)), (4, CAST('2024-01-06' AS DATE)), (5, CAST('2024-01-10' AS DATE))" + ) + val result = db.select( + "SELECT id, COUNT(*) OVER (ORDER BY day RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS c FROM events ORDER BY id ASC" + ) + assertEquals(listOf(2L, 2L, 2L, 2L, 1L), result.rows.map { (it[1] as Value.IntegerValue).value }) + } +