test: cover dumpAndReplayPreservesForeignKeyAndCheckConstraints

This commit is contained in:
dualfroz committed 2026-09-10 13:58:22 +02:00
1 parent d7c5c965a7
commit 074f8aabbe
1 file changed
+161
+161
View File
@@ -482,3 +482,164 @@ class SqlWriterTest {
assertTrue(select("SELECT * FROM employees").rows.isEmpty()) assertTrue(select("SELECT * FROM employees").rows.isEmpty())
} }
@Test
fun dumpAndReplayPreservesForeignKeyAndCheckConstraints() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
run(
"CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id) " +
"ON DELETE CASCADE, age INTEGER CHECK (age > 0))"
)
run("INSERT INTO departments VALUES (1, 'eng')")
run("INSERT INTO employees VALUES (1, 1, 30)")
val dump = SqlWriter.dump(catalog)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
Parser(Lexer("DELETE FROM departments WHERE id = 1").tokenize()).parseProgram()
.forEach { freshExecutor.execute(it) }
val remaining = freshExecutor.execute(
Parser(Lexer("SELECT * FROM employees").tokenize()).parseProgram().single()
) as SelectResult
assertTrue(remaining.rows.isEmpty())
}
@Test
fun createTableWithUniqueConstraintsRoundTripsThroughSqlWriter() {
run(
SqlWriter.write(
parseOne(
"CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT UNIQUE, a INTEGER, b INTEGER, UNIQUE (a, b))"
)
)
)
run("INSERT INTO t VALUES (1, 'ada@example.com', 1, 1)")
assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (2, 'ada@example.com', 2, 2)") }
assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (3, 'grace@example.com', 1, 1)") }
val result = exec("INSERT INTO t VALUES (4, 'grace@example.com', 1, 2)")
assertEquals(InsertResult(1), result)
}
@Test
fun dumpAndReplayPreservesUniqueConstraints() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
run("INSERT INTO t VALUES (1, 'ada@example.com')")
val dump = SqlWriter.dump(catalog)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
assertFailsWith<KestrelException> {
Parser(Lexer("INSERT INTO t VALUES (2, 'ada@example.com')").tokenize()).parseProgram()
.forEach { freshExecutor.execute(it) }
}
}
@Test
fun createUniqueIndexRoundTripsThroughSqlWriter() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT)")
writeThenReplay("CREATE UNIQUE INDEX idx_email ON t (email)")
run("INSERT INTO t VALUES (1, 'ada@example.com')")
assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (2, 'ada@example.com')") }
}
@Test
fun dumpAndReplayPreservesAUniqueIndex() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT)")
run("CREATE UNIQUE INDEX idx_email ON t (email)")
run("INSERT INTO t VALUES (1, 'ada@example.com')")
val dump = SqlWriter.dump(catalog)
assertTrue(dump.contains("CREATE UNIQUE INDEX idx_email ON t (email);"))
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
assertFailsWith<KestrelException> {
Parser(Lexer("INSERT INTO t VALUES (2, 'ada@example.com')").tokenize()).parseProgram()
.forEach { freshExecutor.execute(it) }
}
}
@Test
fun alterTableAddColumnWithDefaultRoundTripsThroughSqlWriter() {
seed()
writeThenReplay("ALTER TABLE t ADD COLUMN status TEXT NOT NULL DEFAULT 'new'")
assertEquals(listOf(text("new"), text("new"), text("new")), select("SELECT status FROM t").rows.map { it.single() })
run("INSERT INTO t (id) VALUES (4)")
assertEquals(listOf(text("new")), select("SELECT status FROM t WHERE id = 4").rows.single())
}
@Test
fun alterTableDropColumnRoundTripsThroughSqlWriter() {
seed()
writeThenReplay("ALTER TABLE t DROP COLUMN bio")
val columns = (select("SELECT * FROM t WHERE id = 1")).columns
assertTrue(columns.none { it.equals("bio", ignoreCase = true) })
}
@Test
fun alterTableRenameColumnRoundTripsThroughSqlWriter() {
seed()
writeThenReplay("ALTER TABLE t RENAME COLUMN bio TO biography")
assertEquals(listOf(text("mathematician")), select("SELECT biography FROM t WHERE id = 1").rows.single())
}
@Test
fun alterTableRenameToRoundTripsThroughSqlWriter() {
seed()
writeThenReplay("ALTER TABLE t RENAME TO renamed")
assertEquals(listOf(text("mathematician")), select("SELECT bio FROM renamed WHERE id = 1").rows.single())
}
@Test
fun dumpAndReplayOfARenamedTableWithADependentViewPreservesTheRewrittenReference() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
run("INSERT INTO departments VALUES (1, 'eng')")
run("CREATE VIEW v AS SELECT id, name FROM departments")
run("ALTER TABLE departments RENAME TO divisions")
val dump = SqlWriter.dump(catalog)
assertTrue(dump.contains("FROM divisions"), dump)
assertTrue(!dump.contains("FROM departments"), dump)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
val result = Parser(Lexer("SELECT * FROM v").tokenize()).parseProgram().single()
.let { freshExecutor.execute(it) } as SelectResult
assertEquals(listOf(text("eng")), listOf(result.rows.single()[1]))
}
@Test
fun dumpAndReplayPreservesColumnDefaultValue() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, status TEXT DEFAULT 'pending')")
run("INSERT INTO t (id) VALUES (1)")
val dump = SqlWriter.dump(catalog)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
freshExecutor.execute(Parser(Lexer("INSERT INTO t (id) VALUES (2)").tokenize()).parseProgram().single())
val result = freshExecutor.execute(
Parser(Lexer("SELECT status FROM t ORDER BY id ASC").tokenize()).parseProgram().single()
) as SelectResult
assertEquals(listOf(listOf(text("pending")), listOf(text("pending"))), result.rows)
}
@Test
fun dumpAndReplayPreservesAGeneratedColumnsExpressionNotJustItsAlreadyComputedValue() {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
run("INSERT INTO t (a) VALUES (5)")
val dump = SqlWriter.dump(catalog)
assertTrue(dump.contains("GENERATED ALWAYS AS"), dump)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
// A row inserted only AFTER replay proves the GENERATED expression itself round-tripped
// (not just the value 10 already sitting in the dump's INSERT statement).
freshExecutor.execute(Parser(Lexer("INSERT INTO t (a) VALUES (7)").tokenize()).parseProgram().single())