Create foreign key triggers in partitioned tables too
While user-defined triggers defined on a partitioned table have a catalog definition for both it and its partitions, internal triggers used by foreign keys defined on partitioned tables only have a catalog definition for its partitions. This commit fixes that so that partitioned tables get the foreign key triggers too, just like user-defined triggers. Moreover, like user-defined triggers, partitions' internal triggers will now also have their tgparentid set appropriately. This is to allow subsequent commit(s) to make the foreign key related events to be fired in some cases using the parent table triggers instead of those of partitions'. This also changes what tgisinternal means in some cases. Currently, it means either that the trigger is an internal implementation object of a foreign key constraint, or a "child" trigger on a partition cloned from the trigger on the parent. This commit changes it to only mean the former to avoid confusion. As for the latter, it can be told by tgparentid being nonzero, which is now true both for user- defined and foreign key's internal triggers. Author: Amit Langote <amitlangote09@gmail.com> Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com> Reviewed-by: Arne Roland <A.Roland@index.de> Discussion: https://postgr.es/m/CA+HiwqG7LQSK+n8Bki8tWv7piHD=PnZro2y6ysU2-28JS6cfgQ@mail.gmail.com
This commit is contained in:
parent
c2e8bd2751
commit
f4566345cf
@ -483,7 +483,8 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
|
|||||||
int numfks, int16 *pkattnum, int16 *fkattnum,
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
bool old_check_ok);
|
bool old_check_ok,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger);
|
||||||
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
||||||
int numfksetcols, const int16 *fksetcolsattnums,
|
int numfksetcols, const int16 *fksetcolsattnums,
|
||||||
List *fksetcols);
|
List *fksetcols);
|
||||||
@ -492,7 +493,8 @@ static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
|
|||||||
int numfks, int16 *pkattnum, int16 *fkattnum,
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
bool old_check_ok, LOCKMODE lockmode);
|
bool old_check_ok, LOCKMODE lockmode,
|
||||||
|
Oid parentInsTrigger, Oid parentUpdTrigger);
|
||||||
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
|
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
|
||||||
Relation partitionRel);
|
Relation partitionRel);
|
||||||
static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
|
static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
|
||||||
@ -500,15 +502,30 @@ static void CloneFkReferencing(List **wqueue, Relation parentRel,
|
|||||||
Relation partRel);
|
Relation partRel);
|
||||||
static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
|
static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
|
||||||
Constraint *fkconstraint, Oid constraintOid,
|
Constraint *fkconstraint, Oid constraintOid,
|
||||||
Oid indexOid);
|
Oid indexOid,
|
||||||
|
Oid parentInsTrigger, Oid parentUpdTrigger,
|
||||||
|
Oid *insertTrigOid, Oid *updateTrigOid);
|
||||||
static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid,
|
static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid,
|
||||||
Constraint *fkconstraint, Oid constraintOid,
|
Constraint *fkconstraint, Oid constraintOid,
|
||||||
Oid indexOid);
|
Oid indexOid,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
||||||
|
Oid *deleteTrigOid, Oid *updateTrigOid);
|
||||||
static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
||||||
Oid partRelid,
|
Oid partRelid,
|
||||||
Oid parentConstrOid, int numfks,
|
Oid parentConstrOid, int numfks,
|
||||||
AttrNumber *mapped_conkey, AttrNumber *confkey,
|
AttrNumber *mapped_conkey, AttrNumber *confkey,
|
||||||
Oid *conpfeqop);
|
Oid *conpfeqop,
|
||||||
|
Oid parentInsTrigger,
|
||||||
|
Oid parentUpdTrigger,
|
||||||
|
Relation trigrel);
|
||||||
|
static void GetForeignKeyActionTriggers(Relation trigrel,
|
||||||
|
Oid conoid, Oid confrelid, Oid conrelid,
|
||||||
|
Oid *deleteTriggerOid,
|
||||||
|
Oid *updateTriggerOid);
|
||||||
|
static void GetForeignKeyCheckTriggers(Relation trigrel,
|
||||||
|
Oid conoid, Oid confrelid, Oid conrelid,
|
||||||
|
Oid *insertTriggerOid,
|
||||||
|
Oid *updateTriggerOid);
|
||||||
static void ATExecDropConstraint(Relation rel, const char *constrName,
|
static void ATExecDropConstraint(Relation rel, const char *constrName,
|
||||||
DropBehavior behavior,
|
DropBehavior behavior,
|
||||||
bool recurse, bool recursing,
|
bool recurse, bool recursing,
|
||||||
@ -9367,7 +9384,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
|||||||
ffeqoperators,
|
ffeqoperators,
|
||||||
numfkdelsetcols,
|
numfkdelsetcols,
|
||||||
fkdelsetcols,
|
fkdelsetcols,
|
||||||
old_check_ok);
|
old_check_ok,
|
||||||
|
InvalidOid, InvalidOid);
|
||||||
|
|
||||||
/* Now handle the referencing side. */
|
/* Now handle the referencing side. */
|
||||||
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
|
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
|
||||||
@ -9382,7 +9400,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
|||||||
numfkdelsetcols,
|
numfkdelsetcols,
|
||||||
fkdelsetcols,
|
fkdelsetcols,
|
||||||
old_check_ok,
|
old_check_ok,
|
||||||
lockmode);
|
lockmode,
|
||||||
|
InvalidOid, InvalidOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Done. Close pk table, but keep lock until we've committed.
|
* Done. Close pk table, but keep lock until we've committed.
|
||||||
@ -9454,6 +9473,9 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
|||||||
* pf/pp/ffeqoperators are OID array of operators between columns.
|
* pf/pp/ffeqoperators are OID array of operators between columns.
|
||||||
* old_check_ok signals that this constraint replaces an existing one that
|
* old_check_ok signals that this constraint replaces an existing one that
|
||||||
* was already validated (thus this one doesn't need validation).
|
* was already validated (thus this one doesn't need validation).
|
||||||
|
* parentDelTrigger and parentUpdTrigger, when being recursively called on
|
||||||
|
* a partition, are the OIDs of the parent action triggers for DELETE and
|
||||||
|
* UPDATE respectively.
|
||||||
*/
|
*/
|
||||||
static ObjectAddress
|
static ObjectAddress
|
||||||
addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
||||||
@ -9462,7 +9484,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
|
int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
|
||||||
Oid *ppeqoperators, Oid *ffeqoperators,
|
Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
bool old_check_ok)
|
bool old_check_ok,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger)
|
||||||
{
|
{
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
Oid constrOid;
|
Oid constrOid;
|
||||||
@ -9470,6 +9493,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
bool conislocal;
|
bool conislocal;
|
||||||
int coninhcount;
|
int coninhcount;
|
||||||
bool connoinherit;
|
bool connoinherit;
|
||||||
|
Oid deleteTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify relkind for each referenced partition. At the top level, this
|
* Verify relkind for each referenced partition. At the top level, this
|
||||||
@ -9568,15 +9593,13 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the referenced table is a plain relation, create the action triggers
|
* Create the action triggers that enforce the constraint.
|
||||||
* that enforce the constraint.
|
|
||||||
*/
|
*/
|
||||||
if (pkrel->rd_rel->relkind == RELKIND_RELATION)
|
createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
|
||||||
{
|
fkconstraint,
|
||||||
createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
|
constrOid, indexOid,
|
||||||
fkconstraint,
|
parentDelTrigger, parentUpdTrigger,
|
||||||
constrOid, indexOid);
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the referenced table is partitioned, recurse on ourselves to handle
|
* If the referenced table is partitioned, recurse on ourselves to handle
|
||||||
@ -9621,7 +9644,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
mapped_pkattnum, fkattnum,
|
mapped_pkattnum, fkattnum,
|
||||||
pfeqoperators, ppeqoperators, ffeqoperators,
|
pfeqoperators, ppeqoperators, ffeqoperators,
|
||||||
numfkdelsetcols, fkdelsetcols,
|
numfkdelsetcols, fkdelsetcols,
|
||||||
old_check_ok);
|
old_check_ok,
|
||||||
|
deleteTriggerOid, updateTriggerOid);
|
||||||
|
|
||||||
/* Done -- clean up (but keep the lock) */
|
/* Done -- clean up (but keep the lock) */
|
||||||
table_close(partRel, NoLock);
|
table_close(partRel, NoLock);
|
||||||
@ -9668,6 +9692,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
* old_check_ok signals that this constraint replaces an existing one that
|
* old_check_ok signals that this constraint replaces an existing one that
|
||||||
* was already validated (thus this one doesn't need validation).
|
* was already validated (thus this one doesn't need validation).
|
||||||
* lockmode is the lockmode to acquire on partitions when recursing.
|
* lockmode is the lockmode to acquire on partitions when recursing.
|
||||||
|
* parentInsTrigger and parentUpdTrigger, when being recursively called on
|
||||||
|
* a partition, are the OIDs of the parent check triggers for INSERT and
|
||||||
|
* UPDATE respectively.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
||||||
@ -9675,8 +9702,12 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
int numfks, int16 *pkattnum, int16 *fkattnum,
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
bool old_check_ok, LOCKMODE lockmode)
|
bool old_check_ok, LOCKMODE lockmode,
|
||||||
|
Oid parentInsTrigger, Oid parentUpdTrigger)
|
||||||
{
|
{
|
||||||
|
Oid insertTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
AssertArg(OidIsValid(parentConstr));
|
AssertArg(OidIsValid(parentConstr));
|
||||||
|
|
||||||
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||||
@ -9685,19 +9716,21 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
errmsg("foreign key constraints are not supported on foreign tables")));
|
errmsg("foreign key constraints are not supported on foreign tables")));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the referencing relation is a plain table, add the check triggers to
|
* Add the check triggers to it and, if necessary, schedule it to be
|
||||||
* it and, if necessary, schedule it to be checked in Phase 3.
|
* checked in Phase 3.
|
||||||
*
|
*
|
||||||
* If the relation is partitioned, drill down to do it to its partitions.
|
* If the relation is partitioned, drill down to do it to its partitions.
|
||||||
*/
|
*/
|
||||||
|
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
||||||
|
RelationGetRelid(pkrel),
|
||||||
|
fkconstraint,
|
||||||
|
parentConstr,
|
||||||
|
indexOid,
|
||||||
|
parentInsTrigger, parentUpdTrigger,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
||||||
{
|
{
|
||||||
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
|
||||||
RelationGetRelid(pkrel),
|
|
||||||
fkconstraint,
|
|
||||||
parentConstr,
|
|
||||||
indexOid);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tell Phase 3 to check that the constraint is satisfied by existing
|
* Tell Phase 3 to check that the constraint is satisfied by existing
|
||||||
* rows. We can skip this during table creation, when requested
|
* rows. We can skip this during table creation, when requested
|
||||||
@ -9726,6 +9759,15 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||||
{
|
{
|
||||||
PartitionDesc pd = RelationGetPartitionDesc(rel, true);
|
PartitionDesc pd = RelationGetPartitionDesc(rel, true);
|
||||||
|
Relation trigrel;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggers of the foreign keys will be manipulated a bunch of times
|
||||||
|
* in the loop below. To avoid repeatedly opening/closing the trigger
|
||||||
|
* catalog relation, we open it here and pass it to the subroutines
|
||||||
|
* called below.
|
||||||
|
*/
|
||||||
|
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recurse to take appropriate action on each partition; either we
|
* Recurse to take appropriate action on each partition; either we
|
||||||
@ -9767,7 +9809,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
numfks,
|
numfks,
|
||||||
mapped_fkattnum,
|
mapped_fkattnum,
|
||||||
pkattnum,
|
pkattnum,
|
||||||
pfeqoperators))
|
pfeqoperators,
|
||||||
|
insertTriggerOid,
|
||||||
|
updateTriggerOid,
|
||||||
|
trigrel))
|
||||||
{
|
{
|
||||||
attached = true;
|
attached = true;
|
||||||
break;
|
break;
|
||||||
@ -9850,10 +9895,14 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
numfkdelsetcols,
|
numfkdelsetcols,
|
||||||
fkdelsetcols,
|
fkdelsetcols,
|
||||||
old_check_ok,
|
old_check_ok,
|
||||||
lockmode);
|
lockmode,
|
||||||
|
insertTriggerOid,
|
||||||
|
updateTriggerOid);
|
||||||
|
|
||||||
table_close(partition, NoLock);
|
table_close(partition, NoLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table_close(trigrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9909,6 +9958,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
ScanKeyData key[2];
|
ScanKeyData key[2];
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
List *clone = NIL;
|
List *clone = NIL;
|
||||||
|
Relation trigrel;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Search for any constraints where this partition's parent is in the
|
* Search for any constraints where this partition's parent is in the
|
||||||
@ -9938,6 +9988,14 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
systable_endscan(scan);
|
systable_endscan(scan);
|
||||||
table_close(pg_constraint, RowShareLock);
|
table_close(pg_constraint, RowShareLock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggers of the foreign keys will be manipulated a bunch of times in
|
||||||
|
* the loop below. To avoid repeatedly opening/closing the trigger
|
||||||
|
* catalog relation, we open it here and pass it to the subroutines called
|
||||||
|
* below.
|
||||||
|
*/
|
||||||
|
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
|
attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
|
||||||
RelationGetDescr(parentRel));
|
RelationGetDescr(parentRel));
|
||||||
foreach(cell, clone)
|
foreach(cell, clone)
|
||||||
@ -9957,6 +10015,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
int numfkdelsetcols;
|
int numfkdelsetcols;
|
||||||
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
|
Oid deleteTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
tuple = SearchSysCache1(CONSTROID, constrOid);
|
tuple = SearchSysCache1(CONSTROID, constrOid);
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
@ -10025,6 +10085,16 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
if (!OidIsValid(partIndexId))
|
if (!OidIsValid(partIndexId))
|
||||||
elog(ERROR, "index for %u not found in partition %s",
|
elog(ERROR, "index for %u not found in partition %s",
|
||||||
indexOid, RelationGetRelationName(partitionRel));
|
indexOid, RelationGetRelationName(partitionRel));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the "action" triggers belonging to the constraint to pass as
|
||||||
|
* parent OIDs for similar triggers that will be created on the
|
||||||
|
* partition in addFkRecurseReferenced().
|
||||||
|
*/
|
||||||
|
GetForeignKeyActionTriggers(trigrel, constrOid,
|
||||||
|
constrForm->confrelid, constrForm->conrelid,
|
||||||
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
addFkRecurseReferenced(NULL,
|
addFkRecurseReferenced(NULL,
|
||||||
fkconstraint,
|
fkconstraint,
|
||||||
fkRel,
|
fkRel,
|
||||||
@ -10039,11 +10109,15 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
conffeqop,
|
conffeqop,
|
||||||
numfkdelsetcols,
|
numfkdelsetcols,
|
||||||
confdelsetcols,
|
confdelsetcols,
|
||||||
true);
|
true,
|
||||||
|
deleteTriggerOid,
|
||||||
|
updateTriggerOid);
|
||||||
|
|
||||||
table_close(fkRel, NoLock);
|
table_close(fkRel, NoLock);
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table_close(trigrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -10066,6 +10140,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
List *partFKs;
|
List *partFKs;
|
||||||
List *clone = NIL;
|
List *clone = NIL;
|
||||||
ListCell *cell;
|
ListCell *cell;
|
||||||
|
Relation trigrel;
|
||||||
|
|
||||||
/* obtain a list of constraints that we need to clone */
|
/* obtain a list of constraints that we need to clone */
|
||||||
foreach(cell, RelationGetFKeyList(parentRel))
|
foreach(cell, RelationGetFKeyList(parentRel))
|
||||||
@ -10087,6 +10162,14 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
errmsg("foreign key constraints are not supported on foreign tables")));
|
errmsg("foreign key constraints are not supported on foreign tables")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggers of the foreign keys will be manipulated a bunch of times in
|
||||||
|
* the loop below. To avoid repeatedly opening/closing the trigger
|
||||||
|
* catalog relation, we open it here and pass it to the subroutines called
|
||||||
|
* below.
|
||||||
|
*/
|
||||||
|
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The constraint key may differ, if the columns in the partition are
|
* The constraint key may differ, if the columns in the partition are
|
||||||
* different. This map is used to convert them.
|
* different. This map is used to convert them.
|
||||||
@ -10118,6 +10201,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
ObjectAddress address,
|
ObjectAddress address,
|
||||||
referenced;
|
referenced;
|
||||||
ListCell *cell;
|
ListCell *cell;
|
||||||
|
Oid insertTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
tuple = SearchSysCache1(CONSTROID, parentConstrOid);
|
tuple = SearchSysCache1(CONSTROID, parentConstrOid);
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
@ -10147,6 +10232,19 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
for (int i = 0; i < numfks; i++)
|
for (int i = 0; i < numfks; i++)
|
||||||
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
|
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the "check" triggers belonging to the constraint to pass as
|
||||||
|
* parent OIDs for similar triggers that will be created on the
|
||||||
|
* partition in addFkRecurseReferencing(). They are also passed to
|
||||||
|
* tryAttachPartitionForeignKey() below to simply assign as parents to
|
||||||
|
* the partition's existing "check" triggers, that is, if the
|
||||||
|
* corresponding constraints is deemed attachable to the parent
|
||||||
|
* constraint.
|
||||||
|
*/
|
||||||
|
GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
|
||||||
|
constrForm->confrelid, constrForm->conrelid,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Before creating a new constraint, see whether any existing FKs are
|
* Before creating a new constraint, see whether any existing FKs are
|
||||||
* fit for the purpose. If one is, attach the parent constraint to
|
* fit for the purpose. If one is, attach the parent constraint to
|
||||||
@ -10165,7 +10263,10 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
numfks,
|
numfks,
|
||||||
mapped_conkey,
|
mapped_conkey,
|
||||||
confkey,
|
confkey,
|
||||||
conpfeqop))
|
conpfeqop,
|
||||||
|
insertTriggerOid,
|
||||||
|
updateTriggerOid,
|
||||||
|
trigrel))
|
||||||
{
|
{
|
||||||
attached = true;
|
attached = true;
|
||||||
table_close(pkrel, NoLock);
|
table_close(pkrel, NoLock);
|
||||||
@ -10268,9 +10369,13 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
numfkdelsetcols,
|
numfkdelsetcols,
|
||||||
confdelsetcols,
|
confdelsetcols,
|
||||||
false, /* no old check exists */
|
false, /* no old check exists */
|
||||||
AccessExclusiveLock);
|
AccessExclusiveLock,
|
||||||
|
insertTriggerOid,
|
||||||
|
updateTriggerOid);
|
||||||
table_close(pkrel, NoLock);
|
table_close(pkrel, NoLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table_close(trigrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -10291,16 +10396,20 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
|||||||
int numfks,
|
int numfks,
|
||||||
AttrNumber *mapped_conkey,
|
AttrNumber *mapped_conkey,
|
||||||
AttrNumber *confkey,
|
AttrNumber *confkey,
|
||||||
Oid *conpfeqop)
|
Oid *conpfeqop,
|
||||||
|
Oid parentInsTrigger,
|
||||||
|
Oid parentUpdTrigger,
|
||||||
|
Relation trigrel)
|
||||||
{
|
{
|
||||||
HeapTuple parentConstrTup;
|
HeapTuple parentConstrTup;
|
||||||
Form_pg_constraint parentConstr;
|
Form_pg_constraint parentConstr;
|
||||||
HeapTuple partcontup;
|
HeapTuple partcontup;
|
||||||
Form_pg_constraint partConstr;
|
Form_pg_constraint partConstr;
|
||||||
Relation trigrel;
|
|
||||||
ScanKeyData key;
|
ScanKeyData key;
|
||||||
SysScanDesc scan;
|
SysScanDesc scan;
|
||||||
HeapTuple trigtup;
|
HeapTuple trigtup;
|
||||||
|
Oid insertTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
parentConstrTup = SearchSysCache1(CONSTROID,
|
parentConstrTup = SearchSysCache1(CONSTROID,
|
||||||
ObjectIdGetDatum(parentConstrOid));
|
ObjectIdGetDatum(parentConstrOid));
|
||||||
@ -10361,12 +10470,10 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
|||||||
* in the partition. We identify them because they have our constraint
|
* in the partition. We identify them because they have our constraint
|
||||||
* OID, as well as being on the referenced rel.
|
* OID, as well as being on the referenced rel.
|
||||||
*/
|
*/
|
||||||
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
|
|
||||||
ScanKeyInit(&key,
|
ScanKeyInit(&key,
|
||||||
Anum_pg_trigger_tgconstraint,
|
Anum_pg_trigger_tgconstraint,
|
||||||
BTEqualStrategyNumber, F_OIDEQ,
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
ObjectIdGetDatum(fk->conoid));
|
ObjectIdGetDatum(fk->conoid));
|
||||||
|
|
||||||
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
|
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
|
||||||
NULL, 1, &key);
|
NULL, 1, &key);
|
||||||
while ((trigtup = systable_getnext(scan)) != NULL)
|
while ((trigtup = systable_getnext(scan)) != NULL)
|
||||||
@ -10399,13 +10506,136 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
|||||||
}
|
}
|
||||||
|
|
||||||
systable_endscan(scan);
|
systable_endscan(scan);
|
||||||
table_close(trigrel, RowExclusiveLock);
|
|
||||||
|
|
||||||
ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid);
|
ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like the constraint, attach partition's "check" triggers to the
|
||||||
|
* corresponding parent triggers.
|
||||||
|
*/
|
||||||
|
GetForeignKeyCheckTriggers(trigrel,
|
||||||
|
fk->conoid, fk->confrelid, fk->conrelid,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
|
||||||
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
|
||||||
|
partRelid);
|
||||||
|
Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
|
||||||
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
||||||
|
partRelid);
|
||||||
|
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetForeignKeyActionTriggers
|
||||||
|
* Returns delete and update "action" triggers of the given relation
|
||||||
|
* belonging to the given constraint
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
GetForeignKeyActionTriggers(Relation trigrel,
|
||||||
|
Oid conoid, Oid confrelid, Oid conrelid,
|
||||||
|
Oid *deleteTriggerOid,
|
||||||
|
Oid *updateTriggerOid)
|
||||||
|
{
|
||||||
|
ScanKeyData key;
|
||||||
|
SysScanDesc scan;
|
||||||
|
HeapTuple trigtup;
|
||||||
|
|
||||||
|
*deleteTriggerOid = *updateTriggerOid = InvalidOid;
|
||||||
|
ScanKeyInit(&key,
|
||||||
|
Anum_pg_trigger_tgconstraint,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(conoid));
|
||||||
|
|
||||||
|
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
|
||||||
|
NULL, 1, &key);
|
||||||
|
while ((trigtup = systable_getnext(scan)) != NULL)
|
||||||
|
{
|
||||||
|
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
|
||||||
|
|
||||||
|
if (trgform->tgconstrrelid != conrelid)
|
||||||
|
continue;
|
||||||
|
if (trgform->tgrelid != confrelid)
|
||||||
|
continue;
|
||||||
|
if (TRIGGER_FOR_DELETE(trgform->tgtype))
|
||||||
|
{
|
||||||
|
Assert(*deleteTriggerOid == InvalidOid);
|
||||||
|
*deleteTriggerOid = trgform->oid;
|
||||||
|
}
|
||||||
|
else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
|
||||||
|
{
|
||||||
|
Assert(*updateTriggerOid == InvalidOid);
|
||||||
|
*updateTriggerOid = trgform->oid;
|
||||||
|
}
|
||||||
|
if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!OidIsValid(*deleteTriggerOid))
|
||||||
|
elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
|
||||||
|
conoid);
|
||||||
|
if (!OidIsValid(*updateTriggerOid))
|
||||||
|
elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
|
||||||
|
conoid);
|
||||||
|
|
||||||
|
systable_endscan(scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetForeignKeyCheckTriggers
|
||||||
|
* Returns insert and update "check" triggers of the given relation
|
||||||
|
* belonging to the given constraint
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
GetForeignKeyCheckTriggers(Relation trigrel,
|
||||||
|
Oid conoid, Oid confrelid, Oid conrelid,
|
||||||
|
Oid *insertTriggerOid,
|
||||||
|
Oid *updateTriggerOid)
|
||||||
|
{
|
||||||
|
ScanKeyData key;
|
||||||
|
SysScanDesc scan;
|
||||||
|
HeapTuple trigtup;
|
||||||
|
|
||||||
|
*insertTriggerOid = *updateTriggerOid = InvalidOid;
|
||||||
|
ScanKeyInit(&key,
|
||||||
|
Anum_pg_trigger_tgconstraint,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(conoid));
|
||||||
|
|
||||||
|
scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
|
||||||
|
NULL, 1, &key);
|
||||||
|
while ((trigtup = systable_getnext(scan)) != NULL)
|
||||||
|
{
|
||||||
|
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
|
||||||
|
|
||||||
|
if (trgform->tgconstrrelid != confrelid)
|
||||||
|
continue;
|
||||||
|
if (trgform->tgrelid != conrelid)
|
||||||
|
continue;
|
||||||
|
if (TRIGGER_FOR_INSERT(trgform->tgtype))
|
||||||
|
{
|
||||||
|
Assert(*insertTriggerOid == InvalidOid);
|
||||||
|
*insertTriggerOid = trgform->oid;
|
||||||
|
}
|
||||||
|
else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
|
||||||
|
{
|
||||||
|
Assert(*updateTriggerOid == InvalidOid);
|
||||||
|
*updateTriggerOid = trgform->oid;
|
||||||
|
}
|
||||||
|
if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!OidIsValid(*insertTriggerOid))
|
||||||
|
elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
|
||||||
|
conoid);
|
||||||
|
if (!OidIsValid(*updateTriggerOid))
|
||||||
|
elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
|
||||||
|
conoid);
|
||||||
|
|
||||||
|
systable_endscan(scan);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ALTER TABLE ALTER CONSTRAINT
|
* ALTER TABLE ALTER CONSTRAINT
|
||||||
@ -11323,10 +11553,19 @@ validateForeignKeyConstraint(char *conname,
|
|||||||
ExecDropSingleTupleTableSlot(slot);
|
ExecDropSingleTupleTableSlot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/*
|
||||||
|
* CreateFKCheckTrigger
|
||||||
|
* Creates the insert (on_insert=true) or update "check" trigger that
|
||||||
|
* implements a given foreign key
|
||||||
|
*
|
||||||
|
* Returns the OID of the so created trigger.
|
||||||
|
*/
|
||||||
|
static Oid
|
||||||
CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
|
CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
|
||||||
Oid constraintOid, Oid indexOid, bool on_insert)
|
Oid constraintOid, Oid indexOid, Oid parentTrigOid,
|
||||||
|
bool on_insert)
|
||||||
{
|
{
|
||||||
|
ObjectAddress trigAddress;
|
||||||
CreateTrigStmt *fk_trigger;
|
CreateTrigStmt *fk_trigger;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -11366,23 +11605,32 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
|
|||||||
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
||||||
fk_trigger->constrrel = NULL;
|
fk_trigger->constrrel = NULL;
|
||||||
|
|
||||||
(void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid,
|
trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
|
||||||
indexOid, InvalidOid, InvalidOid, NULL, true, false);
|
constraintOid, indexOid, InvalidOid,
|
||||||
|
parentTrigOid, NULL, true, false);
|
||||||
|
|
||||||
/* Make changes-so-far visible */
|
/* Make changes-so-far visible */
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
|
|
||||||
|
return trigAddress.objectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* createForeignKeyActionTriggers
|
* createForeignKeyActionTriggers
|
||||||
* Create the referenced-side "action" triggers that implement a foreign
|
* Create the referenced-side "action" triggers that implement a foreign
|
||||||
* key.
|
* key.
|
||||||
|
*
|
||||||
|
* Returns the OIDs of the so created triggers in *deleteTrigOid and
|
||||||
|
* *updateTrigOid.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
|
createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
|
||||||
Oid constraintOid, Oid indexOid)
|
Oid constraintOid, Oid indexOid,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
||||||
|
Oid *deleteTrigOid, Oid *updateTrigOid)
|
||||||
{
|
{
|
||||||
CreateTrigStmt *fk_trigger;
|
CreateTrigStmt *fk_trigger;
|
||||||
|
ObjectAddress trigAddress;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
|
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
|
||||||
@ -11434,9 +11682,12 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
|
trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid,
|
||||||
constraintOid,
|
RelationGetRelid(rel),
|
||||||
indexOid, InvalidOid, InvalidOid, NULL, true, false);
|
constraintOid, indexOid, InvalidOid,
|
||||||
|
parentDelTrigger, NULL, true, false);
|
||||||
|
if (deleteTrigOid)
|
||||||
|
*deleteTrigOid = trigAddress.objectId;
|
||||||
|
|
||||||
/* Make changes-so-far visible */
|
/* Make changes-so-far visible */
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
@ -11491,25 +11742,35 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
|
trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid,
|
||||||
constraintOid,
|
RelationGetRelid(rel),
|
||||||
indexOid, InvalidOid, InvalidOid, NULL, true, false);
|
constraintOid, indexOid, InvalidOid,
|
||||||
|
parentUpdTrigger, NULL, true, false);
|
||||||
|
if (updateTrigOid)
|
||||||
|
*updateTrigOid = trigAddress.objectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* createForeignKeyCheckTriggers
|
* createForeignKeyCheckTriggers
|
||||||
* Create the referencing-side "check" triggers that implement a foreign
|
* Create the referencing-side "check" triggers that implement a foreign
|
||||||
* key.
|
* key.
|
||||||
|
*
|
||||||
|
* Returns the OIDs of the so created triggers in *insertTrigOid and
|
||||||
|
* *updateTrigOid.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
|
createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
|
||||||
Constraint *fkconstraint, Oid constraintOid,
|
Constraint *fkconstraint, Oid constraintOid,
|
||||||
Oid indexOid)
|
Oid indexOid,
|
||||||
|
Oid parentInsTrigger, Oid parentUpdTrigger,
|
||||||
|
Oid *insertTrigOid, Oid *updateTrigOid)
|
||||||
{
|
{
|
||||||
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid,
|
*insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
|
||||||
indexOid, true);
|
constraintOid, indexOid,
|
||||||
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid,
|
parentInsTrigger, true);
|
||||||
indexOid, false);
|
*updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
|
||||||
|
constraintOid, indexOid,
|
||||||
|
parentUpdTrigger, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -17859,19 +18120,10 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal triggers require careful examination. Ideally, we don't
|
* Don't clone internal triggers, because the constraint cloning code
|
||||||
* clone them. However, if our parent is itself a partition, there
|
* will.
|
||||||
* might be internal triggers that must not be skipped; for example,
|
|
||||||
* triggers on our parent that are in turn clones from its parent (our
|
|
||||||
* grandparent) are marked internal, yet they are to be cloned.
|
|
||||||
*
|
|
||||||
* Note we dare not verify that the other trigger belongs to an
|
|
||||||
* ancestor relation of our parent, because that creates deadlock
|
|
||||||
* opportunities.
|
|
||||||
*/
|
*/
|
||||||
if (trigForm->tgisinternal &&
|
if (trigForm->tgisinternal)
|
||||||
(!parent->rd_rel->relispartition ||
|
|
||||||
!OidIsValid(trigForm->tgparentid)))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -18173,6 +18425,7 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
new_repl[Natts_pg_class];
|
new_repl[Natts_pg_class];
|
||||||
HeapTuple tuple,
|
HeapTuple tuple,
|
||||||
newtuple;
|
newtuple;
|
||||||
|
Relation trigrel = NULL;
|
||||||
|
|
||||||
if (concurrent)
|
if (concurrent)
|
||||||
{
|
{
|
||||||
@ -18191,12 +18444,16 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
* additional action triggers.
|
* additional action triggers.
|
||||||
*/
|
*/
|
||||||
fks = copyObject(RelationGetFKeyList(partRel));
|
fks = copyObject(RelationGetFKeyList(partRel));
|
||||||
|
if (fks != NIL)
|
||||||
|
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
|
||||||
foreach(cell, fks)
|
foreach(cell, fks)
|
||||||
{
|
{
|
||||||
ForeignKeyCacheInfo *fk = lfirst(cell);
|
ForeignKeyCacheInfo *fk = lfirst(cell);
|
||||||
HeapTuple contup;
|
HeapTuple contup;
|
||||||
Form_pg_constraint conform;
|
Form_pg_constraint conform;
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
|
Oid insertTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
||||||
if (!HeapTupleIsValid(contup))
|
if (!HeapTupleIsValid(contup))
|
||||||
@ -18214,6 +18471,20 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
/* unset conparentid and adjust conislocal, coninhcount, etc. */
|
/* unset conparentid and adjust conislocal, coninhcount, etc. */
|
||||||
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
|
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Also, look up the partition's "check" triggers corresponding to the
|
||||||
|
* constraint being detached and detach them from the parent triggers.
|
||||||
|
*/
|
||||||
|
GetForeignKeyCheckTriggers(trigrel,
|
||||||
|
fk->conoid, fk->confrelid, fk->conrelid,
|
||||||
|
&insertTriggerOid, &updateTriggerOid);
|
||||||
|
Assert(OidIsValid(insertTriggerOid));
|
||||||
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
|
||||||
|
RelationGetRelid(partRel));
|
||||||
|
Assert(OidIsValid(updateTriggerOid));
|
||||||
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
|
||||||
|
RelationGetRelid(partRel));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make the action triggers on the referenced relation. When this was
|
* Make the action triggers on the referenced relation. When this was
|
||||||
* a partition the action triggers pointed to the parent rel (they
|
* a partition the action triggers pointed to the parent rel (they
|
||||||
@ -18228,11 +18499,15 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
|
|
||||||
createForeignKeyActionTriggers(partRel, conform->confrelid,
|
createForeignKeyActionTriggers(partRel, conform->confrelid,
|
||||||
fkconstraint, fk->conoid,
|
fkconstraint, fk->conoid,
|
||||||
conform->conindid);
|
conform->conindid,
|
||||||
|
InvalidOid, InvalidOid,
|
||||||
|
NULL, NULL);
|
||||||
|
|
||||||
ReleaseSysCache(contup);
|
ReleaseSysCache(contup);
|
||||||
}
|
}
|
||||||
list_free_deep(fks);
|
list_free_deep(fks);
|
||||||
|
if (trigrel)
|
||||||
|
table_close(trigrel, RowExclusiveLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Any sub-constraints that are in the referenced-side of a larger
|
* Any sub-constraints that are in the referenced-side of a larger
|
||||||
@ -18457,6 +18732,14 @@ DropClonedTriggersFromPartition(Oid partitionId)
|
|||||||
if (!OidIsValid(pg_trigger->tgparentid))
|
if (!OidIsValid(pg_trigger->tgparentid))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore internal triggers that are implementation objects of foreign
|
||||||
|
* keys, because these will be detached when the foreign keys
|
||||||
|
* themselves are.
|
||||||
|
*/
|
||||||
|
if (OidIsValid(pg_trigger->tgconstrrelid))
|
||||||
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is ugly, but necessary: remove the dependency markings on the
|
* This is ugly, but necessary: remove the dependency markings on the
|
||||||
* trigger so that it can be removed.
|
* trigger so that it can be removed.
|
||||||
|
@ -132,8 +132,10 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
|
|||||||
* given, stmt->funcname is ignored.
|
* given, stmt->funcname is ignored.
|
||||||
*
|
*
|
||||||
* parentTriggerOid, if nonzero, is a trigger that begets this one; so that
|
* parentTriggerOid, if nonzero, is a trigger that begets this one; so that
|
||||||
* if that trigger is dropped, this one should be too. (This is passed as
|
* if that trigger is dropped, this one should be too. There are two cases
|
||||||
* Invalid by most callers; it's set here when recursing on a partition.)
|
* when a nonzero value is passed for this: 1) when this function recurses to
|
||||||
|
* create the trigger on partitions, 2) when creating child foreign key
|
||||||
|
* triggers; see CreateFKCheckTrigger() and createForeignKeyActionTriggers().
|
||||||
*
|
*
|
||||||
* If whenClause is passed, it is an already-transformed expression for
|
* If whenClause is passed, it is an already-transformed expression for
|
||||||
* WHEN. In this case, we ignore any that may come in stmt->whenClause.
|
* WHEN. In this case, we ignore any that may come in stmt->whenClause.
|
||||||
@ -202,6 +204,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
bool trigger_exists = false;
|
bool trigger_exists = false;
|
||||||
Oid existing_constraint_oid = InvalidOid;
|
Oid existing_constraint_oid = InvalidOid;
|
||||||
bool existing_isInternal = false;
|
bool existing_isInternal = false;
|
||||||
|
bool existing_isClone = false;
|
||||||
|
|
||||||
if (OidIsValid(relOid))
|
if (OidIsValid(relOid))
|
||||||
rel = table_open(relOid, ShareRowExclusiveLock);
|
rel = table_open(relOid, ShareRowExclusiveLock);
|
||||||
@ -741,6 +744,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
trigoid = oldtrigger->oid;
|
trigoid = oldtrigger->oid;
|
||||||
existing_constraint_oid = oldtrigger->tgconstraint;
|
existing_constraint_oid = oldtrigger->tgconstraint;
|
||||||
existing_isInternal = oldtrigger->tgisinternal;
|
existing_isInternal = oldtrigger->tgisinternal;
|
||||||
|
existing_isClone = OidIsValid(oldtrigger->tgparentid);
|
||||||
trigger_exists = true;
|
trigger_exists = true;
|
||||||
/* copy the tuple to use in CatalogTupleUpdate() */
|
/* copy the tuple to use in CatalogTupleUpdate() */
|
||||||
tuple = heap_copytuple(tuple);
|
tuple = heap_copytuple(tuple);
|
||||||
@ -767,17 +771,16 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
stmt->trigname, RelationGetRelationName(rel))));
|
stmt->trigname, RelationGetRelationName(rel))));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An internal trigger cannot be replaced by a user-defined trigger.
|
* An internal trigger or a child trigger (isClone) cannot be replaced
|
||||||
* However, skip this test when in_partition, because then we're
|
* by a user-defined trigger. However, skip this test when
|
||||||
* recursing from a partitioned table and the check was made at the
|
* in_partition, because then we're recursing from a partitioned table
|
||||||
* parent level. Child triggers will always be marked "internal" (so
|
* and the check was made at the parent level.
|
||||||
* this test does protect us from the user trying to replace a child
|
|
||||||
* trigger directly).
|
|
||||||
*/
|
*/
|
||||||
if (existing_isInternal && !isInternal && !in_partition)
|
if ((existing_isInternal || existing_isClone) &&
|
||||||
|
!isInternal && !in_partition)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
||||||
errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
|
errmsg("trigger \"%s\" for relation \"%s\" is an internal or a child trigger",
|
||||||
stmt->trigname, RelationGetRelationName(rel))));
|
stmt->trigname, RelationGetRelationName(rel))));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -876,7 +879,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
|
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
|
||||||
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
|
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
|
||||||
values[Anum_pg_trigger_tgenabled - 1] = trigger_fires_when;
|
values[Anum_pg_trigger_tgenabled - 1] = trigger_fires_when;
|
||||||
values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal || in_partition);
|
values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
|
||||||
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
|
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
|
||||||
values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
|
values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
|
||||||
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
|
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
|
||||||
@ -1245,6 +1248,82 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
return myself;
|
return myself;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TriggerSetParentTrigger
|
||||||
|
* Set a partition's trigger as child of its parent trigger,
|
||||||
|
* or remove the linkage if parentTrigId is InvalidOid.
|
||||||
|
*
|
||||||
|
* This updates the constraint's pg_trigger row to show it as inherited, and
|
||||||
|
* adds PARTITION dependencies to prevent the trigger from being deleted
|
||||||
|
* on its own. Alternatively, reverse that.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
TriggerSetParentTrigger(Relation trigRel,
|
||||||
|
Oid childTrigId,
|
||||||
|
Oid parentTrigId,
|
||||||
|
Oid childTableId)
|
||||||
|
{
|
||||||
|
SysScanDesc tgscan;
|
||||||
|
ScanKeyData skey[1];
|
||||||
|
Form_pg_trigger trigForm;
|
||||||
|
HeapTuple tuple,
|
||||||
|
newtup;
|
||||||
|
ObjectAddress depender;
|
||||||
|
ObjectAddress referenced;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the trigger to delete.
|
||||||
|
*/
|
||||||
|
ScanKeyInit(&skey[0],
|
||||||
|
Anum_pg_trigger_oid,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(childTrigId));
|
||||||
|
|
||||||
|
tgscan = systable_beginscan(trigRel, TriggerOidIndexId, true,
|
||||||
|
NULL, 1, skey);
|
||||||
|
|
||||||
|
tuple = systable_getnext(tgscan);
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
elog(ERROR, "could not find tuple for trigger %u", childTrigId);
|
||||||
|
newtup = heap_copytuple(tuple);
|
||||||
|
trigForm = (Form_pg_trigger) GETSTRUCT(newtup);
|
||||||
|
if (OidIsValid(parentTrigId))
|
||||||
|
{
|
||||||
|
/* don't allow setting parent for a constraint that already has one */
|
||||||
|
if (OidIsValid(trigForm->tgparentid))
|
||||||
|
elog(ERROR, "trigger %u already has a parent trigger",
|
||||||
|
childTrigId);
|
||||||
|
|
||||||
|
trigForm->tgparentid = parentTrigId;
|
||||||
|
|
||||||
|
CatalogTupleUpdate(trigRel, &tuple->t_self, newtup);
|
||||||
|
|
||||||
|
ObjectAddressSet(depender, TriggerRelationId, childTrigId);
|
||||||
|
|
||||||
|
ObjectAddressSet(referenced, TriggerRelationId, parentTrigId);
|
||||||
|
recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_PRI);
|
||||||
|
|
||||||
|
ObjectAddressSet(referenced, RelationRelationId, childTableId);
|
||||||
|
recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_SEC);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trigForm->tgparentid = InvalidOid;
|
||||||
|
|
||||||
|
CatalogTupleUpdate(trigRel, &tuple->t_self, newtup);
|
||||||
|
|
||||||
|
deleteDependencyRecordsForClass(TriggerRelationId, childTrigId,
|
||||||
|
TriggerRelationId,
|
||||||
|
DEPENDENCY_PARTITION_PRI);
|
||||||
|
deleteDependencyRecordsForClass(TriggerRelationId, childTrigId,
|
||||||
|
RelationRelationId,
|
||||||
|
DEPENDENCY_PARTITION_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_freetuple(newtup);
|
||||||
|
systable_endscan(tgscan);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guts of trigger deletion.
|
* Guts of trigger deletion.
|
||||||
|
@ -7195,7 +7195,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
i_tgconstrrelid,
|
i_tgconstrrelid,
|
||||||
i_tgconstrrelname,
|
i_tgconstrrelname,
|
||||||
i_tgenabled,
|
i_tgenabled,
|
||||||
i_tgisinternal,
|
i_tgispartition,
|
||||||
i_tgdeferrable,
|
i_tgdeferrable,
|
||||||
i_tginitdeferred,
|
i_tginitdeferred,
|
||||||
i_tgdef;
|
i_tgdef;
|
||||||
@ -7224,7 +7224,31 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
}
|
}
|
||||||
appendPQExpBufferChar(tbloids, '}');
|
appendPQExpBufferChar(tbloids, '}');
|
||||||
|
|
||||||
if (fout->remoteVersion >= 130000)
|
if (fout->remoteVersion >= 150000)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* NB: think not to use pretty=true in pg_get_triggerdef. It could
|
||||||
|
* result in non-forward-compatible dumps of WHEN clauses due to
|
||||||
|
* under-parenthesization.
|
||||||
|
*
|
||||||
|
* NB: We need to see partition triggers in case the tgenabled flag
|
||||||
|
* has been changed from the parent.
|
||||||
|
*/
|
||||||
|
appendPQExpBuffer(query,
|
||||||
|
"SELECT t.tgrelid, t.tgname, "
|
||||||
|
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
||||||
|
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
||||||
|
"t.tgenabled, t.tableoid, t.oid, "
|
||||||
|
"t.tgparentid <> 0 AS tgispartition\n"
|
||||||
|
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
||||||
|
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
||||||
|
"LEFT JOIN pg_catalog.pg_trigger u ON (u.oid = t.tgparentid) "
|
||||||
|
"WHERE ((NOT t.tgisinternal AND t.tgparentid = 0) "
|
||||||
|
"OR t.tgenabled != u.tgenabled) "
|
||||||
|
"ORDER BY t.tgrelid, t.tgname",
|
||||||
|
tbloids->data);
|
||||||
|
}
|
||||||
|
else if (fout->remoteVersion >= 130000)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* NB: think not to use pretty=true in pg_get_triggerdef. It could
|
* NB: think not to use pretty=true in pg_get_triggerdef. It could
|
||||||
@ -7238,7 +7262,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"SELECT t.tgrelid, t.tgname, "
|
"SELECT t.tgrelid, t.tgname, "
|
||||||
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
||||||
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
||||||
"t.tgenabled, t.tableoid, t.oid, t.tgisinternal\n"
|
"t.tgenabled, t.tableoid, t.oid, t.tgisinternal as tgispartition\n"
|
||||||
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
||||||
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
||||||
"LEFT JOIN pg_catalog.pg_trigger u ON (u.oid = t.tgparentid) "
|
"LEFT JOIN pg_catalog.pg_trigger u ON (u.oid = t.tgparentid) "
|
||||||
@ -7259,7 +7283,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"SELECT t.tgrelid, t.tgname, "
|
"SELECT t.tgrelid, t.tgname, "
|
||||||
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
||||||
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
||||||
"t.tgenabled, t.tableoid, t.oid, t.tgisinternal "
|
"t.tgenabled, t.tableoid, t.oid, t.tgisinternal as tgispartition "
|
||||||
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
||||||
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
||||||
"LEFT JOIN pg_catalog.pg_depend AS d ON "
|
"LEFT JOIN pg_catalog.pg_depend AS d ON "
|
||||||
@ -7278,7 +7302,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"SELECT t.tgrelid, t.tgname, "
|
"SELECT t.tgrelid, t.tgname, "
|
||||||
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
"t.tgfoid::pg_catalog.regproc AS tgfname, "
|
||||||
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
"pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, "
|
||||||
"t.tgenabled, false as tgisinternal, "
|
"t.tgenabled, false as tgispartition, "
|
||||||
"t.tableoid, t.oid "
|
"t.tableoid, t.oid "
|
||||||
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
|
||||||
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
"JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) "
|
||||||
@ -7304,7 +7328,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
i_tgconstrrelid = PQfnumber(res, "tgconstrrelid");
|
i_tgconstrrelid = PQfnumber(res, "tgconstrrelid");
|
||||||
i_tgconstrrelname = PQfnumber(res, "tgconstrrelname");
|
i_tgconstrrelname = PQfnumber(res, "tgconstrrelname");
|
||||||
i_tgenabled = PQfnumber(res, "tgenabled");
|
i_tgenabled = PQfnumber(res, "tgenabled");
|
||||||
i_tgisinternal = PQfnumber(res, "tgisinternal");
|
i_tgispartition = PQfnumber(res, "tgispartition");
|
||||||
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
|
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
|
||||||
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
|
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
|
||||||
i_tgdef = PQfnumber(res, "tgdef");
|
i_tgdef = PQfnumber(res, "tgdef");
|
||||||
@ -7354,7 +7378,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
|
tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
|
||||||
tginfo[j].tgtable = tbinfo;
|
tginfo[j].tgtable = tbinfo;
|
||||||
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
|
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
|
||||||
tginfo[j].tgisinternal = *(PQgetvalue(res, j, i_tgisinternal)) == 't';
|
tginfo[j].tgispartition = *(PQgetvalue(res, j, i_tgispartition)) == 't';
|
||||||
if (i_tgdef >= 0)
|
if (i_tgdef >= 0)
|
||||||
{
|
{
|
||||||
tginfo[j].tgdef = pg_strdup(PQgetvalue(res, j, i_tgdef));
|
tginfo[j].tgdef = pg_strdup(PQgetvalue(res, j, i_tgdef));
|
||||||
@ -16771,11 +16795,13 @@ dumpTrigger(Archive *fout, const TriggerInfo *tginfo)
|
|||||||
"pg_catalog.pg_trigger", "TRIGGER",
|
"pg_catalog.pg_trigger", "TRIGGER",
|
||||||
trigidentity->data);
|
trigidentity->data);
|
||||||
|
|
||||||
if (tginfo->tgisinternal)
|
if (tginfo->tgispartition)
|
||||||
{
|
{
|
||||||
|
Assert(tbinfo->ispartition);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Triggers marked internal only appear here because their 'tgenabled'
|
* Partition triggers only appear here because their 'tgenabled' flag
|
||||||
* flag differs from its parent's. The trigger is created already, so
|
* differs from its parent's. The trigger is created already, so
|
||||||
* remove the CREATE and replace it with an ALTER. (Clear out the
|
* remove the CREATE and replace it with an ALTER. (Clear out the
|
||||||
* DROP query too, so that pg_dump --create does not cause errors.)
|
* DROP query too, so that pg_dump --create does not cause errors.)
|
||||||
*/
|
*/
|
||||||
|
@ -443,7 +443,7 @@ typedef struct _triggerInfo
|
|||||||
Oid tgconstrrelid;
|
Oid tgconstrrelid;
|
||||||
char *tgconstrrelname;
|
char *tgconstrrelname;
|
||||||
char tgenabled;
|
char tgenabled;
|
||||||
bool tgisinternal;
|
bool tgispartition;
|
||||||
bool tgdeferrable;
|
bool tgdeferrable;
|
||||||
bool tginitdeferred;
|
bool tginitdeferred;
|
||||||
char *tgdef;
|
char *tgdef;
|
||||||
|
@ -3001,7 +3001,15 @@ describeOneTableDetails(const char *schemaname,
|
|||||||
" AND u.tgparentid = 0) AS parent" :
|
" AND u.tgparentid = 0) AS parent" :
|
||||||
"NULL AS parent"),
|
"NULL AS parent"),
|
||||||
oid);
|
oid);
|
||||||
if (pset.sversion >= 110000)
|
|
||||||
|
/*
|
||||||
|
* tgisinternal is set true for inherited triggers of partitions in
|
||||||
|
* servers between v11 and v14, though these must still be shown to
|
||||||
|
* the user. So we use another property that is true for such
|
||||||
|
* inherited triggers to avoid them being hidden, which is their
|
||||||
|
* dependendence on another trigger.
|
||||||
|
*/
|
||||||
|
if (pset.sversion >= 110000 && pset.sversion < 150000)
|
||||||
appendPQExpBufferStr(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D') \n"
|
appendPQExpBufferStr(&buf, "(NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D') \n"
|
||||||
" OR EXISTS (SELECT 1 FROM pg_catalog.pg_depend WHERE objid = t.oid \n"
|
" OR EXISTS (SELECT 1 FROM pg_catalog.pg_depend WHERE objid = t.oid \n"
|
||||||
" AND refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass))");
|
" AND refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass))");
|
||||||
|
@ -160,6 +160,10 @@ extern ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *que
|
|||||||
Node *whenClause, bool isInternal, bool in_partition,
|
Node *whenClause, bool isInternal, bool in_partition,
|
||||||
char trigger_fires_when);
|
char trigger_fires_when);
|
||||||
|
|
||||||
|
extern void TriggerSetParentTrigger(Relation trigRel,
|
||||||
|
Oid childTrigId,
|
||||||
|
Oid parentTrigId,
|
||||||
|
Oid childTableId);
|
||||||
extern void RemoveTriggerById(Oid trigOid);
|
extern void RemoveTriggerById(Oid trigOid);
|
||||||
extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok);
|
extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok);
|
||||||
|
|
||||||
|
@ -2105,7 +2105,7 @@ select tgrelid::regclass::text, tgname, tgfoid::regproc, tgenabled, tgisinternal
|
|||||||
tgrelid | tgname | tgfoid | tgenabled | tgisinternal
|
tgrelid | tgname | tgfoid | tgenabled | tgisinternal
|
||||||
-----------+--------+-----------------+-----------+--------------
|
-----------+--------+-----------------+-----------+--------------
|
||||||
trigpart | trg1 | trigger_nothing | O | f
|
trigpart | trg1 | trigger_nothing | O | f
|
||||||
trigpart1 | trg1 | trigger_nothing | O | t
|
trigpart1 | trg1 | trigger_nothing | O | f
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
create table trigpart3 (like trigpart);
|
create table trigpart3 (like trigpart);
|
||||||
@ -3311,7 +3311,7 @@ NOTICE: hello from funcA
|
|||||||
create or replace trigger my_trig
|
create or replace trigger my_trig
|
||||||
after insert on parted_trig_1
|
after insert on parted_trig_1
|
||||||
for each row execute procedure funcB(); -- should fail
|
for each row execute procedure funcB(); -- should fail
|
||||||
ERROR: trigger "my_trig" for relation "parted_trig_1" is an internal trigger
|
ERROR: trigger "my_trig" for relation "parted_trig_1" is an internal or a child trigger
|
||||||
insert into parted_trig (a) values (50);
|
insert into parted_trig (a) values (50);
|
||||||
NOTICE: hello from funcA
|
NOTICE: hello from funcA
|
||||||
drop trigger my_trig on parted_trig;
|
drop trigger my_trig on parted_trig;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user