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

Commit 0464fdf

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 1755440 commit 0464fdf

File tree

3 files changed

+113
-5
lines changed

3 files changed

+113
-5
lines changed

src/backend/commands/tablecmds.c

+69-2
Original file line numberDiff line numberDiff line change
@@ -7851,6 +7851,10 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel,
78517851
ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, cell);
78527852
Form_pg_constraint partConstr;
78537853
HeapTuple partcontup;
7854+
Relation trigrel;
7855+
HeapTuple trigtup;
7856+
SysScanDesc scan;
7857+
ScanKeyData key;
78547858

78557859
attach_it = true;
78567860

@@ -7902,7 +7906,39 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel,
79027906

79037907
ReleaseSysCache(partcontup);
79047908

7905-
/* looks good! Attach this constraint */
7909+
/*
7910+
* Looks good! Attach this constraint. Note that the action
7911+
* triggers are no longer needed, so remove them. We identify
7912+
* them because they have our constraint OID, as well as being
7913+
* on the referenced rel.
7914+
*/
7915+
trigrel = heap_open(TriggerRelationId, RowExclusiveLock);
7916+
ScanKeyInit(&key,
7917+
Anum_pg_trigger_tgconstraint,
7918+
BTEqualStrategyNumber, F_OIDEQ,
7919+
ObjectIdGetDatum(fk->conoid));
7920+
7921+
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
7922+
NULL, 1, &key);
7923+
while ((trigtup = systable_getnext(scan)) != NULL)
7924+
{
7925+
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
7926+
7927+
if (trgform->tgconstrrelid != fk->conrelid)
7928+
continue;
7929+
if (trgform->tgrelid != fk->confrelid)
7930+
continue;
7931+
7932+
deleteDependencyRecordsForClass(TriggerRelationId,
7933+
trgform->oid,
7934+
ConstraintRelationId,
7935+
DEPENDENCY_INTERNAL);
7936+
CatalogTupleDelete(trigrel, &trigtup->t_self);
7937+
}
7938+
7939+
systable_endscan(scan);
7940+
heap_close(trigrel, RowExclusiveLock);
7941+
79067942
ConstraintSetParentConstraint(fk->conoid, parentConstrOid);
79077943
CommandCounterIncrement();
79087944
attach_it = true;
@@ -15073,19 +15109,50 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
1507315109
}
1507415110
table_close(classRel, RowExclusiveLock);
1507515111

15076-
/* Detach foreign keys */
15112+
/*
15113+
* Detach any foreign keys that are inherited. This includes creating
15114+
* additional action triggers.
15115+
*/
1507715116
fks = copyObject(RelationGetFKeyList(partRel));
1507815117
foreach(cell, fks)
1507915118
{
1508015119
ForeignKeyCacheInfo *fk = lfirst(cell);
1508115120
HeapTuple contup;
15121+
Form_pg_constraint conform;
15122+
Constraint *fkconstraint;
1508215123

1508315124
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
1508415125
if (!contup)
1508515126
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
15127+
conform = (Form_pg_constraint) GETSTRUCT(contup);
15128+
15129+
/* consider only the inherited foreign keys */
15130+
if (conform->contype != CONSTRAINT_FOREIGN ||
15131+
!OidIsValid(conform->conparentid))
15132+
{
15133+
ReleaseSysCache(contup);
15134+
continue;
15135+
}
1508615136

15137+
/* unset conparentid and adjust conislocal, coninhcount, etc. */
1508715138
ConstraintSetParentConstraint(fk->conoid, InvalidOid);
1508815139

15140+
/*
15141+
* Make the action triggers on the referenced relation. When this was
15142+
* a partition the action triggers pointed to the parent rel (they
15143+
* still do), but now we need separate ones of our own.
15144+
*/
15145+
fkconstraint = makeNode(Constraint);
15146+
fkconstraint->conname = pstrdup(NameStr(conform->conname));
15147+
fkconstraint->fk_upd_action = conform->confupdtype;
15148+
fkconstraint->fk_del_action = conform->confdeltype;
15149+
fkconstraint->deferrable = conform->condeferrable;
15150+
fkconstraint->initdeferred = conform->condeferred;
15151+
15152+
createForeignKeyActionTriggers(partRel, conform->confrelid,
15153+
fkconstraint, fk->conoid,
15154+
conform->conindid);
15155+
1508915156
ReleaseSysCache(contup);
1509015157
}
1509115158
list_free_deep(fks);

