From b35dadfe6f80ba078d8ff384527d53544ed494fa Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 17:50:37 +0200 Subject: [PATCH] test(sql): cover DiskExecutorExplainTest and tempFile --- .../store/sql/DiskExecutorExplainTest.kt | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt new file mode 100644 index 0000000..591b245 --- /dev/null +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorExplainTest.kt @@ -0,0 +1,158 @@ +package kestrel.store.sql + +import kestrel.engine.ExplainResult +import kestrel.sql.ast.Statement +import kestrel.sql.lexer.Lexer +import kestrel.sql.parser.Parser +import kestrel.storage.Value +import java.io.File +import java.nio.file.Files +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * Closes the "this executor has no `explain`" scope cut documented on [DiskExecutor.executeCreateView]: + * proves EXPLAIN on the disk-backed executor both describes the right plan shape (Seq Scan/Primary Key + * Scan/Index Scan/Nested Loop/GroupAggregate/Sort/Limit/CTE/UNION) AND - the part a plan-text assertion + * alone cannot show - never touches a single row of real table data to produce it, verified against the + * same [DiskTable] access counters [DiskExecutorTest] itself uses to prove index pushdown really happens. + */ +class DiskExecutorExplainTest { + private fun tempFile(prefix: String): File = Files.createTempFile(prefix, ".db").toFile().also { it.deleteOnExit() } + + private fun parse(sql: String): List = Parser(Lexer(sql).tokenize()).parseProgram() + + private fun int(value: Long) = Value.IntegerValue(value) + + private inner class Database(dataFile: File, walFile: File) { + val catalog = DiskCatalog.open(dataFile, walFile) + val executor = DiskExecutor(catalog) + + fun run(sql: String): List = parse(sql).map { executor.execute(it) } + fun explain(sql: String): List = (run(sql).single() as ExplainResult).plan + } + + private fun newDatabase(): Database = Database(tempFile("kestrel-disk-explain-data"), tempFile("kestrel-disk-explain-wal")) + + private fun Database.seedUsers() { + run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)") + run("INSERT INTO users VALUES (1, 'ada', 36), (2, 'linus', 54), (3, 'grace', 85)") + } + + private fun Database.seedOrders() { + run("CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + run("INSERT INTO customers VALUES (1, 'ada'), (2, 'linus'), (3, 'grace')") + run("CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL, amount INTEGER NOT NULL)") + run("INSERT INTO orders VALUES (1, 1, 100), (2, 1, 50), (3, 2, 200)") + } + + @Test + fun explainOnAnUnindexedWhereReportsASeqScanAndTouchesNoRowData() { + val db = newDatabase() + db.seedUsers() + val table = db.catalog.table("users") + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE name = 'linus'") + assertTrue(plan.any { it == "Seq Scan on users" }) + assertEquals(0, table.scanCount) + } + + @Test + fun explainOnAPrimaryKeyEqualityReportsAPrimaryKeyScanAndTouchesNoRowData() { + val db = newDatabase() + db.seedUsers() + val table = db.catalog.table("users") + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE id = 2") + assertTrue(plan.any { it == "Primary Key Scan on users (id = 2)" }) + assertEquals(0, table.pointLookupCount) + assertEquals(0, table.scanCount) + } + + @Test + fun explainOnAPrimaryKeyRangeReportsAPrimaryKeyRangeScanAndTouchesNoRowData() { + val db = newDatabase() + db.seedUsers() + val table = db.catalog.table("users") + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE id > 1 AND id <= 3") + assertTrue(plan.any { it == "Primary Key Range Scan on users (id > 1 AND id <= 3)" }) + assertEquals(0, table.rangeScanCount) + assertEquals(0, table.scanCount) + } + + @Test + fun explainOnACompositePrimaryKeyEqualityReportsAPrimaryKeyScan() { + val db = newDatabase() + db.run("CREATE TABLE order_lines (order_id INTEGER, line_no INTEGER, sku TEXT, PRIMARY KEY (order_id, line_no))") + db.run("INSERT INTO order_lines VALUES (1, 1, 'widget'), (1, 2, 'gadget')") + val table = db.catalog.table("order_lines") + val plan = db.explain("EXPLAIN SELECT sku FROM order_lines WHERE order_id = 1 AND line_no = 2") + assertTrue(plan.any { it == "Primary Key Scan on order_lines (order_id = 1 AND line_no = 2)" }) + assertEquals(0, table.pointLookupCount) + } + + @Test + fun explainOnACompositePrimaryKeyRangeReportsAPrimaryKeyRangeScan() { + val db = newDatabase() + db.run("CREATE TABLE order_lines (order_id INTEGER, line_no INTEGER, sku TEXT, PRIMARY KEY (order_id, line_no))") + db.run("INSERT INTO order_lines VALUES (1, 1, 'widget'), (1, 2, 'gadget')") + val table = db.catalog.table("order_lines") + val plan = db.explain("EXPLAIN SELECT sku FROM order_lines WHERE order_id = 1 AND line_no > 1") + assertTrue(plan.any { it == "Primary Key Range Scan on order_lines (order_id = 1 AND line_no > 1)" }) + assertEquals(0, table.rangeScanCount) + assertEquals(0, table.scanCount) + } + + @Test + fun explainOnASecondaryIndexEqualityNamesTheIndexAndTouchesNoRowData() { + val db = newDatabase() + db.seedUsers() + db.run("CREATE INDEX idx_age ON users(age)") + val table = db.catalog.table("users") + val lookupsBeforeExplain = table.secondaryLookupCount + val scansBeforeExplain = table.scanCount + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE age = 54") + assertTrue(plan.any { it == "Index Scan using idx_age on users (age = 54)" }) + assertEquals(lookupsBeforeExplain, table.secondaryLookupCount) + assertEquals(scansBeforeExplain, table.scanCount) + } + + @Test + fun explainOnASecondaryIndexRangeNamesTheIndexAndTouchesNoRowData() { + val db = newDatabase() + db.seedUsers() + db.run("CREATE INDEX idx_age ON users(age)") + val table = db.catalog.table("users") + val rangeScansBeforeExplain = table.rangeScanCount + val scansBeforeExplain = table.scanCount + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE age > 40 AND age <= 85") + assertTrue(plan.any { it == "Index Range Scan using idx_age on users (age > 40 AND age <= 85)" }) + assertEquals(rangeScansBeforeExplain, table.rangeScanCount) + assertEquals(scansBeforeExplain, table.scanCount) + } + + @Test + fun explainOnACompositeSecondaryIndexPrefixRangeNamesBothThePrefixEqualityAndTheRange() { + val db = newDatabase() + db.run("CREATE TABLE order_lines (order_id INTEGER, line_no INTEGER, sku TEXT)") + db.run("INSERT INTO order_lines VALUES (1, 1, 'apple'), (1, 2, 'mango'), (2, 1, 'kiwi')") + db.run("CREATE INDEX idx_order_sku ON order_lines(order_id, sku)") + val table = db.catalog.table("order_lines") + val rangeScansBeforeExplain = table.rangeScanCount + val scansBeforeExplain = table.scanCount + val plan = db.explain("EXPLAIN SELECT line_no FROM order_lines WHERE order_id = 1 AND sku > 'apple'") + assertTrue(plan.any { it == "Index Range Scan using idx_order_sku on order_lines (order_id = 1 AND sku > 'apple')" }) + assertEquals(rangeScansBeforeExplain, table.rangeScanCount) + assertEquals(scansBeforeExplain, table.scanCount) + } + + @Test + fun explainRevertsToASeqScanAfterTheIndexItWasUsingIsDropped() { + val db = newDatabase() + db.seedUsers() + db.run("CREATE INDEX idx_age ON users(age)") + db.run("DROP INDEX idx_age") + val plan = db.explain("EXPLAIN SELECT name FROM users WHERE age = 54") + assertTrue(plan.any { it == "Seq Scan on users" }) + } +