<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 ] ]
<title>Description</title>
<para>
- <command>CREATE TRIGGER</command> creates a new trigger. The
+ <command>CREATE TRIGGER</command> creates a new trigger.
+ <command>CREATE OR REPLACE TRIGGER</command> will either create a
+ new trigger, or replace an existing trigger. The
trigger will be associated with the specified table, view, or foreign table
and will execute the specified
function <replaceable class="parameter">function_name</replaceable> when
certain operations are performed on that table.
</para>
+ <para>
+ To replace the current definition of an existing trigger, use
+ <command>CREATE OR REPLACE TRIGGER</command>, specifying the existing
+ trigger's name and parent table. All other properties are replaced.
+ </para>
+
<para>
The trigger can be specified to fire before the
operation is attempted on a row (before constraints are checked and
<title>Notes</title>
<para>
- To create a trigger on a table, the user must have the
+ To create or replace a trigger on a table, the user must have the
<literal>TRIGGER</literal> privilege on the table. The user must
also have <literal>EXECUTE</literal> privilege on the trigger function.
</para>
Use <link linkend="sql-droptrigger"><command>DROP TRIGGER</command></link> to remove a trigger.
</para>
+ <para>
+ Creating a row-level trigger on a partitioned table will cause an
+ identical <quote>clone</quote> trigger to be created on each of its
+ existing partitions; and any partitions created or attached later will have
+ an identical trigger, too. If there is a conflictingly-named trigger on a
+ child partition already, an error occurs unless <command>CREATE OR REPLACE
+ TRIGGER</command> is used, in which case that trigger is replaced with a
+ clone trigger. When a partition is detached from its parent, its clone
+ triggers are removed.
+ </para>
+
<para>
A column-specific trigger (one defined using the <literal>UPDATE OF
<replaceable>column_name</replaceable></literal> syntax) will fire when any
value did not change.
</para>
- <para>
- There are a few built-in trigger functions that can be used to
- solve common problems without having to write your own trigger code;
- see <xref linkend="functions-trigger"/>.
- </para>
-
<para>
In a <literal>BEFORE</literal> trigger, the <literal>WHEN</literal> condition is
evaluated just before the function is or would be executed, so using
the ones that are fired.
</para>
- <para>
- Creating a row-level trigger on a partitioned table will cause identical
- triggers to be created in all its existing partitions; and any partitions
- created or attached later will contain an identical trigger, too.
- If the partition is detached from its parent, the trigger is removed.
- Triggers on partitioned tables may not be <literal>INSTEAD OF</literal>.
- </para>
-
<para>
Modifying a partitioned table or a table with inheritance children fires
statement-level triggers attached to the explicitly named table, but not
named by a <literal>REFERENCING</literal> clause, then before and after
images of rows are visible from all affected partitions or child tables.
In the case of inheritance children, the row images include only columns
- that are present in the table that the trigger is attached to. Currently,
- row-level triggers with transition relations cannot be defined on
- partitions or inheritance child tables.
+ that are present in the table that the trigger is attached to.
+ </para>
+
+ <para>
+ Currently, row-level triggers with transition relations cannot be defined
+ on partitions or inheritance child tables. Also, triggers on partitioned
+ tables may not be <literal>INSTEAD OF</literal>.
+ </para>
+
+ <para>
+ Currently, the <literal>OR REPLACE</literal> option is not supported for
+ constraint triggers.
+ </para>
+
+ <para>
+ Replacing an existing trigger within a transaction that has already
+ performed updating actions on the trigger's table is not recommended.
+ Trigger firing decisions, or portions of firing decisions, that have
+ already been made will not be reconsidered, so the effects could be
+ surprising.
+ </para>
+
+ <para>
+ There are a few built-in trigger functions that can be used to
+ solve common problems without having to write your own trigger code;
+ see <xref linkend="functions-trigger"/>.
</para>
</refsect1>
EXECUTE FUNCTION check_account_update();
</programlisting>
- The same, but only execute the function if column <literal>balance</literal>
- is specified as a target in the <command>UPDATE</command> command:
+ Modify that trigger definition to only execute the function if
+ column <literal>balance</literal> is specified as a target in
+ the <command>UPDATE</command> command:
<programlisting>
-CREATE TRIGGER check_update
+CREATE OR REPLACE TRIGGER check_update
BEFORE UPDATE OF balance ON accounts
FOR EACH ROW
EXECUTE FUNCTION check_account_update();
<command>CREATE CONSTRAINT TRIGGER</command> is a
<productname>PostgreSQL</productname> extension of the <acronym>SQL</acronym>
standard.
+ So is the <literal>OR REPLACE</literal> option.
</para>
</refsect1>
*/
if (deferrable)
{
- CreateTrigStmt *trigger;
+ CreateTrigStmt *trigger = makeNode(CreateTrigStmt);
- trigger = makeNode(CreateTrigStmt);
+ trigger->replace = false;
+ trigger->isconstraint = true;
trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
"PK_ConstraintTrigger" :
"Unique_ConstraintTrigger";
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->columns = NIL;
trigger->whenClause = NULL;
- trigger->isconstraint = true;
+ trigger->transitionRels = NIL;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
trigger->constrrel = NULL;
* and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_c";
fk_trigger->relation = NULL;
- fk_trigger->row = true;
- fk_trigger->timing = TRIGGER_TYPE_AFTER;
/* Either ON INSERT or ON UPDATE */
if (on_insert)
fk_trigger->events = TRIGGER_TYPE_UPDATE;
}
+ fk_trigger->args = NIL;
+ fk_trigger->row = true;
+ fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = NULL;
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid,
indexOid, InvalidOid, InvalidOid, NULL, true, false);
* DELETE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_a";
fk_trigger->relation = NULL;
+ fk_trigger->args = NIL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_del_action)
{
(int) fkconstraint->fk_del_action);
break;
}
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
constraintOid,
* UPDATE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
+ fk_trigger->replace = false;
+ fk_trigger->isconstraint = true;
fk_trigger->trigname = "RI_ConstraintTrigger_a";
fk_trigger->relation = NULL;
+ fk_trigger->args = NIL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
- fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
- fk_trigger->isconstraint = true;
+ fk_trigger->transitionRels = NIL;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_upd_action)
{
(int) fkconstraint->fk_upd_action);
break;
}
- fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
constraintOid,
}
trigStmt = makeNode(CreateTrigStmt);
+ trigStmt->replace = false;
+ trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
trigStmt->trigname = NameStr(trigForm->tgname);
trigStmt->relation = NULL;
trigStmt->funcname = NULL; /* passed separately */
trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
trigStmt->columns = cols;
trigStmt->whenClause = NULL; /* passed separately */
- trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
trigStmt->transitionRels = NIL; /* not supported at present */
trigStmt->deferrable = trigForm->tgdeferrable;
trigStmt->initdeferred = trigForm->tginitdeferred;
*
* When called on partitioned tables, this function recurses to create the
* trigger on all the partitions, except if isInternal is true, in which
- * case caller is expected to execute recursion on its own.
+ * case caller is expected to execute recursion on its own. in_partition
+ * indicates such a recursive call; outside callers should pass "false"
+ * (but see CloneRowTriggersToPartition).
*/
ObjectAddress
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Relation rel;
AclResult aclresult;
Relation tgrel;
- SysScanDesc tgscan;
- ScanKeyData key;
Relation pgrel;
- HeapTuple tuple;
+ HeapTuple tuple = NULL;
Oid funcrettype;
- Oid trigoid;
+ Oid trigoid = InvalidOid;
char internaltrigname[NAMEDATALEN];
char *trigname;
Oid constrrelid = InvalidOid;
char *oldtablename = NULL;
char *newtablename = NULL;
bool partition_recurse;
+ bool trigger_exists = false;
+ Oid existing_constraint_oid = InvalidOid;
+ bool existing_isInternal = false;
if (OidIsValid(relOid))
rel = table_open(relOid, ShareRowExclusiveLock);
errmsg("function %s must return type %s",
NameListToString(stmt->funcname), "trigger")));
+ /*
+ * Scan pg_trigger to see if there is already a trigger of the same name.
+ * Skip this for internally generated triggers, since we'll modify the
+ * name to be unique below.
+ *
+ * NOTE that this is cool only because we have ShareRowExclusiveLock on
+ * the relation, so the trigger set won't be changing underneath us.
+ */
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+ if (!isInternal)
+ {
+ ScanKeyData skeys[2];
+ SysScanDesc tgscan;
+
+ ScanKeyInit(&skeys[0],
+ Anum_pg_trigger_tgrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ ScanKeyInit(&skeys[1],
+ Anum_pg_trigger_tgname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->trigname));
+
+ tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+ NULL, 2, skeys);
+
+ /* There should be at most one matching tuple */
+ if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+ {
+ Form_pg_trigger oldtrigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+ trigoid = oldtrigger->oid;
+ existing_constraint_oid = oldtrigger->tgconstraint;
+ existing_isInternal = oldtrigger->tgisinternal;
+ trigger_exists = true;
+ /* copy the tuple to use in CatalogTupleUpdate() */
+ tuple = heap_copytuple(tuple);
+ }
+ systable_endscan(tgscan);
+ }
+
+ if (!trigger_exists)
+ {
+ /* Generate the OID for the new trigger. */
+ trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+ Anum_pg_trigger_oid);
+ }
+ else
+ {
+ /*
+ * If OR REPLACE was specified, we'll replace the old trigger;
+ * otherwise complain about the duplicate name.
+ */
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * An internal trigger cannot be replaced by a user-defined trigger.
+ * However, skip this test when in_partition, because then we're
+ * recursing from a partitioned table and the check was made at the
+ * parent level. Child triggers will always be marked "internal" (so
+ * this test does protect us from the user trying to replace a child
+ * trigger directly).
+ */
+ if (existing_isInternal && !isInternal && !in_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * It is not allowed to replace with a constraint trigger; gram.y
+ * should have enforced this already.
+ */
+ Assert(!stmt->isconstraint);
+
+ /*
+ * It is not allowed to replace an existing constraint trigger,
+ * either. (The reason for these restrictions is partly that it seems
+ * difficult to deal with pending trigger events in such cases, and
+ * partly that the command might imply changing the constraint's
+ * properties as well, which doesn't seem nice.)
+ */
+ if (OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ }
+
/*
* If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a
* corresponding pg_constraint entry.
isInternal); /* is_internal */
}
- /*
- * Generate the trigger's OID now, so that we can use it in the name if
- * needed.
- */
- tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
- trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
- Anum_pg_trigger_oid);
-
/*
* If trigger is internally generated, modify the provided trigger name to
* ensure uniqueness by appending the trigger OID. (Callers will usually
trigname = stmt->trigname;
}
- /*
- * Scan pg_trigger for existing triggers on relation. We do this only to
- * give a nice error message if there's already a trigger of the same
- * name. (The unique index on tgrelid/tgname would complain anyway.) We
- * can skip this for internally generated triggers, since the name
- * modification above should be sufficient.
- *
- * NOTE that this is cool only because we have ShareRowExclusiveLock on
- * the relation, so the trigger set won't be changing underneath us.
- */
- if (!isInternal)
- {
- ScanKeyInit(&key,
- Anum_pg_trigger_tgrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
- tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
- NULL, 1, &key);
- while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
- {
- Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
- if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("trigger \"%s\" for relation \"%s\" already exists",
- trigname, RelationGetRelationName(rel))));
- }
- systable_endscan(tgscan);
- }
-
/*
* Build the new pg_trigger tuple.
*
else
nulls[Anum_pg_trigger_tgnewtable - 1] = true;
- tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
/*
- * Insert tuple into pg_trigger.
+ * Insert or replace tuple in pg_trigger.
*/
- CatalogTupleInsert(tgrel, tuple);
+ if (!trigger_exists)
+ {
+ tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+ CatalogTupleInsert(tgrel, tuple);
+ }
+ else
+ {
+ HeapTuple newtup;
- heap_freetuple(tuple);
+ newtup = heap_form_tuple(tgrel->rd_att, values, nulls);
+ CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+ heap_freetuple(newtup);
+ }
+
+ heap_freetuple(tuple); /* free either original or new tuple */
table_close(tgrel, RowExclusiveLock);
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
heap_freetuple(tuple);
table_close(pgrel, RowExclusiveLock);
+ /*
+ * If we're replacing a trigger, flush all the old dependencies before
+ * recording new ones.
+ */
+ if (trigger_exists)
+ deleteDependencyRecordsFor(TriggerRelationId, trigoid, true);
+
/*
* Record dependencies for trigger. Always place a normal dependency on
* the function.
{
CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
+ COPY_SCALAR_FIELD(replace);
+ COPY_SCALAR_FIELD(isconstraint);
COPY_STRING_FIELD(trigname);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(funcname);
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);
static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
+ COMPARE_SCALAR_FIELD(replace);
+ COMPARE_SCALAR_FIELD(isconstraint);
COMPARE_STRING_FIELD(trigname);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(funcname);
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);
*****************************************************************************/
CreateTrigStmt:
- CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+ CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
qualified_name TriggerReferencing TriggerForSpec TriggerWhen
EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
{
CreateTrigStmt *n = makeNode(CreateTrigStmt);
- n->trigname = $3;
- n->relation = $7;
- n->funcname = $13;
- n->args = $15;
- n->row = $9;
- n->timing = $4;
- n->events = intVal(linitial($5));
- n->columns = (List *) lsecond($5);
- n->whenClause = $10;
- n->transitionRels = $8;
- n->isconstraint = false;
+ n->replace = $2;
+ n->isconstraint = false;
+ n->trigname = $4;
+ n->relation = $8;
+ n->funcname = $14;
+ n->args = $16;
+ n->row = $10;
+ n->timing = $5;
+ n->events = intVal(linitial($6));
+ n->columns = (List *) lsecond($6);
+ n->whenClause = $11;
+ n->transitionRels = $9;
n->deferrable = false;
n->initdeferred = false;
n->constrrel = NULL;
$$ = (Node *)n;
}
- | CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+ | CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
qualified_name OptConstrFromTable ConstraintAttributeSpec
FOR EACH ROW TriggerWhen
EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
{
CreateTrigStmt *n = makeNode(CreateTrigStmt);
- n->trigname = $4;
- n->relation = $8;
- n->funcname = $17;
- n->args = $19;
+ n->replace = $2;
+ if (n->replace) /* not supported, see CreateTrigger */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CREATE OR REPLACE CONSTRAINT TRIGGER is not supported")));
+ n->isconstraint = true;
+ n->trigname = $5;
+ n->relation = $9;
+ n->funcname = $18;
+ n->args = $20;
n->row = true;
n->timing = TRIGGER_TYPE_AFTER;
- n->events = intVal(linitial($6));
- n->columns = (List *) lsecond($6);
- n->whenClause = $14;
+ n->events = intVal(linitial($7));
+ n->columns = (List *) lsecond($7);
+ n->whenClause = $15;
n->transitionRels = NIL;
- n->isconstraint = true;
- processCASbits($10, @10, "TRIGGER",
+ processCASbits($11, @11, "TRIGGER",
&n->deferrable, &n->initdeferred, NULL,
NULL, yyscanner);
- n->constrrel = $9;
+ n->constrrel = $10;
$$ = (Node *)n;
}
;
typedef struct CreateTrigStmt
{
NodeTag type;
+ bool replace; /* 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 */
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 */
drop function dump_insert();
drop function dump_update();
drop function dump_delete();
+--
+-- Tests for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+ raise notice 'hello from funcA';
+ return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+ raise notice 'hello from funcB';
+ return null;
+end; $$ language plpgsql;
+create trigger my_trig
+ after insert on my_table
+ for each row execute procedure funcA();
+create trigger my_trig
+ before insert on my_table
+ for each row execute procedure funcB(); -- should fail
+ERROR: trigger "my_trig" for relation "my_table" already exists
+insert into my_table values (1);
+NOTICE: hello from funcA
+create or replace trigger my_trig
+ before insert on my_table
+ for each row execute procedure funcB(); -- OK
+insert into my_table values (2); -- this insert should become a no-op
+NOTICE: hello from funcB
+table my_table;
+ id
+----
+ 1
+(1 row)
+
+drop table my_table;
+-- test CREATE OR REPLACE TRIGGER on partition table
+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;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcA
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcB
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcA
+create or replace trigger my_trig
+ after insert on parted_trig_1
+ for each row execute procedure funcB(); -- should fail
+ERROR: trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcA
+drop trigger my_trig on parted_trig;
+insert into parted_trig (a) values (50);
+-- test that user trigger can be overwritten by one defined at upper level
+create trigger my_trig
+ after insert on parted_trig_1
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcA
+create trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB(); -- should fail
+ERROR: trigger "my_trig" for relation "parted_trig_1" already exists
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcA
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE: hello from funcB
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
-- Leave around some objects for other tests
create table trigger_parted (a int primary key) partition by list (a);
create function trigger_parted_trigfunc() returns trigger language plpgsql as
drop function dump_update();
drop function dump_delete();
+--
+-- Tests for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+
+create function funcA() returns trigger as $$
+begin
+ raise notice 'hello from funcA';
+ return null;
+end; $$ language plpgsql;
+
+create function funcB() returns trigger as $$
+begin
+ raise notice 'hello from funcB';
+ return null;
+end; $$ language plpgsql;
+
+create trigger my_trig
+ after insert on my_table
+ for each row execute procedure funcA();
+
+create trigger my_trig
+ before insert on my_table
+ for each row execute procedure funcB(); -- should fail
+
+insert into my_table values (1);
+
+create or replace trigger my_trig
+ before insert on my_table
+ for each row execute procedure funcB(); -- OK
+
+insert into my_table values (2); -- this insert should become a no-op
+
+table my_table;
+
+drop table my_table;
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+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;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+ after insert on parted_trig_1
+ for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+insert into parted_trig (a) values (50);
+
+-- test that user trigger can be overwritten by one defined at upper level
+create trigger my_trig
+ after insert on parted_trig_1
+ for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+ after insert on parted_trig
+ for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+
-- Leave around some objects for other tests
create table trigger_parted (a int primary key) partition by list (a);
create function trigger_parted_trigfunc() returns trigger language plpgsql as