src/test/regress/expected/foreign_key.out

+26-2
Original file line numberDiff line numberDiff line change
@@ -1921,7 +1921,31 @@ alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
19211921
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56"
19221922
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
19231923
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56_5"
1924+
-- verify that attaching and detaching partitions maintains the right set of
1925+
-- triggers
1926+
create schema fkpart1
1927+
create table pkey (a int primary key)
1928+
create table fk_part (a int) partition by list (a)
1929+
create table fk_part_1 partition of fk_part for values in (1) partition by list (a)
1930+
create table fk_part_1_1 partition of fk_part_1 for values in (1);
1931+
alter table fkpart1.fk_part add foreign key (a) references fkpart1.pkey;
1932+
insert into fkpart1.fk_part values (1); -- should fail
1933+
ERROR: insert or update on table "fk_part_1_1" violates foreign key constraint "fk_part_a_fkey"
1934+
DETAIL: Key (a)=(1) is not present in table "pkey".
1935+
insert into fkpart1.pkey values (1);
1936+
insert into fkpart1.fk_part values (1);
1937+
delete from fkpart1.pkey where a = 1; -- should fail
1938+
ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part"
1939+
DETAIL: Key (a)=(1) is still referenced from table "fk_part".
1940+
alter table fkpart1.fk_part detach partition fkpart1.fk_part_1;
1941+
create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2);
1942+
insert into fkpart1.fk_part_1 values (2); -- should fail
1943+
ERROR: insert or update on table "fk_part_1_2" violates foreign key constraint "fk_part_a_fkey"
1944+
DETAIL: Key (a)=(2) is not present in table "pkey".
1945+
delete from fkpart1.pkey where a = 1;
1946+
ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part_1"
1947+
DETAIL: Key (a)=(1) is still referenced from table "fk_part_1".
19241948
\set VERBOSITY terse \\ -- suppress cascade details
1925-
drop schema fkpart0 cascade;
1926-
NOTICE: drop cascades to 2 other objects
1949+
drop schema fkpart0, fkpart1 cascade;
1950+
NOTICE: drop cascades to 5 other objects
19271951
\set VERBOSITY default

src/test/regress/sql/foreign_key.sql

+18-1
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,23 @@ create table fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56
13751375
alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
13761376
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
13771377

1378+
-- verify that attaching and detaching partitions maintains the right set of
1379+
-- triggers
1380+
create schema fkpart1
1381+
create table pkey (a int primary key)
1382+
create table fk_part (a int) partition by list (a)
1383+
create table fk_part_1 partition of fk_part for values in (1) partition by list (a)
1384+
create table fk_part_1_1 partition of fk_part_1 for values in (1);
1385+
alter table fkpart1.fk_part add foreign key (a) references fkpart1.pkey;
1386+
insert into fkpart1.fk_part values (1); -- should fail
1387+
insert into fkpart1.pkey values (1);
1388+
insert into fkpart1.fk_part values (1);
1389+
delete from fkpart1.pkey where a = 1; -- should fail
1390+
alter table fkpart1.fk_part detach partition fkpart1.fk_part_1;
1391+
create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2);
1392+
insert into fkpart1.fk_part_1 values (2); -- should fail
1393+
delete from fkpart1.pkey where a = 1;
1394+
13781395
\set VERBOSITY terse \\ -- suppress cascade details
1379-
drop schema fkpart0 cascade;
1396+
drop schema fkpart0, fkpart1 cascade;
13801397
\set VERBOSITY default

0 commit comments

Comments
 (0)