diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 3339a4b..65aeea3 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -26,7 +26,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 ] ] diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 3b8c8b1..45bd58c 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2400,7 +2400,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, is_local, /* conislocal */ inhcount, /* coninhcount */ is_no_inherit, /* connoinherit */ - is_internal); /* internally constructed? */ + is_internal, /* internally constructed? */ + InvalidOid); pfree(ccbin); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index bb60b23..ad5ec0e 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1770,7 +1770,8 @@ index_constraint_create(Relation heapRelation, islocal, inhcount, noinherit, - is_internal); + is_internal, + InvalidOid); /* * Register the index as internally dependent on the constraint. diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index b614559..067e129 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -76,10 +76,11 @@ CreateConstraintEntry(const char *constraintName, bool conIsLocal, int conInhCount, bool conNoInherit, - bool is_internal) + bool is_internal, + Oid existing_constraint_oid) { Relation conDesc; - Oid conOid; + Oid conOid = InvalidOid; HeapTuple tup; bool nulls[Natts_pg_constraint]; Datum values[Natts_pg_constraint]; @@ -92,7 +93,11 @@ 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); @@ -164,9 +169,11 @@ 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); @@ -220,9 +227,44 @@ CreateConstraintEntry(const char *constraintName, else nulls[Anum_pg_constraint_conbin - 1] = true; - tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); - - CatalogTupleInsert(conDesc, tup); + 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); + + /* 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 3aee2d8..9bfdfab 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8083,7 +8083,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, conislocal, /* islocal */ coninhcount, /* inhcount */ connoinherit, /* conNoInherit */ - false); /* is_internal */ + false, /* is_internal */ + InvalidOid); ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -8353,7 +8354,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, false, 1, false, - false); + false, + InvalidOid); /* * Give this constraint partition-type dependencies on the parent @@ -8750,7 +8752,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) false, /* islocal */ 1, /* inhcount */ false, /* conNoInherit */ - true); + true, + InvalidOid); /* Set up partition dependencies for the new constraint */ ObjectAddressSet(address, ConstraintRelationId, constrOid); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 316692b..5025156 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" @@ -181,7 +182,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, HeapTuple tuple; Oid fargtypes[1]; /* dummy */ Oid funcrettype; - Oid trigoid; + Oid trigoid = InvalidOid; char internaltrigname[NAMEDATALEN]; char *trigname; Oid constrrelid = InvalidOid; @@ -191,6 +192,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, 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); else @@ -687,6 +695,59 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, } /* + * Generate the trigger's OID now, so that we can use it in the name if + * needed. + */ + tgrel = heap_open(TriggerRelationId, RowExclusiveLock); + + 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) + { + // trigoid = HeapTupleGetOid(tuple); // raw code + trigoid = pg_trigger->oid; + existing_constraint_oid = pg_trigger->tgconstraint; + trigger_exists = true; + break; + } + } + systable_endscan(tgscan); + + if (!trigger_exists) + trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId, + Anum_pg_trigger_oid); + + /* Replacement between normal triggers and constraint triggers are restricted */ + if (stmt->replace && trigger_exists) + { + if (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)))); + else if (!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)))); + } + else if (trigger_exists && !isInternal) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("trigger \"%s\" for relation \"%s\" already exists", + stmt->trigname, RelationGetRelationName(rel)))); + } + + /* * Find and validate the trigger function. */ if (!OidIsValid(funcoid)) @@ -776,19 +837,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, true, /* islocal */ 0, /* inhcount */ true, /* isnoinherit */ - isInternal); /* is_internal */ + isInternal, /* is_internal */ + existing_constraint_oid); } /* - * 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.) @@ -806,37 +859,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, @@ -963,12 +985,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); - heap_freetuple(tuple); + /* + * 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); + } + table_close(tgrel, RowExclusiveLock); pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1])); @@ -1011,6 +1072,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 e9c8873..f4b414a 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -3174,7 +3174,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, true, /* is local */ 0, /* inhcount */ false, /* connoinherit */ - false); /* is_internal */ + false, /* is_internal */ + InvalidOid); if (constrAddr) ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 208b4a1..b9e9543 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -5322,48 +5322,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 c1e60c7..f0f00b5 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -211,7 +211,9 @@ extern Oid CreateConstraintEntry(const char *constraintName, bool conIsLocal, int conInhCount, bool conNoInherit, - bool is_internal); + bool is_internal, + Oid existing_constraint_oid); + extern void RemoveConstraintById(Oid conId); extern void RenameConstraintById(Oid conId, const char *newname); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 94ded3c..e2da9f6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2418,6 +2418,7 @@ typedef struct CreateTrigStmt bool isconstraint; /* This is a constraint trigger */ /* explicitly named transition data */ List *transitionRels; /* TriggerTransition nodes, or NIL if none */ + bool replace; /* T => replace if already exists */ /* The remaining fields are only used for constraint triggers */ bool deferrable; /* [NOT] DEFERRABLE */ bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index cd2b550..17125be 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -1715,6 +1715,396 @@ select * from self_ref_trigger; drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); +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); +-- Create regular trigger +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +--Expected Result: New trigger with the name my_regular_trigger created. +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) + +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +--Expected Result: New constraint trigger with the name my_constraint_trigger created. +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) + +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT. +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) + +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1 +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT. +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) + +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 +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown. +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 +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown. +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 +--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown. +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 +--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown. +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 +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown. +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 +--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown. +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(); +\d my_table1; + Table "public.my_table1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | +Triggers: + my_constraint_trigger AFTER INSERT ON my_table1 DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION my_insertproc1() + my_regular_trigger AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE FUNCTION my_insertproc1() + my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1() + +CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); +CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +\h CREATE TRIGGER; +Command: CREATE TRIGGER +Description: define a new trigger +Syntax: +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 ] ] + [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ] + [ FOR [ EACH ] { ROW | STATEMENT } ] + [ WHEN ( condition ) ] + EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments ) + +where event can be one of: + + INSERT + UPDATE [ OF column_name [, ... ] ] + DELETE + TRUNCATE + +URL: https://www.postgresql.org/docs/devel/sql-createtrigger.html + +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2'; + pg_get_triggerdef +----------------------------------------------------------------------------------------------------- + CREATE TRIGGER my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1() +(1 row) + +----Clean up begin -------- +drop table my_table1; +drop table my_table2; +drop function my_deleteproc1(); +drop function my_insertproc1(); +drop function my_proc1(); +------Clean up end --------- +------------------------------TESTCASE1-6 BEFORE TRIGGER--------------------------------------------------------- +---Check REPLACE against various before triggers +CREATE TABLE my_table1 (id integer); +CREATE VIEW my_view1 AS SELECT id from my_table1; +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST1 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST2 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST3 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; + --testcase1 +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST1 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 7 | O | f | f | f | 0 | | \x | +(1 row) + + --testcase2 +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST2 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 7 | O | f | f | f | 0 | | \x | +(1 row) + + --testcase3 +DROP TRIGGER sometrig on my_table1; +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST3 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 7 | O | f | f | f | 0 | | \x | +(1 row) + +DROP TRIGGER sometrig on my_table1; +----Clean up begin -------- +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +drop view my_view1; +drop table my_table1; +------Clean up end --------- +------------------------------TESTCASE7-12 AFTER TRIGGER--------------------------------------------------------- +---Check REPLACE against various after triggers +CREATE TABLE my_table1 (id integer); +CREATE VIEW my_view1 AS SELECT id from my_table1; +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST7 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST8 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST9 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; + --testcase7 +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST7 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 5 | O | f | f | f | 0 | | \x | +(1 row) + + --testcase8 +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST8 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 5 | O | f | f | f | 0 | | \x | +(1 row) + + --testcase9 +DROP TRIGGER sometrig on my_table1; +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); +insert into my_table1 (id) values (1); +NOTICE: TEST9 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 5 | O | f | f | f | 0 | | \x | +(1 row) + +DROP TRIGGER sometrig on my_table1; +----Clean up begin -------- +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +drop view my_view1; +drop table my_table1; +------Clean up end --------- +------------------------------TESTCASE13-18 BEFORE TRIGGER--------------------------------------------------------- +---Check REPLACE against various INSTEAD triggers +CREATE TABLE my_table1 (id integer); +CREATE VIEW my_view1 AS SELECT id from my_table1; +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST13 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST14 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST15 Trigger'; + RETURN null; +end;$$ language plpgsql; +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; +--testcase13 +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); +insert into my_view1 (id) values (1); +NOTICE: TEST13 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 69 | O | f | f | f | 0 | | \x | +(1 row) + +--testcase14 +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); +insert into my_view1 (id) values (1); +NOTICE: TEST14 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 69 | O | f | f | f | 0 | | \x | +(1 row) + +--testcase15 +DROP TRIGGER sometrig on my_table1; +ERROR: trigger "sometrig" for table "my_table1" does not exist +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); +insert into my_view1 (id) values (1); +NOTICE: TEST15 Trigger +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + tgname | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual +----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+-------- + sometrig | 69 | O | f | f | f | 0 | | \x | +(1 row) + +----Clean up begin -------- +drop view my_view1; +drop table my_table1; +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +------Clean up end --------- -- -- Check that statement triggers work correctly even with all children excluded -- diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 8f833b7..c0fce2e 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1177,6 +1177,313 @@ drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); +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); +-- Create regular trigger +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +--Expected Result: New trigger with the name my_regular_trigger created. + +delete from my_table1 where id=323; +select * from my_table1; +select * from my_table2; + +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_deleteproc1(); +--Expected Result: New constraint trigger with the name my_constraint_trigger created. + +delete from my_table1 where id=23; +select * from my_table1; +select * from my_table2; + +CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT. + +insert into my_table1 values(323, 'Alex'); +select * from my_table1; +select * from my_table2; + + +CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1 +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE my_insertproc1(); +--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT. + +insert into my_table1 values(23, 'Teddy'); +select * from my_table1; +select * from my_table2; + +CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown. + +CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown. + +CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown. + +CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown. + +CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown. + +CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); +--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown. + +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(); +\d my_table1; + +CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1 +EXECUTE PROCEDURE my_proc1(); + +CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1 +FOR EACH ROW +EXECUTE PROCEDURE my_proc1(); + +\h CREATE TRIGGER; +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2'; +----Clean up begin -------- + +drop table my_table1; +drop table my_table2; +drop function my_deleteproc1(); +drop function my_insertproc1(); +drop function my_proc1(); +------Clean up end --------- + +------------------------------TESTCASE1-6 BEFORE TRIGGER--------------------------------------------------------- +---Check REPLACE against various before triggers + +CREATE TABLE my_table1 (id integer); + +CREATE VIEW my_view1 AS SELECT id from my_table1; + +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST1 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST2 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST3 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; + + --testcase1 +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + --testcase2 +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + --testcase3 +DROP TRIGGER sometrig on my_table1; +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + +DROP TRIGGER sometrig on my_table1; +----Clean up begin -------- +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +drop view my_view1; +drop table my_table1; +------Clean up end --------- +------------------------------TESTCASE7-12 AFTER TRIGGER--------------------------------------------------------- +---Check REPLACE against various after triggers + +CREATE TABLE my_table1 (id integer); + +CREATE VIEW my_view1 AS SELECT id from my_table1; + +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST7 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST8 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST9 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; + + --testcase7 +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + --testcase8 +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + --testcase9 +DROP TRIGGER sometrig on my_table1; +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); + +insert into my_table1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + +DROP TRIGGER sometrig on my_table1; +----Clean up begin -------- +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +drop view my_view1; +drop table my_table1; +------Clean up end --------- + + +------------------------------TESTCASE13-18 BEFORE TRIGGER--------------------------------------------------------- +---Check REPLACE against various INSTEAD triggers + +CREATE TABLE my_table1 (id integer); + +CREATE VIEW my_view1 AS SELECT id from my_table1; + +create function firstproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST13 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function secondproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST14 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function thirdproc() returns trigger as $$ +begin + RAISE NOTICE 'TEST15 Trigger'; + RETURN null; +end;$$ language plpgsql; + +create function someproc() returns trigger as $$ +begin + RAISE NOTICE 'Some Trigger'; + RETURN null; +end;$$ language plpgsql; + +--testcase13 +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc(); + +insert into my_view1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + +--testcase14 +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc(); + +insert into my_view1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + +--testcase15 +DROP TRIGGER sometrig on my_table1; +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc(); +CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc(); + +insert into my_view1 (id) values (1); +select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where tgname='sometrig'; + +----Clean up begin -------- +drop view my_view1; +drop table my_table1; +drop function firstproc(); +drop function secondproc(); +drop function thirdproc(); +drop function someproc(); +------Clean up end --------- -- -- Check that statement triggers work correctly even with all children excluded --