Commit 7fc0f062 authored by Tom Lane's avatar Tom Lane

Add a WHEN clause to CREATE TRIGGER, allowing a boolean expression to be

checked to determine whether the trigger should be fired.

For BEFORE triggers this is mostly a matter of spec compliance; but for AFTER
triggers it can provide a noticeable performance improvement, since queuing of
a deferred trigger event and re-fetching of the row(s) at end of statement can
be short-circuited if the trigger does not need to be fired.

Takahiro Itagaki, reviewed by KaiGai Kohei.
parent 201a45c4
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.210 2009/10/14 22:14:21 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.211 2009/11/20 20:38:09 tgl Exp $ -->
<!--
Documentation of the system catalogs, directed toward PostgreSQL developers
-->
......@@ -4756,6 +4756,15 @@
<entry></entry>
<entry>Argument strings to pass to trigger, each NULL-terminated</entry>
</row>
<row>
<entry><structfield>tgqual</structfield></entry>
<entry><type>text</type></entry>
<entry></entry>
<entry>Expression tree (in <function>nodeToString()</function>
representation) for the trigger's <literal>WHEN</> condition, or NULL
if none</entry>
</row>
</tbody>
</tgroup>
</table>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_constraint.sgml,v 1.20 2009/09/19 10:23:26 petere Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_constraint.sgml,v 1.21 2009/11/20 20:38:09 tgl Exp $
PostgreSQL documentation
-->
......@@ -27,6 +27,7 @@ CREATE CONSTRAINT TRIGGER <replaceable class="parameter">name</replaceable>
[ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
{ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } }
FOR EACH ROW
[ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
EXECUTE PROCEDURE <replaceable class="parameter">function_name</replaceable> ( <replaceable class="parameter">arguments</replaceable> )
</synopsis>
</refsynopsisdiv>
......@@ -109,6 +110,22 @@ CREATE CONSTRAINT TRIGGER <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">condition</replaceable></term>
<listitem>
<para>
A Boolean expression that determines whether the trigger function
will actually be executed. This acts the same as in <xref
linkend="SQL-CREATETRIGGER" endterm="SQL-CREATETRIGGER-TITLE">.
Note in particular that evaluation of the <literal>WHEN</>
condition is not deferred, but occurs immediately after the row
update operation is performed. If the condition does not evaluate
to <literal>true</> then the trigger is not queued for deferred
execution.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">function_name</replaceable></term>
<listitem>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.51 2009/10/14 22:14:21 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.52 2009/11/20 20:38:09 tgl Exp $
PostgreSQL documentation
-->
......@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
ON <replaceable class="PARAMETER">table</replaceable> [ FOR [ EACH ] { ROW | STATEMENT } ]
[ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
</synopsis>
</refsynopsisdiv>
......@@ -72,6 +73,16 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
<literal>FOR EACH STATEMENT</literal>.
</para>
<para>
Also, a trigger definition can specify a boolean <literal>WHEN</>
condition, which will be tested to see whether the trigger should
be fired. In row-level triggers the <literal>WHEN</> condition can
examine the old and/or new values of columns of the row. Statement-level
triggers can also have <literal>WHEN</> conditions, although the feature
is not so useful for them since the condition cannot refer to any values
in the table.
</para>
<para>
If multiple triggers of the same kind are defined for the same event,
they will be fired in alphabetical order by name.
......@@ -159,6 +170,31 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">condition</replaceable></term>
<listitem>
<para>
A Boolean expression that determines whether the trigger function
will actually be executed. If <literal>WHEN</> is specified, the
function will only be called if the <replaceable
class="parameter">condition</replaceable> returns <literal>true</>.
In <literal>FOR EACH ROW</literal> triggers, the <literal>WHEN</>
condition can refer to columns of the old and/or new row values
by writing <literal>OLD.<replaceable
class="parameter">column_name</replaceable></literal> or
<literal>NEW.<replaceable
class="parameter">column_name</replaceable></literal> respectively.
Of course, <literal>INSERT</> triggers cannot refer to <literal>OLD</>
and <literal>DELETE</> triggers cannot refer to <literal>NEW</>.
</para>
<para>
Currently, <literal>WHEN</literal> expressions cannot contain
subqueries.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">function_name</replaceable></term>
<listitem>
......@@ -213,6 +249,29 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
value did not change.
</para>
<para>
In a <literal>BEFORE</> trigger, the <literal>WHEN</> condition is
evaluated just before the function is or would be executed, so using
<literal>WHEN</> is not materially different from testing the same
condition at the beginning of the trigger function. Note in particular
that the <literal>NEW</> row seen by the condition is the current value,
as possibly modified by earlier triggers. Also, a <literal>BEFORE</>
trigger's <literal>WHEN</> condition is not allowed to examine the
system columns of the <literal>NEW</> row (such as <literal>oid</>),
because those won't have been set yet.
</para>
<para>
In an <literal>AFTER</> trigger, the <literal>WHEN</> condition is
evaluated just after the row update occurs, and it determines whether an
event is queued to fire the trigger at the end of statement. So when an
<literal>AFTER</> trigger's <literal>WHEN</> condition does not return
true, it is not necessary to queue an event nor to re-fetch the row at end
of statement. This can result in significant speedups in statements that
modify many rows, if the trigger only needs to be fired for a few of the
rows.
</para>
<para>
In <productname>PostgreSQL</productname> versions before 7.3, it was
necessary to declare trigger functions as returning the placeholder
......@@ -223,11 +282,56 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
</para>
</refsect1>
<refsect1 id="R1-SQL-CREATETRIGGER-2">
<refsect1 id="SQL-CREATETRIGGER-examples">
<title>Examples</title>
<para>
<xref linkend="trigger-example"> contains a complete example.
Execute the function <function>check_account_update</> whenever
a row of the table <literal>accounts</> is about to be updated:
<programlisting>
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
EXECUTE PROCEDURE check_account_update();
</programlisting>
The same, but only execute the function if column <literal>balance</>
is specified as a target in the <command>UPDATE</> command:
<programlisting>
CREATE TRIGGER check_update
BEFORE UPDATE OF balance ON accounts
FOR EACH ROW
EXECUTE PROCEDURE check_account_update();
</programlisting>
This form only executes the function if column <literal>balance</>
has in fact changed value:
<programlisting>
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE PROCEDURE check_account_update();
</programlisting>
Call a function to log updates of <literal>accounts</>, but only if
something changed:
<programlisting>
CREATE TRIGGER log_update
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE log_account_update();
</programlisting>
</para>
<para>
<xref linkend="trigger-example"> contains a complete example of a trigger
function written in C.
</para>
</refsect1>
......@@ -258,7 +362,7 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
<productname>PostgreSQL</productname> only allows the execution
of a user-defined function for the triggered action. The standard
allows the execution of a number of other SQL commands, such as
<command>CREATE TABLE</command> as the triggered action. This
<command>CREATE TABLE</command>, as the triggered action. This
limitation is not hard to work around by creating a user-defined
function that executes the desired commands.
</para>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.59 2009/10/14 22:14:21 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.60 2009/11/20 20:38:09 tgl Exp $ -->
<chapter id="triggers">
<title>Triggers</title>
......@@ -140,6 +140,25 @@
triggers are not fired.
</para>
<para>
A trigger definition can also specify a boolean <literal>WHEN</>
condition, which will be tested to see whether the trigger should
be fired. In row-level triggers the <literal>WHEN</> condition can
examine the old and/or new values of columns of the row. (Statement-level
triggers can also have <literal>WHEN</> conditions, although the feature
is not so useful for them.) In a before trigger, the <literal>WHEN</>
condition is evaluated just before the function is or would be executed,
so using <literal>WHEN</> is not materially different from testing the
same condition at the beginning of the trigger function. However, in
an after trigger, the <literal>WHEN</> condition is evaluated just after
the row update occurs, and it determines whether an event is queued to
fire the trigger at the end of statement. So when an after trigger's
<literal>WHEN</> condition does not return true, it is not necessary
to queue an event nor to re-fetch the row at end of statement. This
can result in significant speedups in statements that modify many
rows, if the trigger only needs to be fired for a few of the rows.
</para>
<para>
Typically, row before triggers are used for checking or
modifying the data that will be inserted or updated. For example,
......@@ -497,6 +516,7 @@ typedef struct Trigger
int16 tgnattr;
int16 *tgattr;
char **tgargs;
char *tgqual;
} Trigger;
</programlisting>
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.323 2009/10/14 22:14:21 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.324 2009/11/20 20:38:09 tgl Exp $
*
*
* INTERFACE ROUTINES
......@@ -793,12 +793,13 @@ index_create(Oid heapRelationId,
trigger->row = true;
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->columns = NIL;
trigger->whenClause = NULL;
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, conOid, indexRelationId,
(void) CreateTrigger(trigger, NULL, conOid, indexRelationId,
isprimary ? "PK_ConstraintTrigger" :
"Unique_ConstraintTrigger",
false);
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.317 2009/09/21 20:10:21 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.318 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1799,8 +1799,12 @@ CopyFrom(CopyState cstate)
resultRelInfo->ri_RelationDesc = cstate->rel;
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(cstate->rel->trigdesc);
if (resultRelInfo->ri_TrigDesc)
{
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
resultRelInfo->ri_TrigWhenExprs = (List **)
palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(List *));
}
resultRelInfo->ri_TrigInstrument = NULL;
ExecOpenIndices(resultRelInfo);
......@@ -1810,7 +1814,8 @@ CopyFrom(CopyState cstate)
estate->es_result_relation_info = resultRelInfo;
/* Set up a tuple slot too */
slot = MakeSingleTupleTableSlot(tupDesc);
slot = ExecInitExtraTupleSlot(estate);
ExecSetSlotDescriptor(slot, tupDesc);
econtext = GetPerTupleExprContext(estate);
......@@ -2198,7 +2203,7 @@ CopyFrom(CopyState cstate)
pfree(defmap);
pfree(defexprs);
ExecDropSingleTupleTableSlot(slot);
ExecResetTupleTable(estate->es_tupleTable, false);
ExecCloseIndices(resultRelInfo);
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.305 2009/11/04 12:24:23 heikki Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.306 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -5403,7 +5403,7 @@ validateForeignKeyConstraint(Constraint *fkconstraint,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
/* we needn't fill in tgargs */
/* we needn't fill in tgargs or tgqual */
/*
* See if we can do it with a single LEFT JOIN query. A FALSE result
......@@ -5476,13 +5476,14 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint,
}
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = fkconstraint->pktable;
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
......@@ -5527,6 +5528,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
fk_trigger->row = true;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->constrrel = myRel;
switch (fkconstraint->fk_del_action)
......@@ -5563,7 +5565,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
}
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
......@@ -5580,6 +5582,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
fk_trigger->row = true;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->constrrel = myRel;
switch (fkconstraint->fk_upd_action)
......@@ -5616,7 +5619,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
}
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
}
......
This diff is collapsed.
......@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.334 2009/10/26 02:26:29 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.335 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -752,6 +752,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
*/
estate->es_tupleTable = NIL;
estate->es_trig_tuple_slot = NULL;
estate->es_trig_oldtup_slot = NULL;
/* mark EvalPlanQual not active */
estate->es_epqTuple = NULL;
......@@ -911,6 +912,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(n * sizeof(FmgrInfo));
resultRelInfo->ri_TrigWhenExprs = (List **)
palloc0(n * sizeof(List *));
if (doInstrument)
resultRelInfo->ri_TrigInstrument = InstrAlloc(n);
else
......@@ -919,6 +922,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
else
{
resultRelInfo->ri_TrigFunctions = NULL;
resultRelInfo->ri_TrigWhenExprs = NULL;
resultRelInfo->ri_TrigInstrument = NULL;
}
resultRelInfo->ri_ConstraintExprs = NULL;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.254 2009/11/04 22:26:05 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.255 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -491,26 +491,15 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
if (isDone)
*isDone = ExprSingleResult;
/*
* Get the input slot and attribute number we want
*
* The asserts check that references to system attributes only appear at
* the level of a relation scan; at higher levels, system attributes must
* be treated as ordinary variables (since we no longer have access to the
* original tuple).
*/
attnum = variable->varattno;
/* Get the input slot and attribute number we want */
switch (variable->varno)
{
case INNER: /* get the tuple from the inner node */
slot = econtext->ecxt_innertuple;
Assert(attnum > 0);
break;
case OUTER: /* get the tuple from the outer node */
slot = econtext->ecxt_outertuple;
Assert(attnum > 0);
break;
default: /* get the tuple from the relation being
......@@ -519,6 +508,8 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
break;
}
attnum = variable->varattno;
if (attnum != InvalidAttrNumber)
{
/*
......@@ -715,7 +706,7 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot = econtext->ecxt_scantuple;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc tupleDesc;
HeapTupleHeader dtuple;
......@@ -724,6 +715,23 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
*isDone = ExprSingleResult;
*isNull = false;
/* Get the input slot we want */
switch (variable->varno)
{
case INNER: /* get the tuple from the inner node */
slot = econtext->ecxt_innertuple;
break;
case OUTER: /* get the tuple from the outer node */
slot = econtext->ecxt_outertuple;
break;
default: /* get the tuple from the relation being
* scanned */
slot = econtext->ecxt_scantuple;
break;
}
tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor;
......@@ -766,7 +774,7 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot = econtext->ecxt_scantuple;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc var_tupdesc;
HeapTupleHeader dtuple;
......@@ -775,6 +783,23 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
*isDone = ExprSingleResult;
*isNull = false;
/* Get the input slot we want */
switch (variable->varno)
{
case INNER: /* get the tuple from the inner node */
slot = econtext->ecxt_innertuple;
break;
case OUTER: /* get the tuple from the outer node */
slot = econtext->ecxt_outertuple;
break;
default: /* get the tuple from the relation being
* scanned */
slot = econtext->ecxt_scantuple;
break;
}
/*
* Currently, the only case handled here is stripping of trailing resjunk
* fields, which we do in a slightly chintzy way by just adjusting the
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.165 2009/10/26 02:26:29 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.166 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -117,6 +117,7 @@ CreateExecutorState(void)
estate->es_trig_target_relations = NIL;
estate->es_trig_tuple_slot = NULL;
estate->es_trig_oldtup_slot = NULL;
estate->es_param_list_info = NULL;
estate->es_param_exec_vals = NULL;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeModifyTable.c,v 1.2 2009/10/26 02:26:31 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeModifyTable.c,v 1.3 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -215,9 +215,10 @@ ExecInsert(TupleTableSlot *slot,
* slot should not try to clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(resultRelationDesc);
if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
tuple = newtuple;
......@@ -467,9 +468,10 @@ ExecUpdate(ItemPointer tupleid,
* slot should not try to clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(resultRelationDesc);
if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
tuple = newtuple;
......
......@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.451 2009/11/16 21:32:06 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.452 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -3180,6 +3180,7 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
COPY_SCALAR_FIELD(row);
COPY_SCALAR_FIELD(events);
COPY_NODE_FIELD(columns);
COPY_NODE_FIELD(whenClause);
COPY_SCALAR_FIELD(isconstraint);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
......
......@@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.373 2009/11/16 21:32:06 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.374 2009/11/20 20:38:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1670,6 +1670,7 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
COMPARE_SCALAR_FIELD(row);
COMPARE_SCALAR_FIELD(events);
COMPARE_NODE_FIELD(columns);
COMPARE_NODE_FIELD(whenClause);
COMPARE_SCALAR_FIELD(isconstraint);
COMPARE_SCALAR_FIELD(deferrable);
COMPARE_SCALAR_FIELD(initdeferred);
......
......@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.693 2009/11/16 21:32:06 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.694 2009/11/20 20:38:10 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -249,6 +249,7 @@ static TypeName *TableFuncTypeName(List *columns);
%type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <node> TriggerWhen
%type <str> copy_file_name
database_name access_method_clause access_method attr_name
......@@ -3227,18 +3228,19 @@ AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generi
CreateTrigStmt:
CREATE TRIGGER name TriggerActionTime TriggerEvents ON
qualified_name TriggerForSpec EXECUTE PROCEDURE
func_name '(' TriggerFuncArgs ')'
qualified_name TriggerForSpec TriggerWhen
EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
{
CreateTrigStmt *n = makeNode(CreateTrigStmt);
n->trigname = $3;
n->relation = $7;
n->funcname = $11;
n->args = $13;
n->funcname = $12;
n->args = $14;
n->before = $4;
n->row = $8;
n->events = intVal(linitial($5));
n->columns = (List *) lsecond($5);
n->whenClause = $9;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
......@@ -3246,20 +3248,20 @@ CreateTrigStmt:
$$ = (Node *)n;
}
| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
qualified_name OptConstrFromTable
ConstraintAttributeSpec
FOR EACH ROW EXECUTE PROCEDURE
func_name '(' TriggerFuncArgs ')'
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 = $16;
n->args = $18;
n->funcname = $17;
n->args = $19;
n->before = FALSE;
n->row = TRUE;
n->events = intVal(linitial($6));
n->columns = (List *) lsecond($6);
n->whenClause = $14;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
......@@ -3335,6 +3337,11 @@ TriggerForType:
| STATEMENT { $$ = FALSE; }
;
TriggerWhen:
WHEN '(' a_expr ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NULL; }
;
TriggerFuncArgs:
TriggerFuncArg { $$ = list_make1($1); }
| TriggerFuncArgs ',' TriggerFuncArg { $$ = lappend($1, $3); }
......
......@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.317 2009/11/16 21:32:07 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.318 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -939,7 +939,7 @@ ProcessUtility(Node *parsetree,
break;
case T_CreateTrigStmt:
CreateTrigger((CreateTrigStmt *) parsetree,
CreateTrigger((CreateTrigStmt *) parsetree, queryString,
InvalidOid, InvalidOid, NULL, true);
break;
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.314 2009/11/05 23:24:25 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.315 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -487,6 +487,8 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
SysScanDesc tgscan;
int findx = 0;
char *tgname;
Datum value;
bool isnull;
/*
* Fetch the pg_trigger tuple by the Oid of the trigger
......@@ -543,6 +545,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
findx++;
/* tgattr is first var-width field, so OK to access directly */
if (trigrec->tgattr.dim1 > 0)
{
......@@ -567,6 +570,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, " OR TRUNCATE");
else
appendStringInfo(&buf, " TRUNCATE");
findx++;
}
appendStringInfo(&buf, " ON %s",
generate_relation_name(trigrec->tgrelid, NIL));
......@@ -574,7 +578,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
if (trigrec->tgisconstraint)
{
if (trigrec->tgconstrrelid != InvalidOid)
if (OidIsValid(trigrec->tgconstrrelid))
{
appendStringInfo(&buf, "FROM %s",
generate_relation_name(trigrec->tgconstrrelid, NIL));
......@@ -596,23 +600,70 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, "FOR EACH STATEMENT");
appendStringInfoString(&buf, pretty ? "\n " : " ");
/* If the trigger has a WHEN qualification, add that */
value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
tgrel->rd_att, &isnull);
if (!isnull)
{
Node *qual;
deparse_context context;
deparse_namespace dpns;
RangeTblEntry *oldrte;
RangeTblEntry *newrte;
appendStringInfoString(&buf, "WHEN (");
qual = stringToNode(TextDatumGetCString(value));
/* Build minimal OLD and NEW RTEs for the rel */
oldrte = makeNode(RangeTblEntry);
oldrte->rtekind = RTE_RELATION;
oldrte->relid = trigrec->tgrelid;
oldrte->eref = makeAlias("old", NIL);
oldrte->inh = false;
oldrte->inFromCl = true;
newrte = makeNode(RangeTblEntry);
newrte->rtekind = RTE_RELATION;
newrte->relid = trigrec->tgrelid;
newrte->eref = makeAlias("new", NIL);
newrte->inh = false;
newrte->inFromCl = true;
/* Build two-element rtable */
dpns.rtable = list_make2(oldrte, newrte);
dpns.ctes = NIL;
dpns.subplans = NIL;
dpns.outer_plan = dpns.inner_plan = NULL;
/* Set up context with one-deep namespace stack */
context.buf = &buf;
context.namespaces = list_make1(&dpns);
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = true;
context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
context.indentLevel = PRETTYINDENT_STD;
get_rule_expr(qual, &context, false);
appendStringInfo(&buf, ")%s", pretty ? "\n " : " ");
}
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0,
NIL, NULL, NULL));
if (trigrec->tgnargs > 0)
{
bytea *val;
bool isnull;
char *p;
int i;
val = DatumGetByteaP(fastgetattr(ht_trig,
Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull));
value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "tgargs is null for trigger %u", trigid);
p = (char *) VARDATA(val);
p = (char *) VARDATA(DatumGetByteaP(value));
for (i = 0; i < trigrec->tgnargs; i++)
{
if (i > 0)
......
......@@ -12,7 +12,7 @@
* by PostgreSQL
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.552 2009/10/14 22:14:23 tgl Exp $
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.553 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -4305,10 +4305,15 @@ getTriggers(TableInfo tblinfo[], int numTables)
resetPQExpBuffer(query);
if (g_fout->remoteVersion >= 80500)
{
/*
* NB: think not to use pretty=true in pg_get_triggerdef. It
* could result in non-forward-compatible dumps of WHEN clauses
* due to under-parenthesization.
*/
appendPQExpBuffer(query,
"SELECT tgname, "
"tgfoid::pg_catalog.regproc AS tgfname, "
"pg_catalog.pg_get_triggerdef(oid, true) AS tgdef, "
"pg_catalog.pg_get_triggerdef(oid, false) AS tgdef, "
"tgenabled, tableoid, oid "
"FROM pg_catalog.pg_trigger t "
"WHERE tgrelid = '%u'::pg_catalog.oid "
......@@ -11323,6 +11328,7 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBuffer(query, " OR UPDATE");
else
appendPQExpBuffer(query, " UPDATE");
findx++;
}
if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
{
......@@ -11330,6 +11336,7 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBuffer(query, " OR TRUNCATE");
else
appendPQExpBuffer(query, " TRUNCATE");
findx++;
}
appendPQExpBuffer(query, " ON %s\n",
fmtId(tbinfo->dobj.name));
......
......@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.550 2009/11/05 23:24:26 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.551 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200911051
#define CATALOG_VERSION_NO 200911201
#endif
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.35 2009/10/14 22:14:24 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.36 2009/11/20 20:38:11 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -52,9 +52,10 @@ CATALOG(pg_trigger,2620)
bool tginitdeferred; /* constraint trigger is deferred initially */
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS (note: these are not supposed to be null) */
/* VARIABLE LENGTH FIELDS (note: tgattr and tgargs must not be null) */
int2vector tgattr; /* column numbers, if trigger is on columns */
bytea tgargs; /* first\000second\000tgnargs\000 */
text tgqual; /* WHEN expression, or NULL if none */
} FormData_pg_trigger;
/* ----------------
......@@ -68,7 +69,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
#define Natts_pg_trigger 15
#define Natts_pg_trigger 16
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
......@@ -84,6 +85,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgnargs 13
#define Anum_pg_trigger_tgattr 14
#define Anum_pg_trigger_tgargs 15
#define Anum_pg_trigger_tgqual 16
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/toasting.h,v 1.9 2009/10/07 22:14:25 alvherre Exp $
* $PostgreSQL: pgsql/src/include/catalog/toasting.h,v 1.10 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -47,6 +47,7 @@ DECLARE_TOAST(pg_description, 2834, 2835);
DECLARE_TOAST(pg_proc, 2836, 2837);
DECLARE_TOAST(pg_rewrite, 2838, 2839);
DECLARE_TOAST(pg_statistic, 2840, 2841);
DECLARE_TOAST(pg_trigger, 2336, 2337);
/* shared catalogs */
DECLARE_TOAST(pg_authid, 2842, 2843);
......
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.77 2009/10/26 02:26:41 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.78 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -104,7 +104,7 @@ extern PGDLLIMPORT int SessionReplicationRole;
#define TRIGGER_FIRES_ON_REPLICA 'R'
#define TRIGGER_DISABLED 'D'
extern Oid CreateTrigger(CreateTrigStmt *stmt,
extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Oid constraintOid, Oid indexOid, const char *prefix,
bool checkPermissions);
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.211 2009/10/26 02:26:41 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.212 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -294,6 +294,7 @@ typedef struct JunkFilter
* IndexRelationInfo array of key/attr info for indices
* TrigDesc triggers to be fired, if any
* TrigFunctions cached lookup info for trigger functions
* TrigWhenExprs array of trigger WHEN expr states
* TrigInstrument optional runtime measurements for triggers
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
......@@ -310,6 +311,7 @@ typedef struct ResultRelInfo
IndexInfo **ri_IndexRelationInfo;
TriggerDesc *ri_TrigDesc;
FmgrInfo *ri_TrigFunctions;
List **ri_TrigWhenExprs;
struct Instrumentation *ri_TrigInstrument;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
......@@ -345,7 +347,8 @@ typedef struct EState
/* Stuff used for firing triggers: */
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
TupleTableSlot *es_trig_oldtup_slot; /* for trigger old tuples */
/* Parameter info: */
ParamListInfo es_param_list_info; /* values of external params */
......
......@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.415 2009/11/16 21:32:07 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.416 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1571,6 +1571,7 @@ typedef struct CreateTrigStmt
/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */
List *columns; /* column names, or NIL for all columns */
Node *whenClause; /* qual expression, or NULL if none */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.115 2009/07/28 02:56:31 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.116 2009/11/20 20:38:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -66,6 +66,7 @@ typedef struct Trigger
int16 tgnattr;
int16 *tgattr;
char **tgargs;
char *tgqual;
} Trigger;
typedef struct TriggerDesc
......
......@@ -322,6 +322,90 @@ SELECT * FROM main_table ORDER BY a, b;
|
(8 rows)
--
-- test triggers with WHEN clause
--
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');
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');
CREATE TRIGGER insert_a AFTER INSERT ON main_table
FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a');
CREATE TRIGGER delete_a AFTER DELETE ON main_table
FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a');
CREATE TRIGGER insert_when BEFORE INSERT ON main_table
FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when');
CREATE TRIGGER delete_when AFTER DELETE ON main_table
FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when');
INSERT INTO main_table (a) VALUES (123), (456);
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW
NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
COPY main_table FROM stdin;
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW
NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
DELETE FROM main_table WHERE a IN (123, 456);
NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW
NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW
NOTICE: trigger_func(delete_when) called: action = DELETE, when = AFTER, level = STATEMENT
UPDATE main_table SET a = 50, b = 60;
NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
6 | 10
21 | 20
30 | 40
31 | 10
50 | 35
50 | 60
81 | 15
|
(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')
(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')
(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')
(1 row)
DROP TRIGGER modified_a ON main_table;
DROP TRIGGER modified_any ON main_table;
DROP TRIGGER insert_a ON main_table;
DROP TRIGGER delete_a ON main_table;
DROP TRIGGER insert_when ON main_table;
DROP TRIGGER delete_when ON main_table;
-- Test column-level triggers
DROP TRIGGER after_upd_row_trig ON main_table;
CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table
......@@ -393,6 +477,30 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a');
ERROR: syntax error at or near "OF"
LINE 1: CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table
^
CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table
FOR EACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_ins_old');
ERROR: INSERT trigger's WHEN condition cannot reference OLD values
LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a)
^
CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table
FOR EACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_del_new');
ERROR: DELETE trigger's WHEN condition cannot reference NEW values
LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a)
^
CREATE TRIGGER error_del_when BEFORE INSERT OR UPDATE ON main_table
FOR EACH ROW WHEN (NEW.tableoid <> 0)
EXECUTE PROCEDURE trigger_func('error_when_sys_column');
ERROR: BEFORE trigger's WHEN condition cannot reference NEW system columns
LINE 2: FOR EACH ROW WHEN (NEW.tableoid <> 0)
^
CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table
FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE trigger_func('error_stmt_when');
ERROR: statement trigger's WHEN condition cannot reference column values
LINE 2: FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*)
^
-- check dependency restrictions
ALTER TABLE main_table DROP COLUMN b;
ERROR: cannot drop table main_table column b because other objects depend on it
......
......@@ -254,6 +254,40 @@ COPY main_table (a, b) FROM stdin;
SELECT * FROM main_table ORDER BY a, b;
--
-- test triggers with WHEN clause
--
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');
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');
CREATE TRIGGER insert_a AFTER INSERT ON main_table
FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a');
CREATE TRIGGER delete_a AFTER DELETE ON main_table
FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a');
CREATE TRIGGER insert_when BEFORE INSERT ON main_table
FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when');
CREATE TRIGGER delete_when AFTER DELETE ON main_table
FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when');
INSERT INTO main_table (a) VALUES (123), (456);
COPY main_table FROM stdin;
123 999
456 999
\.
DELETE FROM main_table WHERE a IN (123, 456);
UPDATE main_table SET a = 50, b = 60;
SELECT * FROM main_table ORDER BY a, b;
SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
SELECT pg_get_triggerdef(oid, false) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any';
DROP TRIGGER modified_a ON main_table;
DROP TRIGGER modified_any ON main_table;
DROP TRIGGER insert_a ON main_table;
DROP TRIGGER delete_a ON main_table;
DROP TRIGGER insert_when ON main_table;
DROP TRIGGER delete_when ON main_table;
-- Test column-level triggers
DROP TRIGGER after_upd_row_trig ON main_table;
......@@ -282,6 +316,18 @@ CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a');
CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a');
CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table
FOR EACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_ins_old');
CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table
FOR EACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_del_new');
CREATE TRIGGER error_del_when BEFORE INSERT OR UPDATE ON main_table
FOR EACH ROW WHEN (NEW.tableoid <> 0)
EXECUTE PROCEDURE trigger_func('error_when_sys_column');
CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table
FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE trigger_func('error_stmt_when');
-- check dependency restrictions
ALTER TABLE main_table DROP COLUMN b;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment