diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt index 305e2a7..b33df99 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -1744,3 +1744,160 @@ class DiskExecutorTest { assertEquals(listOf(listOf(text("linus"))), db.select("SELECT name FROM t1 INTERSECT SELECT name FROM t2").rows) } + @Test + fun exceptReturnsRowsOnlyInTheLeftQuery() { + val db = newDatabase() + db.seedCompound() + assertEquals(listOf(listOf(text("ada"))), db.select("SELECT name FROM t1 EXCEPT SELECT name FROM t2").rows) + } + + @Test + fun intersectAllAndExceptAllPreserveMultisetCounts() { + val db = newDatabase() + db.run("CREATE TABLE bag_a (v INTEGER)") + db.run("CREATE TABLE bag_b (v INTEGER)") + db.run("INSERT INTO bag_a VALUES (1), (1), (1), (2)") + db.run("INSERT INTO bag_b VALUES (1), (1), (2), (2), (3)") + assertEquals( + listOf(listOf(int(1)), listOf(int(1)), listOf(int(2))), + db.select("SELECT v FROM bag_a INTERSECT ALL SELECT v FROM bag_b ORDER BY v").rows + ) + assertEquals(listOf(listOf(int(1))), db.select("SELECT v FROM bag_a EXCEPT ALL SELECT v FROM bag_b ORDER BY v").rows) + } + + @Test + fun compoundQueryWithMismatchedColumnCountsFails() { + val db = newDatabase() + db.seedCompound() + assertFailsWith { db.select("SELECT id, name FROM t1 UNION SELECT id FROM t2") } + } + + @Test + fun compoundOperatorsChainLeftAssociatively() { + val db = newDatabase() + db.seedCompound() + db.run("CREATE TABLE t3 (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + db.run("INSERT INTO t3 VALUES (2, 'linus')") + val result = db.select("SELECT name FROM t1 UNION ALL SELECT name FROM t2 INTERSECT SELECT name FROM t3 ORDER BY name") + assertEquals(listOf(listOf(text("linus"))), result.rows) + } + + @Test + fun orderByAndLimitApplyToTheWholeCompoundResultNotEachSide() { + val db = newDatabase() + db.seedCompound() + val result = db.select("SELECT name FROM t1 UNION ALL SELECT name FROM t2 ORDER BY name DESC LIMIT 2") + assertEquals(listOf(listOf(text("linus")), listOf(text("linus"))), result.rows) + } + + @Test + fun offsetSkipsRowsOfTheWholeCompoundResultBeforeLimitApplies() { + val db = newDatabase() + db.seedCompound() + val result = db.select("SELECT name FROM t1 UNION ALL SELECT name FROM t2 ORDER BY name DESC LIMIT 2 OFFSET 1") + assertEquals(listOf(listOf(text("linus")), listOf(text("grace"))), result.rows) + } + + @Test + fun withClauseMakesACteAvailableInFrom() { + val db = newDatabase() + db.seedCompound() + val result = db.select("WITH recent AS (SELECT id, name FROM t1) SELECT name FROM recent WHERE id = 1") + assertEquals(listOf(listOf(text("ada"))), result.rows) + } + + @Test + fun aCteShadowsARealTableOfTheSameName() { + val db = newDatabase() + db.seedCompound() + val result = db.select("WITH t1 AS (SELECT id FROM t1 WHERE id = 1) SELECT * FROM t1") + assertEquals(listOf("id"), result.columns) + assertEquals(listOf(listOf(int(1))), result.rows) + } + + @Test + fun laterCteCanReferenceAnEarlierCteInTheSameWithClause() { + val db = newDatabase() + db.seedCompound() + val result = db.select( + "WITH first_cte AS (SELECT id, name FROM t1), second_cte AS (SELECT name FROM first_cte WHERE id = 2) " + + "SELECT name FROM second_cte" + ) + assertEquals(listOf(listOf(text("linus"))), result.rows) + } + + @Test + fun cteCanBeJoinedWithARealDiskTable() { + val db = newDatabase() + db.seedCompound() + val result = db.select("WITH t1_names AS (SELECT id, name FROM t1) SELECT t2.name FROM t1_names JOIN t2 ON t1_names.id = t2.id") + assertEquals(listOf(listOf(text("linus"))), result.rows) + } + + @Test + fun cteWithExplicitColumnListRenamesOutputColumns() { + val db = newDatabase() + db.seedCompound() + val result = db.select("WITH renamed(label) AS (SELECT name FROM t1) SELECT label FROM renamed ORDER BY label") + assertEquals(listOf(listOf(text("ada")), listOf(text("linus"))), result.rows) + } + + @Test + fun cteColumnListSizeMustMatchQueryColumnCount() { + val db = newDatabase() + db.seedCompound() + val failure = assertFailsWith { db.select("WITH bad(a, b, c) AS (SELECT id FROM t1) SELECT * FROM bad") } + assertTrue(failure.message!!.contains("bad")) + } + + @Test + fun recursiveCteGeneratesABoundedSequence() { + val db = newDatabase() + db.seedCompound() + val result = db.select( + "WITH RECURSIVE counter(n) AS (" + + "SELECT 1 FROM dual " + + "UNION ALL " + + "SELECT n + 1 FROM counter WHERE n < 5" + + ") SELECT n FROM counter ORDER BY n" + ) + assertEquals((1..5).map { listOf(int(it.toLong())) }, result.rows) + } + + @Test + fun recursiveCteWithUnionDeduplicatesAndTerminatesOnACycleThroughARealDiskTable() { + val db = newDatabase() + db.seedCompound() + db.run("CREATE TABLE edges (from_node INTEGER, to_node INTEGER)") + db.run("INSERT INTO edges VALUES (1, 2), (2, 3), (3, 1), (3, 4)") + val result = db.select( + "WITH RECURSIVE reachable(node) AS (" + + "SELECT 1 FROM dual " + + "UNION " + + "SELECT edges.to_node FROM edges JOIN reachable ON edges.from_node = reachable.node" + + ") SELECT node FROM reachable ORDER BY node" + ) + assertEquals(listOf(1L, 2L, 3L, 4L).map { listOf(int(it)) }, result.rows) + } + + @Test + fun recursiveCteWithoutSelfReferenceBehavesAsAPlainCompoundQuery() { + val db = newDatabase() + db.seedCompound() + val result = db.select( + "WITH RECURSIVE notActuallyRecursive AS (SELECT 1 AS n FROM dual UNION ALL SELECT 2 FROM dual) " + + "SELECT n FROM notActuallyRecursive ORDER BY n" + ) + assertEquals(listOf(listOf(int(1)), listOf(int(2))), result.rows) + } + + @Test + fun recursiveCteReferencingItselfOnBothSidesFails() { + val db = newDatabase() + db.seedCompound() + val failure = assertFailsWith { + db.select("WITH RECURSIVE bad(n) AS (SELECT n FROM bad UNION ALL SELECT n FROM bad) SELECT n FROM bad") + } + assertTrue(failure.message!!.contains("bad")) + } +