test(sql): cover DiskExecutorGeneratedColumnTest and tempFile

This commit is contained in:
dualfroz committed 2026-09-11 19:24:39 +02:00
1 parent aae2ff034b
commit 108a2845bc
1 file changed
+162
@@ -0,0 +1,162 @@
package kestrel.store.sql
import kestrel.KestrelException
import kestrel.engine.MergeResult
import kestrel.engine.SelectResult
import kestrel.engine.StatementResult
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
/**
* `GENERATED ALWAYS AS (expr) STORED` through [DiskExecutor] against a real [DiskCatalog] backed by
* temp files - scenarios ported 1:1 from [kestrel.GeneratedColumnTest] (the in-memory engine's
* equivalent) wherever they translate, plus [generatedColumnSurvivesAReopen] proving
* [DirectoryEntryCodec] round-trips [kestrel.catalog.Column.generated] the same way it already did
* [kestrel.catalog.Column.default].
*/
class DiskExecutorGeneratedColumnTest {
private fun tempFile(prefix: String): File = Files.createTempFile(prefix, ".db").toFile().also { it.deleteOnExit() }
private fun int(value: Long) = Value.IntegerValue(value)
private 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> = Parser(Lexer(sql).tokenize()).parseProgram().map { executor.execute(it) }
fun exec(sql: String): StatementResult = run(sql).single()
fun select(sql: String): SelectResult = exec(sql) as SelectResult
fun reopen() {
catalog.close()
catalog = DiskCatalog.open(dataFile, walFile)
executor = DiskExecutor(catalog)
}
}
private fun newDatabase(): Database = Database(tempFile("kestrel-disk-generated-data"), tempFile("kestrel-disk-generated-wal"))
@Test
fun generatedColumnIsComputedWhenOmittedFromInsert() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("INSERT INTO t (a) VALUES (5)")
assertEquals(listOf(listOf(int(5), int(10))), db.select("SELECT a, b FROM t").rows)
}
@Test
fun insertRejectsAnExplicitValueForAGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
val error = assertFailsWith<KestrelException> { db.run("INSERT INTO t (a, b) VALUES (5, 99)") }
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun updatingADependencyColumnRecomputesTheGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("INSERT INTO t (a) VALUES (5)")
db.run("UPDATE t SET a = 9")
assertEquals(listOf(listOf(int(18))), db.select("SELECT b FROM t").rows)
}
@Test
fun updateRejectsAnExplicitValueForAGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("INSERT INTO t (a) VALUES (5)")
assertFailsWith<KestrelException> { db.run("UPDATE t SET b = 1") }
}
@Test
fun onConflictDoUpdateRecomputesTheGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("INSERT INTO t (id, a) VALUES (1, 5)")
db.run("INSERT INTO t (id, a) VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET a = EXCLUDED.a")
assertEquals(listOf(listOf(int(40))), db.select("SELECT b FROM t").rows)
}
@Test
fun mergeInsertComputesTheGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("CREATE TABLE s (id INTEGER, a INTEGER)")
db.run("INSERT INTO s VALUES (1, 5)")
val result = db.exec(
"MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id, a) VALUES (s.id, s.a)"
) as MergeResult
assertEquals(MergeResult(1, 0, 0), result)
assertEquals(listOf(listOf(int(10))), db.select("SELECT b FROM t").rows)
}
@Test
fun mergeUpdateRecomputesTheGeneratedColumn() {
val db = newDatabase()
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("CREATE TABLE s (id INTEGER, a INTEGER)")
db.run("INSERT INTO t (id, a) VALUES (1, 5)")
db.run("INSERT INTO s VALUES (1, 9)")
db.exec("MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a")
assertEquals(listOf(listOf(int(18))), db.select("SELECT b FROM t").rows)
}
@Test
fun rejectsGeneratedExpressionReferencingANonexistentColumn() {
val db = newDatabase()
val error = assertFailsWith<KestrelException> {
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (missing) STORED)")
}
assertTrue(error.message!!.contains("no such column"))
}
@Test
fun rejectsAGeneratedColumnReferencingAnotherGeneratedColumn() {
val db = newDatabase()
val error = assertFailsWith<KestrelException> {
db.run(
"CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED, " +
"c INTEGER GENERATED ALWAYS AS (b + 1) STORED)"
)
}
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun renameColumnPropagatesIntoADependentGeneratedExpression() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
db.run("ALTER TABLE t RENAME COLUMN a TO a2")
db.run("INSERT INTO t (a2) VALUES (5)")
assertEquals(listOf(listOf(int(10))), db.select("SELECT b FROM t").rows)
}
@Test
fun dropColumnRestrictsWhenAGeneratedColumnDependsOnIt() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
val error = assertFailsWith<KestrelException> { db.run("ALTER TABLE t DROP COLUMN a") }
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun dropColumnCascadeRemovesTheDependentGeneratedColumnToo() {
val db = newDatabase()
db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED, c TEXT)")
db.run("INSERT INTO t (a, c) VALUES (5, 'x')")
db.run("ALTER TABLE t DROP COLUMN a CASCADE")
val result = db.select("SELECT * FROM t")
assertEquals(listOf("c"), result.columns)
assertEquals(listOf(listOf(Value.TextValue("x"))), result.rows)
}