test: cover transactions and persistence
This commit is contained in:
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") }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user