aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/heap.c1
-rw-r--r--src/backend/catalog/index.c4
-rw-r--r--src/backend/catalog/pg_constraint.c3
-rw-r--r--src/backend/commands/tablecmds.c150
-rw-r--r--src/backend/commands/trigger.c313
-rw-r--r--src/backend/commands/typecmds.c1
-rw-r--r--src/backend/tcop/utility.c3
7 files changed, 437 insertions, 38 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ed4654875e..b69bb1e2a41 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2121,6 +2121,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
false, /* Is Deferrable */
false, /* Is Deferred */
is_validated,
+ InvalidOid, /* no parent constraint */
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
keycount, /* # attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f4a1efbf549..bfac37f9d17 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1282,6 +1282,7 @@ index_constraint_create(Relation heapRelation,
deferrable,
initdeferred,
true,
+ parentConstraintId,
RelationGetRelid(heapRelation),
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
@@ -1360,7 +1361,8 @@ index_constraint_create(Relation heapRelation,
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
- InvalidOid, conOid, indexRelationId, true);
+ InvalidOid, conOid, indexRelationId, InvalidOid,
+ InvalidOid, NULL, true, false);
}
/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 731c5e4317e..4f1a27a7d34 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -52,6 +52,7 @@ CreateConstraintEntry(const char *constraintName,
bool isDeferrable,
bool isDeferred,
bool isValidated,
+ Oid parentConstrId,
Oid relId,
const int16 *constraintKey,
int constraintNKeys,
@@ -170,6 +171,7 @@ CreateConstraintEntry(const char *constraintName,
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId);
+ values[Anum_pg_constraint_conparentid - 1] = ObjectIdGetDatum(parentConstrId);
values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId);
values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType);
values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType);
@@ -772,6 +774,7 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId)
constrForm = (Form_pg_constraint) GETSTRUCT(newtup);
constrForm->conislocal = false;
constrForm->coninhcount++;
+ constrForm->conparentid = parentConstrId;
CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f5c744b9f5a..e74fb1f4691 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -487,6 +487,7 @@ static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *scanrel_children,
List *partConstraint,
bool validate_default);
+static void CloneRowTriggersToPartition(Relation parent, Relation partition);
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
RangeVar *name);
@@ -906,9 +907,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
/*
- * If we're creating a partition, create now all the indexes defined in
- * the parent. We can't do it earlier, because DefineIndex wants to know
- * the partition key which we just stored.
+ * If we're creating a partition, create now all the indexes and triggers
+ * defined in the parent.
+ *
+ * We can't do it earlier, because DefineIndex wants to know the partition
+ * key which we just stored.
*/
if (stmt->partbound)
{
@@ -949,6 +952,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
list_free(idxlist);
+
+ /*
+ * If there are any row-level triggers, clone them to the new
+ * partition.
+ */
+ if (parent->trigdesc != NULL)
+ CloneRowTriggersToPartition(parent, rel);
+
heap_close(parent, NoLock);
}
@@ -7491,6 +7502,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkconstraint->deferrable,
fkconstraint->initdeferred,
fkconstraint->initially_valid,
+ InvalidOid, /* no parent constraint */
RelationGetRelid(rel),
fkattnum,
numfks,
@@ -8445,7 +8457,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid,
- indexOid, true);
+ indexOid, InvalidOid, InvalidOid, NULL, true, false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -8519,7 +8531,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid,
- indexOid, true);
+ indexOid, InvalidOid, InvalidOid, NULL, true, false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -8574,7 +8586,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid,
- indexOid, true);
+ indexOid, InvalidOid, InvalidOid, NULL, true, false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -11114,7 +11126,7 @@ static void
ATExecEnableDisableTrigger(Relation rel, const char *trigname,
char fires_when, bool skip_system, LOCKMODE lockmode)
{
- EnableDisableTrigger(rel, trigname, fires_when, skip_system);
+ EnableDisableTrigger(rel, trigname, fires_when, skip_system, lockmode);
}
/*
@@ -14031,6 +14043,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* Ensure there exists a correct set of indexes in the partition. */
AttachPartitionEnsureIndexes(rel, attachrel);
+ /* and triggers */
+ CloneRowTriggersToPartition(rel, attachrel);
+
/*
* Generate partition constraint from the partition bound specification.
* If the parent itself is a partition, make sure to include its
@@ -14255,6 +14270,127 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
}
/*
+ * CloneRowTriggersToPartition
+ * subroutine for ATExecAttachPartition/DefineRelation to create row
+ * triggers on partitions
+ */
+static void
+CloneRowTriggersToPartition(Relation parent, Relation partition)
+{
+ Relation pg_trigger;
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+ MemoryContext oldcxt,
+ perTupCxt;
+
+ ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
+ pg_trigger = heap_open(TriggerRelationId, RowExclusiveLock);
+ scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
+ true, NULL, 1, &key);
+
+ perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
+ "clone trig", ALLOCSET_SMALL_SIZES);
+ oldcxt = MemoryContextSwitchTo(perTupCxt);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_trigger trigForm;
+ CreateTrigStmt *trigStmt;
+ Node *qual = NULL;
+ Datum value;
+ bool isnull;
+ List *cols = NIL;
+
+ trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
+
+ /*
+ * Ignore statement-level triggers; those are not cloned.
+ */
+ if (!TRIGGER_FOR_ROW(trigForm->tgtype))
+ continue;
+
+ /*
+ * Complain if we find an unexpected trigger type.
+ */
+ if (!TRIGGER_FOR_AFTER(trigForm->tgtype))
+ elog(ERROR, "unexpected trigger \"%s\" found",
+ NameStr(trigForm->tgname));
+
+ /*
+ * If there is a WHEN clause, generate a 'cooked' version of it that's
+ * appropriate for the partition.
+ */
+ value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
+ RelationGetDescr(pg_trigger), &isnull);
+ if (!isnull)
+ {
+ bool found_whole_row;
+
+ qual = stringToNode(TextDatumGetCString(value));
+ qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
+ partition, parent,
+ &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
+ qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
+ partition, parent,
+ &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
+ }
+
+ /*
+ * If there is a column list, transform it to a list of column names.
+ * Note we don't need to map this list in any way ...
+ */
+ if (trigForm->tgattr.dim1 > 0)
+ {
+ int i;
+
+ for (i = 0; i < trigForm->tgattr.dim1; i++)
+ {
+ Form_pg_attribute col;
+
+ col = TupleDescAttr(parent->rd_att,
+ trigForm->tgattr.values[i] - 1);
+ cols = lappend(cols, makeString(NameStr(col->attname)));
+ }
+ }
+
+ trigStmt = makeNode(CreateTrigStmt);
+ trigStmt->trigname = NameStr(trigForm->tgname);
+ trigStmt->relation = NULL;
+ trigStmt->funcname = NULL; /* passed separately */
+ trigStmt->args = NULL; /* passed separately */
+ trigStmt->row = true;
+ trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
+ trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
+ trigStmt->columns = cols;
+ trigStmt->whenClause = NULL; /* passed separately */
+ trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
+ trigStmt->transitionRels = NIL; /* not supported at present */
+ trigStmt->deferrable = trigForm->tgdeferrable;
+ trigStmt->initdeferred = trigForm->tginitdeferred;
+ trigStmt->constrrel = NULL; /* passed separately */
+
+ CreateTrigger(trigStmt, NULL, RelationGetRelid(partition),
+ trigForm->tgconstrrelid, InvalidOid, InvalidOid,
+ trigForm->tgfoid, HeapTupleGetOid(tuple), qual,
+ false, true);
+
+ MemoryContextReset(perTupCxt);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ MemoryContextDelete(perTupCxt);
+
+ systable_endscan(scan);
+ heap_close(pg_trigger, RowExclusiveLock);
+}
+
+/*
* ALTER TABLE DETACH PARTITION
*
* Return the address of the relation that is no longer a partition of rel.
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fbd176b5d03..9d8df5986ec 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -20,6 +20,7 @@
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
+#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_constraint.h"
@@ -123,7 +124,20 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
* TRIGGER, we build a pg_constraint entry internally.)
*
* indexOid, if nonzero, is the OID of an index associated with the constraint.
- * We do nothing with this except store it into pg_trigger.tgconstrindid.
+ * We do nothing with this except store it into pg_trigger.tgconstrindid;
+ * but when creating a trigger for a deferrable unique constraint on a
+ * partitioned table, its children are looked up. Note we don't cope with
+ * invalid indexes in that case.
+ *
+ * funcoid, if nonzero, is the OID of the function to invoke. When this is
+ * given, stmt->funcname is ignored.
+ *
+ * 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
+ * Invalid by most callers; it's set here when recursing on a partition.)
+ *
+ * If whenClause is passed, it is an already-transformed expression for
+ * WHEN. In this case, we ignore any that may come in stmt->whenClause.
*
* If isInternal is true then this is an internally-generated trigger.
* This argument sets the tgisinternal field of the pg_trigger entry, and
@@ -133,6 +147,10 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
* relation, as well as ACL_EXECUTE on the trigger function. For internal
* triggers the caller must apply any required permission checks.
*
+ * When called on partitioned tables, this function recurses to create the
+ * trigger on all the partitions, except if isInternal is true, in which
+ * case caller is expected to execute recursion on its own.
+ *
* Note: can return InvalidObjectAddress if we decided to not create a trigger
* at all, but a foreign-key constraint. This is a kluge for backwards
* compatibility.
@@ -140,13 +158,13 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
ObjectAddress
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
- bool isInternal)
+ Oid funcoid, Oid parentTriggerOid, Node *whenClause,
+ bool isInternal, bool in_partition)
{
int16 tgtype;
int ncolumns;
int16 *columns;
int2vector *tgattr;
- Node *whenClause;
List *whenRtable;
char *qual;
Datum values[Natts_pg_trigger];
@@ -159,7 +177,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Relation pgrel;
HeapTuple tuple;
Oid fargtypes[1]; /* dummy */
- Oid funcoid;
Oid funcrettype;
Oid trigoid;
char internaltrigname[NAMEDATALEN];
@@ -169,6 +186,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
referenced;
char *oldtablename = NULL;
char *newtablename = NULL;
+ bool partition_recurse;
if (OidIsValid(relOid))
rel = heap_open(relOid, ShareRowExclusiveLock);
@@ -179,8 +197,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ if (rel->rd_rel->relkind == RELKIND_RELATION)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -190,13 +207,53 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
- /* Disallow ROW triggers on partitioned tables */
- if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ }
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ /* Partitioned tables can't have INSTEAD OF triggers */
+ if (stmt->timing != TRIGGER_TYPE_BEFORE &&
+ stmt->timing != TRIGGER_TYPE_AFTER)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is a partitioned table",
+ errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
- errdetail("Partitioned tables cannot have ROW triggers.")));
+ errdetail("Tables cannot have INSTEAD OF triggers.")));
+
+ /*
+ * FOR EACH ROW triggers have further restrictions
+ */
+ if (stmt->row)
+ {
+ /*
+ * BEFORE triggers FOR EACH ROW are forbidden, because they would
+ * allow the user to direct the row to another partition, which
+ * isn't implemented in the executor.
+ */
+ if (stmt->timing != TRIGGER_TYPE_AFTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have BEFORE / FOR EACH ROW triggers.")));
+
+ /*
+ * Disallow use of transition tables.
+ *
+ * Note that we have another restriction about transition tables
+ * in partitions; search for 'has_superclass' below for an
+ * explanation. The check here is just to protect from the fact
+ * that if we allowed it here, the creation would succeed for a
+ * partitioned table with no partitions, but would be blocked by
+ * the other restriction when the first partition was created,
+ * which is very unfriendly behavior.
+ */
+ if (stmt->transitionRels != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Triggers on partitioned tables cannot have transition tables.")));
+ }
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -297,6 +354,18 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
}
+ /*
+ * When called on a partitioned table to create a FOR EACH ROW trigger
+ * that's not internal, we create one trigger for each partition, too.
+ *
+ * For that, we'd better hold lock on all of them ahead of time.
+ */
+ partition_recurse = !isInternal && stmt->row &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ if (partition_recurse)
+ list_free(find_all_inheritors(RelationGetRelid(rel),
+ ShareRowExclusiveLock, NULL));
+
/* Compute tgtype */
TRIGGER_CLEAR_TYPE(tgtype);
if (stmt->row)
@@ -484,9 +553,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/*
- * Parse the WHEN clause, if any
+ * Parse the WHEN clause, if any and we weren't passed an already
+ * transformed one.
+ *
+ * Note that as a side effect, we fill whenRtable when parsing. If we got
+ * an already parsed clause, this does not occur, which is what we want --
+ * no point in adding redundant dependencies below.
*/
- if (stmt->whenClause)
+ if (!whenClause && stmt->whenClause)
{
ParseState *pstate;
RangeTblEntry *rte;
@@ -577,17 +651,23 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
free_parsestate(pstate);
}
- else
+ else if (!whenClause)
{
whenClause = NULL;
whenRtable = NIL;
qual = NULL;
}
+ else
+ {
+ qual = nodeToString(whenClause);
+ whenRtable = NIL;
+ }
/*
* Find and validate the trigger function.
*/
- funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
+ if (!OidIsValid(funcoid))
+ funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
if (!isInternal)
{
aclresult = pg_proc_aclcheck(funcoid, GetUserId(), ACL_EXECUTE);
@@ -651,6 +731,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
stmt->deferrable,
stmt->initdeferred,
true,
+ InvalidOid, /* no parent */
RelationGetRelid(rel),
NULL, /* no conkey */
0,
@@ -733,6 +814,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
/*
* Build the new pg_trigger tuple.
+ *
+ * When we're creating a trigger in a partition, we mark it as internal,
+ * even though we don't do the isInternal magic in this function. This
+ * makes the triggers in partitions identical to the ones in the
+ * partitioned tables, except that they are marked internal.
*/
memset(nulls, false, sizeof(nulls));
@@ -742,7 +828,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
values[Anum_pg_trigger_tgenabled - 1] = CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
- values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
+ values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal || in_partition);
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
@@ -872,9 +958,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
pfree(DatumGetPointer(values[Anum_pg_trigger_tgnewtable - 1]));
/*
- * Update relation's pg_class entry. Crucial side-effect: other backends
- * (and this one too!) are sent SI message to make them rebuild relcache
- * entries.
+ * Update relation's pg_class entry; if necessary; and if not, send an SI
+ * message to make other backends (and this one) rebuild relcache entries.
*/
pgrel = heap_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(RELOID,
@@ -882,21 +967,21 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(rel));
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relhastriggers)
+ {
+ ((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;
- ((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;
+ CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
- CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
+ CommandCounterIncrement();
+ }
+ else
+ CacheInvalidateRelcacheByTuple(tuple);
heap_freetuple(tuple);
heap_close(pgrel, RowExclusiveLock);
/*
- * We used to try to update the rel's relcache entry here, but that's
- * fairly pointless since it will happen as a byproduct of the upcoming
- * CommandCounterIncrement...
- */
-
- /*
* Record dependencies for trigger. Always place a normal dependency on
* the function.
*/
@@ -928,11 +1013,18 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* User CREATE TRIGGER, so place dependencies. We make trigger be
* auto-dropped if its relation is dropped or if the FK relation is
* dropped. (Auto drop is compatible with our pre-7.3 behavior.)
+ *
+ * Exception: if this trigger comes from a parent partitioned table,
+ * then it's not separately drop-able, but goes away if the partition
+ * does.
*/
referenced.classId = RelationRelationId;
referenced.objectId = RelationGetRelid(rel);
referenced.objectSubId = 0;
- recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+ recordDependencyOn(&myself, &referenced, OidIsValid(parentTriggerOid) ?
+ DEPENDENCY_INTERNAL_AUTO :
+ DEPENDENCY_AUTO);
+
if (OidIsValid(constrrelid))
{
referenced.classId = RelationRelationId;
@@ -954,6 +1046,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
}
+
+ /* Depends on the parent trigger, if there is one. */
+ if (OidIsValid(parentTriggerOid))
+ {
+ ObjectAddressSet(referenced, TriggerRelationId, parentTriggerOid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL_AUTO);
+ }
}
/* If column-specific trigger, add normal dependencies on columns */
@@ -974,7 +1073,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* If it has a WHEN clause, add dependencies on objects mentioned in the
* expression (eg, functions, as well as any columns used).
*/
- if (whenClause != NULL)
+ if (whenRtable != NIL)
recordDependencyOnExpr(&myself, whenClause, whenRtable,
DEPENDENCY_NORMAL);
@@ -982,6 +1081,112 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0,
isInternal);
+ /*
+ * Lastly, create the trigger on child relations, if needed.
+ */
+ if (partition_recurse)
+ {
+ PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+ List *idxs = NIL;
+ List *childTbls = NIL;
+ ListCell *l;
+ int i;
+ MemoryContext oldcxt,
+ perChildCxt;
+
+ perChildCxt = AllocSetContextCreate(CurrentMemoryContext,
+ "part trig clone",
+ ALLOCSET_SMALL_SIZES);
+
+ /*
+ * When a trigger is being created associated with an index, we'll
+ * need to associate the trigger in each child partition with the
+ * corresponding index on it.
+ */
+ if (OidIsValid(indexOid))
+ {
+ ListCell *l;
+ List *idxs = NIL;
+
+ idxs = find_inheritance_children(indexOid, ShareRowExclusiveLock);
+ foreach(l, idxs)
+ childTbls = lappend_oid(childTbls,
+ IndexGetRelation(lfirst_oid(l),
+ false));
+ }
+
+ oldcxt = MemoryContextSwitchTo(perChildCxt);
+
+ /* Iterate to create the trigger on each existing partition */
+ for (i = 0; i < partdesc->nparts; i++)
+ {
+ Oid indexOnChild = InvalidOid;
+ ListCell *l2;
+ CreateTrigStmt *childStmt;
+ Relation childTbl;
+ Node *qual;
+ bool found_whole_row;
+
+ childTbl = heap_open(partdesc->oids[i], ShareRowExclusiveLock);
+
+ /* Find which of the child indexes is the one on this partition */
+ if (OidIsValid(indexOid))
+ {
+ forboth(l, idxs, l2, childTbls)
+ {
+ if (lfirst_oid(l2) == partdesc->oids[i])
+ {
+ indexOnChild = lfirst_oid(l);
+ break;
+ }
+ }
+ if (!OidIsValid(indexOnChild))
+ elog(ERROR, "failed to find index matching index \"%s\" in partition \"%s\"",
+ get_rel_name(indexOid),
+ get_rel_name(partdesc->oids[i]));
+ }
+
+ /*
+ * Initialize our fabricated parse node by copying the original
+ * one, then resetting fields that we pass separately.
+ */
+ childStmt = (CreateTrigStmt *) copyObject(stmt);
+ childStmt->funcname = NIL;
+ childStmt->args = NIL;
+ childStmt->whenClause = NULL;
+
+ /* If there is a WHEN clause, create a modified copy of it */
+ qual = copyObject(whenClause);
+ qual = (Node *)
+ map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
+ childTbl, rel,
+ &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
+ qual = (Node *)
+ map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
+ childTbl, rel,
+ &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
+
+ CreateTrigger(childStmt, queryString,
+ partdesc->oids[i], refRelOid,
+ InvalidOid, indexOnChild,
+ funcoid, trigoid, qual,
+ isInternal, true);
+
+ heap_close(childTbl, NoLock);
+
+ MemoryContextReset(perChildCxt);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ MemoryContextDelete(perChildCxt);
+ list_free(idxs);
+ list_free(childTbls);
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
@@ -1579,7 +1784,7 @@ renametrig(RenameStmt *stmt)
*/
void
EnableDisableTrigger(Relation rel, const char *tgname,
- char fires_when, bool skip_system)
+ char fires_when, bool skip_system, LOCKMODE lockmode)
{
Relation tgrel;
int nkeys;
@@ -1642,6 +1847,27 @@ EnableDisableTrigger(Relation rel, const char *tgname,
heap_freetuple(newtup);
+ /*
+ * When altering FOR EACH ROW triggers on a partitioned table, do
+ * the same on the partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ (TRIGGER_FOR_ROW(oldtrig->tgtype)))
+ {
+ PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ for (i = 0; i < partdesc->nparts; i++)
+ {
+ Relation part;
+
+ part = relation_open(partdesc->oids[i], lockmode);
+ EnableDisableTrigger(part, NameStr(oldtrig->tgname),
+ fires_when, skip_system, lockmode);
+ heap_close(part, NoLock); /* keep lock till commit */
+ }
+ }
+
changed = true;
}
@@ -5123,6 +5349,9 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
* constraints within the first search-path schema that has any
* matches, but disregard matches in schemas beyond the first match.
* (This is a bit odd but it's the historical behavior.)
+ *
+ * A constraint in a partitioned table may have corresponding
+ * constraints in the partitions. Grab those too.
*/
conrel = heap_open(ConstraintRelationId, AccessShareLock);
@@ -5217,6 +5446,32 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
constraint->relname)));
}
+ /*
+ * Scan for any possible descendants of the constraints. We append
+ * whatever we find to the same list that we're scanning; this has the
+ * effect that we create new scans for those, too, so if there are
+ * further descendents, we'll also catch them.
+ */
+ foreach(lc, conoidlist)
+ {
+ Oid parent = lfirst_oid(lc);
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conparentid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(parent));
+
+ scan = systable_beginscan(conrel, ConstraintParentIndexId, true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ conoidlist = lappend_oid(conoidlist, HeapTupleGetOid(tuple));
+
+ systable_endscan(scan);
+ }
+
heap_close(conrel, AccessShareLock);
/*
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e375af4cd01..25221965e9e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3153,6 +3153,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
false, /* Is Deferrable */
false, /* Is Deferred */
!constr->skip_validation, /* Is Validated */
+ InvalidOid, /* no parent constraint */
InvalidOid, /* not a relation constraint */
NULL,
0,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ed55521a0cd..8481fcca367 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1492,7 +1492,8 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateTrigStmt:
address = CreateTrigger((CreateTrigStmt *) parsetree,
queryString, InvalidOid, InvalidOid,
- InvalidOid, InvalidOid, false);
+ InvalidOid, InvalidOid, InvalidOid,
+ InvalidOid, NULL, false, false);
break;
case T_CreatePLangStmt: