test: cover firstValueAndLastValueRespectAnExplicitFrameInsteadOfTheP...

This commit is contained in:
dualfroz committed 2026-09-10 20:58:36 +02:00
1 parent c90e6c12e1
commit bfd93c3811
1 file changed
+156
@@ -462,3 +462,159 @@ class WindowFunctionTest {
) )
} }
@Test
fun firstValueAndLastValueRespectAnExplicitFrameInsteadOfThePeerGroupDefault() {
seed()
val result = 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() {
seed()
val explicit = 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 = 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() {
seed()
val error = assertFailsWith<KestrelException> {
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() {
seed()
val error = assertFailsWith<KestrelException> {
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() {
seed()
val error = assertFailsWith<KestrelException> {
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() {
seed()
val error = assertFailsWith<KestrelException> {
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!!)
}
@Test
fun explicitFrameSurvivesViewDefinitionRoundTrip() {
seed()
run(
"CREATE VIEW movingTotals AS SELECT id, " +
"SUM(salary) OVER (PARTITION BY deptId ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS s " +
"FROM employees"
)
val result = select("SELECT id, s FROM movingTotals ORDER BY id ASC")
assertEquals(
listOf(400L, 800L, 700L, 200L, 400L, 300L),
result.rows.map { (it[1] as Value.IntegerValue).value }
)
}
/** 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 seedSales() {
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() {
seedSales()
val result = 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 rangeFrameGivesTheSameRowSetRegardlessOfOrderDirectionForASymmetricWindow() {
seedSales()
val ascending = select(
"SELECT id, SUM(amount) OVER (ORDER BY amount ASC RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS s " +
"FROM sales ORDER BY id ASC"
)
val descending = select(
"SELECT id, SUM(amount) OVER (ORDER BY amount DESC RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING) AS s " +
"FROM sales ORDER BY id ASC"
)
assertEquals(ascending.rows, descending.rows)
}
@Test
fun rangeBetweenPrecedingAndFollowingOnADescendingOrderByFlipsWhichDirectionIsPreceding() {
seedSales()
val result = 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() {
seedSales()
val range = 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 = 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() {
run("CREATE TABLE prices (id INTEGER PRIMARY KEY, price DECIMAL(10, 2))")
run("INSERT INTO prices VALUES (1, 1.00), (2, 1.40), (3, 1.50), (4, 2.00)")
val result = 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 })
}