test(sql): cover frameEntirelyPastThePartitionEdgeAggregatesToNullAnd...

This commit is contained in:
dualfroz committed 2026-09-13 12:50:15 +02:00
1 parent f5cf5f187c
commit 613cb1ecef
1 file changed
+161
@@ -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<DiskExecutorException> {
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<DiskExecutorException> {
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<DiskExecutorException> {
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<DiskExecutorException> {
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 })
}