test(sql): cover nonTerminatingRecursiveCteFailsInsteadOfLoopingForever
This commit is contained in:
1 parent
d32d2a4a08
commit
c971037db6
1 file changed
+156
@@ -1901,3 +1901,159 @@ class DiskExecutorTest {
|
|||||||
assertTrue(failure.message!!.contains("bad"))
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user