1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-30 21:42:05 +03:00

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
This commit is contained in:
Tom Lane
2020-11-14 17:05:34 -05:00
parent dbca94510c
commit 92bf7e2d02
10 changed files with 408 additions and 114 deletions

View File

@ -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);
@ -688,6 +691,100 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("function %s must return type %s",
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.
@ -727,15 +824,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
isInternal); /* is_internal */
}
/*
* 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
@ -753,37 +841,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
trigname = stmt->trigname;
}
/*
* 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.
*
@ -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]));
@ -952,6 +1019,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
heap_freetuple(tuple);
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.