FOREIGN KEY constraint failed while `foreign_key_check` finds no problems
(1) By William Manley (wmanley) on 2022-07-27 12:28:33 [source]
When using PRAGMA defer_foreign_keys = ON
I'm seeing "FOREIGN KEY constraint failed" errors on COMMIT
. PRAGMA foreign_key_check
reports no problems. If I set PRAGMA defer_foreign_keys = OFF
before the COMMIT
everything works fine.
Minimised reproduction of an attempted table migration following https://www.sqlite.org/lang_altertable.html but using defer_foreign_keys = ON
rather than foreign_keys = OFF
:
PRAGMA foreign_keys = 1;
CREATE TABLE Node(node_oid INTEGER PRIMARY KEY NOT NULL);
CREATE TABLE Job(
node_oid INTEGER NOT NULL,
FOREIGN KEY(node_oid) REFERENCES Node(node_oid));
INSERT INTO Node(node_oid) VALUES (0);
INSERT INTO Job(node_oid) VALUES (0);
BEGIN;
PRAGMA defer_foreign_keys = ON;
CREATE TABLE Node_migration_new(node_oid INTEGER PRIMARY KEY NOT NULL);
INSERT INTO Node_migration_new(node_oid)
SELECT node_oid FROM Node;
DROP TABLE Node;
ALTER TABLE Node_migration_new RENAME TO Node;
PRAGMA foreign_key_check;
-- Uncomment this and it succeeds:
-- PRAGMA defer_foreign_keys = OFF;
COMMIT;
I'm not sure what the semantics of setting PRAGMA defer_foreign_keys = OFF
. Does it do a check then and there, or does it skip checking altogether?
I've reproduced this with sqlite 3.31.1 from Ubuntu 20.04 and with a freshly built 3.39.2 (latest available from sqlite.org).
(2) By Richard Hipp (drh) on 2022-07-27 13:08:53 in reply to 1 [link] [source]
SQLite manages foreign key constraints using a counter.
When a constraint is violated, the counter increments.
When a constraint violation is resolved, the counter decrements.
SQLite raises a foreign key constraint error when the counter is non-zero. The counter is checked at either:
End end of each SQL statement.
Just before the transaction commits.
The PRAGMA defer_foreign_keys statement simply changes the time when the counter is checked.
I think what is happening in your situation is that the DROP TABLE is causing lots of new foreign key constraint violations, but the ALTER TABLE RENAME is not detecting the resolution of those violations (because ALTER TABLE does not know how to do that). That means that the fault counter is non-zero at the start of the COMMIT.
But if you switch from deferred to immediate, that bypasses the counter test and causes the non-zero counter to be ignored.
(3) By William Manley (wmanley) on 2022-07-27 13:21:52 in reply to 2 [link] [source]
I see. So if there was a genuine constraint violation between PRAGMA defer_foreign_keys = ON;
and PRAGMA defer_foreign_keys = OFF;
it wouldn't be detected short of explicitly calling PRAGMA foreign_key_check
.
Thanks, this is clearer for me now.