Here is a rebased patch this time I did not indent the part under if(SessionReplicationRole != SESSION_REPLICATION_ROLE_REPLICA) { ... (<did not add indent> }
so it is immediately obviuos from the patch what is added. I can add the indent later, or just let pg_ident take care of this in due time On Sat, May 24, 2025 at 4:02 PM Hannu Krosing <han...@google.com> wrote: > > I would also argue for treating this as a bug and back-porting to all > supported versions - a quick look at v13 seems to confirm that the > wrapped code has not changed at least since then. > > I don't think we can claim the current state is Working As Intended > unless someone stands up and says they really intended it to work this > way :) > > On Sat, May 24, 2025 at 3:47 PM Hannu Krosing <han...@google.com> wrote: > > > > Hello Everybody, > > > > Currently setting `session_replication_role` to `replica` disables > > foreign key checks allowing, among other things, free table copy order > > and faster CDC apply in logical replication. > > > > But two other cases of foreign keys are still restricted or blocked > > even with this setting > > > > > > 1. Foreign Key checks during `ALTER TABLE <fkt> ADD FOREIGN KEY > > (<fkcol>) REFERENCES <pkt>(<pkcol>);` > > > > These can be really, Really, REALLY slow when the PK table is huge > > (hundreds of billions of rows). And here I mean taking-tens-of-days > > slow in extreme cases. > > > > And they are also completely unnecessary when the table data comes > > from a known good source. > > > > > > 2. `TRUNCATE <referenced table>` is blocked when there are any foreign > > keys referencing the <referenced table> > > > > But you still can mess up your database in exactly the same way by > > running `DELETE FROM <referenced table>`, just a little (or a lot) > > slower. > > > > > > The attached patch fixes this, allowing both cases to work when `SET > > session_replication_role=replica;` is in force: > > > > ### Test for `ALTER TABLE <fkt> ADD FOREIGN KEY (<fkcol>) REFERENCES > > <pkt>(<pkcol>);` > > > > CREATE TABLE pkt(id int PRIMARY KEY); > > CREATE TABLE fkt(fk int); > > INSERT INTO fkt VALUES(1); > > > > ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- fails > > > > SET session_replication_role=replica; > > ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- now succeeds > > > > ### Test for `TRUNCATE <referenced table>` > > > > CREATE TABLE pkt(id int PRIMARY KEY); > > CREATE TABLE fkt(fk int REFERENCES pkt(id)); > > > > TRUNCATE pkt; -- fails > > > > SET session_replication_role=replica; > > TRUNCATE pkt; -- now succeeds > > > > > > > > The patch just wraps two sections of code in > > > > if (SessionReplicationRole != SESSION_REPLICATION_ROLE_REPLICA) { > > ... > > } > > > > and the rest is added indentation for the wrapped. > > > > Plus I added tests, including the until now missing test for > > explicitly the foreign key checks (which can be argued to already be > > covered by generic trigger tests) > > > > > > I am not sure if we need to add anything to current documentation[*] > > which says just this about foreign keys and > > `session_replication_role=replica` > > > > > Since foreign keys are implemented as triggers, setting this parameter to > > > replica also disables all foreign key checks, which can leave data in an > > > inconsistent state if improperly used. > > > > [*] > > https://www.postgresql.org/docs/17/runtime-config-client.html#GUC-SESSION-REPLICATION-ROLE > > > > Any comments and suggestions are welcome
From 2191e2beec99fc6ca767d248e9adcc38d0339509 Mon Sep 17 00:00:00 2001 From: Hannu Krosing <han...@google.com> Date: Sun, 6 Jul 2025 13:39:04 +0200 Subject: [PATCH v2] rebased, also did not indent the contents of if to make it clearer what is added --- src/backend/commands/tablecmds.c | 18 ++++++-- src/test/regress/expected/foreign_key.out | 35 ++++++++++++++++ src/test/regress/sql/foreign_key.sql | 51 +++++++++++++++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cb811520c29..bdd786b4b43 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2035,13 +2035,19 @@ ExecuteTruncateGuts(List *explicit_rels, * Check foreign key references. In CASCADE mode, this should be * unnecessary since we just pulled in all the references; but as a * cross-check, do it anyway if in an Assert-enabled build. + * + * Skip foreign key checks when `session_replication_role = replica` to + * match the behaviour of disabling FK triggers in the same situation */ + if (SessionReplicationRole != SESSION_REPLICATION_ROLE_REPLICA) + { #ifdef USE_ASSERT_CHECKING - heap_truncate_check_FKs(rels, false); -#else - if (behavior == DROP_RESTRICT) heap_truncate_check_FKs(rels, false); +#else + if (behavior == DROP_RESTRICT) + heap_truncate_check_FKs(rels, false); #endif + } /* * If we are asked to restart sequences, find all the sequences, lock them @@ -6043,7 +6049,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, * theoretically possible that we have changed both relations of the * foreign key, and we'd better have finished both rewrites before we try * to read the tables. + * + * Skip the check when `session_replication_mode = replica` to save time + * and to match the FK trigger behaviour in the same situation */ ++ if (SessionReplicationRole != SESSION_REPLICATION_ROLE_REPLICA) + { foreach(ltab, *wqueue) { AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); @@ -6088,6 +6099,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, if (rel) table_close(rel, NoLock); } + } /* Finally, run any afterStmts that were queued up */ foreach(ltab, *wqueue) diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index f9bd252444f..32f39e8a57d 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -3402,6 +3402,41 @@ ALTER TABLE fk_r DROP CONSTRAINT fk_r_p_id_p_jd_fkey_1; ERROR: cannot drop inherited constraint "fk_r_p_id_p_jd_fkey_1" of relation "fk_r" ALTER TABLE fk_r_2 DROP CONSTRAINT fk_r_p_id_p_jd_fkey; ERROR: cannot drop inherited constraint "fk_r_p_id_p_jd_fkey" of relation "fk_r_2" +-- tests for SET session_replication_role = replica; +RESET session_replication_role; +-- disabling FK checks +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int REFERENCES pkt(id)); +INSERT INTO fkt VALUES(1); -- should fail +ERROR: insert or update on table "fkt" violates foreign key constraint "fkt_fk_fkey" +DETAIL: Key (fk)=(1) is not present in table "pkt". +SET session_replication_role=replica; +INSERT INTO fkt VALUES(1); -- should succeed now +DROP TABLE fkt, pkt; +RESET session_replication_role; +-- skipping FK validation during ALTER TABLE ... ADD FOREIGN KEY +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int); +INSERT INTO fkt VALUES(1); +ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- should fail +ERROR: insert or update on table "fkt" violates foreign key constraint "fkt_fk_fkey" +DETAIL: Key (fk)=(1) is not present in table "pkt". +SET session_replication_role=replica; +ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- should succeed now +DROP TABLE fkt, pkt; +RESET session_replication_role; +-- skipping FK existence checks during TRUNCATE +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int REFERENCES pkt(id)); +TRUNCATE pkt; -- should fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "fkt" references "pkt". +HINT: Truncate table "fkt" at the same time, or use TRUNCATE ... CASCADE. +SET session_replication_role=replica; +TRUNCATE pkt; -- should succeed now +DROP TABLE fkt, pkt; +RESET session_replication_role; +-- end of tests for SET session_replication_role = replica; SET client_min_messages TO warning; DROP SCHEMA fkpart12 CASCADE; RESET client_min_messages; diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index cfcecb4e911..d826694947f 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1208,6 +1208,10 @@ commit; drop table pktable2, fktable2; +-- +-- +-- + -- -- Test keys that "look" different but compare as equal -- @@ -2382,7 +2386,54 @@ ALTER TABLE fk_r_1 DROP CONSTRAINT fk_r_p_id_p_jd_fkey; ALTER TABLE fk_r DROP CONSTRAINT fk_r_p_id_p_jd_fkey_1; ALTER TABLE fk_r_2 DROP CONSTRAINT fk_r_p_id_p_jd_fkey; +-- tests for SET session_replication_role = replica; + +RESET session_replication_role; + +-- disabling FK checks + +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int REFERENCES pkt(id)); + +INSERT INTO fkt VALUES(1); -- should fail + +SET session_replication_role=replica; +INSERT INTO fkt VALUES(1); -- should succeed now + +DROP TABLE fkt, pkt; +RESET session_replication_role; + +-- skipping FK validation during ALTER TABLE ... ADD FOREIGN KEY + +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int); +INSERT INTO fkt VALUES(1); + +ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- should fail + +SET session_replication_role=replica; +ALTER TABLE fkt ADD FOREIGN KEY (fk) REFERENCES pkt(id); -- should succeed now + +DROP TABLE fkt, pkt; +RESET session_replication_role; + +-- skipping FK existence checks during TRUNCATE + +CREATE TABLE pkt(id int PRIMARY KEY); +CREATE TABLE fkt(fk int REFERENCES pkt(id)); + +TRUNCATE pkt; -- should fail + +SET session_replication_role=replica; +TRUNCATE pkt; -- should succeed now + +DROP TABLE fkt, pkt; +RESET session_replication_role; + +-- end of tests for SET session_replication_role = replica; + SET client_min_messages TO warning; DROP SCHEMA fkpart12 CASCADE; RESET client_min_messages; RESET search_path; + -- 2.50.0.727.gbf7dc18ff4-goog