diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 8590e22..40b695e 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] } +CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] } ON table_name [ FROM referenced_table_name ] [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] @@ -43,11 +43,12 @@ CREATE [ CONSTRAINT ] TRIGGER name Description - CREATE TRIGGER creates a new trigger. The + CREATE TRIGGER creates a new trigger. The trigger will be associated with the specified table, view, or foreign table and will execute the specified function function_name when - certain events occur. + certain events occur. CREATE OR REPLACE TRIGGER will + either create a new trigger, or replace an existing definition. @@ -68,6 +69,17 @@ CREATE [ CONSTRAINT ] TRIGGER name + To replace the current definition of an existing trigger, + use CREATE OR REPLACE TRIGGER. + It is not possible to change name + or table_name this way + (if you tried, you would actually be creating a new, distinct trigger). + Also, CREATE OR REPLACE TRIGGER will not let you + change the type (regular/constraint) of an existing trigger. + To do that, you must drop and recreate the trigger. + + + A trigger that is marked FOR EACH ROW is called once for every row that the operation modifies. For example, a DELETE that affects 10 rows will cause any diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 62be80d..96955fd 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -76,7 +76,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]; @@ -224,9 +224,70 @@ CreateConstraintEntry(const char *constraintName, else nulls[Anum_pg_constraint_consrc - 1] = true; - tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); + /* + * Since 'OR REPLACE' is supported in CREATE TRIGGER command,there are + * chances for replacing existing constraint table entry. So a new check + * is needed to know whether new constraint entry should be created + * or existing entry should be replaced. + */ + if (constraintType == CONSTRAINT_TRIGGER && + ConstraintNameIsUsed(CONSTRAINT_RELATION, + relId, + constraintNamespace, + constraintName)) + { + SysScanDesc conscan; + ScanKeyData skey[2]; + HeapTuple oldtup; + + ScanKeyInit(&skey[0], + Anum_pg_constraint_conname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(constraintName)); + + ScanKeyInit(&skey[1], + Anum_pg_constraint_connamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(constraintNamespace)); + + conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true, + NULL, 2, skey); + + while (HeapTupleIsValid(oldtup = systable_getnext(conscan))) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(oldtup); + + if (namestrcmp(&(con->conname), constraintName) == 0 && + con->conrelid == relId) + { - conOid = CatalogTupleInsert(conDesc, tup); + bool replaces[Natts_pg_constraint]; + conOid = HeapTupleGetOid(oldtup); + + memset(replaces, true, sizeof(replaces)); + replaces[Anum_pg_constraint_conname - 1] = false; + replaces[Anum_pg_constraint_confrelid - 1] = false; + + /* Modify the existing constraint entry */ + tup = heap_modify_tuple(oldtup, RelationGetDescr(conDesc), values, nulls, replaces); + CatalogTupleUpdate(conDesc, &oldtup->t_self, tup); + heap_freetuple(tup); + + /* Remove all old dependencies before registering new ones */ + deleteDependencyRecordsFor(ConstraintRelationId, conOid, true); + break; + } + } + + systable_endscan(conscan); + + } + else + { + tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); + conOid = CatalogTupleInsert(conDesc, tup); + heap_freetuple(tup); + } conobject.classId = ConstraintRelationId; conobject.objectId = conOid; diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d80bff6..2c02029 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -166,6 +166,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, referenced; char *oldtablename = NULL; char *newtablename = NULL; + bool is_update = false; if (OidIsValid(relOid)) rel = heap_open(relOid, ShareRowExclusiveLock); @@ -616,37 +617,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. */ memset(nulls, false, sizeof(nulls)); @@ -765,17 +735,108 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, else nulls[Anum_pg_trigger_tgnewtable - 1] = true; - tuple = heap_form_tuple(tgrel->rd_att, values, nulls); - - /* force tuple to have the desired OID */ - HeapTupleSetOid(tuple, trigoid); - /* - * Insert tuple into pg_trigger. + * 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 and no 'OR REPLACE' is specified. (The unique index on tgrelid/tgname + * would complain anyway.). And, in case of replace trigger, + * existing trigger definition will be updated. + * 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. */ - CatalogTupleInsert(tgrel, tuple); - heap_freetuple(tuple); + 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) + { + /* If 'OR REPLACE' is specified, then update the existing pg_trigger tuple. */ + if(stmt->replace) + { + + bool constraint_exists; + bool replaces[Natts_pg_trigger]; + + constraint_exists = ConstraintNameIsUsed(CONSTRAINT_RELATION, + RelationGetRelid(rel), + RelationGetNamespace(rel), + stmt->trigname); + memset(replaces, true, sizeof(replaces)); + + /* Replacement between normal triggers and constraint triggers are restricted */ + if((stmt->isconstraint && constraint_exists) || + (!stmt->isconstraint && !constraint_exists)) + { + HeapTuple newtup; + TupleDesc tupDesc; + + tupDesc = RelationGetDescr(tgrel); + replaces[Anum_pg_trigger_tgrelid - 1] = false; + replaces[Anum_pg_trigger_tgname - 1] = false; + trigoid = HeapTupleGetOid(tuple); + 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; + } + else + { + if(stmt->isconstraint) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger", + trigname, RelationGetRelationName(rel)))); + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger", + trigname, RelationGetRelationName(rel)))); + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("trigger \"%s\" for relation \"%s\" already exists", + trigname, RelationGetRelationName(rel)))); + } + } + } + systable_endscan(tgscan); + } + + if(!is_update) + { + + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); + + /* force tuple to have the desired OID */ + HeapTupleSetOid(tuple, trigoid); + + /* + * Insert tuple into pg_trigger. + */ + CatalogTupleInsert(tgrel, tuple); + + heap_freetuple(tuple); + + } + heap_close(tgrel, RowExclusiveLock); pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1])); @@ -819,6 +880,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/parser/gram.y b/src/backend/parser/gram.y index cf97be5..bc6183f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -5118,48 +5118,50 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name *****************************************************************************/ CreateTrigStmt: - CREATE TRIGGER name TriggerActionTime TriggerEvents ON + CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON qualified_name TriggerReferencing TriggerForSpec TriggerWhen EXECUTE 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 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/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index f355954..2c7be2e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -853,7 +853,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) initStringInfo(&buf); tgname = NameStr(trigrec->tgname); - appendStringInfo(&buf, "CREATE %sTRIGGER %s ", + appendStringInfo(&buf, "CREATE OR REPLACE %sTRIGGER %s ", OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "", quote_identifier(tgname)); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 242d3c0..f13ef67 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -1486,7 +1486,7 @@ qr/CREATE CAST \(timestamp with time zone AS interval\) WITH FUNCTION pg_catalog FOR EACH ROW WHEN (NEW.col1 > 10) EXECUTE PROCEDURE dump_test.trigger_func();', regexp => qr/^ - \QCREATE TRIGGER test_trigger BEFORE INSERT ON test_table \E + \QCREATE OR REPLACE TRIGGER test_trigger BEFORE INSERT ON test_table \E \QFOR EACH ROW WHEN ((new.col1 > 10)) \E \QEXECUTE PROCEDURE trigger_func();\E /xm, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 07a8436..648f6b4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2250,6 +2250,7 @@ typedef struct CreateTrigStmt List *columns; /* column names, or NIL for all columns */ Node *whenClause; /* qual expression, or NULL if none */ bool isconstraint; /* This is a constraint trigger */ + bool replace; /* T => replace if already exists */ /* 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 f408475..0efb218 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -385,21 +385,21 @@ SELECT * FROM main_table ORDER BY a, b; (8 rows) SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; - pg_get_triggerdef --------------------------------------------------------------------------------------------------------------------------------------------- - CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.a <> new.a) EXECUTE PROCEDURE trigger_func('modified_a') + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE TRIGGER modified_a BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.a <> new.a) EXECUTE PROCEDURE trigger_func('modified_a') (1 row) SELECT pg_get_triggerdef(oid, false) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; - pg_get_triggerdef ----------------------------------------------------------------------------------------------------------------------------------------------- - CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN ((old.a <> new.a)) EXECUTE PROCEDURE trigger_func('modified_a') + pg_get_triggerdef +--------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE TRIGGER modified_a BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN ((old.a <> new.a)) EXECUTE PROCEDURE trigger_func('modified_a') (1 row) SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any'; - pg_get_triggerdef --------------------------------------------------------------------------------------------------------------------------------------------------------------- - CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.* IS DISTINCT FROM new.*) EXECUTE PROCEDURE trigger_func('modified_any') + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE TRIGGER modified_any BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.* IS DISTINCT FROM new.*) EXECUTE PROCEDURE trigger_func('modified_any') (1 row) DROP TRIGGER modified_a ON main_table; @@ -421,9 +421,9 @@ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt'); CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt'); SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig'; - pg_get_triggerdef -------------------------------------------------------------------------------------------------------------------------------------------- - CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row') + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE OR REPLACE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row') (1 row) UPDATE main_table SET a = 50; @@ -1674,6 +1674,156 @@ drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); -- +-- test triggers with OR REPLACE clause +-- +CREATE TABLE my_table1 (id integer, name text); +CREATE TABLE my_table2 (id integer); +CREATE FUNCTION my_proc1() RETURNS trigger AS $$ +begin + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_updateproc1() RETURNS trigger AS $$ +begin + UPDATE my_table2 SET id=NEW.id WHERE id=OLD.id; + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_deleteproc1() RETURNS trigger AS $$ +begin + DELETE FROM my_table2 WHERE id=OLD.id; + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_insertproc1() RETURNS trigger AS $$ +begin + INSERT INTO my_table2 VALUES(NEW.id); + RETURN NULL; +end;$$ LANGUAGE plpgsql; +INSERT INTO my_table1 VALUES(323, 'Alex'); +INSERT INTO my_table1 VALUES(23, 'Teddy'); +INSERT INTO my_table1 VALUES(38, 'Bob'); +INSERT INTO my_table2 VALUES(323); +INSERT INTO my_table2 VALUES(23); +INSERT INTO my_table2 VALUES(38); +--With OR REPALCE clause, create a new trigger with the name my_regular_trigger. +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +DELETE FROM my_table1 WHERE id=323; +SELECT * FROM my_table1; + id | name +----+------- + 23 | Teddy + 38 | Bob +(2 rows) + +SELECT * FROM my_table2; + id +---- + 23 + 38 +(2 rows) + +--With OR REPALCE clause, create a new constraint trigger with the name my_constraint_trigger. +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +DELETE FROM my_table1 WHERE id=23; +SELECT * FROM my_table1; + id | name +----+------ + 38 | Bob +(1 row) + +SELECT * FROM my_table2; + id +---- + 38 +(1 row) + +-- Replace my_regular_trigger definition with new definition. +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +INSERT INTO my_table1 VALUES(323, 'Alex'); +SELECT * FROM my_table1; + id | name +-----+------ + 38 | Bob + 323 | Alex +(2 rows) + +SELECT * FROM my_table2; + id +----- + 38 + 323 +(2 rows) + +-- Replace my_constraint_trigger definition with new definition. +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1 +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +INSERT INTO my_table1 VALUES(23, 'Teddy'); +SELECT * FROM my_table1; + id | name +-----+------- + 38 | Bob + 323 | Alex + 23 | Teddy +(3 rows) + +SELECT * FROM my_table2; + id +----- + 38 + 323 + 23 + 23 +(4 rows) + +--Without OR REPALCE clause, Cannot create a new constraint trigger with same name of regular trigger already exisiting. +CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +ERROR: trigger "my_regular_trigger" for relation "my_table1" already exists +--With OR REPALCE clause, Cannot repalce a regular trigger with a constraint trigger. +CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +ERROR: Trigger "my_regular_trigger" for relation "my_table1" cannot be replaced with constraint trigger +--Without OR REPALCE clause, Cannot create a new constraint trigger with same name of constraint trigger already exisiting. +CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +ERROR: trigger "my_constraint_trigger" for relation "my_table1" already exists +--With OR REPALCE clause, Cannot replace a regular triiger with a constraint trigger. +CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +ERROR: Constraint trigger "my_constraint_trigger" for relation "my_table1" cannot be replaced with non-constraint trigger +--Without OR REPALCE clause, cannot replace my_regular_trigger definition with new definition. +CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); +ERROR: trigger "my_regular_trigger" for relation "my_table1" already exists +--Without OR REPALCE clause, cannot replace my_constraint_trigger definition with new definition. +CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +ERROR: trigger "my_constraint_trigger" for relation "my_table1" already exists +-- Delete the dependency when replaces are executed. +CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_updateproc1(); +CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); +ALTER TABLE my_table1 DROP COLUMN name; +DROP FUNCTION my_updateproc1(); +DROP TABLE my_table1; +DROP TABLE my_table2; +DROP FUNCTION my_deleteproc1(); +DROP FUNCTION my_insertproc1(); +DROP FUNCTION my_proc1(); +-- -- Verify behavior of before and after triggers with INSERT...ON CONFLICT -- DO UPDATE -- diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index b6de1b3..7c5e228 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1182,6 +1182,121 @@ select * from self_ref_trigger; drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); +-- +-- test triggers with OR REPLACE clause +-- + +CREATE TABLE my_table1 (id integer, name text); +CREATE TABLE my_table2 (id integer); +CREATE FUNCTION my_proc1() RETURNS trigger AS $$ +begin + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_updateproc1() RETURNS trigger AS $$ +begin + UPDATE my_table2 SET id=NEW.id WHERE id=OLD.id; + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_deleteproc1() RETURNS trigger AS $$ +begin + DELETE FROM my_table2 WHERE id=OLD.id; + RETURN NULL; +end;$$ LANGUAGE plpgsql; +CREATE FUNCTION my_insertproc1() RETURNS trigger AS $$ +begin + INSERT INTO my_table2 VALUES(NEW.id); + RETURN NULL; +end;$$ LANGUAGE plpgsql; +INSERT INTO my_table1 VALUES(323, 'Alex'); +INSERT INTO my_table1 VALUES(23, 'Teddy'); +INSERT INTO my_table1 VALUES(38, 'Bob'); +INSERT INTO my_table2 VALUES(323); +INSERT INTO my_table2 VALUES(23); +INSERT INTO my_table2 VALUES(38); + +--With OR REPALCE clause, create a new trigger with the name my_regular_trigger. +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); + +DELETE FROM my_table1 WHERE id=323; +SELECT * FROM my_table1; +SELECT * FROM my_table2; + +--With OR REPALCE clause, create a new constraint trigger with the name my_constraint_trigger. +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); + +DELETE FROM my_table1 WHERE id=23; +SELECT * FROM my_table1; +SELECT * FROM my_table2; + +-- Replace my_regular_trigger definition with new definition. +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); + +INSERT INTO my_table1 VALUES(323, 'Alex'); +SELECT * FROM my_table1; +SELECT * FROM my_table2; + +-- Replace my_constraint_trigger definition with new definition. +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1 +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); + +INSERT INTO my_table1 VALUES(23, 'Teddy'); +SELECT * FROM my_table1; +SELECT * FROM my_table2; + +--Without OR REPALCE clause, Cannot create a new constraint trigger with same name of regular trigger already exisiting. +CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +--With OR REPALCE clause, Cannot repalce a regular trigger with a constraint trigger. +CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +--Without OR REPALCE clause, Cannot create a new constraint trigger with same name of constraint trigger already exisiting. +CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +--With OR REPALCE clause, Cannot replace a regular triiger with a constraint trigger. +CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +--Without OR REPALCE clause, cannot replace my_regular_trigger definition with new definition. +CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); + +--Without OR REPALCE clause, cannot replace my_constraint_trigger definition with new definition. +CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +-- Delete the dependency when replaces are executed. +CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_updateproc1(); + +CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); + +ALTER TABLE my_table1 DROP COLUMN name; +DROP FUNCTION my_updateproc1(); + +DROP TABLE my_table1; +DROP TABLE my_table2; +DROP FUNCTION my_deleteproc1(); +DROP FUNCTION my_insertproc1(); +DROP FUNCTION my_proc1(); + -- -- Verify behavior of before and after triggers with INSERT...ON CONFLICT