diff options
| author | Tom Lane | 2020-11-14 22:05:34 +0000 |
|---|---|---|
| committer | Tom Lane | 2020-11-14 22:05:34 +0000 |
| commit | 92bf7e2d027466d750b4ac5b026f6f4ac29be881 (patch) | |
| tree | 0ec4fdd45c7bf135e11d446110e5ac5013a63ea6 /src/backend | |
| parent | dbca94510c9e564708a10a1259f6f1d8ad09862c (diff) | |
Provide the OR REPLACE option for CREATE TRIGGER.
This is mostly straightforward. However, we disallow replacing
constraint triggers or changing the is-constraint property; perhaps
that can be added later, but the complexity versus benefit tradeoff
doesn't look very good.
Also, no special thought is taken here for whether replacing an
existing trigger should result in changes to queued-but-not-fired
trigger actions. We just document that if you're surprised by the
results, too bad, don't do that. (Note that any such pending trigger
activity would have to be within the current session.)
Takamichi Osumi, reviewed at various times by Surafel Temesgen,
Peter Smith, and myself
Discussion: https://postgr.es/m/0DDF369B45A1B44B8A687ED43F06557C010BC362@G01JPEXMBYT03
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/catalog/index.c | 7 | ||||
| -rw-r--r-- | src/backend/commands/tablecmds.c | 28 | ||||
| -rw-r--r-- | src/backend/commands/trigger.c | 174 | ||||
| -rw-r--r-- | src/backend/nodes/copyfuncs.c | 3 | ||||
| -rw-r--r-- | src/backend/nodes/equalfuncs.c | 3 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 52 |
6 files changed, 177 insertions, 90 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index b88b4a1f12b..731610c7019 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2093,9 +2093,10 @@ index_constraint_create(Relation heapRelation, */ if (deferrable) { - CreateTrigStmt *trigger; + CreateTrigStmt *trigger = makeNode(CreateTrigStmt); - trigger = makeNode(CreateTrigStmt); + trigger->replace = false; + trigger->isconstraint = true; trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ? "PK_ConstraintTrigger" : "Unique_ConstraintTrigger"; @@ -2107,7 +2108,7 @@ index_constraint_create(Relation heapRelation, trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE; trigger->columns = NIL; trigger->whenClause = NULL; - trigger->isconstraint = true; + trigger->transitionRels = NIL; trigger->deferrable = true; trigger->initdeferred = initdeferred; trigger->constrrel = NULL; 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. */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 3031c52991d..5a591d0a751 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4304,6 +4304,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from) { CreateTrigStmt *newnode = makeNode(CreateTrigStmt); + COPY_SCALAR_FIELD(replace); + COPY_SCALAR_FIELD(isconstraint); COPY_STRING_FIELD(trigname); COPY_NODE_FIELD(relation); COPY_NODE_FIELD(funcname); @@ -4313,7 +4315,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from) COPY_SCALAR_FIELD(events); COPY_NODE_FIELD(columns); COPY_NODE_FIELD(whenClause); - COPY_SCALAR_FIELD(isconstraint); COPY_NODE_FIELD(transitionRels); COPY_SCALAR_FIELD(deferrable); COPY_SCALAR_FIELD(initdeferred); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9aa853748de..e2895a8985d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2011,6 +2011,8 @@ _equalCreateAmStmt(const CreateAmStmt *a, const CreateAmStmt *b) static bool _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) { + COMPARE_SCALAR_FIELD(replace); + COMPARE_SCALAR_FIELD(isconstraint); COMPARE_STRING_FIELD(trigname); COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(funcname); @@ -2020,7 +2022,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) COMPARE_SCALAR_FIELD(events); COMPARE_NODE_FIELD(columns); COMPARE_NODE_FIELD(whenClause); - COMPARE_SCALAR_FIELD(isconstraint); COMPARE_NODE_FIELD(transitionRels); COMPARE_SCALAR_FIELD(deferrable); COMPARE_SCALAR_FIELD(initdeferred); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 051f1f1d492..6ca9072684c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -5218,48 +5218,54 @@ am_type: *****************************************************************************/ CreateTrigStmt: - CREATE TRIGGER name TriggerActionTime TriggerEvents ON + CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON qualified_name TriggerReferencing TriggerForSpec TriggerWhen EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); - n->trigname = $3; - n->relation = $7; - n->funcname = $13; - n->args = $15; - n->row = $9; - n->timing = $4; - n->events = intVal(linitial($5)); - n->columns = (List *) lsecond($5); - n->whenClause = $10; - n->transitionRels = $8; - n->isconstraint = false; + n->replace = $2; + n->isconstraint = false; + n->trigname = $4; + n->relation = $8; + n->funcname = $14; + n->args = $16; + n->row = $10; + n->timing = $5; + n->events = intVal(linitial($6)); + n->columns = (List *) lsecond($6); + n->whenClause = $11; + n->transitionRels = $9; n->deferrable = false; n->initdeferred = false; n->constrrel = NULL; $$ = (Node *)n; } - | CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON + | CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON qualified_name OptConstrFromTable ConstraintAttributeSpec FOR EACH ROW TriggerWhen EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); - n->trigname = $4; - n->relation = $8; - n->funcname = $17; - n->args = $19; + n->replace = $2; + if (n->replace) /* not supported, see CreateTrigger */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE OR REPLACE CONSTRAINT TRIGGER is not supported"))); + n->isconstraint = true; + n->trigname = $5; + n->relation = $9; + n->funcname = $18; + n->args = $20; n->row = true; n->timing = TRIGGER_TYPE_AFTER; - n->events = intVal(linitial($6)); - n->columns = (List *) lsecond($6); - n->whenClause = $14; + n->events = intVal(linitial($7)); + n->columns = (List *) lsecond($7); + n->whenClause = $15; n->transitionRels = NIL; - n->isconstraint = true; - processCASbits($10, @10, "TRIGGER", + processCASbits($11, @11, "TRIGGER", &n->deferrable, &n->initdeferred, NULL, NULL, yyscanner); - n->constrrel = $9; + n->constrrel = $10; $$ = (Node *)n; } ; |
