Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 123cc69

Browse files
committed
Create action triggers when partitions are detached
Detaching a partition from a partitioned table that's constrained by foreign keys requires additional action triggers on the referenced side; otherwise, DELETE/UPDATE actions there fail to notice rows in the table that was partition, and so are incorrectly allowed through. With this commit, those triggers are now created. Conversely, when a table that has a foreign key is attached as a partition to a table that also has the same foreign key, those action triggers are no longer needed, so we remove them. Add a minimal test case verifying (part of) this. Authors: Amit Langote, Álvaro Herrera Discussion: https://postgr.es/m/f2b8ead5-4131-d5a8-8016-2ea0a31250af@lab.ntt.co.jp
1 parent a747430 commit 123cc69

File tree

3 files changed

+113
-5
lines changed

3 files changed

+113
-5
lines changed

src/backend/commands/tablecmds.c

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7945,6 +7945,10 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel,
79457945
ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, cell);
79467946
Form_pg_constraint partConstr;
79477947
HeapTuple partcontup;
7948+
Relation trigrel;
7949+
HeapTuple trigtup;
7950+
SysScanDesc scan;
7951+
ScanKeyData key;
79487952

79497953
attach_it = true;
79507954

@@ -7996,7 +8000,39 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel,
79968000

79978001
ReleaseSysCache(partcontup);
79988002

7999-
/* looks good! Attach this constraint */
8003+
/*
8004+
* Looks good! Attach this constraint. Note that the action
8005+
* triggers are no longer needed, so remove them. We identify
8006+
* them because they have our constraint OID, as well as being
8007+
* on the referenced rel.
8008+
*/
8009+
trigrel = heap_open(TriggerRelationId, RowExclusiveLock);
8010+
ScanKeyInit(&key,
8011+
Anum_pg_trigger_tgconstraint,
8012+
BTEqualStrategyNumber, F_OIDEQ,
8013+
ObjectIdGetDatum(fk->conoid));
8014+
8015+
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
8016+
NULL, 1, &key);
8017+
while ((trigtup = systable_getnext(scan)) != NULL)
8018+
{
8019+
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
8020+
8021+
if (trgform->tgconstrrelid != fk->conrelid)
8022+
continue;
8023+
if (trgform->tgrelid != fk->confrelid)
8024+
continue;
8025+
8026+
deleteDependencyRecordsForClass(TriggerRelationId,
8027+
HeapTupleGetOid(trigtup),
8028+
ConstraintRelationId,
8029+
DEPENDENCY_INTERNAL);
8030+
CatalogTupleDelete(trigrel, &trigtup->t_self);
8031+
}
8032+
8033+
systable_endscan(scan);
8034+
heap_close(trigrel, RowExclusiveLock);
8035+
80008036
ConstraintSetParentConstraint(fk->conoid, parentConstrOid);
80018037
CommandCounterIncrement();
80028038
attach_it = true;
@@ -15211,19 +15247,50 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
1521115247
}
1521215248
heap_close(classRel, RowExclusiveLock);
1521315249

15214-
/* Detach foreign keys */
15250+
/*
15251+
* Detach any foreign keys that are inherited. This includes creating
15252+
* additional action triggers.
15253+
*/
1521515254
fks = copyObject(RelationGetFKeyList(partRel));
1521615255
foreach(cell, fks)
1521715256
{
1521815257
ForeignKeyCacheInfo *fk = lfirst(cell);
1521915258
HeapTuple contup;
15259+
Form_pg_constraint conform;
15260+
Constraint *fkconstraint;
1522015261

1522115262
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
1522215263
if (!contup)
1522315264
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
15265+
conform = (Form_pg_constraint) GETSTRUCT(contup);
15266+
15267+
/* consider only the inherited foreign keys */
15268+
if (conform->contype != CONSTRAINT_FOREIGN ||
15269+
!OidIsValid(conform->conparentid))
15270+
{
15271+
ReleaseSysCache(contup);
15272+
continue;
15273+
}
1522415274

15275+
/* unset conparentid and adjust conislocal, coninhcount, etc. */
1522515276
ConstraintSetParentConstraint(fk->conoid, InvalidOid);
1522615277

15278+
/*
15279+
* Make the action triggers on the referenced relation. When this was
15280+
* a partition the action triggers pointed to the parent rel (they
15281+
* still do), but now we need separate ones of our own.
15282+
*/
15283+
fkconstraint = makeNode(Constraint);
15284+
fkconstraint->conname = pstrdup(NameStr(conform->conname));
15285+
fkconstraint->fk_upd_action = conform->confupdtype;
15286+
fkconstraint->fk_del_action = conform->confdeltype;
15287+
fkconstraint->deferrable = conform->condeferrable;
15288+
fkconstraint->initdeferred = conform->condeferred;
15289+
15290+
createForeignKeyActionTriggers(partRel, conform->confrelid,
15291+
fkconstraint, fk->conoid,
15292+
conform->conindid);
15293+
1522715294
ReleaseSysCache(contup);
1522815295
}
1522915296
list_free_deep(fks);

