test: cover dumpAndReplayPreservesForeignKeyAndCheckConstraints
This commit is contained in:
1 parent
d7c5c965a7
commit
074f8aabbe
1 file changed
+161
@@ -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())
|
||||||
|
|
||||||
Reference in new issue
Block a user