From a592b5d26d41ffefca9817f97d057f898ff6fec9 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 17:55:21 +0200 Subject: [PATCH] test(sql): cover seedDerivedTableUsers --- .../kestrel/store/sql/DiskExecutorTest.kt | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt index 8ad9d06..b1ccfdd 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -473,3 +473,165 @@ class DiskExecutorTest { assertEquals(listOf(listOf(text("ada"))), result.rows) } + private fun Database.seedDerivedTableUsers() { + run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER, dept TEXT)") + run( + "INSERT INTO users VALUES " + + "(1, 'ada', 36, 'eng'), (2, 'linus', 54, 'eng'), (3, 'grace', 85, 'navy'), (4, 'alan', 41, NULL)" + ) + } + + @Test + fun selectsFromASimpleDerivedTable() { + val db = newDatabase() + db.seedDerivedTableUsers() + val result = db.select("SELECT s.name FROM (SELECT id, name FROM users WHERE age >= 40) AS s ORDER BY s.id ASC") + assertEquals(listOf(text("linus"), text("grace"), text("alan")), result.rows.map { it.single() }) + } + + @Test + fun derivedTableOverAnAggregateQueryWorks() { + val db = newDatabase() + db.seedDerivedTableUsers() + val result = db.select( + "SELECT d.dept, d.n FROM (SELECT dept, COUNT(*) AS n FROM users GROUP BY dept) AS d " + + "WHERE d.n > 1 ORDER BY d.dept ASC" + ) + assertEquals(listOf(listOf(text("eng"), int(2))), result.rows) + } + + @Test + fun twoDerivedTablesCanBeJoinedTogether() { + val db = newDatabase() + db.seedDerivedTableUsers() + val result = db.select( + "SELECT a.dept, a.n FROM (SELECT dept, COUNT(*) AS n FROM users GROUP BY dept) AS a " + + "JOIN (SELECT dept, COUNT(*) AS n FROM users GROUP BY dept) AS b ON a.dept = b.dept " + + "WHERE a.n = b.n ORDER BY a.dept ASC" + ) + assertEquals(listOf(listOf(text("eng"), int(2)), listOf(text("navy"), int(1))), result.rows) + } + + @Test + fun derivedTableColumnsAreScopedToItsAliasNotTheUnderlyingTableName() { + val db = newDatabase() + db.seedDerivedTableUsers() + assertFailsWith { db.run("SELECT users.name FROM (SELECT id, name FROM users) AS s") } + } + + @Test + fun derivedTableIsReEvaluatedEachQueryReflectingLiveData() { + val db = newDatabase() + db.seedDerivedTableUsers() + val before = db.select("SELECT COUNT(*) AS n FROM (SELECT id FROM users WHERE dept = 'eng') AS e") + assertEquals(listOf(listOf(int(2))), before.rows) + db.run("INSERT INTO users VALUES (5, 'margaret', 61, 'eng')") + val after = db.select("SELECT COUNT(*) AS n FROM (SELECT id FROM users WHERE dept = 'eng') AS e") + assertEquals(listOf(listOf(int(3))), after.rows) + } + + @Test + fun derivedTableCanContainACorrelatedSubqueryOfItsOwn() { + val db = newDatabase() + db.seedDerivedTableUsers() + val result = db.select( + "SELECT s.name FROM (SELECT id, name, dept FROM users u WHERE age > " + + "(SELECT AVG(age) FROM users u2 WHERE u2.dept = u.dept)) AS s ORDER BY s.id ASC" + ) + assertEquals(listOf(text("linus")), result.rows.map { it.single() }) + } + + @Test + fun creatingAViewFromADerivedTableWorks() { + val db = newDatabase() + db.seedDerivedTableUsers() + db.run("CREATE VIEW dept_counts AS SELECT d.dept, d.n FROM (SELECT dept, COUNT(*) AS n FROM users GROUP BY dept) AS d") + val result = db.select("SELECT dept, n FROM dept_counts WHERE n > 1") + assertEquals(listOf(listOf(text("eng"), int(2))), result.rows) + } + + private fun Database.seedDepartmentsAndSalaries() { + 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', 300), (2, 1, 'linus', 250), (3, 1, 'grace', 400), " + + "(4, 2, 'sam', 150), (5, 2, 'kay', 200)" + ) + } + + @Test + fun lateralJoinComputesTopOnePerGroupWhichAPlainJoinCannotExpress() { + val db = newDatabase() + db.seedDepartmentsAndSalaries() + val result = db.select( + "SELECT d.name, top.name, top.salary FROM departments d " + + "JOIN LATERAL (SELECT e.name, e.salary FROM employees e WHERE e.deptId = d.id " + + "ORDER BY e.salary DESC LIMIT 1) AS top ON TRUE " + + "ORDER BY d.name ASC" + ) + assertEquals( + listOf( + listOf(text("eng"), text("grace"), int(400)), + listOf(text("sales"), text("kay"), int(200)) + ), + result.rows + ) + } + + @Test + fun lateralJoinWithLimitGreaterThanOneReturnsMultipleRowsPerLeftRow() { + val db = newDatabase() + db.seedDepartmentsAndSalaries() + val result = db.select( + "SELECT d.name, top.name FROM departments d " + + "JOIN LATERAL (SELECT e.name FROM employees e WHERE e.deptId = d.id " + + "ORDER BY e.salary DESC LIMIT 2) AS top ON TRUE " + + "ORDER BY d.name ASC, top.name ASC" + ) + assertEquals( + listOf( + listOf(text("eng"), text("ada")), + listOf(text("eng"), text("grace")), + listOf(text("sales"), text("kay")), + listOf(text("sales"), text("sam")) + ), + result.rows + ) + } + + @Test + fun innerJoinLateralDropsGroupsWithNoMatchingRows() { + val db = newDatabase() + db.seedDepartmentsAndSalaries() + db.run("INSERT INTO departments VALUES (3, 'empty')") + val result = db.select( + "SELECT d.name FROM departments d " + + "JOIN LATERAL (SELECT e.name FROM employees e WHERE e.deptId = d.id LIMIT 1) AS top ON TRUE" + ) + assertTrue(result.rows.none { it == listOf(text("empty")) }) + assertEquals(2, result.rows.size) + } + + @Test + fun leftJoinLateralKeepsGroupsWithNoMatchingRowsAsNull() { + val db = newDatabase() + db.seedDepartmentsAndSalaries() + db.run("INSERT INTO departments VALUES (3, 'empty')") + val result = db.select( + "SELECT d.name, top.name FROM departments d " + + "LEFT JOIN LATERAL (SELECT e.name FROM employees e WHERE e.deptId = d.id " + + "ORDER BY e.salary DESC LIMIT 1) AS top ON TRUE " + + "ORDER BY d.name ASC" + ) + assertEquals( + listOf( + listOf(text("empty"), Value.NullValue), + listOf(text("eng"), text("grace")), + listOf(text("sales"), text("kay")) + ), + result.rows + ) + } +