test: cover transactions and persistence

This commit is contained in:
dualfroz committed 2026-08-28 16:26:08 +02:00
1 parent bf8e197760
commit eac947c911
2 files changed
+188

No files matched your search

@@ -0,0 +1,92 @@
package kestrel
import java.io.File
import java.nio.file.Files
import kestrel.cli.Repl
import kestrel.persistence.Persistence
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertTrue
class PersistenceTest {
private fun tempDir(): File = Files.createTempDirectory("kestrelData").toFile()
private fun runScript(dir: File, script: String): String {
val output = StringBuilder()
Repl(output, output, Persistence(dir)).run(script.reader().buffered())
return output.toString()
}
@Test
fun dataSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER);
INSERT INTO users VALUES (1, 'ada', 36), (2, 'linus', 54);
CREATE INDEX idxAge ON users (age);
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT name FROM users ORDER BY id ASC;\n.indexes\n.exit\n")
assertContains(reopened, "ada")
assertContains(reopened, "linus")
assertContains(reopened, "idxAge")
}
@Test
fun updatesAndDeletesSurviveReopen() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE t (id INTEGER PRIMARY KEY, score INTEGER);
INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);
UPDATE t SET score = 99 WHERE id = 2;
DELETE FROM t WHERE id = 3;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT id, score FROM t ORDER BY id ASC;\n.exit\n")
assertContains(reopened, "99")
assertTrue(!reopened.contains("30"), "deleted row must not come back")
}
@Test
fun committedTransactionSurvivesButRollbackDoesNot() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE t (id INTEGER PRIMARY KEY);
BEGIN;
INSERT INTO t VALUES (1);
COMMIT;
BEGIN;
INSERT INTO t VALUES (2);
ROLLBACK;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT id FROM t ORDER BY id ASC;\n.exit\n")
assertContains(reopened, "1")
assertTrue(!reopened.contains("2"), "rolled back insert must not persist")
}
@Test
fun walReplayRecoversWithoutCheckpoint() {
val dir = tempDir()
val persistence = Persistence(dir)
persistence.appendWal(
listOf(
"CREATE TABLE t (id INTEGER);",
"INSERT INTO t VALUES (1), (2);"
)
)
val output = StringBuilder()
Repl(output, output, Persistence(dir)).run("SELECT id FROM t ORDER BY id ASC;\n.exit\n".reader().buffered())
assertContains(output.toString(), "1")
assertContains(output.toString(), "2")
}
}
@@ -0,0 +1,96 @@
package kestrel
import kestrel.catalog.Catalog
import kestrel.engine.Executor
import kestrel.engine.SelectResult
import kestrel.engine.StatementResult
import kestrel.sql.lexer.Lexer
import kestrel.sql.parser.Parser
import kestrel.storage.Value
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class TransactionTest {
private val catalog = Catalog()
private val executor = Executor(catalog)
private fun run(sql: String): List<StatementResult> =
Parser(Lexer(sql).tokenize()).parseProgram().map { executor.execute(it) }
private fun select(sql: String): SelectResult = run(sql).single() as SelectResult
private fun rowCount(sql: String): Int = select(sql).rows.size
private fun seed() {
run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)")
run("INSERT INTO accounts VALUES (1, 100), (2, 200)")
}
@Test
fun rollbackUndoesInsert() {
seed()
run("BEGIN")
run("INSERT INTO accounts VALUES (3, 300)")
assertEquals(3, rowCount("SELECT * FROM accounts"))
run("ROLLBACK")
assertEquals(2, rowCount("SELECT * FROM accounts"))
}
@Test
fun commitKeepsChanges() {
seed()
run("BEGIN")
run("INSERT INTO accounts VALUES (3, 300)")
run("COMMIT")
assertEquals(3, rowCount("SELECT * FROM accounts"))
}
@Test
fun rollbackUndoesUpdateAndDelete() {
seed()
run("BEGIN")
run("UPDATE accounts SET balance = 0 WHERE id = 1")
run("DELETE FROM accounts WHERE id = 2")
assertEquals(Value.IntegerValue(0), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
run("ROLLBACK")
assertEquals(Value.IntegerValue(100), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
assertEquals(2, rowCount("SELECT * FROM accounts"))
}
@Test
fun rollbackUndoesCreateTable() {
seed()
run("BEGIN")
run("CREATE TABLE scratch (a INTEGER)")
run("ROLLBACK")
assertFailsWith<KestrelException> { run("SELECT * FROM scratch") }
}
@Test
fun rollbackRestoresIndexState() {
seed()
run("CREATE INDEX idxBalance ON accounts (balance)")
run("BEGIN")
run("INSERT INTO accounts VALUES (3, 100)")
assertEquals(2, rowCount("SELECT * FROM accounts WHERE balance = 100"))
run("ROLLBACK")
assertEquals(1, rowCount("SELECT * FROM accounts WHERE balance = 100"))
}
@Test
fun commitWithoutBeginIsRejected() {
assertFailsWith<KestrelException> { run("COMMIT") }
}
@Test
fun rollbackWithoutBeginIsRejected() {
assertFailsWith<KestrelException> { run("ROLLBACK") }
}
@Test
fun nestedBeginIsRejected() {
run("BEGIN")
assertFailsWith<KestrelException> { run("BEGIN") }
}
}