Files
turso/testing/foreign_keys.test
2025-10-07 16:45:23 -04:00

1107 lines
39 KiB
Tcl
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env tclsh
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/sqlite3/tester.tcl
do_execsql_test_on_specific_db {:memory:} fk-basic-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1,'x'),(2,'y');
INSERT INTO t2 VALUES (10,1),(11,NULL); -- NULL child ok
SELECT id,tid FROM t2 ORDER BY id;
} {10|1
11|}
do_execsql_test_in_memory_any_error fk-insert-child-missing-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t2 VALUES (20,99);
}
do_execsql_test_in_memory_any_error fk-update-child-to-missing-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1,'x');
INSERT INTO t2 VALUES (10,1);
UPDATE t2 SET tid = 42 WHERE id = 10; -- now missing
}
do_execsql_test_on_specific_db {:memory:} fk-update-child-to-null-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1);
INSERT INTO t2 VALUES (7,1);
UPDATE t2 SET tid = NULL WHERE id = 7;
SELECT id, tid FROM t2;
} {7|}
do_execsql_test_in_memory_any_error fk-delete-parent-blocked {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1,'x'),(2,'y');
INSERT INTO t2 VALUES (10,2);
DELETE FROM t WHERE id=2;
}
do_execsql_test_on_specific_db {:memory:} fk-delete-parent-ok-when-no-child {
PRAGMA foreign_keys=ON;
CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE t2 (id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1,'x'),(2,'y');
INSERT INTO t2 VALUES (10,1);
DELETE FROM t WHERE id=2;
SELECT id FROM t ORDER BY id;
} {1}
do_execsql_test_on_specific_db {:memory:} fk-composite-pk-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(
a INT NOT NULL,
b INT NOT NULL,
PRIMARY KEY(a,b)
);
CREATE TABLE c(
id INT PRIMARY KEY,
x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b)
);
INSERT INTO p VALUES (1,1),(1,2);
INSERT INTO c VALUES (10,1,1),(11,1,2),(12,NULL,2); -- NULL in child allowed
SELECT id,x,y FROM c ORDER BY id;
} {10|1|1
11|1|2
12||2}
do_execsql_test_in_memory_any_error fk-composite-pk-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE p(
a INT NOT NULL,
b INT NOT NULL,
PRIMARY KEY(a,b)
);
CREATE TABLE c(
id INT PRIMARY KEY,
x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b)
);
INSERT INTO p VALUES (1,1);
INSERT INTO c VALUES (20,1,2); -- (1,2) missing
}
do_execsql_test_in_memory_any_error fk-composite-update-child-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE c(id INT PRIMARY KEY, x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b));
INSERT INTO p VALUES (1,1),(2,2);
INSERT INTO c VALUES (5,1,1);
UPDATE c SET x=2,y=3 WHERE id=5;
}
do_execsql_test_on_specific_db {:memory:} fk-composite-unique-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(u TEXT, v TEXT, pad INT, UNIQUE(u,v));
CREATE TABLE child(id INT PRIMARY KEY, cu TEXT, cv TEXT,
FOREIGN KEY(cu,cv) REFERENCES parent(u,v));
INSERT INTO parent VALUES ('A','B',0),('A','C',0);
INSERT INTO child VALUES (1,'A','B');
SELECT id, cu, cv FROM child ORDER BY id;
} {1|A|B}
do_execsql_test_in_memory_any_error fk-composite-unique-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(u TEXT, v TEXT, pad INT, UNIQUE(u,v));
CREATE TABLE child(id INT PRIMARY KEY, cu TEXT, cv TEXT,
FOREIGN KEY(cu,cv) REFERENCES parent(u,v));
INSERT INTO parent VALUES ('A','B',0);
INSERT INTO child VALUES (2,'A','X'); -- no ('A','X') in parent
}
do_execsql_test_in_memory_any_error fk-rowid-alias-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE c(cid INTEGER PRIMARY KEY, rid REFERENCES t(rowid));
INSERT INTO t VALUES (100,'x');
INSERT INTO c VALUES (1, 100);
}
do_execsql_test_in_memory_any_error fk-rowid-alias-parent-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY, a TEXT);
CREATE TABLE c(cid INTEGER PRIMARY KEY, rid REFERENCES t(rowid));
INSERT INTO c VALUES (1, 9999);
}
do_execsql_test_on_specific_db {:memory:} fk-update-child-noop-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid REFERENCES p(id));
INSERT INTO p VALUES (1);
INSERT INTO c VALUES (10,1);
UPDATE c SET id = id WHERE id = 10; -- no FK column touched
SELECT id, pid FROM c;
} {10|1}
do_execsql_test_in_memory_any_error fk-delete-parent-composite-scan {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE c(id INT PRIMARY KEY, x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b));
INSERT INTO p VALUES (1,2),(2,3);
INSERT INTO c VALUES (7,2,3);
DELETE FROM p WHERE a=2 AND b=3;
}
do_execsql_test_on_specific_db {:memory:} fk-update-child-to-existing-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY);
CREATE TABLE t2(id INTEGER PRIMARY KEY, tid REFERENCES t(id));
INSERT INTO t VALUES (1),(2);
INSERT INTO t2 VALUES (9,1);
UPDATE t2 SET tid = 2 WHERE id = 9;
SELECT id, tid FROM t2;
} {9|2}
do_execsql_test_on_specific_db {:memory:} fk-composite-pk-delete-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE c(id INT PRIMARY KEY, x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b));
INSERT INTO p VALUES (1,2),(2,3);
INSERT INTO c VALUES (7,2,3);
-- Deleting a non-referenced parent tuple is OK
DELETE FROM p WHERE a=1 AND b=2;
SELECT a,b FROM p ORDER BY a,b;
} {2|3}
do_execsql_test_in_memory_any_error fk-composite-pk-delete-violate {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE c(id INT PRIMARY KEY, x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p(a,b));
INSERT INTO p VALUES (2,3);
INSERT INTO c VALUES (7,2,3);
-- Deleting the referenced tuple should fail
DELETE FROM p WHERE a=2 AND b=3;
}
# Parent columns omitted: should default to parent's declared PRIMARY KEY (composite)
do_execsql_test_on_specific_db {:memory:} fk-default-parent-pk-composite-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(
a INT NOT NULL,
b INT NOT NULL,
PRIMARY KEY(a,b)
);
-- Parent columns omitted in REFERENCES p
CREATE TABLE c(
id INT PRIMARY KEY,
x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p
);
INSERT INTO p VALUES (1,1), (1,2);
INSERT INTO c VALUES (10,1,1), (11,1,2), (12,NULL,2); -- NULL in child allowed
SELECT id,x,y FROM c ORDER BY id;
} {10|1|1
11|1|2
12||2}
do_execsql_test_in_memory_any_error fk-default-parent-pk-composite-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE c(id INT PRIMARY KEY, x INT, y INT,
FOREIGN KEY(x,y) REFERENCES p); -- omit parent cols
INSERT INTO p VALUES (1,1);
INSERT INTO c VALUES (20,1,2); -- (1,2) missing in parent
}
# Parent has no explicitly declared PK, so we throw parse error when referencing bare table
do_execsql_test_in_memory_any_error fk-default-parent-rowid-no-parent-pk {
PRAGMA foreign_keys=ON;
CREATE TABLE p_no_pk(v TEXT);
CREATE TABLE c_rowid(id INT PRIMARY KEY,
r REFERENCES p_no_pk);
INSERT INTO p_no_pk(v) VALUES ('a'), ('b');
INSERT INTO c_rowid VALUES (1, 1);
}
do_execsql_test_on_specific_db {:memory:} fk-parent-omit-cols-parent-has-pk {
PRAGMA foreign_keys=ON;
CREATE TABLE p_pk(id INTEGER PRIMARY KEY, v TEXT);
CREATE TABLE c_ok(id INT PRIMARY KEY, r REFERENCES p_pk); -- binds to p_pk(id)
INSERT INTO p_pk VALUES (1,'a'),(2,'b');
INSERT INTO c_ok VALUES (10,1);
INSERT INTO c_ok VALUES (11,2);
SELECT id, r FROM c_ok ORDER BY id;
} {10|1 11|2}
# Self-reference (same table) with INTEGER PRIMARY KEY: single-row insert should pass
do_execsql_test_on_specific_db {:memory:} fk-self-ipk-single-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
rid REFERENCES t(id) -- child->parent in same table
);
INSERT INTO t(id,rid) VALUES(5,5); -- self-reference, single-row
SELECT id, rid FROM t;
} {5|5}
# Self-reference with mismatched value: should fail immediately (no counter semantics used)
do_execsql_test_in_memory_any_error fk-self-ipk-single-mismatch {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
rid REFERENCES t(id)
);
INSERT INTO t(id,rid) VALUES(5,4); -- rid!=id -> FK violation
}
# Self-reference on composite PRIMARY KEY: single-row insert should pass
do_execsql_test_on_specific_db {:memory:} fk-self-composite-single-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
a INT NOT NULL,
b INT NOT NULL,
x INT,
y INT,
PRIMARY KEY(a,b),
FOREIGN KEY(x,y) REFERENCES t(a,b)
);
INSERT INTO t(a,b,x,y) VALUES(1,2,1,2); -- self-reference matches PK
SELECT a,b,x,y FROM t;
} {1|2|1|2}
# Rowid parent path: text '10' must be coerced to integer (MustBeInt) and succeed
do_execsql_test_on_specific_db {:memory:} fk-rowid-mustbeint-coercion-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(cid INTEGER PRIMARY KEY, pid REFERENCES p(id));
INSERT INTO p(id) VALUES(10);
INSERT INTO c VALUES(1, '10'); -- text -> int via MustBeInt; should match
SELECT pid FROM c;
} {10}
# Rowid parent path: non-numeric text cannot be coerced -> violation
do_execsql_test_in_memory_any_error fk-rowid-mustbeint-coercion-fail {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(cid INTEGER PRIMARY KEY, pid REFERENCES p(id));
INSERT INTO p(id) VALUES(10);
INSERT INTO c VALUES(2, 'abc'); -- MustBeInt fails to match any parent row
}
# Parent match via UNIQUE index (non-rowid), success path
do_execsql_test_on_specific_db {:memory:} fk-parent-unique-index-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(u TEXT, v TEXT, pad INT, UNIQUE(u,v));
CREATE TABLE child(id INT PRIMARY KEY, cu TEXT, cv TEXT,
FOREIGN KEY(cu,cv) REFERENCES parent(u,v));
INSERT INTO parent VALUES ('A','B',0),('A','C',0);
INSERT INTO child VALUES (1,'A','B');
SELECT id, cu, cv FROM child ORDER BY id;
} {1|A|B}
# Parent UNIQUE index path: missing key -> immediate violation
do_execsql_test_in_memory_any_error fk-parent-unique-index-missing {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(u TEXT, v TEXT, pad INT, UNIQUE(u,v));
CREATE TABLE child(id INT PRIMARY KEY, cu TEXT, cv TEXT,
FOREIGN KEY(cu,cv) REFERENCES parent(u,v));
INSERT INTO parent VALUES ('A','B',0);
INSERT INTO child VALUES (2,'A','X'); -- no ('A','X') in parent
}
# NULL in child short-circuits FK check
do_execsql_test_on_specific_db {:memory:} fk-child-null-shortcircuit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid REFERENCES p(id));
INSERT INTO c VALUES (1, NULL); -- NULL child is allowed
SELECT id, pid FROM c;
} {1|}
do_execsql_test_on_specific_db {:memory:} fk-self-unique-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
u TEXT,
v TEXT,
cu TEXT,
cv TEXT,
UNIQUE(u,v),
FOREIGN KEY(cu,cv) REFERENCES t(u,v)
);
-- Single row insert where child points to its own (u,v): allowed
INSERT INTO t(u,v,cu,cv) VALUES('A','B','A','B');
SELECT u, v, cu, cv FROM t;
} {A|B|A|B}
do_execsql_test_in_memory_any_error fk-self-unique-mismatch {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
u TEXT,
v TEXT,
cu TEXT,
cv TEXT,
UNIQUE(u,v),
FOREIGN KEY(cu,cv) REFERENCES t(u,v)
);
-- Child points to a different (u,v) that doesn't exist: must fail
INSERT INTO t(u,v,cu,cv) VALUES('A','B','A','X');
}
do_execsql_test_on_specific_db {:memory:} fk-self-unique-reference-existing-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
u TEXT,
v TEXT,
cu TEXT,
cv TEXT,
UNIQUE(u,v),
FOREIGN KEY(cu,cv) REFERENCES t(u,v)
);
-- Insert a parent row first
INSERT INTO t(u,v,cu,cv) VALUES('P','Q',NULL,NULL);
-- Now insert a row whose FK references the existing ('P','Q'): OK
INSERT INTO t(u,v,cu,cv) VALUES('X','Y','P','Q');
SELECT u, v, cu, cv FROM t ORDER BY u, v, cu, cv;
} {P|Q|| X|Y|P|Q}
do_execsql_test_on_specific_db {:memory:} fk-self-unique-multirow-no-fastpath {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
u TEXT,
v TEXT,
cu TEXT,
cv TEXT,
UNIQUE(u,v),
FOREIGN KEY(cu,cv) REFERENCES t(u,v)
);
INSERT INTO t(u,v,cu,cv) VALUES
('C','D','C','D'),
('E','F','E','F');
} {}
do_execsql_test_in_memory_any_error fk-self-multirow-one-bad {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY, rid INTEGER,
FOREIGN KEY(rid) REFERENCES t(id));
INSERT INTO t(id,rid) VALUES (1,1),(3,99); -- 99 has no parent -> error
}
# doesnt fail because tx is un-committed
do_execsql_test_on_specific_db {:memory:} fk-deferred-commit-doesnt-fail-early {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 99); -- shouldnt fail because we are mid-tx
} {}
# it should fail here because we actuall COMMIT
do_execsql_test_in_memory_any_error fk-deferred-commit-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 99);
COMMIT;
}
# If we fix it before COMMIT, COMMIT succeeds
do_execsql_test_on_specific_db {:memory:} fk-deferred-fix-before-commit-succeeds {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 99); -- temporary violation
INSERT INTO p VALUES(99); -- fix parent
COMMIT;
SELECT * FROM p ORDER BY 1;
} {99}
# ROLLBACK clears deferred state; a new tx can still fail if violation persists
do_execsql_test_on_specific_db {:memory:} fk-deferred-rollback-clears {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 123);
ROLLBACK;
-- Now start over and *fix* it, COMMIT should pass.
BEGIN;
INSERT INTO p VALUES(123);
INSERT INTO c VALUES(1, 123);
COMMIT;
SELECT * FROM c ORDER BY 1;
} {1|123}
do_execsql_test_on_specific_db {:memory:} fk-deferred-insert-parent-fixes-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- violation
INSERT INTO p VALUES(50); -- resolve
COMMIT;
SELECT * FROM c ORDER BY 1;
} {1|50}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-fixes-child-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- violation
INSERT INTO p VALUES(32);
UPDATE c SET pid=32 WHERE id=1; -- resolve child
COMMIT;
SELECT * FROM c ORDER BY 1;
} {1|32}
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-fixes-child-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- violation
INSERT INTO p VALUES(32);
DELETE FROM c WHERE id=1; -- resolve by deleting child
COMMIT;
SELECT * FROM c ORDER BY 1;
} {}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-fixes-parent-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- violation
INSERT INTO p VALUES(32);
UPDATE p SET id=50 WHERE id=32; -- resolve via parent
COMMIT;
SELECT * FROM c ORDER BY 1;
} {1|50}
# Self-referential: row referencing itself should succeed
do_execsql_test_on_specific_db {:memory:} fk-deferred-self-ref-succeeds {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO t VALUES(1, 1); -- self-match
COMMIT;
SELECT * FROM t ORDER BY 1;
} {1|1}
# Two-step self-ref: insert invalid, then create parent before COMMIT
do_execsql_test_on_specific_db {:memory:} fk-deferred-self-ref-late-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO t VALUES(2, 3); -- currently invalid
INSERT INTO t VALUES(3, 3); -- now parent exists
COMMIT;
SELECT * FROM t ORDER BY 1;
} {2|3
3|3}
# counter must not be neutralized by later good statements
do_execsql_test_in_memory_any_error fk-deferred-neutralize.1 {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE parent_comp(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE child_deferred(id INTEGER PRIMARY KEY, pid INT,
FOREIGN KEY(pid) REFERENCES parent(id));
CREATE TABLE child_comp_deferred(id INTEGER PRIMARY KEY, ca INT, cb INT,
FOREIGN KEY(ca,cb) REFERENCES parent_comp(a,b));
INSERT INTO parent_comp VALUES (4,-1);
BEGIN;
INSERT INTO child_deferred VALUES (1, 999);
INSERT INTO child_comp_deferred VALUES (2, 4, -1);
COMMIT;
}
do_execsql_test_on_specific_db {:memory:} fk-deferred-upsert-late-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- deferred violation
INSERT INTO p VALUES(32); -- parent exists, but pid still 50
INSERT INTO c(id,pid) VALUES(1,32)
ON CONFLICT(id) DO UPDATE SET pid=excluded.pid; -- resolve child via UPSERT
COMMIT;
-- Expect: row is (1,32) and no violations remain
SELECT * FROM c ORDER BY id;
} {1|32}
do_execsql_test_on_specific_db {:memory:} fk-deferred-upsert-late-child {
PRAGMA foreign_keys=ON;
CREATE TABLE p(
id INTEGER PRIMARY KEY,
u INT UNIQUE
);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 50); -- deferred violation (no parent 50)
INSERT INTO p VALUES(32, 7); -- parent row with u=7
-- Trigger DO UPDATE via conflict on p.u, then change the PK id to 50,
-- which satisfies the child reference.
INSERT INTO p(id,u) VALUES(999,7)
ON CONFLICT(u) DO UPDATE SET id=50;
COMMIT;
-- Expect: parent is now (50,7), child (1,50), no violations remain
SELECT p.id, c.id FROM p join c on c.pid = p.id;
} {50|1}
do_execsql_test_in_memory_any_error fk-deferred-insert-commit-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INTEGER REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 99); -- no parent -> deferred violation
COMMIT; -- must fail
}
do_execsql_test_on_specific_db {:memory:} fk-deferred-insert-parent-fix-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INTEGER REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO c VALUES(1, 99); -- violation
INSERT INTO p VALUES(99); -- fix by inserting parent
COMMIT;
SELECT id, pid FROM c ORDER BY id;
} {1|99}
do_execsql_test_on_specific_db {:memory:} fk-deferred-insert-multi-children-one-parent-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 50);
INSERT INTO c VALUES(2, 50); -- two violations pointing to same parent
INSERT INTO p VALUES(50); -- one parent fixes both
COMMIT;
SELECT id, pid FROM c ORDER BY id;
} {1|50 2|50}
do_execsql_test_on_specific_db {:memory:} fk-deferred-insert-then-delete-child-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 77); -- violation
DELETE FROM c WHERE id=1; -- resolve by removing the child
COMMIT;
SELECT count(*) FROM c;
} {0}
do_execsql_test_on_specific_db {:memory:} fk-deferred-insert-self-ref-succeeds {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO t VALUES(1, 1); -- self-reference, legal at COMMIT
COMMIT;
SELECT id, pid FROM t;
} {1|1}
do_execsql_test_in_memory_any_error fk-deferred-update-child-breaks-commit-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10); -- valid
BEGIN;
UPDATE c SET pid=99 WHERE id=1; -- create violation
COMMIT; -- must fail
}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-child-fix-before-commit {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10);
BEGIN;
UPDATE c SET pid=99 WHERE id=1; -- violation
UPDATE c SET pid=10 WHERE id=1; -- fix child back
COMMIT;
SELECT id, pid FROM c;
} {1|10}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-child-fix-by-inserting-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10);
BEGIN;
UPDATE c SET pid=50 WHERE id=1; -- violation
INSERT INTO p VALUES(50); -- fix by adding parent
COMMIT;
SELECT id, pid FROM c;
} {1|50}
do_execsql_test_in_memory_any_error fk-deferred-update-parent-breaks-commit-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(32);
INSERT INTO c VALUES(1, 32); -- valid
BEGIN;
UPDATE p SET id=50 WHERE id=32; -- break child reference
COMMIT; -- must fail (no fix)
}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-parent-fix-by-updating-child {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(32);
INSERT INTO c VALUES(1, 32);
BEGIN;
UPDATE p SET id=50 WHERE id=32; -- break
UPDATE c SET pid=50 WHERE id=1; -- fix child to new parent key
COMMIT;
SELECT id, pid FROM c;
} {1|50}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-parent-fix-by-reverting-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(32);
INSERT INTO c VALUES(1, 32);
BEGIN;
UPDATE p SET id=50 WHERE id=32; -- break
UPDATE p SET id=32 WHERE id=50; -- revert (fix)
COMMIT;
SELECT id, pid FROM c;
} {1|32}
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-self-ref-id-change-and-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO t VALUES(1,1);
BEGIN;
UPDATE t SET id=2 WHERE id=1; -- break self-ref
UPDATE t SET pid=2 WHERE id=2; -- fix to new self
COMMIT;
SELECT id, pid FROM t;
} {2|2}
do_execsql_test_in_memory_any_error fk-deferred-delete-parent-commit-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10); -- valid
BEGIN;
DELETE FROM p WHERE id=10; -- break reference
COMMIT; -- must fail
}
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-parent-then-delete-child-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10);
BEGIN;
DELETE FROM p WHERE id=10; -- break
DELETE FROM c WHERE id=1; -- fix by removing child
COMMIT;
SELECT count(*) FROM p, c; -- both empty
} {0}
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-parent-then-reinsert-parent-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
INSERT INTO c VALUES(1, 10);
BEGIN;
DELETE FROM p WHERE id=10; -- break
INSERT INTO p VALUES(10); -- fix by re-creating parent
COMMIT;
SELECT id, pid FROM c;
} {1|10}
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-self-ref-row-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(
id INTEGER PRIMARY KEY,
pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO t VALUES(1,1); -- valid
BEGIN;
DELETE FROM t WHERE id=1; -- removes both child+parent (same row)
COMMIT; -- should succeed
SELECT count(*) FROM t;
} {0}
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-parent-then-update-child-to-null-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(
id INTEGER PRIMARY KEY,
pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO p VALUES(5);
INSERT INTO c VALUES(1,5);
BEGIN;
DELETE FROM p WHERE id=5; -- break
UPDATE c SET pid=NULL WHERE id=1; -- fix (NULL never violates)
COMMIT;
SELECT id, pid FROM c;
} {1|}
# AUTOCOMMIT: deferred FK still fails at end-of-statement
do_execsql_test_in_memory_any_error fk-deferred-autocommit-insert-missing-parent {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(id INTEGER PRIMARY KEY, pid INT REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO child VALUES(1, 3); -- no BEGIN; should fail at statement end
}
# AUTOCOMMIT: self-referential insert is OK (parent is same row)
do_execsql_test_on_specific_db {:memory:} fk-deferred-autocommit-selfref-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY, pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO t VALUES(1,1);
SELECT * FROM t;
} {1|1}
# AUTOCOMMIT: deleting a parent that has a child → fails at statement end
do_execsql_test_in_memory_any_error fk-deferred-autocommit-delete-parent-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(1);
INSERT INTO c VALUES(10,1);
DELETE FROM p WHERE id=1; -- no BEGIN; should fail at statement end
}
# TX: delete a referenced parent then reinsert before COMMIT -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-tx-delete-parent-then-reinsert-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(5);
INSERT INTO c VALUES(1,5);
BEGIN;
DELETE FROM p WHERE id=5; -- violation (deferred)
INSERT INTO p VALUES(5); -- fix in same tx
COMMIT;
SELECT count(*) FROM p WHERE id=5;
} {1}
# TX: multiple violating children, later insert parent, COMMIT -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-tx-multi-children-fixed-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1,99);
INSERT INTO c VALUES(2,99);
INSERT INTO p VALUES(99);
COMMIT;
SELECT id,pid FROM c ORDER BY id;
} {1|99 2|99}
# one of several children left unfixed -> COMMIT fails
do_execsql_test_in_memory_any_error fk-deferred-tx-multi-children-one-left-fails {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1,42);
INSERT INTO c VALUES(2,42);
INSERT INTO p VALUES(42);
UPDATE c SET pid=777 WHERE id=2; -- reintroduce a bad reference
COMMIT; -- should fail
}
# composite PK parent, fix via parent UPDATE before COMMIT -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-composite-parent-update-fix {
PRAGMA foreign_keys=ON;
CREATE TABLE parent(a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a,b));
CREATE TABLE child(id INT PRIMARY KEY, ca INT, cb INT,
FOREIGN KEY(ca,cb) REFERENCES parent(a,b) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO parent VALUES(1,1);
BEGIN;
INSERT INTO child VALUES(10, 7, 7); -- violation
UPDATE parent SET a=7, b=7 WHERE a=1 AND b=1; -- fix composite PK
COMMIT;
SELECT id, ca, cb FROM child;
} {10|7|7}
# TX: NULL in child FK -> never a violation
do_execsql_test_on_specific_db {:memory:} fk-deferred-null-fk-never-violates {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, NULL); -- always OK
COMMIT;
SELECT id, pid FROM c;
} {1|}
# TX: child UPDATE to NULL resolves before COMMIT
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-child-null-resolves {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 500); -- violation
UPDATE c SET pid=NULL WHERE id=1; -- resolves
COMMIT;
SELECT * FROM c;
} {1|}
# TX: delete violating child resolves before COMMIT
do_execsql_test_on_specific_db {:memory:} fk-deferred-delete-child-resolves {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 777); -- violation
DELETE FROM c WHERE id=1; -- resolves
COMMIT;
SELECT count(*) FROM c;
} {0}
# TX: update parent PK to match child before COMMIT -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-update-parent-pk-resolves {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO p VALUES(10);
BEGIN;
INSERT INTO c VALUES(1, 20); -- violation
UPDATE p SET id=20 WHERE id=10; -- resolve via parent
COMMIT;
SELECT * FROM c;
} {1|20}
# Two-table cycle; both inserted before COMMIT -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-cycle-two-tables-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE a(id INT PRIMARY KEY, b_id INT, FOREIGN KEY(b_id) REFERENCES b(id) DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE b(id INT PRIMARY KEY, a_id INT, FOREIGN KEY(a_id) REFERENCES a(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO a VALUES(1, 1); -- refers to b(1) (not yet present)
INSERT INTO b VALUES(1, 1); -- refers to a(1)
COMMIT;
SELECT count(b.id), count(a.id) FROM a, b;
} {1|1}
# Delete a row that self-references (child==parent) within a tx -> OK
do_execsql_test_on_specific_db {:memory:} fk-deferred-selfref-delete-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE t(id INTEGER PRIMARY KEY, pid INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED);
INSERT INTO t VALUES(1,1);
BEGIN;
DELETE FROM t WHERE id=1;
COMMIT;
SELECT count(*) FROM t;
} {0}
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-donothing-noconflict-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent (id INTEGER PRIMARY KEY, a INT, b INT);
CREATE TABLE child_deferred (
id INTEGER PRIMARY KEY, pid INT, x INT,
FOREIGN KEY(pid) REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED
);
CREATE TABLE parent_comp (a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred (
id INTEGER PRIMARY KEY, ca INT, cb INT, z INT,
FOREIGN KEY (ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
-- No conflict on (a,b); should insert 1 row, no FK noise
INSERT INTO parent_comp VALUES (-1,-1,9) ON CONFLICT DO NOTHING;
SELECT a,b,c FROM parent_comp ORDER BY a,b;
} {-1|-1|9}
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-donothing-conflict-noop {
PRAGMA foreign_keys=ON;
CREATE TABLE parent_comp (a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred (
id INTEGER PRIMARY KEY, ca INT, cb INT, z INT,
FOREIGN KEY (ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO parent_comp VALUES (10,20,1);
-- Conflicts with existing (10,20); must do nothing (no triggers, no FK scans that mutate counters)
INSERT INTO parent_comp VALUES (10,20,999) ON CONFLICT DO NOTHING;
SELECT a,b,c FROM parent_comp;
} {10|20|1}
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-donothing-unrelated-immediate-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent (id INTEGER PRIMARY KEY);
CREATE TABLE child_immediate (
id INTEGER PRIMARY KEY, pid INT,
FOREIGN KEY(pid) REFERENCES parent(id) -- IMMEDIATE
);
CREATE TABLE parent_comp (a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred (
id INTEGER PRIMARY KEY, ca INT, cb INT, z INT,
FOREIGN KEY(ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO parent_comp VALUES (-1,-1,9) ON CONFLICT DO NOTHING;
SELECT a,b,c FROM parent_comp;
} {-1|-1|9}
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-deferred-fix-inside-tx-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent_comp (a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred (
id INTEGER PRIMARY KEY, ca INT, cb INT,
FOREIGN KEY(ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO child_comp_deferred VALUES (1, -5, -6); -- violation
INSERT INTO parent_comp VALUES (-5, -6, 9); -- fix via parent insert
COMMIT;
SELECT id,ca,cb FROM child_comp_deferred;
} {1|-5|-6}
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-autocommit-unrelated-children-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE parent_comp (a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred (
id INTEGER PRIMARY KEY, ca INT, cb INT,
FOREIGN KEY(ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO parent_comp VALUES (1,1,0);
INSERT INTO child_comp_deferred VALUES (10,1,1); -- valid
INSERT INTO parent_comp VALUES (2,2,0) ON CONFLICT DO NOTHING; -- unrelated insert; must not raise
SELECT a,b,c FROM parent_comp ORDER BY a,b;
} {1|1|0
2|2|0}
# ROLLBACK must clear any deferred state; next statement must not trip.
do_execsql_test_on_specific_db {:memory:} fk-rollback-clears-then-donothing-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE parent_comp(a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
BEGIN;
INSERT INTO c VALUES(1, 456); -- create deferred violation
ROLLBACK; -- must clear counters
INSERT INTO parent_comp VALUES(-2,-2,0) ON CONFLICT DO NOTHING;
SELECT a,b,c FROM parent_comp;
} {-2|-2|0}
# DO NOTHING conflict path must touch no FK maintenance at all.
do_execsql_test_on_specific_db {:memory:} fk-parentcomp-donothing-conflict-stays-quiet {
PRAGMA foreign_keys=ON;
CREATE TABLE parent_comp(a INT NOT NULL, b INT NOT NULL, c INT, PRIMARY KEY(a,b));
CREATE TABLE child_comp_deferred(
id INTEGER PRIMARY KEY, ca INT, cb INT,
FOREIGN KEY(ca,cb) REFERENCES parent_comp(a,b) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO parent_comp VALUES(10,20,1);
-- This conflicts with (10,20) and must be a no-op; if counters move here, its a bug.
INSERT INTO parent_comp VALUES(10,20,999) ON CONFLICT DO NOTHING;
-- Prove DB is sane afterwards (no stray FK error)
INSERT INTO parent_comp VALUES(11,22,3) ON CONFLICT DO NOTHING;
SELECT a,b FROM parent_comp ORDER BY a,b;
} {10|20
11|22}
# Two-statement fix inside an explicit transaction (separate statements).
#Insert child (violation), then insert parent in a new statement; commit must pass.
do_execsql_test_on_specific_db {:memory:} fk-deferred-two-stmt-fix-inside-tx-ok {
PRAGMA foreign_keys=ON;
CREATE TABLE p(id INTEGER PRIMARY KEY);
CREATE TABLE c(id INTEGER PRIMARY KEY, pid INT REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO c VALUES(1, 777); -- violation recorded in tx
INSERT INTO p VALUES(777); -- next statement fixes it
COMMIT;
SELECT * FROM c;
} {1|777}
do_execsql_test_on_specific_db {:memory:} fk-delete-composite-bounds {
PRAGMA foreign_keys=ON;
CREATE TABLE p(a INT NOT NULL, b INT NOT NULL, v INT, PRIMARY KEY(a,b));
CREATE TABLE c(id INTEGER PRIMARY KEY, x INT, y INT, w INT,
FOREIGN KEY(x,y) REFERENCES p(a,b));
INSERT INTO p VALUES (5,1,0),(5,2,0),(5,4,0);
INSERT INTO c VALUES (1,5,4,0); -- child references (5,4)
-- This should be a no-op (no row (5,3)), and MUST NOT error.
DELETE FROM p WHERE a=5 AND b=3;
} {}