test: cover droppingTheTableDropsItsTriggersToo

This commit is contained in:
dualfroz committed 2026-09-10 17:40:11 +02:00
1 parent e4bac803db
commit 628235ed19
1 file changed
+178
+178
View File
@@ -157,3 +157,181 @@ class TriggerTest {
assertFailsWith<KestrelException> { run("DROP TRIGGER log") } assertFailsWith<KestrelException> { run("DROP TRIGGER log") }
} }
@Test
fun droppingTheTableDropsItsTriggersToo() {
run("CREATE TABLE t (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN UPDATE t SET id = id; END")
run("DROP TABLE t")
assertFailsWith<KestrelException> { run("DROP TRIGGER log") }
}
@Test
fun renamingTheTableKeepsItsTriggersFiring() {
run("CREATE TABLE t (id INTEGER)")
run("CREATE TABLE audit (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END")
run("ALTER TABLE t RENAME TO t2")
run("INSERT INTO t2 VALUES (42)")
assertEquals(listOf(listOf(int(42))), select("SELECT * FROM audit").rows)
}
@Test
fun rollbackUndoesCreateTrigger() {
run("CREATE TABLE t (id INTEGER)")
run("BEGIN")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END")
run("ROLLBACK")
assertFailsWith<KestrelException> { run("DROP TRIGGER log") }
}
@Test
fun createTriggerRejectsUnknownTable() {
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log AFTER INSERT ON missing FOR EACH ROW BEGIN DELETE FROM missing WHERE 1 = 0; END")
}
}
@Test
fun createTriggerRejectsDuplicateName() {
run("CREATE TABLE t (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END")
}
}
@Test
fun whenClauseRejectsAnUnqualifiedColumn() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW WHEN id > 0 BEGIN DELETE FROM t WHERE 1 = 0; END")
}
}
@Test
fun whenClauseRejectsNewOnADeleteTrigger() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log BEFORE DELETE ON t FOR EACH ROW WHEN NEW.id > 0 BEGIN DELETE FROM t WHERE 1 = 0; END")
}
}
@Test
fun whenClauseRejectsOldOnAnInsertTrigger() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log BEFORE INSERT ON t FOR EACH ROW WHEN OLD.id > 0 BEGIN DELETE FROM t WHERE 1 = 0; END")
}
}
@Test
fun whenClauseAcceptsBothSidesOnAnUpdateTrigger() {
run("CREATE TABLE t (id INTEGER)")
run("CREATE TRIGGER log BEFORE UPDATE ON t FOR EACH ROW WHEN NEW.id > OLD.id BEGIN DELETE FROM t WHERE 1 = 0; END")
}
@Test
fun whenClauseRejectsAnUnknownColumn() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW WHEN NEW.nope > 0 BEGIN DELETE FROM t WHERE 1 = 0; END")
}
}
@Test
fun whenClauseRejectsASubquery() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run(
"CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW WHEN EXISTS (SELECT 1 FROM t) " +
"BEGIN DELETE FROM t WHERE 1 = 0; END"
)
}
}
@Test
fun bodyStatementRejectsAnUnknownTargetTable() {
run("CREATE TABLE t (id INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM missing WHERE 1 = 0; END")
}
}
@Test
fun onConflictRowThatLandsWithNoConflictFiresInsertTriggers() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, x INTEGER)")
run("CREATE TABLE audit (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END")
run("INSERT INTO t VALUES (1, 1) ON CONFLICT (id) DO NOTHING")
assertEquals(listOf(listOf(int(1))), select("SELECT * FROM audit").rows)
}
@Test
fun onConflictDoNothingFiresNoTriggerAtAllForTheConflictingRow() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, x INTEGER)")
run("INSERT INTO t VALUES (1, 1)")
run("CREATE TABLE audit (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END")
run("INSERT INTO t VALUES (1, 99) ON CONFLICT (id) DO NOTHING")
assertEquals(0, select("SELECT * FROM audit").rows.size)
}
@Test
fun onConflictDoUpdateFiresUpdateTriggersNotInsertTriggers() {
run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)")
run("INSERT INTO accounts VALUES (1, 100)")
run("CREATE TABLE inserts (id INTEGER)")
run("CREATE TABLE updates (id INTEGER, old_balance INTEGER, new_balance INTEGER)")
run("CREATE TRIGGER log_insert AFTER INSERT ON accounts FOR EACH ROW BEGIN INSERT INTO inserts VALUES (NEW.id); END")
run(
"CREATE TRIGGER log_update AFTER UPDATE ON accounts FOR EACH ROW " +
"BEGIN INSERT INTO updates VALUES (NEW.id, OLD.balance, NEW.balance); END"
)
run("INSERT INTO accounts VALUES (1, 999) ON CONFLICT (id) DO UPDATE SET balance = excluded.balance")
assertEquals(0, select("SELECT * FROM inserts").rows.size)
assertEquals(listOf(listOf(int(1), int(100), int(999))), select("SELECT * FROM updates").rows)
}
@Test
fun onConflictBeforeUpdateTriggerCanRejectTheWholeStatement() {
run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)")
run("INSERT INTO accounts VALUES (1, 100)")
run("CREATE TABLE guard (x INTEGER NOT NULL)")
run("CREATE TRIGGER reject BEFORE UPDATE ON accounts FOR EACH ROW BEGIN INSERT INTO guard VALUES (NULL); END")
assertFailsWith<KestrelException> {
run("INSERT INTO accounts VALUES (1, 999) ON CONFLICT (id) DO UPDATE SET balance = excluded.balance")
}
assertEquals(int(100), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
}
@Test
fun onConflictMultiRowStatementFiresTheRightTriggerPerRow() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, hits INTEGER)")
run("INSERT INTO t VALUES (1, 10)")
run("CREATE TABLE inserts (id INTEGER)")
run("CREATE TABLE updates (id INTEGER)")
run("CREATE TRIGGER log_insert AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO inserts VALUES (NEW.id); END")
run("CREATE TRIGGER log_update AFTER UPDATE ON t FOR EACH ROW BEGIN INSERT INTO updates VALUES (NEW.id); END")
run(
"""
INSERT INTO t VALUES (1, 1), (2, 2)
ON CONFLICT (id) DO UPDATE SET hits = t.hits + excluded.hits
""".trimIndent()
)
assertEquals(listOf(listOf(int(2))), select("SELECT * FROM inserts").rows)
assertEquals(listOf(listOf(int(1))), select("SELECT * FROM updates").rows)
}
@Test
fun bodyStatementInAnInsertTriggerCanOnlySeeNewNotOld() {
run("CREATE TABLE t (id INTEGER)")
run("CREATE TABLE audit (id INTEGER)")
run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END")
run("INSERT INTO t VALUES (5)")
assertEquals(listOf(listOf(int(5))), select("SELECT * FROM audit").rows)
// OLD is simply not bound for an INSERT trigger, so an OLD.id reference in the body falls
// through exactly like any other unresolvable column in an INSERT's VALUES list would.
run("CREATE TRIGGER bad AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (OLD.id); END")
assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (6)") }
}
}