From 8858671825355321f63a476652f7593f37475434 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 16:11:29 +0200 Subject: [PATCH] test(sql): cover DiskExecutorTest and tempFile --- .../kestrel/store/sql/DiskExecutorTest.kt | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt new file mode 100644 index 0000000..5f8edb9 --- /dev/null +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -0,0 +1,155 @@ +package kestrel.store.sql + +import kestrel.KestrelException +import kestrel.engine.AlterTableResult +import kestrel.engine.CreateIndexResult +import kestrel.engine.CreateSequenceResult +import kestrel.engine.CreateTableResult +import kestrel.engine.CreateViewResult +import kestrel.engine.DeallocateResult +import kestrel.engine.DeleteResult +import kestrel.engine.DropIndexResult +import kestrel.engine.DropTableResult +import kestrel.engine.DropViewResult +import kestrel.engine.InsertResult +import kestrel.engine.PrepareResult +import kestrel.engine.SelectResult +import kestrel.engine.StatementResult +import kestrel.engine.UpdateResult +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 + +/** + * End-to-end tests that parse real SQL text through the same [Lexer]/[Parser] + * the in-memory engine uses, then run it through [DiskExecutor] against a + * real [DiskCatalog] backed by temp files - proving the disk storage engine + * (page/heap/btree/wal/txn) can actually serve SQL, not just its own + * unit tests. + */ +class DiskExecutorTest { + 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 fun text(value: String) = Value.TextValue(value) + + private inner class Database(val dataFile: File, val walFile: File) { + var catalog = DiskCatalog.open(dataFile, walFile) + private set + var executor = DiskExecutor(catalog) + private set + + fun run(sql: String): List = parse(sql).map { executor.execute(it) } + fun select(sql: String): SelectResult = run(sql).single() as SelectResult + + /** Closes and reopens the catalog against the same files, simulating a fresh process. */ + fun reopen() { + catalog.close() + catalog = DiskCatalog.open(dataFile, walFile) + executor = DiskExecutor(catalog) + } + } + + private fun newDatabase(): Database = Database(tempFile("kestrel-disk-exec-data"), tempFile("kestrel-disk-exec-wal")) + + private fun Database.seedUsers() { + run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)") + run("INSERT INTO users VALUES (1, 'ada', 36), (2, 'linus', 54), (3, 'grace', 85)") + } + + @Test + fun createTableReturnsItsNameAndTheTableStartsEmpty() { + val db = newDatabase() + val created = db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)").single() + assertEquals(CreateTableResult("users"), created) + assertEquals(SelectResult(listOf("id", "name"), emptyList()), db.select("SELECT * FROM users")) + } + + @Test + fun creatingTheSameTableTwiceFails() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + assertFailsWith { db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") } + } + + @Test + fun insertFillsOmittedColumnsFromDeclaredDefaultAndReportsRowCount() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)") + + val inserted = db.run("INSERT INTO users (id, name) VALUES (1, 'ada')").single() + assertEquals(InsertResult(1), inserted) + + val result = db.select("SELECT * FROM users") + assertEquals(listOf(listOf(int(1), text("ada"), int(0))), result.rows) + } + + @Test + fun insertValuesDefaultKeywordUsesTheDeclaredDefault() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)") + db.run("INSERT INTO users VALUES (1, 'ada', DEFAULT)") + assertEquals(listOf(listOf(int(1), text("ada"), int(0))), db.select("SELECT * FROM users").rows) + } + + /** Positive coverage lives in [DiskExecutorOnConflictTest] - this table only proves it is no longer a scope-cut. */ + @Test + fun insertOnConflictDoNothingSkipsAConflictingRowInsteadOfThrowing() { + val db = newDatabase() + db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + db.run("INSERT INTO users VALUES (1, 'ada')") + val inserted = db.run("INSERT INTO users VALUES (1, 'ignored') ON CONFLICT (id) DO NOTHING").single() + assertEquals(InsertResult(0), inserted) + assertEquals(listOf(listOf(int(1), text("ada"))), db.select("SELECT * FROM users").rows) + } + + /** Positive coverage lives in [DiskExecutorTriggerTest] - this table only proves it is no longer a scope-cut. */ + @Test + fun createTriggerSucceedsAndFiresOnInsert() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER)") + db.run("CREATE TABLE audit (id INTEGER)") + db.run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END") + db.run("INSERT INTO t VALUES (1)") + assertEquals(listOf(listOf(int(1))), db.select("SELECT * FROM audit").rows) + } + + /** Full SEQUENCE/NEXTVAL/CURRVAL coverage lives in [DiskExecutorSequenceTest]; this just proves the wiring. */ + @Test + fun createSequenceSucceedsAndNextvalCanBackAColumnDefault() { + val db = newDatabase() + assertEquals(CreateSequenceResult("seq"), db.run("CREATE SEQUENCE seq").single()) + db.run("CREATE TABLE t (id INTEGER DEFAULT NEXTVAL('seq'))") + db.run("INSERT INTO t (id) VALUES (DEFAULT)") + assertEquals(listOf(listOf(int(1))), db.select("SELECT id FROM t").rows) + } + + @Test + fun nextvalInACheckConstraintIsRejected() { + val db = newDatabase() + db.run("CREATE SEQUENCE seq") + val error = assertFailsWith { + db.run("CREATE TABLE t (id INTEGER CHECK (id = NEXTVAL('seq')))") + } + assertTrue(error.message!!.contains("not allowed"), error.message!!) + } + + @Test + fun selectStarReturnsEveryColumnInSchemaOrder() { + val db = newDatabase() + db.seedUsers() + val result = db.select("SELECT * FROM users") + assertEquals(listOf("id", "name", "age"), result.columns) + assertEquals(3, result.rows.size) + assertTrue(listOf(int(1), text("ada"), int(36)) in result.rows) + } +