Files
turso/testing/trigger.test

761 lines
32 KiB
Tcl
Executable File

#!/usr/bin/env tclsh
set testdir [file dirname $argv0]
source $testdir/tester.tcl
# Basic CREATE TRIGGER
do_execsql_test_on_specific_db {:memory:} trigger-create-basic {
CREATE TABLE test1 (x INTEGER, y TEXT);
CREATE TRIGGER t1 BEFORE INSERT ON test1 BEGIN INSERT INTO test1 VALUES (100, 'triggered'); END;
INSERT INTO test1 VALUES (1, 'hello');
SELECT * FROM test1 ORDER BY rowid;
} {100|triggered
1|hello}
# CREATE TRIGGER IF NOT EXISTS
do_execsql_test_on_specific_db {:memory:} trigger-create-if-not-exists {
CREATE TABLE test2 (x INTEGER PRIMARY KEY);
CREATE TRIGGER IF NOT EXISTS t2 BEFORE INSERT ON test2 BEGIN SELECT 1; END;
CREATE TRIGGER IF NOT EXISTS t2 BEFORE INSERT ON test2 BEGIN SELECT 1; END;
SELECT name FROM sqlite_schema WHERE type='trigger' AND name='t2';
} {t2}
# DROP TRIGGER
do_execsql_test_on_specific_db {:memory:} trigger-drop-basic {
CREATE TABLE test3 (x INTEGER PRIMARY KEY);
CREATE TRIGGER t3 BEFORE INSERT ON test3 BEGIN SELECT 1; END;
DROP TRIGGER t3;
SELECT name FROM sqlite_schema WHERE type='trigger' AND name='t3';
} {}
# DROP TRIGGER IF EXISTS
do_execsql_test_on_specific_db {:memory:} trigger-drop-if-exists {
CREATE TABLE test4 (x INTEGER PRIMARY KEY);
DROP TRIGGER IF EXISTS nonexistent;
CREATE TRIGGER t4 BEFORE INSERT ON test4 BEGIN SELECT 1; END;
DROP TRIGGER IF EXISTS t4;
SELECT name FROM sqlite_schema WHERE type='trigger' AND name='t4';
} {}
# BEFORE INSERT trigger
do_execsql_test_on_specific_db {:memory:} trigger-before-insert {
CREATE TABLE test5 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TRIGGER t5 BEFORE INSERT ON test5 BEGIN UPDATE test5 SET y = 'before_' || y WHERE x = NEW.x; END;
INSERT INTO test5 VALUES (1, 'hello');
SELECT * FROM test5;
} {1|hello}
# AFTER INSERT trigger
do_execsql_test_on_specific_db {:memory:} trigger-after-insert {
CREATE TABLE test6 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log6 (x INTEGER, y TEXT);
CREATE TRIGGER t6 AFTER INSERT ON test6 BEGIN INSERT INTO log6 VALUES (NEW.x, NEW.y); END;
INSERT INTO test6 VALUES (1, 'hello');
SELECT * FROM log6;
} {1|hello}
# BEFORE UPDATE trigger
do_execsql_test_on_specific_db {:memory:} trigger-before-update {
CREATE TABLE test7 (x INTEGER PRIMARY KEY, y TEXT);
INSERT INTO test7 VALUES (1, 'hello');
CREATE TRIGGER t7 BEFORE UPDATE ON test7 BEGIN UPDATE test7 SET y = 'before_' || NEW.y WHERE x = OLD.x; END;
UPDATE test7 SET y = 'world' WHERE x = 1;
SELECT * FROM test7;
} {1|world}
# AFTER UPDATE trigger
do_execsql_test_on_specific_db {:memory:} trigger-after-update {
CREATE TABLE test8 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log8 (old_x INTEGER, old_y TEXT, new_x INTEGER, new_y TEXT);
INSERT INTO test8 VALUES (1, 'hello');
CREATE TRIGGER t8 AFTER UPDATE ON test8 BEGIN INSERT INTO log8 VALUES (OLD.x, OLD.y, NEW.x, NEW.y); END;
UPDATE test8 SET y = 'world' WHERE x = 1;
SELECT * FROM log8;
} {1|hello|1|world}
# BEFORE DELETE trigger
do_execsql_test_on_specific_db {:memory:} trigger-before-delete {
CREATE TABLE test9 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log9 (x INTEGER, y TEXT);
INSERT INTO test9 VALUES (1, 'hello');
CREATE TRIGGER t9 BEFORE DELETE ON test9 BEGIN INSERT INTO log9 VALUES (OLD.x, OLD.y); END;
DELETE FROM test9 WHERE x = 1;
SELECT * FROM log9;
} {1|hello}
# AFTER DELETE trigger
do_execsql_test_on_specific_db {:memory:} trigger-after-delete {
CREATE TABLE test10 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log10 (x INTEGER, y TEXT);
INSERT INTO test10 VALUES (1, 'hello');
CREATE TRIGGER t10 AFTER DELETE ON test10 BEGIN INSERT INTO log10 VALUES (OLD.x, OLD.y); END;
DELETE FROM test10 WHERE x = 1;
SELECT * FROM log10;
} {1|hello}
# Trigger with WHEN clause
do_execsql_test_on_specific_db {:memory:} trigger-when-clause {
CREATE TABLE test11 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log11 (x INTEGER);
CREATE TRIGGER t11 AFTER INSERT ON test11 WHEN NEW.y > 10 BEGIN INSERT INTO log11 VALUES (NEW.x); END;
INSERT INTO test11 VALUES (1, 5);
INSERT INTO test11 VALUES (2, 15);
SELECT * FROM log11;
} {2}
# Multiple triggers on same event
do_execsql_test_on_specific_db {:memory:} trigger-multiple-same-event {
CREATE TABLE test12 (x INTEGER PRIMARY KEY);
CREATE TABLE log12 (msg TEXT);
CREATE TRIGGER t12a BEFORE INSERT ON test12 BEGIN INSERT INTO log12 VALUES ('trigger1'); END;
CREATE TRIGGER t12b BEFORE INSERT ON test12 BEGIN INSERT INTO log12 VALUES ('trigger2'); END;
INSERT INTO test12 VALUES (1);
SELECT * FROM log12 ORDER BY msg;
} {trigger1
trigger2}
# Triggers dropped when table is dropped
do_execsql_test_on_specific_db {:memory:} trigger-drop-table-drops-triggers {
CREATE TABLE test13 (x INTEGER PRIMARY KEY);
CREATE TRIGGER t13 BEFORE INSERT ON test13 BEGIN SELECT 1; END;
DROP TABLE test13;
SELECT name FROM sqlite_schema WHERE type='trigger' AND name='t13';
} {}
# NEW and OLD references
do_execsql_test_on_specific_db {:memory:} trigger-new-old-references {
CREATE TABLE test14 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log14 (msg TEXT);
INSERT INTO test14 VALUES (1, 'hello');
CREATE TRIGGER t14 AFTER UPDATE ON test14 BEGIN INSERT INTO log14 VALUES ('old=' || OLD.y || ' new=' || NEW.y); END;
UPDATE test14 SET y = 'world' WHERE x = 1;
SELECT * FROM log14;
} {"old=hello new=world"}
# Trigger with UPDATE OF clause
do_execsql_test_on_specific_db {:memory:} trigger-update-of {
CREATE TABLE test15 (x INTEGER PRIMARY KEY, y TEXT, z TEXT);
CREATE TABLE log15 (msg TEXT);
INSERT INTO test15 VALUES (1, 'hello', 'world');
CREATE TRIGGER t15 AFTER UPDATE OF y ON test15 BEGIN INSERT INTO log15 VALUES ('y changed'); END;
UPDATE test15 SET z = 'foo' WHERE x = 1;
SELECT * FROM log15;
UPDATE test15 SET y = 'bar' WHERE x = 1;
SELECT * FROM log15;
} {"y changed"}
# Recursive trigger - AFTER INSERT inserting into same table
do_execsql_test_on_specific_db {:memory:} trigger-recursive-after-insert {
CREATE TABLE test16 (x INTEGER PRIMARY KEY);
CREATE TRIGGER t16 AFTER INSERT ON test16 BEGIN INSERT INTO test16 VALUES (NEW.x + 1); END;
INSERT INTO test16 VALUES (1);
SELECT * FROM test16 ORDER BY x;
} {1
2}
# Multiple UPDATE OF columns
do_execsql_test_on_specific_db {:memory:} trigger-update-of-multiple {
CREATE TABLE test17 (x INTEGER PRIMARY KEY, y TEXT, z TEXT);
CREATE TABLE log17 (msg TEXT);
INSERT INTO test17 VALUES (1, 'a', 'b');
CREATE TRIGGER t17 AFTER UPDATE OF y, z ON test17 BEGIN INSERT INTO log17 VALUES ('updated'); END;
UPDATE test17 SET y = 'c' WHERE x = 1;
SELECT COUNT(*) FROM log17;
UPDATE test17 SET x = 2 WHERE x = 1;
SELECT COUNT(*) FROM log17;
} {1
1}
# Complex WHEN clause with AND
do_execsql_test_on_specific_db {:memory:} trigger-when-complex {
CREATE TABLE test18 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log18 (x INTEGER);
CREATE TRIGGER t18 BEFORE INSERT ON test18 WHEN NEW.y > 5 AND NEW.y < 10 BEGIN INSERT INTO log18 VALUES (NEW.x); END;
INSERT INTO test18 VALUES (1, 3);
INSERT INTO test18 VALUES (2, 7);
INSERT INTO test18 VALUES (3, 12);
SELECT * FROM log18;
} {2}
# Nested triggers - trigger firing another trigger
do_execsql_test_on_specific_db {:memory:} trigger-nested {
CREATE TABLE test19 (x INTEGER PRIMARY KEY);
CREATE TABLE test20 (x INTEGER PRIMARY KEY);
CREATE TABLE log19 (msg TEXT);
CREATE TRIGGER t19 AFTER INSERT ON test19 BEGIN INSERT INTO test20 VALUES (NEW.x); END;
CREATE TRIGGER t20 AFTER INSERT ON test20 BEGIN INSERT INTO log19 VALUES ('t20 inserted: ' || NEW.x); END;
INSERT INTO test19 VALUES (1);
SELECT * FROM log19;
} {"t20 inserted: 1"}
# BEFORE INSERT inserting into same table (recursive)
do_execsql_test_on_specific_db {:memory:} trigger-recursive-before-insert {
CREATE TABLE test21 (x INTEGER PRIMARY KEY);
CREATE TRIGGER t21 BEFORE INSERT ON test21 BEGIN INSERT INTO test21 VALUES (NEW.x + 100); END;
INSERT INTO test21 VALUES (1);
SELECT * FROM test21 ORDER BY x;
} {1
101}
# AFTER UPDATE with WHEN clause and UPDATE OF
do_execsql_test_on_specific_db {:memory:} trigger-update-of-when {
CREATE TABLE test22 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TRIGGER t22 AFTER UPDATE OF y ON test22 WHEN OLD.y != NEW.y BEGIN UPDATE test22 SET y = UPPER(NEW.y) WHERE x = NEW.x; END;
INSERT INTO test22 VALUES (1, 'hello');
UPDATE test22 SET y = 'world' WHERE x = 1;
SELECT * FROM test22;
} {1|WORLD}
# Multiple statements in BEFORE INSERT trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-before-insert {
CREATE TABLE test23 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log23a (msg TEXT);
CREATE TABLE log23b (msg TEXT);
CREATE TRIGGER t23 BEFORE INSERT ON test23 BEGIN INSERT INTO log23a VALUES ('before1'); INSERT INTO log23b VALUES ('before2'); END;
INSERT INTO test23 VALUES (1, 'hello');
SELECT * FROM log23a;
SELECT * FROM log23b;
} {before1
before2}
# Multiple statements in AFTER INSERT trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-after-insert {
CREATE TABLE test24 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log24a (msg TEXT);
CREATE TABLE log24b (msg TEXT);
CREATE TRIGGER t24 AFTER INSERT ON test24 BEGIN INSERT INTO log24a VALUES ('after1'); INSERT INTO log24b VALUES ('after2'); END;
INSERT INTO test24 VALUES (1, 'hello');
SELECT * FROM log24a;
SELECT * FROM log24b;
} {after1
after2}
# Multiple statements in BEFORE UPDATE trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-before-update {
CREATE TABLE test25 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log25a (msg TEXT);
CREATE TABLE log25b (msg TEXT);
INSERT INTO test25 VALUES (1, 'hello');
CREATE TRIGGER t25 BEFORE UPDATE ON test25 BEGIN INSERT INTO log25a VALUES ('before_update1'); INSERT INTO log25b VALUES ('before_update2'); END;
UPDATE test25 SET y = 'world' WHERE x = 1;
SELECT * FROM log25a;
SELECT * FROM log25b;
} {before_update1
before_update2}
# Multiple statements in AFTER UPDATE trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-after-update {
CREATE TABLE test26 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log26a (msg TEXT);
CREATE TABLE log26b (msg TEXT);
INSERT INTO test26 VALUES (1, 'hello');
CREATE TRIGGER t26 AFTER UPDATE ON test26 BEGIN INSERT INTO log26a VALUES ('after_update1'); INSERT INTO log26b VALUES ('after_update2'); END;
UPDATE test26 SET y = 'world' WHERE x = 1;
SELECT * FROM log26a;
SELECT * FROM log26b;
} {after_update1
after_update2}
# Multiple statements in BEFORE DELETE trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-before-delete {
CREATE TABLE test27 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log27a (msg TEXT);
CREATE TABLE log27b (msg TEXT);
INSERT INTO test27 VALUES (1, 'hello');
CREATE TRIGGER t27 BEFORE DELETE ON test27 BEGIN INSERT INTO log27a VALUES ('before_delete1'); INSERT INTO log27b VALUES ('before_delete2'); END;
DELETE FROM test27 WHERE x = 1;
SELECT * FROM log27a;
SELECT * FROM log27b;
} {before_delete1
before_delete2}
# Multiple statements in AFTER DELETE trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-after-delete {
CREATE TABLE test28 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log28a (msg TEXT);
CREATE TABLE log28b (msg TEXT);
INSERT INTO test28 VALUES (1, 'hello');
CREATE TRIGGER t28 AFTER DELETE ON test28 BEGIN INSERT INTO log28a VALUES ('after_delete1'); INSERT INTO log28b VALUES ('after_delete2'); END;
DELETE FROM test28 WHERE x = 1;
SELECT * FROM log28a;
SELECT * FROM log28b;
} {after_delete1
after_delete2}
# Multiple statements with mixed operations in BEFORE INSERT trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-mixed-before-insert {
CREATE TABLE test29 (x INTEGER PRIMARY KEY, y TEXT);
CREATE TABLE log29 (x INTEGER, y TEXT);
CREATE TABLE counter29 (cnt INTEGER);
INSERT INTO counter29 VALUES (0);
CREATE TRIGGER t29 BEFORE INSERT ON test29 BEGIN INSERT INTO log29 VALUES (NEW.x, NEW.y); UPDATE counter29 SET cnt = cnt + 1; END;
INSERT INTO test29 VALUES (1, 'hello');
INSERT INTO test29 VALUES (2, 'world');
SELECT * FROM log29 ORDER BY x;
SELECT * FROM counter29;
} {1|hello
2|world
2}
# Multiple statements with conditional logic in trigger
do_execsql_test_on_specific_db {:memory:} trigger-multiple-statements-conditional {
CREATE TABLE test30 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log30 (msg TEXT);
CREATE TRIGGER t30 AFTER INSERT ON test30 BEGIN INSERT INTO log30 VALUES ('inserted: ' || NEW.x); INSERT INTO log30 VALUES ('value: ' || NEW.y); INSERT INTO log30 VALUES ('done'); END;
INSERT INTO test30 VALUES (1, 10);
INSERT INTO test30 VALUES (2, 20);
SELECT * FROM log30 ORDER BY msg;
} {done
done
"inserted: 1"
"inserted: 2"
"value: 10"
"value: 20"}
# Trigger that updates the same row being updated (recursive update)
do_execsql_test_on_specific_db {:memory:} trigger-update-same-row {
CREATE TABLE test31 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
INSERT INTO test31 VALUES (1, 10, 0);
CREATE TRIGGER t31 AFTER UPDATE OF y ON test31 BEGIN UPDATE test31 SET z = z + 1 WHERE x = NEW.x; END;
UPDATE test31 SET y = 20 WHERE x = 1;
SELECT * FROM test31;
} {1|20|1}
# Trigger that deletes the row being inserted
do_execsql_test_on_specific_db {:memory:} trigger-delete-inserted-row {
CREATE TABLE test32 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log32 (msg TEXT);
CREATE TRIGGER t32 AFTER INSERT ON test32 BEGIN DELETE FROM test32 WHERE x = NEW.x AND NEW.y < 0; INSERT INTO log32 VALUES ('processed: ' || NEW.x); END;
INSERT INTO test32 VALUES (1, 10);
INSERT INTO test32 VALUES (2, -5);
SELECT * FROM test32;
SELECT * FROM log32 ORDER BY msg;
} {1|10
"processed: 1"
"processed: 2"}
# Trigger chain: INSERT triggers UPDATE which triggers DELETE
do_execsql_test_on_specific_db {:memory:} trigger-chain-insert-update-delete {
CREATE TABLE test34 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log34 (msg TEXT);
CREATE TRIGGER t34a AFTER INSERT ON test34 BEGIN UPDATE test34 SET y = y + 1 WHERE x = NEW.x; END;
CREATE TRIGGER t34b AFTER UPDATE ON test34 BEGIN DELETE FROM test34 WHERE y > 100; INSERT INTO log34 VALUES ('updated: ' || NEW.x); END;
INSERT INTO test34 VALUES (1, 50);
INSERT INTO test34 VALUES (2, 100);
SELECT * FROM test34 ORDER BY x;
SELECT * FROM log34 ORDER BY msg;
} {1|51
"updated: 1"
"updated: 2"}
# Trigger that inserts into the same table (recursive insert)
do_execsql_test_on_specific_db {:memory:} trigger-recursive-insert {
CREATE TABLE test35 (x INTEGER PRIMARY KEY, y INTEGER, depth INTEGER);
CREATE TRIGGER t35 AFTER INSERT ON test35 WHEN NEW.depth < 3 BEGIN INSERT INTO test35 VALUES (NEW.x * 10, NEW.y + 1, NEW.depth + 1); END;
INSERT INTO test35 VALUES (1, 0, 0);
SELECT * FROM test35 ORDER BY x;
} {1|0|0
10|1|1}
# Trigger that updates OLD values in BEFORE UPDATE
do_execsql_test_on_specific_db {:memory:} trigger-update-old-reference {
CREATE TABLE test36 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
CREATE TABLE log36 (old_y INTEGER, new_y INTEGER);
INSERT INTO test36 VALUES (1, 10, 0);
CREATE TRIGGER t36 BEFORE UPDATE OF y ON test36 BEGIN INSERT INTO log36 VALUES (OLD.y, NEW.y); UPDATE test36 SET z = OLD.y + NEW.y WHERE x = NEW.x; END;
UPDATE test36 SET y = 20 WHERE x = 1;
SELECT * FROM test36;
SELECT * FROM log36;
} {1|20|30
10|20}
# Trigger that causes constraint violation in another table
do_execsql_test_on_specific_db {:memory:} trigger-constraint-violation {
CREATE TABLE test37a (x INTEGER PRIMARY KEY);
CREATE TABLE test37b (x INTEGER PRIMARY KEY, y INTEGER);
INSERT INTO test37b VALUES (1, 100);
CREATE TRIGGER t37 AFTER INSERT ON test37a BEGIN INSERT OR IGNORE INTO test37b VALUES (NEW.x, NEW.x * 10); END;
INSERT INTO test37a VALUES (1);
SELECT * FROM test37b ORDER BY x;
} {1|100}
# Multiple triggers on same event with interdependencies
do_execsql_test_on_specific_db {:memory:} trigger-multiple-interdependent {
CREATE TABLE test38 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
CREATE TRIGGER t38a AFTER INSERT ON test38 BEGIN UPDATE test38 SET y = 100 WHERE x = NEW.x; END;
CREATE TRIGGER t38b AFTER INSERT ON test38 BEGIN UPDATE test38 SET z = 200 WHERE x = NEW.x; END;
INSERT INTO test38 VALUES (1, 10, 20);
SELECT * FROM test38;
} {1|100|200}
# Trigger that references both OLD and NEW in UPDATE
do_execsql_test_on_specific_db {:memory:} trigger-old-new-arithmetic {
CREATE TABLE test39 (x INTEGER PRIMARY KEY, y INTEGER, delta INTEGER);
INSERT INTO test39 VALUES (1, 10, 0);
CREATE TRIGGER t39 BEFORE UPDATE OF y ON test39 BEGIN UPDATE test39 SET delta = NEW.y - OLD.y WHERE x = NEW.x; END;
UPDATE test39 SET y = 25 WHERE x = 1;
SELECT * FROM test39;
} {1|25|15}
# Trigger that performs DELETE based on NEW values
do_execsql_test_on_specific_db {:memory:} trigger-delete-on-insert-condition {
CREATE TABLE test40 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE archive40 (x INTEGER, y INTEGER);
INSERT INTO test40 VALUES (1, 10);
INSERT INTO test40 VALUES (2, 20);
CREATE TRIGGER t40 AFTER INSERT ON test40 BEGIN DELETE FROM test40 WHERE y < NEW.y; INSERT INTO archive40 SELECT * FROM test40 WHERE x = NEW.x; END;
INSERT INTO test40 VALUES (3, 15);
SELECT * FROM test40 ORDER BY x;
SELECT * FROM archive40 ORDER BY x;
} {2|20
3|15
3|15}
# Trigger with WHEN clause that modifies the condition column
do_execsql_test_on_specific_db {:memory:} trigger-when-clause-modification {
CREATE TABLE test41 (x INTEGER PRIMARY KEY, y INTEGER, processed INTEGER DEFAULT 0);
CREATE TRIGGER t41 AFTER INSERT ON test41 WHEN NEW.y > 50 BEGIN UPDATE test41 SET processed = 1 WHERE x = NEW.x; END;
INSERT INTO test41 (x, y) VALUES (1, 30);
INSERT INTO test41 (x, y) VALUES (2, 60);
INSERT INTO test41 (x, y) VALUES (3, 70);
SELECT * FROM test41 ORDER BY x;
} {1|30|0
2|60|1
3|70|1}
# Trigger that creates circular dependency between two tables
do_execsql_test_on_specific_db {:memory:} trigger-circular-dependency {
CREATE TABLE test42a (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE test42b (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TRIGGER t42a AFTER INSERT ON test42a BEGIN INSERT OR IGNORE INTO test42b VALUES (NEW.x, NEW.y + 1); END;
CREATE TRIGGER t42b AFTER INSERT ON test42b BEGIN INSERT OR IGNORE INTO test42a VALUES (NEW.x + 100, NEW.y + 1); END;
INSERT INTO test42a VALUES (1, 10);
SELECT * FROM test42a ORDER BY x;
SELECT * FROM test42b ORDER BY x;
} {1|10
101|12
1|11}
# BEFORE trigger modifying primary key value
do_execsql_test_on_specific_db {:memory:} trigger-before-modify-primary-key {
CREATE TABLE test43 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TRIGGER t43 BEFORE INSERT ON test43 BEGIN UPDATE test43 SET x = NEW.x + 1000 WHERE x = NEW.x; END;
INSERT INTO test43 VALUES (1, 10);
SELECT * FROM test43;
} {1|10}
# UPDATE OF trigger modifying the same column it watches
do_execsql_test_on_specific_db {:memory:} trigger-update-of-modify-same-column {
CREATE TABLE test44 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log44 (msg TEXT);
INSERT INTO test44 VALUES (1, 10);
CREATE TRIGGER t44 AFTER UPDATE OF y ON test44 BEGIN UPDATE test44 SET y = y * 2 WHERE x = NEW.x; INSERT INTO log44 VALUES ('triggered'); END;
UPDATE test44 SET y = 5 WHERE x = 1;
SELECT * FROM test44;
SELECT COUNT(*) FROM log44;
} {1|10
1}
# BEFORE trigger modifying column used in UPDATE OF clause
do_execsql_test_on_specific_db {:memory:} trigger-before-modify-update-of-column {
CREATE TABLE test45 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
CREATE TABLE log45 (msg TEXT);
INSERT INTO test45 VALUES (1, 10, 0);
CREATE TRIGGER t45a BEFORE UPDATE OF y ON test45 BEGIN UPDATE test45 SET y = y + 100 WHERE x = NEW.x; END;
CREATE TRIGGER t45b AFTER UPDATE OF y ON test45 BEGIN INSERT INTO log45 VALUES ('y=' || NEW.y); END;
UPDATE test45 SET y = 20 WHERE x = 1;
SELECT * FROM test45;
SELECT * FROM log45;
} {1|20|0
y=110
y=20}
# Trigger modifying column used in WHEN clause
do_execsql_test_on_specific_db {:memory:} trigger-modify-when-clause-column {
CREATE TABLE test47 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
CREATE TABLE log47 (msg TEXT);
INSERT INTO test47 VALUES (1, 10, 0);
CREATE TRIGGER t47 BEFORE UPDATE ON test47 WHEN NEW.y > 5 BEGIN UPDATE test47 SET y = y - 10 WHERE x = NEW.x; INSERT INTO log47 VALUES ('modified'); END;
UPDATE test47 SET y = 15 WHERE x = 1;
SELECT * FROM test47;
SELECT COUNT(*) FROM log47;
} {1|15|0
1}
# Trigger causing unique constraint violation with INSERT OR IGNORE
do_execsql_test_on_specific_db {:memory:} trigger-unique-violation-ignore {
CREATE TABLE test48 (x INTEGER PRIMARY KEY, y INTEGER UNIQUE);
CREATE TABLE log48 (x INTEGER);
INSERT INTO test48 VALUES (1, 100);
CREATE TRIGGER t48 AFTER INSERT ON test48 BEGIN INSERT OR IGNORE INTO test48 VALUES (NEW.x + 1, NEW.y); INSERT INTO log48 VALUES (NEW.x); END;
INSERT INTO test48 VALUES (2, 200);
SELECT * FROM test48 ORDER BY x;
SELECT * FROM log48 ORDER BY x;
} {1|100
2|200
2}
# Multiple triggers modifying same column in different ways
do_execsql_test_on_specific_db {:memory:} trigger-multiple-modify-same-column {
CREATE TABLE test49 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TRIGGER t49a BEFORE INSERT ON test49 BEGIN UPDATE test49 SET y = y + 1 WHERE x = NEW.x; END;
CREATE TRIGGER t49b BEFORE INSERT ON test49 BEGIN UPDATE test49 SET y = y * 2 WHERE x = NEW.x; END;
CREATE TRIGGER t49c BEFORE INSERT ON test49 BEGIN UPDATE test49 SET y = y + 10 WHERE x = NEW.x; END;
INSERT INTO test49 VALUES (1, 5);
SELECT * FROM test49;
} {1|5}
# BEFORE trigger modifying NEW values used in WHEN clause
do_execsql_test_on_specific_db {:memory:} trigger-before-modify-new-in-when {
CREATE TABLE test51 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log51 (msg TEXT);
INSERT INTO test51 VALUES (1, 5);
CREATE TRIGGER t51a BEFORE UPDATE ON test51 BEGIN UPDATE test51 SET y = y + 20 WHERE x = NEW.x; END;
CREATE TRIGGER t51b AFTER UPDATE ON test51 WHEN NEW.y > 10 BEGIN INSERT INTO log51 VALUES ('y=' || NEW.y); END;
UPDATE test51 SET y = 8 WHERE x = 1;
SELECT * FROM test51;
SELECT * FROM log51;
} {1|8
y=25}
# UPDATE OF on non-existent column (should be silently ignored)
do_execsql_test_on_specific_db {:memory:} trigger-update-of-nonexistent-column {
CREATE TABLE test52 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log52 (msg TEXT);
INSERT INTO test52 VALUES (1, 10);
CREATE TRIGGER t52 AFTER UPDATE OF nonexistent ON test52 BEGIN INSERT INTO log52 VALUES ('triggered'); END;
UPDATE test52 SET y = 20 WHERE x = 1;
SELECT COUNT(*) FROM log52;
} {0}
# Trigger modifying same column multiple times in one trigger body
do_execsql_test_on_specific_db {:memory:} trigger-modify-column-multiple-times {
CREATE TABLE test53 (x INTEGER PRIMARY KEY, y INTEGER);
INSERT INTO test53 VALUES (1, 10);
CREATE TRIGGER t53 AFTER UPDATE OF y ON test53 BEGIN UPDATE test53 SET y = y + 1 WHERE x = NEW.x; UPDATE test53 SET y = y * 2 WHERE x = NEW.x; UPDATE test53 SET y = y + 5 WHERE x = NEW.x; END;
UPDATE test53 SET y = 5 WHERE x = 1;
SELECT * FROM test53;
} {1|17}
# Trigger that modifies rowid indirectly through updates
do_execsql_test_on_specific_db {:memory:} trigger-modify-rowid-indirect {
CREATE TABLE test55 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55 (old_rowid INTEGER, new_rowid INTEGER);
INSERT INTO test55 VALUES (1, 10);
INSERT INTO test55 VALUES (2, 20);
CREATE TRIGGER t55 AFTER UPDATE ON test55 BEGIN INSERT INTO log55 VALUES (OLD.x, NEW.x); END;
UPDATE test55 SET x = 3 WHERE x = 1;
SELECT * FROM test55 ORDER BY x;
SELECT * FROM log55;
} {2|20
3|10
1|3}
# Trigger that references OLD.rowid and NEW.rowid with rowid alias column
do_execsql_test_on_specific_db {:memory:} trigger-rowid-alias-old-new {
CREATE TABLE test55b (id INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55b (old_id INTEGER, new_id INTEGER, old_rowid INTEGER, new_rowid INTEGER);
INSERT INTO test55b VALUES (1, 10);
INSERT INTO test55b VALUES (2, 20);
CREATE TRIGGER t55b AFTER UPDATE ON test55b BEGIN INSERT INTO log55b VALUES (OLD.id, NEW.id, OLD.rowid, NEW.rowid); END;
UPDATE test55b SET id = 3 WHERE id = 1;
SELECT * FROM test55b ORDER BY id;
SELECT * FROM log55b;
} {2|20
3|10
1|3|1|3}
# Trigger with BEFORE UPDATE that modifies rowid alias in WHEN clause
do_execsql_test_on_specific_db {:memory:} trigger-before-update-rowid-alias-when {
CREATE TABLE test55c (id INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55c (msg TEXT);
INSERT INTO test55c VALUES (1, 10);
CREATE TRIGGER t55c BEFORE UPDATE ON test55c WHEN NEW.id > 5 BEGIN UPDATE test55c SET y = y + 100 WHERE id = NEW.id; END;
UPDATE test55c SET id = 10, y = 20 WHERE id = 1;
SELECT * FROM test55c;
SELECT COUNT(*) FROM log55c;
} {10|20
0}
# Trigger referencing OLD.rowid in DELETE with rowid alias column
do_execsql_test_on_specific_db {:memory:} trigger-delete-old-rowid-alias {
CREATE TABLE test55d (pk INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55d (deleted_pk INTEGER, deleted_rowid INTEGER);
INSERT INTO test55d VALUES (1, 10);
INSERT INTO test55d VALUES (2, 20);
CREATE TRIGGER t55d AFTER DELETE ON test55d BEGIN INSERT INTO log55d VALUES (OLD.pk, OLD.rowid); END;
DELETE FROM test55d WHERE pk = 1;
SELECT * FROM test55d;
SELECT * FROM log55d;
} {2|20
1|1}
# Trigger with NEW.rowid in INSERT with rowid alias
do_execsql_test_on_specific_db {:memory:} trigger-insert-new-rowid-alias {
CREATE TABLE test55e (myid INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55e (new_myid INTEGER, new_rowid INTEGER);
CREATE TRIGGER t55e AFTER INSERT ON test55e BEGIN INSERT INTO log55e VALUES (NEW.myid, NEW.rowid); END;
INSERT INTO test55e VALUES (5, 50);
INSERT INTO test55e VALUES (10, 100);
SELECT * FROM test55e ORDER BY myid;
SELECT * FROM log55e ORDER BY new_myid;
} {5|50
10|100
5|5
10|10}
# Trigger modifying rowid through UPDATE with complex WHERE using rowid alias
do_execsql_test_on_specific_db {:memory:} trigger-update-rowid-complex-where {
CREATE TABLE test55f (rid INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55f (old_rid INTEGER, new_rid INTEGER);
INSERT INTO test55f VALUES (1, 10);
INSERT INTO test55f VALUES (2, 20);
INSERT INTO test55f VALUES (3, 30);
CREATE TRIGGER t55f AFTER UPDATE ON test55f BEGIN INSERT INTO log55f VALUES (OLD.rid, NEW.rid); UPDATE test55f SET y = y + 1 WHERE rid = NEW.rid; END;
UPDATE test55f SET rid = 100 WHERE rid = 2;
SELECT * FROM test55f ORDER BY rid;
SELECT * FROM log55f;
} {1|10
3|30
100|21
2|100}
# Trigger using both rowid and rowid alias in same expression
do_execsql_test_on_specific_db {:memory:} trigger-rowid-and-alias-mixed {
CREATE TABLE test55g (id INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log55g (id_val INTEGER, rowid_val INTEGER, match INTEGER);
INSERT INTO test55g VALUES (1, 10);
CREATE TRIGGER t55g AFTER INSERT ON test55g BEGIN INSERT INTO log55g VALUES (NEW.id, NEW.rowid, NEW.id = NEW.rowid); END;
INSERT INTO test55g VALUES (5, 50);
SELECT * FROM log55g;
} {5|5|1}
# Trigger that causes cascading updates through multiple tables
do_execsql_test_on_specific_db {:memory:} trigger-cascading-updates {
CREATE TABLE test57a (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE test57b (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log57 (msg TEXT);
INSERT INTO test57a VALUES (1, 10);
INSERT INTO test57b VALUES (1, 100);
CREATE TRIGGER t57a AFTER UPDATE ON test57a BEGIN UPDATE test57b SET y = NEW.y * 10 WHERE x = NEW.x; END;
CREATE TRIGGER t57b AFTER UPDATE ON test57b BEGIN INSERT INTO log57 VALUES ('b updated: ' || NEW.y); END;
UPDATE test57a SET y = 20 WHERE x = 1;
SELECT * FROM test57a;
SELECT * FROM test57b;
SELECT * FROM log57;
} {1|20
1|200
"b updated: 200"}
# Trigger modifying columns used in unique constraint
do_execsql_test_on_specific_db {:memory:} trigger-modify-unique-column {
CREATE TABLE test58 (x INTEGER PRIMARY KEY, y INTEGER UNIQUE);
CREATE TABLE log58 (msg TEXT);
INSERT INTO test58 VALUES (1, 10);
INSERT INTO test58 VALUES (2, 20);
CREATE TRIGGER t58 AFTER UPDATE ON test58 BEGIN UPDATE test58 SET y = y + 1 WHERE x != NEW.x; INSERT INTO log58 VALUES ('updated'); END;
UPDATE test58 SET y = 15 WHERE x = 1;
SELECT * FROM test58 ORDER BY x;
SELECT COUNT(*) FROM log58;
} {1|15
2|21
1}
# BEFORE INSERT trigger that modifies NEW.x before insertion
do_execsql_test_on_specific_db {:memory:} trigger-before-insert-modify-new {
CREATE TABLE test59 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TRIGGER t59 BEFORE INSERT ON test59 BEGIN UPDATE test59 SET x = NEW.x + 100 WHERE x = NEW.x; END;
INSERT INTO test59 VALUES (1, 10);
SELECT * FROM test59;
} {1|10}
# Trigger with UPDATE OF multiple columns, modifying one of them
do_execsql_test_on_specific_db {:memory:} trigger-update-of-multiple-modify-one {
CREATE TABLE test60 (x INTEGER PRIMARY KEY, y INTEGER, z INTEGER);
CREATE TABLE log60 (msg TEXT);
INSERT INTO test60 VALUES (1, 10, 100);
CREATE TRIGGER t60 AFTER UPDATE OF y, z ON test60 BEGIN UPDATE test60 SET y = y + 50 WHERE x = NEW.x; INSERT INTO log60 VALUES ('triggered'); END;
UPDATE test60 SET y = 20 WHERE x = 1;
SELECT * FROM test60;
SELECT COUNT(*) FROM log60;
UPDATE test60 SET z = 200 WHERE x = 1;
SELECT * FROM test60;
SELECT COUNT(*) FROM log60;
} {1|70|100
1
1|120|200
2}
# Trigger modifying column that's part of composite unique constraint
do_execsql_test_on_specific_db {:memory:} trigger-modify-composite-unique {
CREATE TABLE test62 (x INTEGER, y INTEGER, UNIQUE(x, y));
CREATE TABLE log62 (msg TEXT);
INSERT INTO test62 VALUES (1, 10);
INSERT INTO test62 VALUES (2, 20);
CREATE TRIGGER t62 AFTER UPDATE ON test62 BEGIN UPDATE test62 SET y = y + 1 WHERE x = NEW.x + 1; INSERT INTO log62 VALUES ('updated'); END;
UPDATE test62 SET y = 15 WHERE x = 1;
SELECT * FROM test62 ORDER BY x;
SELECT COUNT(*) FROM log62;
} {1|15
2|21
1}
# Trigger with WHEN clause that becomes false after BEFORE trigger modification
do_execsql_test_on_specific_db {:memory:} trigger-when-becomes-false-after-before {
CREATE TABLE test63 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TABLE log63 (msg TEXT);
INSERT INTO test63 VALUES (1, 10);
CREATE TRIGGER t63a BEFORE UPDATE ON test63 BEGIN UPDATE test63 SET y = y - 15 WHERE x = NEW.x; END;
CREATE TRIGGER t63b AFTER UPDATE ON test63 WHEN NEW.y > 0 BEGIN INSERT INTO log63 VALUES ('y=' || NEW.y); END;
UPDATE test63 SET y = 20 WHERE x = 1;
SELECT * FROM test63;
SELECT COUNT(*) FROM log63;
} {1|20
1}
# Trigger that inserts into same table with different rowid
do_execsql_test_on_specific_db {:memory:} trigger-recursive-different-rowid {
CREATE TABLE test64 (x INTEGER PRIMARY KEY, y INTEGER);
CREATE TRIGGER t64 AFTER INSERT ON test64 WHEN NEW.y < 100 BEGIN INSERT INTO test64 VALUES (NEW.x + 1000, NEW.y + 10); END;
INSERT INTO test64 VALUES (1, 5);
INSERT INTO test64 VALUES (2, 50);
SELECT * FROM test64 ORDER BY x;
} {1|5
2|50
1001|15
1002|60}
# Trigger modifying primary key through UPDATE in BEFORE trigger
do_execsql_test_on_specific_db {:memory:} trigger-before-update-primary-key {
CREATE TABLE test65 (x INTEGER PRIMARY KEY, y INTEGER);
INSERT INTO test65 VALUES (1, 10);
CREATE TRIGGER t65 BEFORE UPDATE ON test65 BEGIN UPDATE test65 SET x = NEW.x + 100 WHERE x = OLD.x; END;
UPDATE test65 SET y = 20 WHERE x = 1;
SELECT * FROM test65 ORDER BY x;
} {101|10}
# Trigger modifying row during BEFORE UPDATE - parent expression behavior
# The parent UPDATE's SET clause expressions (c0 = c1+1) use OLD values for columns
# referenced in the expression (c1), but the final row gets the modified values from
# the BEFORE trigger for columns not in the parent's SET clause (c1, c2).
do_execsql_test_on_specific_db {:memory:} trigger-before-update-parent-expression-old-values {
CREATE TABLE test66 (c0 INTEGER, c1 INTEGER, c2 INTEGER);
CREATE TRIGGER tu66 BEFORE UPDATE ON test66 BEGIN UPDATE test66 SET c1=666, c2=666; END;
INSERT INTO test66 VALUES (1,1,1);
UPDATE test66 SET c0 = c1+1;
SELECT * FROM test66;
} {2|666|666}
# Multiple BEFORE INSERT triggers - last added trigger fires first
# SQLite evaluates triggers in reverse order of creation (LIFO)
do_execsql_test_on_specific_db {:memory:} trigger-multiple-before-insert-lifo {
CREATE TABLE t67(c0 INTEGER, c1 INTEGER, c2 INTEGER, whodunnit TEXT);
CREATE TRIGGER t67_first BEFORE INSERT ON t67 BEGIN INSERT INTO t67 VALUES (NEW.c0+1, NEW.c1+2, NEW.c2+3, 't67_first'); END;
CREATE TRIGGER t67_second BEFORE INSERT ON t67 BEGIN INSERT INTO t67 VALUES (NEW.c0+2, NEW.c1+3, NEW.c2+4, 't67_second'); END;
INSERT INTO t67 VALUES (1, 1, 1, 'jussi');
SELECT rowid, * FROM t67 ORDER BY rowid;
} {1|4|6|8|t67_first
2|3|4|5|t67_second
3|4|6|8|t67_second
4|2|3|4|t67_first
5|1|1|1|jussi}