summaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/tablecmds.c28
-rw-r--r--src/backend/commands/trigger.c174
2 files changed, 140 insertions, 62 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e3cfaf8b074..46f1637e774 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10550,10 +10550,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
* and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_c";
fk_trigger->relation = NULL;
- fk_trigger->row = true;
- fk_trigger->timing = TRIGGER_TYPE_AFTER;
/* Either ON INSERT or ON UPDATE */
if (on_insert)
@@ -10567,14 +10567,15 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->events = TRIGGER_TYPE_UPDATE;
}
+ fk_trigger->args = NIL;
+ fk_trigger->row = true;
+ fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = NULL;
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid,
indexOid, InvalidOid, InvalidOid, NULL, true, false);
@@ -10599,15 +10600,17 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
* DELETE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_a";
fk_trigger->relation = NULL;
+ fk_trigger->args = NIL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_del_action)
{
@@ -10641,7 +10644,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
(int) fkconstraint->fk_del_action);
break;
}
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
constraintOid,
@@ -10655,15 +10657,17 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
* UPDATE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_a";
fk_trigger->relation = NULL;
+ fk_trigger->args = NIL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_upd_action)
{
@@ -10697,7 +10701,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
(int) fkconstraint->fk_upd_action);
break;
}
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
constraintOid,
@@ -16898,6 +16901,8 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
}
trigStmt = makeNode(CreateTrigStmt);
+ trigStmt->replace = false;
+ trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
trigStmt->trigname = NameStr(trigForm->tgname);
trigStmt->relation = NULL;
trigStmt->funcname = NULL; /* passed separately */
@@ -16907,7 +16912,6 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
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;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e1f3472eca9..c336b238aac 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -152,7 +152,9 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
*
* 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.
+ * case caller is expected to execute recursion on its own. in_partition
+ * indicates such a recursive call; outside callers should pass "false"
+ * (but see CloneRowTriggersToPartition).
*/
ObjectAddress
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
@@ -171,12 +173,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Relation rel;
AclResult aclresult;
Relation tgrel;
- SysScanDesc tgscan;
- ScanKeyData key;
Relation pgrel;
- HeapTuple tuple;
+ HeapTuple tuple = NULL;
Oid funcrettype;
- Oid trigoid;
+ Oid trigoid = InvalidOid;
char internaltrigname[NAMEDATALEN];
char *trigname;
Oid constrrelid = InvalidOid;
@@ -185,6 +185,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
char *oldtablename = NULL;
char *newtablename = NULL;
bool partition_recurse;
+ bool trigger_exists = false;
+ Oid existing_constraint_oid = InvalidOid;
+ bool existing_isInternal = false;
if (OidIsValid(relOid))
rel = table_open(relOid, ShareRowExclusiveLock);
@@ -689,6 +692,100 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
NameListToString(stmt->funcname), "trigger")));
/*
+ * Scan pg_trigger to see if there is already a trigger of the same name.
+ * Skip this for internally generated triggers, since we'll modify the
+ * name to be unique below.
+ *
+ * NOTE that this is cool only because we have ShareRowExclusiveLock on
+ * the relation, so the trigger set won't be changing underneath us.
+ */
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+ if (!isInternal)
+ {
+ ScanKeyData skeys[2];
+ SysScanDesc tgscan;
+
+ ScanKeyInit(&skeys[0],
+ Anum_pg_trigger_tgrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ ScanKeyInit(&skeys[1],
+ Anum_pg_trigger_tgname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->trigname));
+
+ tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+ NULL, 2, skeys);
+
+ /* There should be at most one matching tuple */
+ if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+ {
+ Form_pg_trigger oldtrigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+ trigoid = oldtrigger->oid;
+ existing_constraint_oid = oldtrigger->tgconstraint;
+ existing_isInternal = oldtrigger->tgisinternal;
+ trigger_exists = true;
+ /* copy the tuple to use in CatalogTupleUpdate() */
+ tuple = heap_copytuple(tuple);
+ }
+ systable_endscan(tgscan);
+ }
+
+ if (!trigger_exists)
+ {
+ /* Generate the OID for the new trigger. */
+ trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+ Anum_pg_trigger_oid);
+ }
+ else
+ {
+ /*
+ * If OR REPLACE was specified, we'll replace the old trigger;
+ * otherwise complain about the duplicate name.
+ */
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * An internal trigger cannot be replaced by a user-defined trigger.
+ * However, skip this test when in_partition, because then we're
+ * recursing from a partitioned table and the check was made at the
+ * parent level. Child triggers will always be marked "internal" (so
+ * this test does protect us from the user trying to replace a child
+ * trigger directly).
+ */
+ if (existing_isInternal && !isInternal && !in_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * It is not allowed to replace with a constraint trigger; gram.y
+ * should have enforced this already.
+ */
+ Assert(!stmt->isconstraint);
+
+ /*
+ * It is not allowed to replace an existing constraint trigger,
+ * either. (The reason for these restrictions is partly that it seems
+ * difficult to deal with pending trigger events in such cases, and
+ * partly that the command might imply changing the constraint's
+ * properties as well, which doesn't seem nice.)
+ */
+ if (OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ }
+
+ /*
* If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a
* corresponding pg_constraint entry.
*/
@@ -728,15 +825,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/*
- * Generate the trigger's OID now, so that we can use it in the name if
- * needed.
- */
- tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
- trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
- Anum_pg_trigger_oid);
-
- /*
* If trigger is internally generated, modify the provided trigger name to
* ensure uniqueness by appending the trigger OID. (Callers will usually
* supply a simple constant trigger name in these cases.)
@@ -754,37 +842,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/*
- * Scan pg_trigger for existing triggers on relation. We do this only to
- * give a nice error message if there's already a trigger of the same
- * name. (The unique index on tgrelid/tgname would complain anyway.) We
- * can skip this for internally generated triggers, since the name
- * modification above should be sufficient.
- *
- * NOTE that this is cool only because we have ShareRowExclusiveLock on
- * the relation, so the trigger set won't be changing underneath us.
- */
- if (!isInternal)
- {
- ScanKeyInit(&key,
- Anum_pg_trigger_tgrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
- tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
- NULL, 1, &key);
- while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
- {
- Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
- if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("trigger \"%s\" for relation \"%s\" already exists",
- trigname, RelationGetRelationName(rel))));
- }
- systable_endscan(tgscan);
- }
-
- /*
* Build the new pg_trigger tuple.
*
* When we're creating a trigger in a partition, we mark it as internal,
@@ -910,14 +967,24 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
else
nulls[Anum_pg_trigger_tgnewtable - 1] = true;
- tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
/*
- * Insert tuple into pg_trigger.
+ * Insert or replace tuple in pg_trigger.
*/
- CatalogTupleInsert(tgrel, tuple);
+ if (!trigger_exists)
+ {
+ tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+ CatalogTupleInsert(tgrel, tuple);
+ }
+ else
+ {
+ HeapTuple newtup;
- heap_freetuple(tuple);
+ newtup = heap_form_tuple(tgrel->rd_att, values, nulls);
+ CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+ heap_freetuple(newtup);
+ }
+
+ heap_freetuple(tuple); /* free either original or new tuple */
table_close(tgrel, RowExclusiveLock);
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -953,6 +1020,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
table_close(pgrel, RowExclusiveLock);
/*
+ * If we're replacing a trigger, flush all the old dependencies before
+ * recording new ones.
+ */
+ if (trigger_exists)
+ deleteDependencyRecordsFor(TriggerRelationId, trigoid, true);
+
+ /*
* Record dependencies for trigger. Always place a normal dependency on
* the function.
*/