test(sql): cover nonTerminatingRecursiveCteFailsInsteadOfLoopingForever

This commit is contained in:
dualfroz committed 2026-09-12 22:48:01 +02:00
1 parent d32d2a4a08
commit c971037db6
1 file changed
+156
@@ -1901,3 +1901,159 @@ class DiskExecutorTest {
assertTrue(failure.message!!.contains("bad"))
}
@Test
fun nonTerminatingRecursiveCteFailsInsteadOfLoopingForever() {
val db = newDatabase()
db.seedCompound()
val failure = assertFailsWith<DiskExecutorException> {
db.select(
"WITH RECURSIVE counter(n) AS (SELECT 1 FROM dual UNION ALL SELECT n + 1 FROM counter) SELECT n FROM counter"
)
}
assertTrue(failure.message!!.contains("did not terminate"))
}
@Test
fun aWithClauseThatNeverReferencesItsCteLeavesOrdinaryPrimaryKeyPushdownUntouched() {
val db = newDatabase()
db.seedUsers()
val table = db.catalog.table("users")
val pointLookupsBefore = table.pointLookupCount
val scansBefore = table.scanCount
val result = db.select("WITH unused AS (SELECT id FROM users) SELECT name FROM users WHERE id = 1")
assertEquals(listOf(listOf(text("ada"))), result.rows)
// Every CTE is materialized up front regardless of whether the body ends up using it (mirroring
// kestrel.engine.SelectRunner), so "unused" itself contributes exactly one full scan - the point is
// that the OUTER query's own WHERE id = 1 still gets its point lookup rather than also falling
// back to a scan just because a WITH clause is present.
assertEquals(scansBefore + 1, table.scanCount)
assertEquals(pointLookupsBefore + 1, table.pointLookupCount)
}
/**
* eng (deptId 1): ada/linus tie at 400 (ada inserted first), grace at 300. sales (deptId 2):
* sam/kay tie at 200 (sam inserted first), meg at 100. Mirrors kestrel.WindowFunctionTest's seed()
* exactly so results can be cross-checked against the in-memory engine's own known-good values.
*/
private fun Database.seedWindowEmployees() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, deptId INTEGER, name TEXT, salary INTEGER)")
run("INSERT INTO departments VALUES (1, 'eng'), (2, 'sales')")
run(
"INSERT INTO employees VALUES " +
"(1, 1, 'ada', 400), (2, 1, 'linus', 400), (3, 1, 'grace', 300), " +
"(4, 2, 'sam', 200), (5, 2, 'kay', 200), (6, 2, 'meg', 100)"
)
}
@Test
fun rowNumberAssignsSequentialNumbersWithinEachPartition() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select(
"SELECT id, ROW_NUMBER() OVER (PARTITION BY deptId ORDER BY salary DESC) AS rn " +
"FROM employees ORDER BY id ASC"
)
assertEquals(
listOf(
listOf(int(1), int(1)), listOf(int(2), int(2)), listOf(int(3), int(3)),
listOf(int(4), int(1)), listOf(int(5), int(2)), listOf(int(6), int(3))
),
result.rows
)
}
@Test
fun rowNumberWithoutOverOrderByFallsBackToPartitionArrivalOrder() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select("SELECT id, ROW_NUMBER() OVER (PARTITION BY deptId) AS rn FROM employees ORDER BY id ASC")
assertEquals(
listOf(
listOf(int(1), int(1)), listOf(int(2), int(2)), listOf(int(3), int(3)),
listOf(int(4), int(1)), listOf(int(5), int(2)), listOf(int(6), int(3))
),
result.rows
)
}
@Test
fun rankLeavesGapsAfterTiesButDenseRankDoesNot() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select(
"SELECT id, RANK() OVER (PARTITION BY deptId ORDER BY salary DESC) AS r, " +
"DENSE_RANK() OVER (PARTITION BY deptId ORDER BY salary DESC) AS dr " +
"FROM employees ORDER BY id ASC"
)
assertEquals(
listOf(
listOf(int(1), int(1), int(1)),
listOf(int(2), int(1), int(1)),
listOf(int(3), int(3), int(2)),
listOf(int(4), int(1), int(1)),
listOf(int(5), int(1), int(1)),
listOf(int(6), int(3), int(2))
),
result.rows
)
}
@Test
fun rankWithoutPartitionOrdersTheWholeResultSet() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select("SELECT id, RANK() OVER (ORDER BY salary DESC) AS r FROM employees ORDER BY id ASC")
assertEquals(
listOf(
listOf(int(1), int(1)), listOf(int(2), int(1)), listOf(int(3), int(3)),
listOf(int(4), int(4)), listOf(int(5), int(4)), listOf(int(6), int(6))
),
result.rows
)
}
@Test
fun aggregateWindowWithoutOrderByRepeatsTheWholePartitionTotalOnEveryRow() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select("SELECT id, SUM(salary) OVER (PARTITION BY deptId) AS deptTotal FROM employees ORDER BY id ASC")
assertEquals(
listOf(
listOf(int(1), int(1100)), listOf(int(2), int(1100)), listOf(int(3), int(1100)),
listOf(int(4), int(500)), listOf(int(5), int(500)), listOf(int(6), int(500))
),
result.rows
)
}
@Test
fun aggregateWindowWithOrderByRunsCumulativelyAndTiesSharePeerGroupTotal() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select(
"SELECT id, SUM(salary) OVER (PARTITION BY deptId ORDER BY salary DESC) AS running FROM employees ORDER BY id ASC"
)
assertEquals(
listOf(
listOf(int(1), int(800)), listOf(int(2), int(800)), listOf(int(3), int(1100)),
listOf(int(4), int(400)), listOf(int(5), int(400)), listOf(int(6), int(500))
),
result.rows
)
}
@Test
fun aggregateWindowWithUniqueOrderKeyProducesARunningTotal() {
val db = newDatabase()
db.seedWindowEmployees()
val result = db.select("SELECT id, SUM(salary) OVER (ORDER BY id) AS running FROM employees ORDER BY id ASC")
assertEquals(
listOf(
listOf(int(1), int(400)), listOf(int(2), int(800)), listOf(int(3), int(1100)),
listOf(int(4), int(1300)), listOf(int(5), int(1500)), listOf(int(6), int(1600))
),
result.rows
)
}