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.
This commit is contained in:
parent
201a45c4fa
commit
7fc0f06221
doc/src/sgml
src
backend
catalog
commands
executor
nodes
parser
tcop
utils/adt
bin/pg_dump
include
test/regress
@ -1,4 +1,4 @@
|
||||
<!-- $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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$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>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $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);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.256 2009/10/27 20:14:27 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.257 2009/11/20 20:38:10 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -32,10 +32,14 @@
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/bitmapset.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "parser/parse_clause.h"
|
||||
#include "parser/parse_func.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "pgstat.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "tcop/utility.h"
|
||||
#include "utils/acl.h"
|
||||
@ -65,21 +69,27 @@ static HeapTuple GetTupleForTrigger(EState *estate,
|
||||
ResultRelInfo *relinfo,
|
||||
ItemPointer tid,
|
||||
TupleTableSlot **newSlot);
|
||||
static bool TriggerEnabled(Trigger *trigger, TriggerEvent event,
|
||||
Bitmapset *modifiedCols);
|
||||
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
|
||||
Trigger *trigger, TriggerEvent event,
|
||||
Bitmapset *modifiedCols,
|
||||
HeapTuple oldtup, HeapTuple newtup);
|
||||
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
||||
int tgindx,
|
||||
FmgrInfo *finfo,
|
||||
Instrumentation *instr,
|
||||
MemoryContext per_tuple_context);
|
||||
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
|
||||
static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
int event, bool row_trigger,
|
||||
HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes, Bitmapset *modifiedCols);
|
||||
|
||||
|
||||
/*
|
||||
* Create a trigger. Returns the OID of the created trigger.
|
||||
*
|
||||
* queryString is the source text of the CREATE TRIGGER command.
|
||||
* This must be supplied if a whenClause is specified, else it can be NULL.
|
||||
*
|
||||
* constraintOid, if nonzero, says that this trigger is being created
|
||||
* internally to implement that constraint. A suitable pg_depend entry will
|
||||
* be made to link the trigger to that constraint. constraintOid is zero when
|
||||
@ -101,7 +111,7 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||
* but a foreign-key constraint. This is a kluge for backwards compatibility.
|
||||
*/
|
||||
Oid
|
||||
CreateTrigger(CreateTrigStmt *stmt,
|
||||
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
||||
Oid constraintOid, Oid indexOid, const char *prefix,
|
||||
bool checkPermissions)
|
||||
{
|
||||
@ -109,6 +119,9 @@ CreateTrigger(CreateTrigStmt *stmt,
|
||||
int ncolumns;
|
||||
int2 *columns;
|
||||
int2vector *tgattr;
|
||||
Node *whenClause;
|
||||
List *whenRtable;
|
||||
char *qual;
|
||||
Datum values[Natts_pg_trigger];
|
||||
bool nulls[Natts_pg_trigger];
|
||||
Relation rel;
|
||||
@ -179,6 +192,120 @@ CreateTrigger(CreateTrigStmt *stmt,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));
|
||||
|
||||
/*
|
||||
* Parse the WHEN clause, if any
|
||||
*/
|
||||
if (stmt->whenClause)
|
||||
{
|
||||
ParseState *pstate;
|
||||
RangeTblEntry *rte;
|
||||
List *varList;
|
||||
ListCell *lc;
|
||||
|
||||
/* Set up a pstate to parse with */
|
||||
pstate = make_parsestate(NULL);
|
||||
pstate->p_sourcetext = queryString;
|
||||
|
||||
/*
|
||||
* Set up RTEs for OLD and NEW references.
|
||||
*
|
||||
* 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
|
||||
*/
|
||||
rte = addRangeTableEntryForRelation(pstate, rel,
|
||||
makeAlias("old", NIL),
|
||||
false, false);
|
||||
addRTEtoQuery(pstate, rte, false, true, true);
|
||||
rte = addRangeTableEntryForRelation(pstate, rel,
|
||||
makeAlias("new", NIL),
|
||||
false, false);
|
||||
addRTEtoQuery(pstate, rte, false, true, true);
|
||||
|
||||
/* Transform expression. Copy to be sure we don't modify original */
|
||||
whenClause = transformWhereClause(pstate,
|
||||
copyObject(stmt->whenClause),
|
||||
"WHEN");
|
||||
|
||||
/*
|
||||
* No subplans or aggregates, please
|
||||
*/
|
||||
if (pstate->p_hasSubLinks)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use subquery in trigger WHEN condition")));
|
||||
if (pstate->p_hasAggs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in trigger WHEN condition")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in trigger WHEN condition")));
|
||||
|
||||
/*
|
||||
* Check for disallowed references to OLD/NEW.
|
||||
*
|
||||
* NB: pull_var_clause is okay here only because we don't allow
|
||||
* subselects in WHEN clauses; it would fail to examine the contents
|
||||
* of subselects.
|
||||
*/
|
||||
varList = pull_var_clause(whenClause, PVC_REJECT_PLACEHOLDERS);
|
||||
foreach(lc, varList)
|
||||
{
|
||||
Var *var = (Var *) lfirst(lc);
|
||||
|
||||
switch (var->varno)
|
||||
{
|
||||
case PRS2_OLD_VARNO:
|
||||
if (!TRIGGER_FOR_ROW(tgtype))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("statement trigger's WHEN condition cannot reference column values"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
if (TRIGGER_FOR_INSERT(tgtype))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("INSERT trigger's WHEN condition cannot reference OLD values"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
/* system columns are okay here */
|
||||
break;
|
||||
case PRS2_NEW_VARNO:
|
||||
if (!TRIGGER_FOR_ROW(tgtype))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("statement trigger's WHEN condition cannot reference column values"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
if (TRIGGER_FOR_DELETE(tgtype))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("DELETE trigger's WHEN condition cannot reference NEW values"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
|
||||
parser_errposition(pstate, var->location)));
|
||||
break;
|
||||
default:
|
||||
/* can't happen without add_missing_from, so just elog */
|
||||
elog(ERROR, "trigger WHEN condition cannot contain references to other relations");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* we'll need the rtable for recordDependencyOnExpr */
|
||||
whenRtable = pstate->p_rtable;
|
||||
|
||||
qual = nodeToString(whenClause);
|
||||
|
||||
free_parsestate(pstate);
|
||||
}
|
||||
else
|
||||
{
|
||||
whenClause = NULL;
|
||||
whenRtable = NIL;
|
||||
qual = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and validate the trigger function.
|
||||
*/
|
||||
@ -387,6 +514,12 @@ CreateTrigger(CreateTrigStmt *stmt,
|
||||
tgattr = buildint2vector(columns, ncolumns);
|
||||
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
|
||||
|
||||
/* set tgqual if trigger has WHEN clause */
|
||||
if (qual)
|
||||
values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(qual);
|
||||
else
|
||||
nulls[Anum_pg_trigger_tgqual - 1] = true;
|
||||
|
||||
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
|
||||
|
||||
/* force tuple to have the desired OID */
|
||||
@ -495,6 +628,14 @@ CreateTrigger(CreateTrigStmt *stmt,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If it has a WHEN clause, add dependencies on objects mentioned in
|
||||
* the expression (eg, functions, as well as any columns used).
|
||||
*/
|
||||
if (whenClause != NULL)
|
||||
recordDependencyOnExpr(&myself, whenClause, whenRtable,
|
||||
DEPENDENCY_NORMAL);
|
||||
|
||||
/* Keep lock on target rel until end of xact */
|
||||
heap_close(rel, NoLock);
|
||||
|
||||
@ -1184,6 +1325,8 @@ RelationBuildTriggers(Relation relation)
|
||||
{
|
||||
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
|
||||
Trigger *build;
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
|
||||
if (numtrigs >= maxtrigs)
|
||||
{
|
||||
@ -1218,7 +1361,6 @@ RelationBuildTriggers(Relation relation)
|
||||
if (build->tgnargs > 0)
|
||||
{
|
||||
bytea *val;
|
||||
bool isnull;
|
||||
char *p;
|
||||
|
||||
val = DatumGetByteaP(fastgetattr(htup,
|
||||
@ -1237,6 +1379,12 @@ RelationBuildTriggers(Relation relation)
|
||||
}
|
||||
else
|
||||
build->tgargs = NULL;
|
||||
datum = fastgetattr(htup, Anum_pg_trigger_tgqual,
|
||||
tgrel->rd_att, &isnull);
|
||||
if (!isnull)
|
||||
build->tgqual = TextDatumGetCString(datum);
|
||||
else
|
||||
build->tgqual = NULL;
|
||||
|
||||
numtrigs++;
|
||||
}
|
||||
@ -1396,6 +1544,8 @@ CopyTriggerDesc(TriggerDesc *trigdesc)
|
||||
newargs[j] = pstrdup(trigger->tgargs[j]);
|
||||
trigger->tgargs = newargs;
|
||||
}
|
||||
if (trigger->tgqual)
|
||||
trigger->tgqual = pstrdup(trigger->tgqual);
|
||||
trigger++;
|
||||
}
|
||||
|
||||
@ -1497,6 +1647,8 @@ FreeTriggerDesc(TriggerDesc *trigdesc)
|
||||
pfree(trigger->tgargs[trigger->tgnargs]);
|
||||
pfree(trigger->tgargs);
|
||||
}
|
||||
if (trigger->tgqual)
|
||||
pfree(trigger->tgqual);
|
||||
trigger++;
|
||||
}
|
||||
pfree(trigdesc->triggers);
|
||||
@ -1520,6 +1672,11 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
|
||||
*
|
||||
* As of 7.3 we assume trigger set ordering is significant in the
|
||||
* comparison; so we just compare corresponding slots of the two sets.
|
||||
*
|
||||
* Note: comparing the stringToNode forms of the WHEN clauses means that
|
||||
* parse column locations will affect the result. This is okay as long
|
||||
* as this function is only used for detecting exact equality, as for
|
||||
* example in checking for staleness of a cache entry.
|
||||
*/
|
||||
if (trigdesc1 != NULL)
|
||||
{
|
||||
@ -1565,6 +1722,12 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
|
||||
for (j = 0; j < trig1->tgnargs; j++)
|
||||
if (strcmp(trig1->tgargs[j], trig2->tgargs[j]) != 0)
|
||||
return false;
|
||||
if (trig1->tgqual == NULL && trig2->tgqual == NULL)
|
||||
/* ok */ ;
|
||||
else if (trig1->tgqual == NULL || trig2->tgqual == NULL)
|
||||
return false;
|
||||
else if (strcmp(trig1->tgqual, trig2->tgqual) != 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (trigdesc2 != NULL)
|
||||
@ -1687,7 +1850,8 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
HeapTuple newtuple;
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
NULL, NULL, NULL))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigger = trigger;
|
||||
@ -1710,7 +1874,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
}
|
||||
|
||||
@ -1737,7 +1901,8 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
{
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
NULL, NULL, newtuple))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
|
||||
@ -1763,7 +1928,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
|
||||
true, NULL, trigtuple, recheckIndexes, NULL);
|
||||
}
|
||||
|
||||
@ -1800,7 +1965,8 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
HeapTuple newtuple;
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
NULL, NULL, NULL))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigger = trigger;
|
||||
@ -1823,7 +1989,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
}
|
||||
|
||||
@ -1858,7 +2024,8 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
|
||||
{
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
NULL, trigtuple, NULL))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigtuple = trigtuple;
|
||||
@ -1893,7 +2060,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
|
||||
tupleid, NULL);
|
||||
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
|
||||
true, trigtuple, NULL, NIL, NULL);
|
||||
heap_freetuple(trigtuple);
|
||||
}
|
||||
@ -1935,7 +2102,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
HeapTuple newtuple;
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
modifiedCols, NULL, NULL))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigger = trigger;
|
||||
@ -1958,7 +2126,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
|
||||
false, NULL, NULL, NIL,
|
||||
GetModifiedColumns(relinfo, estate));
|
||||
}
|
||||
@ -2003,7 +2171,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
|
||||
{
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
modifiedCols, trigtuple, newtuple))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigtuple = trigtuple;
|
||||
@ -2037,7 +2206,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
|
||||
tupleid, NULL);
|
||||
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
|
||||
true, trigtuple, newtuple, recheckIndexes,
|
||||
GetModifiedColumns(relinfo, estate));
|
||||
heap_freetuple(trigtuple);
|
||||
@ -2077,7 +2246,8 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
HeapTuple newtuple;
|
||||
|
||||
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
|
||||
NULL, NULL, NULL))
|
||||
continue;
|
||||
|
||||
LocTriggerData.tg_trigger = trigger;
|
||||
@ -2100,7 +2270,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||
|
||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
|
||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
|
||||
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE,
|
||||
false, NULL, NULL, NIL, NULL);
|
||||
}
|
||||
|
||||
@ -2219,7 +2389,10 @@ ltrmark:;
|
||||
* Is trigger enabled to fire?
|
||||
*/
|
||||
static bool
|
||||
TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols)
|
||||
TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
|
||||
Trigger *trigger, TriggerEvent event,
|
||||
Bitmapset *modifiedCols,
|
||||
HeapTuple oldtup, HeapTuple newtup)
|
||||
{
|
||||
/* Check replication-role-dependent enable state */
|
||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
||||
@ -2258,6 +2431,94 @@ TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for WHEN clause */
|
||||
if (trigger->tgqual)
|
||||
{
|
||||
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
|
||||
List **predicate;
|
||||
ExprContext *econtext;
|
||||
TupleTableSlot *oldslot = NULL;
|
||||
TupleTableSlot *newslot = NULL;
|
||||
MemoryContext oldContext;
|
||||
int i;
|
||||
|
||||
Assert(estate != NULL);
|
||||
|
||||
/*
|
||||
* trigger is an element of relinfo->ri_TrigDesc->triggers[];
|
||||
* find the matching element of relinfo->ri_TrigWhenExprs[]
|
||||
*/
|
||||
i = trigger - relinfo->ri_TrigDesc->triggers;
|
||||
predicate = &relinfo->ri_TrigWhenExprs[i];
|
||||
|
||||
/*
|
||||
* If first time through for this WHEN expression, build expression
|
||||
* nodetrees for it. Keep them in the per-query memory context so
|
||||
* they'll survive throughout the query.
|
||||
*/
|
||||
if (*predicate == NIL)
|
||||
{
|
||||
Node *tgqual;
|
||||
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
tgqual = stringToNode(trigger->tgqual);
|
||||
/* Change references to OLD and NEW to INNER and OUTER */
|
||||
ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER, 0);
|
||||
ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER, 0);
|
||||
/* ExecQual wants implicit-AND form */
|
||||
tgqual = (Node *) make_ands_implicit((Expr *) tgqual);
|
||||
*predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will use the EState's per-tuple context for evaluating WHEN
|
||||
* expressions (creating it if it's not already there).
|
||||
*/
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
|
||||
/*
|
||||
* Put OLD and NEW tuples into tupleslots for expression evaluation.
|
||||
* These slots can be shared across the whole estate, but be careful
|
||||
* that they have the current resultrel's tupdesc.
|
||||
*/
|
||||
if (HeapTupleIsValid(oldtup))
|
||||
{
|
||||
if (estate->es_trig_oldtup_slot == NULL)
|
||||
{
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
oldslot = estate->es_trig_oldtup_slot;
|
||||
if (oldslot->tts_tupleDescriptor != tupdesc)
|
||||
ExecSetSlotDescriptor(oldslot, tupdesc);
|
||||
ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false);
|
||||
}
|
||||
if (HeapTupleIsValid(newtup))
|
||||
{
|
||||
if (estate->es_trig_tuple_slot == NULL)
|
||||
{
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
newslot = estate->es_trig_tuple_slot;
|
||||
if (newslot->tts_tupleDescriptor != tupdesc)
|
||||
ExecSetSlotDescriptor(newslot, tupdesc);
|
||||
ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally evaluate the expression, making the old and/or new tuples
|
||||
* available as INNER/OUTER respectively.
|
||||
*/
|
||||
econtext->ecxt_innertuple = oldslot;
|
||||
econtext->ecxt_outertuple = newslot;
|
||||
if (!ExecQual(*predicate, econtext, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3883,7 +4144,8 @@ AfterTriggerPendingOnRel(Oid relid)
|
||||
* ----------
|
||||
*/
|
||||
static void
|
||||
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||
AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
|
||||
int event, bool row_trigger,
|
||||
HeapTuple oldtup, HeapTuple newtup,
|
||||
List *recheckIndexes, Bitmapset *modifiedCols)
|
||||
{
|
||||
@ -3993,7 +4255,8 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||
{
|
||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||
|
||||
if (!TriggerEnabled(trigger, event, modifiedCols))
|
||||
if (!TriggerEnabled(estate, relinfo, trigger, event,
|
||||
modifiedCols, oldtup, newtup))
|
||||
continue;
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user