diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 8fa43ab..68e0924 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -54,6 +54,7 @@ #include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/fmgroids.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -1283,7 +1284,42 @@ lreplace:; /* * Row movement, part 1. Delete the tuple, but skip RETURNING * processing. We want to return rows from INSERT. + * + * However, deleting the tuple might inadvertently delete a tuple + * in a referencing relation due to the relevant foreign key's ON + * DELETE CASCADE action. That should not be allowed to happen, + * because the row being moved is not actually being "deleted" as + * such from this relation. We can check if that may happen by + * looking for a constraint trigger in this relation that + * implements the cascaded delete. */ + if (resultRelInfo->ri_TrigDesc != NULL && + resultRelInfo->ri_TrigDesc->trig_delete_after_row) + { + TriggerDesc *trigdesc = resultRelInfo->ri_TrigDesc; + int i; + bool found = false; + + for (i = 0; i < trigdesc->numtriggers; i++) + { + Trigger *trigger = &trigdesc->triggers[i]; + + if (trigger->tgisinternal && + OidIsValid(trigger->tgconstrrelid) && + trigger->tgfoid == F_RI_FKEY_CASCADE_DEL) + { + found = true; + break; + } + } + + if (found) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move row being updated to another partition"), + errdetail("Moving the row may cause a foreign key involving the source partition to be violated."))); + } + ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate, estate, false, false /* canSetTag */ , true /* changingPart */ , &tuple_deleted, &epqslot); diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index dc34ac6..28c0f30 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -942,3 +942,21 @@ update hash_parted set b = b + 8 where b = 1; drop table hash_parted; drop operator class custom_opclass using hash; drop function dummy_hashint4(a int4, seed int8); +-- Disallow moving a row that is referenced in a foreign key relationship +create table fk_parted_parent (id int primary key) partition by list (id); +create table fk_parted_parent_1 partition of fk_parted_parent for values in (1, 2); +create table fk_parted_paernt_2 partition of fk_parted_parent for values in (3); +create table fk_child (a int, id int references fk_parted_parent on delete cascade on update cascade); +insert into fk_parted_parent values (1); +insert into fk_child values (1, 1); +update fk_parted_parent set id = 2; -- ok +table fk_child; + a | id +---+---- + 1 | 2 +(1 row) + +update fk_parted_parent set id = 3; -- error +ERROR: cannot move row being updated to another partition +DETAIL: Moving the row may cause a foreign key involving the source partition to be violated. +drop table fk_child, fk_parted_parent; diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 8c558a7..f353e3b 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -612,3 +612,15 @@ update hash_parted set b = b + 8 where b = 1; drop table hash_parted; drop operator class custom_opclass using hash; drop function dummy_hashint4(a int4, seed int8); + +-- Disallow moving a row that is referenced in a foreign key relationship +create table fk_parted_parent (id int primary key) partition by list (id); +create table fk_parted_parent_1 partition of fk_parted_parent for values in (1, 2); +create table fk_parted_paernt_2 partition of fk_parted_parent for values in (3); +create table fk_child (a int, id int references fk_parted_parent on delete cascade on update cascade); +insert into fk_parted_parent values (1); +insert into fk_child values (1, 1); +update fk_parted_parent set id = 2; -- ok +table fk_child; +update fk_parted_parent set id = 3; -- error +drop table fk_child, fk_parted_parent;