diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt index 591b245..e270dcc 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt @@ -156,3 +156,161 @@ class DiskExecutorExplainTest { assertTrue(plan.any { it == "Seq Scan on users" }) } + @Test + fun explainOnACompositeSecondaryIndexEqualityNamesEveryColumn() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT)") + db.run("INSERT INTO users VALUES (1, 36, 'ada'), (2, 36, 'grace')") + db.run("CREATE INDEX idx_both ON users(age, name)") + val plan = db.explain("EXPLAIN SELECT id FROM users WHERE age = 36 AND name = 'grace'") + assertTrue(plan.any { it == "Index Scan using idx_both on users (age = 36 AND name = 'grace')" }) + } + + @Test + fun explainOnOnlyOneColumnOfACompositeIndexFallsBackToASeqScan() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT)") + db.run("INSERT INTO users VALUES (1, 36, 'ada')") + db.run("CREATE INDEX idx_both ON users(age, name)") + val plan = db.explain("EXPLAIN SELECT id FROM users WHERE age = 36") + assertTrue(plan.any { it == "Seq Scan on users" }) + } + + @Test + fun explainWithAJoinNeverPushesDownEvenWhenWhereMatchesThePrimaryKey() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain( + "EXPLAIN SELECT customers.name, orders.amount FROM customers JOIN orders " + + "ON customers.id = orders.customer_id WHERE customers.id = 1" + ) + assertTrue(plan.any { it.trim() == "-> Seq Scan on customers" }) + assertTrue(plan.none { it.contains("Primary Key") }) + } + + @Test + fun explainNeverPrintsHashJoinEvenForAnEqualityCondition() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain( + "EXPLAIN SELECT customers.name, orders.amount FROM customers " + + "JOIN orders ON customers.id = orders.customer_id" + ) + assertTrue(plan.any { it == "Nested Loop Inner" }) + assertTrue(plan.none { it.contains("Hash Join") }) + assertTrue(plan.any { it.trim() == "-> Seq Scan on orders" }) + } + + @Test + fun explainAnnotatesRightFullAndCrossJoinsWithTheirLabel() { + val db = newDatabase() + db.seedOrders() + val right = db.explain("EXPLAIN SELECT customers.name FROM customers RIGHT JOIN orders ON customers.id = orders.customer_id") + assertTrue(right.any { it == "Nested Loop Right" }) + val full = db.explain("EXPLAIN SELECT customers.name FROM customers FULL JOIN orders ON customers.id = orders.customer_id") + assertTrue(full.any { it == "Nested Loop Full" }) + val cross = db.explain("EXPLAIN SELECT customers.name FROM customers CROSS JOIN orders") + assertTrue(cross.any { it == "Nested Loop Cross" }) + } + + @Test + fun explainAnnotatesALateralJoinAndNamesItsRightSideALateralSubqueryScan() { + val db = newDatabase() + db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + db.run("CREATE TABLE employees (id INTEGER PRIMARY KEY, deptId INTEGER, name TEXT, salary INTEGER)") + db.run("INSERT INTO departments VALUES (1, 'eng')") + db.run("INSERT INTO employees VALUES (1, 1, 'ada', 300)") + val plan = db.explain( + "EXPLAIN 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 1) AS top ON TRUE" + ) + assertTrue(plan.any { it == "Nested Loop Inner Lateral" }) + assertTrue(plan.any { it.trim() == "-> Lateral Subquery Scan on top" }) + } + + @Test + fun explainOnAGroupByReportsGroupAggregateWithItsKeys() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain("EXPLAIN SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id") + assertTrue(plan.any { it.trim() == "GroupAggregate keys: customer_id" }) + } + + @Test + fun explainOnAPlainAggregateWithNoGroupByReportsAggregate() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain("EXPLAIN SELECT COUNT(*) FROM orders") + assertTrue(plan.any { it.trim() == "Aggregate" }) + } + + @Test + fun explainOnARollupReportsTheNumberOfGroupingSets() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain("EXPLAIN SELECT customer_id, SUM(amount) FROM orders GROUP BY ROLLUP (customer_id)") + assertTrue(plan.any { it.contains("(2 grouping sets)") }) + } + + @Test + fun explainOnAPlainGroupByDoesNotMentionGroupingSets() { + val db = newDatabase() + db.seedOrders() + val plan = db.explain("EXPLAIN SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id") + assertTrue(plan.none { it.contains("grouping sets") }) + } + + @Test + fun explainWithOrderByWrapsSortAroundThePlan() { + val db = newDatabase() + db.seedUsers() + val plan = db.explain("EXPLAIN SELECT name FROM users ORDER BY age") + assertEquals("Sort", plan.first()) + } + + @Test + fun explainOnDistinctReportsUnique() { + val db = newDatabase() + db.seedUsers() + val plan = db.explain("EXPLAIN SELECT DISTINCT age FROM users") + assertEquals("Unique", plan.first()) + } + + @Test + fun explainOnDistinctOnReportsUniqueDistinctOn() { + val db = newDatabase() + db.seedUsers() + val plan = db.explain("EXPLAIN SELECT DISTINCT ON (age) name FROM users ORDER BY age") + assertEquals("Unique (DISTINCT ON)", plan.first()) + } + + @Test + fun explainWithLimitWrapsLimitAroundThePlan() { + val db = newDatabase() + db.seedUsers() + val plan = db.explain("EXPLAIN SELECT name FROM users LIMIT 5") + assertEquals("Limit 5", plan.first()) + } + + @Test + fun explainWithLimitAndOffsetWrapsLimitOutermostAndOffsetJustInside() { + val db = newDatabase() + db.seedUsers() + val plan = db.explain("EXPLAIN SELECT name FROM users ORDER BY name LIMIT 5 OFFSET 2") + assertEquals("Limit 5", plan[0]) + assertEquals(" Offset 2", plan[1]) + } + + @Test + fun explainOverACompoundQueryWithOffsetWrapsOffsetJustInsideLimit() { + val db = newDatabase() + db.run("CREATE TABLE t1 (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + db.run("CREATE TABLE t2 (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + db.run("INSERT INTO t1 VALUES (1, 'ada')") + db.run("INSERT INTO t2 VALUES (2, 'linus')") + val plan = db.explain("EXPLAIN SELECT name FROM t1 UNION SELECT name FROM t2 ORDER BY name LIMIT 1 OFFSET 1") + assertEquals("Limit 1", plan[0]) + assertEquals(" Offset 1", plan[1]) + } +