CREATE_OR_REPLACE_TRIGGER_v06.patch
application/octet-stream
Filename: CREATE_OR_REPLACE_TRIGGER_v06.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
Series: patch v6
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/create_trigger.sgml | 1 | 1 |
| src/backend/catalog/heap.c | 2 | 1 |
| src/backend/catalog/index.c | 4 | 2 |
| src/backend/catalog/pg_constraint.c | 57 | 9 |
| src/backend/commands/tablecmds.c | 13 | 7 |
| src/backend/commands/trigger.c | 121 | 47 |
| src/backend/commands/typecmds.c | 2 | 1 |
| src/backend/nodes/copyfuncs.c | 2 | 1 |
| src/backend/nodes/equalfuncs.c | 2 | 1 |
| src/backend/parser/gram.y | 23 | 21 |
| src/include/catalog/pg_constraint.h | 2 | 1 |
| src/include/nodes/parsenodes.h | 2 | 1 |
| src/test/regress/expected/triggers.out | 149 | 0 |
| src/test/regress/sql/triggers.sql | 130 | 0 |
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index bde3a63..675e3b6 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
ON <replaceable class="parameter">table_name</replaceable>
[ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
[ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..fefea5d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2404,7 +2404,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
* Create the Check Constraint
*/
constrOid =
- CreateConstraintEntry(ccname, /* Constraint Name */
+ CreateConstraintEntry(InvalidOid,
+ ccname, /* Constraint Name */
RelationGetNamespace(rel), /* namespace */
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..3c2a503 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1873,7 +1873,8 @@ index_constraint_create(Relation heapRelation,
/*
* Construct a pg_constraint entry.
*/
- conOid = CreateConstraintEntry(constraintName,
+ conOid = CreateConstraintEntry(InvalidOid,
+ constraintName,
namespaceId,
constraintType,
deferrable,
@@ -1938,6 +1939,8 @@ index_constraint_create(Relation heapRelation,
CreateTrigStmt *trigger;
trigger = makeNode(CreateTrigStmt);
+ trigger->replace = false;
+ trigger->isconstraint = true;
trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
"PK_ConstraintTrigger" :
"Unique_ConstraintTrigger";
@@ -1949,7 +1952,6 @@ index_constraint_create(Relation heapRelation,
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->columns = NIL;
trigger->whenClause = NULL;
- trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be..dc801a7 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
* constraint) are *not* created here. But we do make dependency links
* from the constraint to the things it depends on.
*
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)
*/
Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+ const char *constraintName,
Oid constraintNamespace,
char constraintType,
bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
bool is_internal)
{
Relation conDesc;
- Oid conOid;
+ Oid conOid = InvalidOid;
HeapTuple tup;
bool nulls[Natts_pg_constraint];
Datum values[Natts_pg_constraint];
@@ -91,7 +93,12 @@ CreateConstraintEntry(const char *constraintName,
NameData cname;
int i;
ObjectAddress conobject;
-
+ SysScanDesc conscan;
+ ScanKeyData skey[2];
+ HeapTuple tuple;
+ bool replaces[Natts_pg_constraint];
+ Form_pg_constraint constrForm;
+
conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
Assert(constraintName);
@@ -163,9 +170,12 @@ CreateConstraintEntry(const char *constraintName,
values[i] = (Datum) NULL;
}
- conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
- Anum_pg_constraint_oid);
- values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ if (!existing_constraint_oid)
+ {
+ conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);
+ values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ }
values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -219,9 +229,47 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_conbin - 1] = true;
- tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+ if (OidIsValid(existing_constraint_oid))
+ {
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(existing_constraint_oid));
+
+ conscan = systable_beginscan(conDesc,
+ ConstraintOidIndexId,
+ true,
+ NULL,
+ 1,
+ skey);
+
+ tuple = systable_getnext(conscan);
+ Assert (HeapTupleIsValid(tuple));
+ constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+ conOid = constrForm->oid;
+ Assert(conOid == existing_constraint_oid);
+
+ memset(replaces, true, sizeof(replaces));
+ replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+ replaces[Anum_pg_constraint_conname - 1] = false;
+ replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+ /* Modify the existing constraint entry */
+ tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+ CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+ heap_freetuple(tup);
- CatalogTupleInsert(conDesc, tup);
+ /* Remove all old dependencies before registering new ones */
+ deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+ systable_endscan(conscan);
+ }
+ else
+ {
+ tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+ CatalogTupleInsert(conDesc, tup);
+ heap_freetuple(tup);
+ }
conobject.classId = ConstraintRelationId;
conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..3481ced 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8583,7 +8583,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
/*
* Record the FK constraint in pg_constraint.
*/
- constrOid = CreateConstraintEntry(conname,
+ constrOid = CreateConstraintEntry(InvalidOid,
+ conname,
RelationGetNamespace(rel),
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
@@ -8611,7 +8612,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
conislocal, /* islocal */
coninhcount, /* inhcount */
connoinherit, /* conNoInherit */
- false); /* is_internal */
+ false); /* is_internal */
ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8851,7 +8852,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
else
conname = fkconstraint->conname;
constrOid =
- CreateConstraintEntry(conname,
+ CreateConstraintEntry(InvalidOid,
+ conname,
RelationGetNamespace(partition),
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
@@ -9253,7 +9255,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
indexOid = constrForm->conindid;
constrOid =
- CreateConstraintEntry(fkconstraint->conname,
+ CreateConstraintEntry(InvalidOid,
+ fkconstraint->conname,
constrForm->connamespace,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
@@ -10319,6 +10322,8 @@ 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;
@@ -10339,7 +10344,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->columns = NIL;
fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = NULL;
@@ -10368,6 +10372,8 @@ 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->row = true;
@@ -10376,7 +10382,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
fk_trigger->columns = NIL;
fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_del_action)
{
@@ -10424,6 +10429,8 @@ 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->row = true;
@@ -10432,7 +10439,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
fk_trigger->columns = NIL;
fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_upd_action)
{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index ed551ab..4fc446b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Relation pgrel;
HeapTuple tuple;
Oid funcrettype;
- Oid trigoid;
+ Oid trigoid = InvalidOid;
char internaltrigname[NAMEDATALEN];
char *trigname;
Oid constrrelid = InvalidOid;
@@ -184,6 +185,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
char *oldtablename = NULL;
char *newtablename = NULL;
bool partition_recurse;
+ bool is_update = false;
+ HeapTuple newtup;
+ TupleDesc tupDesc;
+ bool replaces[Natts_pg_trigger];
+ Oid existing_constraint_oid = InvalidOid;
+ bool trigger_exists = false;
if (OidIsValid(relOid))
rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +675,64 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
whenRtable = NIL;
}
+ /* Check if there is a pre-existing trigger of the same name */
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+ 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), stmt->trigname) == 0)
+ {
+ /* Store values for replacement of this trigger */
+ trigoid = pg_trigger->oid;
+ existing_constraint_oid = pg_trigger->tgconstraint;
+ trigger_exists = true;
+ break;
+ }
+ }
+ systable_endscan(tgscan);
+ }
+
+ /* Generate the trigger's oid because there was no same name trigger. */
+ if (!trigger_exists)
+ trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+ Anum_pg_trigger_oid);
+ else
+ {
+ /*
+ * without OR REPLACE clause, can't override the trigger with the same name.
+ */
+ if (!stmt->replace && !isInternal)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+ */
+ if (stmt->replace && stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+ */
+ if (stmt->replace && !stmt->isconstraint && OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ }
+
/*
* Find and validate the trigger function.
*/
@@ -695,7 +760,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
{
/* Internal callers should have made their own constraints */
Assert(!isInternal);
- constraintOid = CreateConstraintEntry(stmt->trigname,
+ constraintOid = CreateConstraintEntry(existing_constraint_oid,
+ stmt->trigname,
RelationGetNamespace(rel),
CONSTRAINT_TRIGGER,
stmt->deferrable,
@@ -727,15 +793,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.)
@@ -753,37 +810,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,
@@ -911,12 +937,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
- /*
- * Insert tuple into pg_trigger.
- */
- CatalogTupleInsert(tgrel, tuple);
+ if (!trigger_exists)
+ {
+ tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+ /*
+ * Insert tuple into pg_trigger.
+ */
+ CatalogTupleInsert(tgrel, tuple);
+
+ heap_freetuple(tuple);
+ }
+ else
+ {
+ memset(replaces, true, sizeof(replaces));
+
+ 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)
+ {
+ tupDesc = RelationGetDescr(tgrel);
+ replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+ replaces[Anum_pg_trigger_tgrelid - 1] = false;
+ replaces[Anum_pg_trigger_tgname - 1] = false;
+ trigoid = pg_trigger->oid;
+ newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+ /* Update tuple in pg_trigger */
+ CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+ heap_freetuple(newtup);
+ is_update = true;
+ break;
+ }
+ }
+ systable_endscan(tgscan);
+ }
- heap_freetuple(tuple);
table_close(tgrel, RowExclusiveLock);
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1024,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
myself.objectId = trigoid;
myself.objectSubId = 0;
+ /*
+ * In case of replace trigger, trigger should no-more dependent on old
+ * referenced objects. Always remove the old dependencies and then
+ * register new ones. In that way, even if the old referenced object gets
+ * dropped, trigger will remain in the database.
+ */
+ if (is_update)
+ deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
referenced.classId = ProcedureRelationId;
referenced.objectId = funcoid;
referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d..5a44533 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3077,7 +3077,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
* Store the constraint in pg_constraint
*/
ccoid =
- CreateConstraintEntry(constr->conname, /* Constraint Name */
+ CreateConstraintEntry(InvalidOid,
+ constr->conname, /* Constraint Name */
domainNamespace, /* namespace */
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1525c0d..f7e9f58 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4314,6 +4314,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
COPY_STRING_FIELD(trigname);
+ COPY_SCALAR_FIELD(replace);
+ COPY_SCALAR_FIELD(isconstraint);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(funcname);
COPY_NODE_FIELD(args);
@@ -4322,7 +4324,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 4f34189..f398597 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2019,6 +2019,8 @@ static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
COMPARE_STRING_FIELD(trigname);
+ COMPARE_SCALAR_FIELD(replace);
+ COMPARE_SCALAR_FIELD(isconstraint);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(funcname);
COMPARE_NODE_FIELD(args);
@@ -2027,7 +2029,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 1219ac8..5aeffdc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5391,48 +5391,50 @@ 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->replace = $2;
+ 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->isconstraint = false;
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;
+ 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;
}
;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
} ConstraintCategory;
-extern Oid CreateConstraintEntry(const char *constraintName,
+extern Oid CreateConstraintEntry(Oid existing_constraint_oid,
+ const char *constraintName,
Oid constraintNamespace,
char constraintType,
bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 518abe4..821e02d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2427,6 +2427,8 @@ typedef struct CreateAmStmt
typedef struct CreateTrigStmt
{
NodeTag type;
+ bool replace; /* when true, replace trigger if already exists */
+ bool isconstraint; /* This is a constraint trigger */
char *trigname; /* TRIGGER's name */
RangeVar *relation; /* relation trigger is on */
List *funcname; /* qual. name of function to call */
@@ -2438,7 +2440,6 @@ typedef struct CreateTrigStmt
int16 events; /* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
List *columns; /* column names, or NIL for all columns */
Node *whenClause; /* qual expression, or NULL if none */
- bool isconstraint; /* This is a constraint trigger */
/* explicitly named transition data */
List *transitionRels; /* TriggerTransition nodes, or NIL if none */
/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index e9da4ef..e9a9551 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3007,3 +3007,152 @@ create trigger aft_row after insert or update on trigger_parted
create table trigger_parted_p1 partition of trigger_parted for values in (1)
partition by list (a);
create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE: function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE: function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+NOTICE: table "parted_trig" does not exist, skipping
+drop trigger if exists my_trig on parted_trig;
+NOTICE: relation "parted_trig" does not exist, skipping
+drop function if exists before_replacement;
+NOTICE: function before_replacement() does not exist, skipping
+drop function if exists after_replacement;
+NOTICE: function after_replacement() does not exist, skipping
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: before
+insert into parted_trig_1 (a) values (1);
+NOTICE: before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: before
+insert into parted_trig_2 (a) values (1500);
+NOTICE: before
+insert into parted_trig (a) values (2500);
+NOTICE: before
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: after
+insert into parted_trig_1 (a) values (1);
+NOTICE: after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: after
+insert into parted_trig_2 (a) values (1500);
+NOTICE: after
+insert into parted_trig (a) values (2500);
+NOTICE: after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: before
+insert into parted_trig_1 (a) values (1);
+NOTICE: before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: before
+insert into parted_trig_2 (a) values (1500);
+NOTICE: before
+insert into parted_trig (a) values (2500);
+NOTICE: before
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: after
+insert into parted_trig_1 (a) values (1);
+NOTICE: after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: after
+insert into parted_trig_2 (a) values (1500);
+NOTICE: before
+insert into parted_trig (a) values (2500);
+NOTICE: before
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: before
+insert into parted_trig_1 (a) values (1);
+NOTICE: before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: after
+insert into parted_trig_1 (a) values (1);
+NOTICE: after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: after
+insert into parted_trig_2 (a) values (1500);
+NOTICE: after
+insert into parted_trig (a) values (2500);
+NOTICE: after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: before
+insert into parted_trig_1 (a) values (1);
+NOTICE: before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE: after
+insert into parted_trig_1 (a) values (1);
+NOTICE: after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE: after
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 80ffbb4..ceb27c6 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2274,3 +2274,133 @@ create trigger aft_row after insert or update on trigger_parted
create table trigger_parted_p1 partition of trigger_parted for values in (1)
partition by list (a);
create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+drop trigger if exists my_trig on parted_trig;
+drop function if exists before_replacement;
+drop function if exists after_replacement;
+
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;