src/test/regress/expected/foreign_key.out

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,31 @@ alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
18601860
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56"
18611861
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
18621862
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56_5"
1863+
-- verify that attaching and detaching partitions maintains the right set of
1864+
-- triggers
1865+
create schema fkpart1
1866+
create table pkey (a int primary key)
1867+
create table fk_part (a int) partition by list (a)
1868+
create table fk_part_1 partition of fk_part for values in (1) partition by list (a)
1869+
create table fk_part_1_1 partition of fk_part_1 for values in (1);
1870+
alter table fkpart1.fk_part add foreign key (a) references fkpart1.pkey;
1871+
insert into fkpart1.fk_part values (1); -- should fail
1872+
ERROR: insert or update on table "fk_part_1_1" violates foreign key constraint "fk_part_a_fkey"
1873+
DETAIL: Key (a)=(1) is not present in table "pkey".
1874+
insert into fkpart1.pkey values (1);
1875+
insert into fkpart1.fk_part values (1);
1876+
delete from fkpart1.pkey where a = 1; -- should fail
1877+
ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part"
1878+
DETAIL: Key (a)=(1) is still referenced from table "fk_part".
1879+
alter table fkpart1.fk_part detach partition fkpart1.fk_part_1;
1880+
create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2);
1881+
insert into fkpart1.fk_part_1 values (2); -- should fail
1882+
ERROR: insert or update on table "fk_part_1_2" violates foreign key constraint "fk_part_a_fkey"
1883+
DETAIL: Key (a)=(2) is not present in table "pkey".
1884+
delete from fkpart1.pkey where a = 1;
1885+
ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part_1"
1886+
DETAIL: Key (a)=(1) is still referenced from table "fk_part_1".
18631887
\set VERBOSITY terse \\ -- suppress cascade details
1864-
drop schema fkpart0 cascade;
1865-
NOTICE: drop cascades to 2 other objects
1888+
drop schema fkpart0, fkpart1 cascade;
1889+
NOTICE: drop cascades to 5 other objects
18661890
\set VERBOSITY default

src/test/regress/sql/foreign_key.sql

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,23 @@ create table fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56
13241324
alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
13251325
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
13261326

1327+
-- verify that attaching and detaching partitions maintains the right set of
1328+
-- triggers
1329+
create schema fkpart1
1330+
create table pkey (a int primary key)
1331+
create table fk_part (a int) partition by list (a)
1332+
create table fk_part_1 partition of fk_part for values in (1) partition by list (a)
1333+
create table fk_part_1_1 partition of fk_part_1 for values in (1);
1334+
alter table fkpart1.fk_part add foreign key (a) references fkpart1.pkey;
1335+
insert into fkpart1.fk_part values (1); -- should fail
1336+
insert into fkpart1.pkey values (1);
1337+
insert into fkpart1.fk_part values (1);
1338+
delete from fkpart1.pkey where a = 1; -- should fail
1339+
alter table fkpart1.fk_part detach partition fkpart1.fk_part_1;
1340+
create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2);
1341+
insert into fkpart1.fk_part_1 values (2); -- should fail
1342+
delete from fkpart1.pkey where a = 1;
1343+
13271344
\set VERBOSITY terse \\ -- suppress cascade details
1328-
drop schema fkpart0 cascade;
1345+
drop schema fkpart0, fkpart1 cascade;
13291346
\set VERBOSITY default

0 commit comments

Comments
 (0)