test(sql): cover DiskExecutorTest and tempFile

This commit is contained in:
dualfroz committed 2026-09-12 16:11:29 +02:00
1 parent b50f24109e
commit 8858671825
1 file changed
+155
@@ -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<Statement> = 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<StatementResult> = 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<DiskCatalogException> { 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<DiskExecutorException> {
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)
}