mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Support SQL-compliant triggers on columns, ie fire only if certain columns
are named in the UPDATE's SET list. Note: the schema of pg_trigger has not actually changed; we've just started to use a column that was there all along. catversion bumped anyway so that this commit is included in the history of potentially interesting changes to system catalog contents. Itagaki Takahiro
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.209 2009/10/07 22:14:14 alvherre Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.210 2009/10/14 22:14:21 tgl Exp $ -->
|
||||||
<!--
|
<!--
|
||||||
Documentation of the system catalogs, directed toward PostgreSQL developers
|
Documentation of the system catalogs, directed toward PostgreSQL developers
|
||||||
-->
|
-->
|
||||||
@ -4745,8 +4745,9 @@
|
|||||||
<row>
|
<row>
|
||||||
<entry><structfield>tgattr</structfield></entry>
|
<entry><structfield>tgattr</structfield></entry>
|
||||||
<entry><type>int2vector</type></entry>
|
<entry><type>int2vector</type></entry>
|
||||||
<entry></entry>
|
<entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
|
||||||
<entry>Currently unused</entry>
|
<entry>column numbers, if trigger is column-specific; otherwise an
|
||||||
|
empty array</entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
<row>
|
<row>
|
||||||
@ -4759,6 +4760,14 @@
|
|||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Currently, column-specific triggering is supported only for
|
||||||
|
<literal>UPDATE</> events, and so <structfield>tgattr</> is relevant
|
||||||
|
only for that event type. <structfield>tgtype</structfield> might
|
||||||
|
contain bits for other event types as well, but those are presumed
|
||||||
|
to be table-wide regardless of what is in <structfield>tgattr</>.
|
||||||
|
</para>
|
||||||
|
|
||||||
<note>
|
<note>
|
||||||
<para>
|
<para>
|
||||||
When <structfield>tgconstraint</> is nonzero,
|
When <structfield>tgconstraint</> is nonzero,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.50 2009/09/19 10:23:27 petere Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.51 2009/10/14 22:14:21 tgl Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
current row, or change the row being inserted (for
|
current row, or change the row being inserted (for
|
||||||
<command>INSERT</command> and <command>UPDATE</command> operations
|
<command>INSERT</command> and <command>UPDATE</command> operations
|
||||||
only). If the trigger fires after the event, all changes, including
|
only). If the trigger fires after the event, all changes, including
|
||||||
the last insertion, update, or deletion, are <quote>visible</quote>
|
the effects of other triggers, are <quote>visible</quote>
|
||||||
to the trigger.
|
to the trigger.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -122,6 +122,16 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
this specifies the event that will fire the trigger. Multiple
|
this specifies the event that will fire the trigger. Multiple
|
||||||
events can be specified using <literal>OR</literal>.
|
events can be specified using <literal>OR</literal>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For <command>UPDATE</command> triggers, it is possible to
|
||||||
|
specify a list of columns using this syntax:
|
||||||
|
<synopsis>
|
||||||
|
UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</replaceable> ... ]
|
||||||
|
</synopsis>
|
||||||
|
The trigger will only fire if at least one of the listed columns
|
||||||
|
is mentioned as a target of the update.
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
@ -169,8 +179,8 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
literal string constants. Simple names and numeric constants
|
literal string constants. Simple names and numeric constants
|
||||||
can be written here, too, but they will all be converted to
|
can be written here, too, but they will all be converted to
|
||||||
strings. Please check the description of the implementation
|
strings. Please check the description of the implementation
|
||||||
language of the trigger function about how the trigger arguments
|
language of the trigger function to find out how these arguments
|
||||||
are accessible within the function; it might be different from
|
can be accessed within the function; it might be different from
|
||||||
normal function arguments.
|
normal function arguments.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -191,6 +201,18 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
endterm="sql-droptrigger-title"> to remove a trigger.
|
endterm="sql-droptrigger-title"> to remove a trigger.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A column-specific trigger (<literal>FOR UPDATE OF
|
||||||
|
<replaceable>column_name</replaceable></literal>) will fire when any
|
||||||
|
of its columns are listed as targets in the <command>UPDATE</>
|
||||||
|
command's <literal>SET</> list. It is possible for a column's value
|
||||||
|
to change even when the trigger is not fired, because changes made to the
|
||||||
|
row's contents by <literal>BEFORE UPDATE</> triggers are not considered.
|
||||||
|
Conversely, a command such as <literal>UPDATE ... SET x = x ...</>
|
||||||
|
will fire a trigger on column <literal>x</>, even though the column's
|
||||||
|
value did not change.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
In <productname>PostgreSQL</productname> versions before 7.3, it was
|
In <productname>PostgreSQL</productname> versions before 7.3, it was
|
||||||
necessary to declare trigger functions as returning the placeholder
|
necessary to declare trigger functions as returning the placeholder
|
||||||
@ -218,13 +240,6 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
<acronym>SQL</> standard. The following functionality is currently missing:
|
<acronym>SQL</> standard. The following functionality is currently missing:
|
||||||
|
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
SQL allows triggers to fire on updates to specific columns
|
|
||||||
(e.g., <literal>AFTER UPDATE OF col1, col2</literal>).
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
SQL allows you to define aliases for the <quote>old</quote>
|
SQL allows you to define aliases for the <quote>old</quote>
|
||||||
@ -264,9 +279,10 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
The <productname>PostgreSQL</productname> behavior is for <literal>BEFORE
|
The <productname>PostgreSQL</productname> behavior is for <literal>BEFORE
|
||||||
DELETE</literal> to always fire before the delete action, even a cascading
|
DELETE</literal> to always fire before the delete action, even a cascading
|
||||||
one. This is considered more consistent. There is also unpredictable
|
one. This is considered more consistent. There is also unpredictable
|
||||||
behavior when <literal>BEFORE</literal> triggers modify rows that are later
|
behavior when <literal>BEFORE</literal> triggers modify rows or prevent
|
||||||
to be modified by referential actions. This can lead to constraint violations
|
updates during an update that is caused by a referential action. This can
|
||||||
or stored data that does not honor the referential constraint.
|
lead to constraint violations or stored data that does not honor the
|
||||||
|
referential constraint.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.58 2009/08/04 22:04:37 petere Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.59 2009/10/14 22:14:21 tgl Exp $ -->
|
||||||
|
|
||||||
<chapter id="triggers">
|
<chapter id="triggers">
|
||||||
<title>Triggers</title>
|
<title>Triggers</title>
|
||||||
@ -36,10 +36,13 @@
|
|||||||
performed. Triggers can be defined to execute either before or after any
|
performed. Triggers can be defined to execute either before or after any
|
||||||
<command>INSERT</command>, <command>UPDATE</command>, or
|
<command>INSERT</command>, <command>UPDATE</command>, or
|
||||||
<command>DELETE</command> operation, either once per modified row,
|
<command>DELETE</command> operation, either once per modified row,
|
||||||
or once per <acronym>SQL</acronym> statement. Triggers can also fire
|
or once per <acronym>SQL</acronym> statement.
|
||||||
for <command>TRUNCATE</command> statements. If a trigger event occurs,
|
<command>UPDATE</command> triggers can moreover be set to fire only if
|
||||||
the trigger's function is called at the appropriate time to handle the
|
certain columns are mentioned in the <literal>SET</literal> clause of the
|
||||||
event.
|
<command>UPDATE</command> statement.
|
||||||
|
Triggers can also fire for <command>TRUNCATE</command> statements.
|
||||||
|
If a trigger event occurs, the trigger's function is called at the
|
||||||
|
appropriate time to handle the event.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.322 2009/10/05 19:24:35 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.323 2009/10/14 22:14:21 tgl Exp $
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* INTERFACE ROUTINES
|
* INTERFACE ROUTINES
|
||||||
@ -792,6 +792,7 @@ index_create(Oid heapRelationId,
|
|||||||
trigger->before = false;
|
trigger->before = false;
|
||||||
trigger->row = true;
|
trigger->row = true;
|
||||||
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
|
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
|
||||||
|
trigger->columns = NIL;
|
||||||
trigger->isconstraint = true;
|
trigger->isconstraint = true;
|
||||||
trigger->deferrable = true;
|
trigger->deferrable = true;
|
||||||
trigger->initdeferred = initdeferred;
|
trigger->initdeferred = initdeferred;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.303 2009/10/13 00:53:07 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.304 2009/10/14 22:14:21 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -5441,6 +5441,7 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint,
|
|||||||
fk_trigger->events = TRIGGER_TYPE_UPDATE;
|
fk_trigger->events = TRIGGER_TYPE_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fk_trigger->columns = NIL;
|
||||||
fk_trigger->isconstraint = true;
|
fk_trigger->isconstraint = true;
|
||||||
fk_trigger->deferrable = fkconstraint->deferrable;
|
fk_trigger->deferrable = fkconstraint->deferrable;
|
||||||
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
||||||
@ -5491,6 +5492,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
|
|||||||
fk_trigger->before = false;
|
fk_trigger->before = false;
|
||||||
fk_trigger->row = true;
|
fk_trigger->row = true;
|
||||||
fk_trigger->events = TRIGGER_TYPE_DELETE;
|
fk_trigger->events = TRIGGER_TYPE_DELETE;
|
||||||
|
fk_trigger->columns = NIL;
|
||||||
fk_trigger->isconstraint = true;
|
fk_trigger->isconstraint = true;
|
||||||
fk_trigger->constrrel = myRel;
|
fk_trigger->constrrel = myRel;
|
||||||
switch (fkconstraint->fk_del_action)
|
switch (fkconstraint->fk_del_action)
|
||||||
@ -5543,6 +5545,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
|
|||||||
fk_trigger->before = false;
|
fk_trigger->before = false;
|
||||||
fk_trigger->row = true;
|
fk_trigger->row = true;
|
||||||
fk_trigger->events = TRIGGER_TYPE_UPDATE;
|
fk_trigger->events = TRIGGER_TYPE_UPDATE;
|
||||||
|
fk_trigger->columns = NIL;
|
||||||
fk_trigger->isconstraint = true;
|
fk_trigger->isconstraint = true;
|
||||||
fk_trigger->constrrel = myRel;
|
fk_trigger->constrrel = myRel;
|
||||||
switch (fkconstraint->fk_upd_action)
|
switch (fkconstraint->fk_upd_action)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.253 2009/10/10 01:43:45 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.254 2009/10/14 22:14:21 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -30,8 +30,11 @@
|
|||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
#include "executor/instrument.h"
|
#include "executor/instrument.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "nodes/bitmapset.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
|
#include "parser/parse_relation.h"
|
||||||
|
#include "parser/parsetree.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "tcop/utility.h"
|
#include "tcop/utility.h"
|
||||||
@ -51,6 +54,9 @@
|
|||||||
int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
|
int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
|
||||||
|
|
||||||
|
|
||||||
|
#define GetModifiedColumns(relinfo, estate) \
|
||||||
|
(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
|
||||||
|
|
||||||
/* Local function prototypes */
|
/* Local function prototypes */
|
||||||
static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid);
|
static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid);
|
||||||
static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
|
static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
|
||||||
@ -59,6 +65,8 @@ static HeapTuple GetTupleForTrigger(EState *estate,
|
|||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tid,
|
ItemPointer tid,
|
||||||
TupleTableSlot **newSlot);
|
TupleTableSlot **newSlot);
|
||||||
|
static bool TriggerEnabled(Trigger *trigger, TriggerEvent event,
|
||||||
|
Bitmapset *modifiedCols);
|
||||||
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
||||||
int tgindx,
|
int tgindx,
|
||||||
FmgrInfo *finfo,
|
FmgrInfo *finfo,
|
||||||
@ -66,7 +74,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
|||||||
MemoryContext per_tuple_context);
|
MemoryContext per_tuple_context);
|
||||||
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
||||||
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
|
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
|
||||||
List *recheckIndexes);
|
List *recheckIndexes, Bitmapset *modifiedCols);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -98,6 +106,8 @@ CreateTrigger(CreateTrigStmt *stmt,
|
|||||||
bool checkPermissions)
|
bool checkPermissions)
|
||||||
{
|
{
|
||||||
int16 tgtype;
|
int16 tgtype;
|
||||||
|
int ncolumns;
|
||||||
|
int2 *columns;
|
||||||
int2vector *tgattr;
|
int2vector *tgattr;
|
||||||
Datum values[Natts_pg_trigger];
|
Datum values[Natts_pg_trigger];
|
||||||
bool nulls[Natts_pg_trigger];
|
bool nulls[Natts_pg_trigger];
|
||||||
@ -337,8 +347,44 @@ CreateTrigger(CreateTrigStmt *stmt,
|
|||||||
CStringGetDatum(""));
|
CStringGetDatum(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tgattr is currently always a zero-length array */
|
/* build column number array if it's a column-specific trigger */
|
||||||
tgattr = buildint2vector(NULL, 0);
|
ncolumns = list_length(stmt->columns);
|
||||||
|
if (ncolumns == 0)
|
||||||
|
columns = NULL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ListCell *cell;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
columns = (int2 *) palloc(ncolumns * sizeof(int2));
|
||||||
|
foreach(cell, stmt->columns)
|
||||||
|
{
|
||||||
|
char *name = strVal(lfirst(cell));
|
||||||
|
int2 attnum;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
/* Lookup column name. System columns are not allowed */
|
||||||
|
attnum = attnameAttNum(rel, name, false);
|
||||||
|
if (attnum == InvalidAttrNumber)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||||
|
name, RelationGetRelationName(rel))));
|
||||||
|
|
||||||
|
/* Check for duplicates */
|
||||||
|
for (j = i - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
if (columns[j] == attnum)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
||||||
|
errmsg("column \"%s\" specified more than once",
|
||||||
|
name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
columns[i++] = attnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tgattr = buildint2vector(columns, ncolumns);
|
||||||
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
|
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
|
||||||
|
|
||||||
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
|
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
|
||||||
@ -358,6 +404,7 @@ CreateTrigger(CreateTrigStmt *stmt,
|
|||||||
|
|
||||||
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
|
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
|
||||||
pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
|
pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
|
||||||
|
pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update relation's pg_class entry. Crucial side-effect: other backends
|
* Update relation's pg_class entry. Crucial side-effect: other backends
|
||||||
@ -434,6 +481,20 @@ CreateTrigger(CreateTrigStmt *stmt,
|
|||||||
Assert(!OidIsValid(indexOid));
|
Assert(!OidIsValid(indexOid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If column-specific trigger, add normal dependencies on columns */
|
||||||
|
if (columns != NULL)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
referenced.classId = RelationRelationId;
|
||||||
|
referenced.objectId = RelationGetRelid(rel);
|
||||||
|
for (i = 0; i < ncolumns; i++)
|
||||||
|
{
|
||||||
|
referenced.objectSubId = columns[i];
|
||||||
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Keep lock on target rel until end of xact */
|
/* Keep lock on target rel until end of xact */
|
||||||
heap_close(rel, NoLock);
|
heap_close(rel, NoLock);
|
||||||
|
|
||||||
@ -1626,18 +1687,9 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
HeapTuple newtuple;
|
HeapTuple newtuple;
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
||||||
tgindx[i],
|
tgindx[i],
|
||||||
@ -1659,7 +1711,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
|
|
||||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
|
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||||
false, NULL, NULL, NIL);
|
false, NULL, NULL, NIL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
HeapTuple
|
HeapTuple
|
||||||
@ -1685,18 +1737,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
{
|
{
|
||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
|
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
|
||||||
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
@ -1721,7 +1764,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
|
|
||||||
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
|
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
|
||||||
true, NULL, trigtuple, recheckIndexes);
|
true, NULL, trigtuple, recheckIndexes, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -1757,18 +1800,9 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
HeapTuple newtuple;
|
HeapTuple newtuple;
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
||||||
tgindx[i],
|
tgindx[i],
|
||||||
@ -1790,7 +1824,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
|
|
||||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
|
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||||
false, NULL, NULL, NIL);
|
false, NULL, NULL, NIL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -1824,18 +1858,9 @@ ExecBRDeleteTriggers(EState *estate, PlanState *subplanstate,
|
|||||||
{
|
{
|
||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigtuple = trigtuple;
|
LocTriggerData.tg_trigtuple = trigtuple;
|
||||||
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
@ -1869,7 +1894,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
tupleid, NULL);
|
tupleid, NULL);
|
||||||
|
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||||
true, trigtuple, NULL, NIL);
|
true, trigtuple, NULL, NIL, NULL);
|
||||||
heap_freetuple(trigtuple);
|
heap_freetuple(trigtuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1882,6 +1907,7 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
int *tgindx;
|
int *tgindx;
|
||||||
int i;
|
int i;
|
||||||
TriggerData LocTriggerData;
|
TriggerData LocTriggerData;
|
||||||
|
Bitmapset *modifiedCols;
|
||||||
|
|
||||||
trigdesc = relinfo->ri_TrigDesc;
|
trigdesc = relinfo->ri_TrigDesc;
|
||||||
|
|
||||||
@ -1894,6 +1920,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
if (ntrigs == 0)
|
if (ntrigs == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
modifiedCols = GetModifiedColumns(relinfo, estate);
|
||||||
|
|
||||||
LocTriggerData.type = T_TriggerData;
|
LocTriggerData.type = T_TriggerData;
|
||||||
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
|
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
|
||||||
TRIGGER_EVENT_BEFORE;
|
TRIGGER_EVENT_BEFORE;
|
||||||
@ -1907,18 +1935,9 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
HeapTuple newtuple;
|
HeapTuple newtuple;
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
||||||
tgindx[i],
|
tgindx[i],
|
||||||
@ -1940,7 +1959,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
|
|
||||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
|
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||||
false, NULL, NULL, NIL);
|
false, NULL, NULL, NIL,
|
||||||
|
GetModifiedColumns(relinfo, estate));
|
||||||
}
|
}
|
||||||
|
|
||||||
HeapTuple
|
HeapTuple
|
||||||
@ -1957,6 +1977,7 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
|
|||||||
HeapTuple intuple = newtuple;
|
HeapTuple intuple = newtuple;
|
||||||
TupleTableSlot *newSlot;
|
TupleTableSlot *newSlot;
|
||||||
int i;
|
int i;
|
||||||
|
Bitmapset *modifiedCols;
|
||||||
|
|
||||||
trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
|
trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
|
||||||
&newSlot);
|
&newSlot);
|
||||||
@ -1971,6 +1992,8 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
|
|||||||
if (newSlot != NULL)
|
if (newSlot != NULL)
|
||||||
intuple = newtuple = ExecRemoveJunk(relinfo->ri_junkFilter, newSlot);
|
intuple = newtuple = ExecRemoveJunk(relinfo->ri_junkFilter, newSlot);
|
||||||
|
|
||||||
|
modifiedCols = GetModifiedColumns(relinfo, estate);
|
||||||
|
|
||||||
LocTriggerData.type = T_TriggerData;
|
LocTriggerData.type = T_TriggerData;
|
||||||
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
|
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
|
||||||
TRIGGER_EVENT_ROW |
|
TRIGGER_EVENT_ROW |
|
||||||
@ -1980,18 +2003,9 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
|
|||||||
{
|
{
|
||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigtuple = trigtuple;
|
LocTriggerData.tg_trigtuple = trigtuple;
|
||||||
LocTriggerData.tg_newtuple = oldtuple = newtuple;
|
LocTriggerData.tg_newtuple = oldtuple = newtuple;
|
||||||
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
|
||||||
@ -2024,7 +2038,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
tupleid, NULL);
|
tupleid, NULL);
|
||||||
|
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||||
true, trigtuple, newtuple, recheckIndexes);
|
true, trigtuple, newtuple, recheckIndexes,
|
||||||
|
GetModifiedColumns(relinfo, estate));
|
||||||
heap_freetuple(trigtuple);
|
heap_freetuple(trigtuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2062,18 +2077,9 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
HeapTuple newtuple;
|
HeapTuple newtuple;
|
||||||
|
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
|
||||||
{
|
continue;
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LocTriggerData.tg_trigger = trigger;
|
LocTriggerData.tg_trigger = trigger;
|
||||||
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
newtuple = ExecCallTriggerFunc(&LocTriggerData,
|
||||||
tgindx[i],
|
tgindx[i],
|
||||||
@ -2095,7 +2101,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
|
|||||||
|
|
||||||
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
|
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
|
||||||
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
|
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
|
||||||
false, NULL, NULL, NIL);
|
false, NULL, NULL, NIL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2201,6 +2207,52 @@ ltrmark:;
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is trigger enabled to fire?
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols)
|
||||||
|
{
|
||||||
|
/* Check replication-role-dependent enable state */
|
||||||
|
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
||||||
|
{
|
||||||
|
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
||||||
|
trigger->tgenabled == TRIGGER_DISABLED)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else /* ORIGIN or LOCAL role */
|
||||||
|
{
|
||||||
|
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
||||||
|
trigger->tgenabled == TRIGGER_DISABLED)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for column-specific trigger (only possible for UPDATE, and in
|
||||||
|
* fact we *must* ignore tgattr for other event types)
|
||||||
|
*/
|
||||||
|
if (trigger->tgnattr > 0 && TRIGGER_FIRED_BY_UPDATE(event))
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
bool modified;
|
||||||
|
|
||||||
|
modified = false;
|
||||||
|
for (i = 0; i < trigger->tgnattr; i++)
|
||||||
|
{
|
||||||
|
if (bms_is_member(trigger->tgattr[i] - FirstLowInvalidHeapAttributeNumber,
|
||||||
|
modifiedCols))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!modified)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* After-trigger stuff
|
* After-trigger stuff
|
||||||
@ -3825,7 +3877,7 @@ AfterTriggerPendingOnRel(Oid relid)
|
|||||||
static void
|
static void
|
||||||
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||||
HeapTuple oldtup, HeapTuple newtup,
|
HeapTuple oldtup, HeapTuple newtup,
|
||||||
List *recheckIndexes)
|
List *recheckIndexes, Bitmapset *modifiedCols)
|
||||||
{
|
{
|
||||||
Relation rel = relinfo->ri_RelationDesc;
|
Relation rel = relinfo->ri_RelationDesc;
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
||||||
@ -3927,26 +3979,15 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
|||||||
{
|
{
|
||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
|
|
||||||
/* Ignore disabled triggers */
|
if (!TriggerEnabled(trigger, event, modifiedCols))
|
||||||
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
|
continue;
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else /* ORIGIN or LOCAL role */
|
|
||||||
{
|
|
||||||
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
|
|
||||||
trigger->tgenabled == TRIGGER_DISABLED)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If this is an UPDATE of a PK table or FK table that does not change
|
* If this is an UPDATE of a PK table or FK table that does not change
|
||||||
* the PK or FK respectively, we can skip queuing the event: there is
|
* the PK or FK respectively, we can skip queuing the event: there is
|
||||||
* no need to fire the trigger.
|
* no need to fire the trigger.
|
||||||
*/
|
*/
|
||||||
if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
|
if (TRIGGER_FIRED_BY_UPDATE(event))
|
||||||
{
|
{
|
||||||
switch (RI_FKey_trigger_type(trigger->tgfoid))
|
switch (RI_FKey_trigger_type(trigger->tgfoid))
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.447 2009/10/13 00:53:08 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.448 2009/10/14 22:14:21 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -3160,6 +3160,7 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
|
|||||||
COPY_SCALAR_FIELD(before);
|
COPY_SCALAR_FIELD(before);
|
||||||
COPY_SCALAR_FIELD(row);
|
COPY_SCALAR_FIELD(row);
|
||||||
COPY_SCALAR_FIELD(events);
|
COPY_SCALAR_FIELD(events);
|
||||||
|
COPY_NODE_FIELD(columns);
|
||||||
COPY_SCALAR_FIELD(isconstraint);
|
COPY_SCALAR_FIELD(isconstraint);
|
||||||
COPY_SCALAR_FIELD(deferrable);
|
COPY_SCALAR_FIELD(deferrable);
|
||||||
COPY_SCALAR_FIELD(initdeferred);
|
COPY_SCALAR_FIELD(initdeferred);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.369 2009/10/13 00:53:08 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.370 2009/10/14 22:14:21 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -1671,6 +1671,7 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
|
|||||||
COMPARE_SCALAR_FIELD(before);
|
COMPARE_SCALAR_FIELD(before);
|
||||||
COMPARE_SCALAR_FIELD(row);
|
COMPARE_SCALAR_FIELD(row);
|
||||||
COMPARE_SCALAR_FIELD(events);
|
COMPARE_SCALAR_FIELD(events);
|
||||||
|
COMPARE_NODE_FIELD(columns);
|
||||||
COMPARE_SCALAR_FIELD(isconstraint);
|
COMPARE_SCALAR_FIELD(isconstraint);
|
||||||
COMPARE_SCALAR_FIELD(deferrable);
|
COMPARE_SCALAR_FIELD(deferrable);
|
||||||
COMPARE_SCALAR_FIELD(initdeferred);
|
COMPARE_SCALAR_FIELD(initdeferred);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.152 2009/10/12 18:10:48 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.153 2009/10/14 22:14:22 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -193,7 +193,8 @@ set_plan_references(PlannerGlobal *glob, Plan *plan,
|
|||||||
* needed by the executor; this reduces the storage space and copying cost
|
* needed by the executor; this reduces the storage space and copying cost
|
||||||
* for cached plans. We keep only the alias and eref Alias fields, which
|
* for cached plans. We keep only the alias and eref Alias fields, which
|
||||||
* are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
|
* are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
|
||||||
* which are needed for executor-startup permissions checking.
|
* which are needed for executor-startup permissions checking and for
|
||||||
|
* trigger event checking.
|
||||||
*/
|
*/
|
||||||
foreach(lc, rtable)
|
foreach(lc, rtable)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.685 2009/10/12 23:41:43 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.686 2009/10/14 22:14:22 tgl Exp $
|
||||||
*
|
*
|
||||||
* HISTORY
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -251,7 +251,7 @@ static TypeName *TableFuncTypeName(List *columns);
|
|||||||
|
|
||||||
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
|
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
|
||||||
|
|
||||||
%type <ival> TriggerEvents TriggerOneEvent
|
%type <list> TriggerEvents TriggerOneEvent
|
||||||
%type <value> TriggerFuncArg
|
%type <value> TriggerFuncArg
|
||||||
|
|
||||||
%type <str> relation_name copy_file_name
|
%type <str> relation_name copy_file_name
|
||||||
@ -3240,7 +3240,8 @@ CreateTrigStmt:
|
|||||||
n->args = $13;
|
n->args = $13;
|
||||||
n->before = $4;
|
n->before = $4;
|
||||||
n->row = $8;
|
n->row = $8;
|
||||||
n->events = $5;
|
n->events = intVal(linitial($5));
|
||||||
|
n->columns = (List *) lsecond($5);
|
||||||
n->isconstraint = FALSE;
|
n->isconstraint = FALSE;
|
||||||
n->deferrable = FALSE;
|
n->deferrable = FALSE;
|
||||||
n->initdeferred = FALSE;
|
n->initdeferred = FALSE;
|
||||||
@ -3260,7 +3261,8 @@ CreateTrigStmt:
|
|||||||
n->args = $18;
|
n->args = $18;
|
||||||
n->before = FALSE;
|
n->before = FALSE;
|
||||||
n->row = TRUE;
|
n->row = TRUE;
|
||||||
n->events = $6;
|
n->events = intVal(linitial($6));
|
||||||
|
n->columns = (List *) lsecond($6);
|
||||||
n->isconstraint = TRUE;
|
n->isconstraint = TRUE;
|
||||||
n->deferrable = ($10 & 1) != 0;
|
n->deferrable = ($10 & 1) != 0;
|
||||||
n->initdeferred = ($10 & 2) != 0;
|
n->initdeferred = ($10 & 2) != 0;
|
||||||
@ -3279,17 +3281,36 @@ TriggerEvents:
|
|||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| TriggerEvents OR TriggerOneEvent
|
| TriggerEvents OR TriggerOneEvent
|
||||||
{
|
{
|
||||||
if ($1 & $3)
|
int events1 = intVal(linitial($1));
|
||||||
|
int events2 = intVal(linitial($3));
|
||||||
|
List *columns1 = (List *) lsecond($1);
|
||||||
|
List *columns2 = (List *) lsecond($3);
|
||||||
|
|
||||||
|
if (events1 & events2)
|
||||||
parser_yyerror("duplicate trigger events specified");
|
parser_yyerror("duplicate trigger events specified");
|
||||||
$$ = $1 | $3;
|
/*
|
||||||
|
* concat'ing the columns lists loses information about
|
||||||
|
* which columns went with which event, but so long as
|
||||||
|
* only UPDATE carries columns and we disallow multiple
|
||||||
|
* UPDATE items, it doesn't matter. Command execution
|
||||||
|
* should just ignore the columns for non-UPDATE events.
|
||||||
|
*/
|
||||||
|
$$ = list_make2(makeInteger(events1 | events2),
|
||||||
|
list_concat(columns1, columns2));
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
TriggerOneEvent:
|
TriggerOneEvent:
|
||||||
INSERT { $$ = TRIGGER_TYPE_INSERT; }
|
INSERT
|
||||||
| DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
|
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
|
||||||
| UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
|
| DELETE_P
|
||||||
| TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
|
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_DELETE), NIL); }
|
||||||
|
| UPDATE
|
||||||
|
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), NIL); }
|
||||||
|
| UPDATE OF columnList
|
||||||
|
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), $3); }
|
||||||
|
| TRUNCATE
|
||||||
|
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_TRUNCATE), NIL); }
|
||||||
;
|
;
|
||||||
|
|
||||||
TriggerForSpec:
|
TriggerForSpec:
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.309 2009/10/10 01:43:49 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.310 2009/10/14 22:14:23 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -543,6 +543,23 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
|
|||||||
appendStringInfo(&buf, " OR UPDATE");
|
appendStringInfo(&buf, " OR UPDATE");
|
||||||
else
|
else
|
||||||
appendStringInfo(&buf, " UPDATE");
|
appendStringInfo(&buf, " UPDATE");
|
||||||
|
/* tgattr is first var-width field, so OK to access directly */
|
||||||
|
if (trigrec->tgattr.dim1 > 0)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
appendStringInfoString(&buf, " OF ");
|
||||||
|
for (i = 0; i < trigrec->tgattr.dim1; i++)
|
||||||
|
{
|
||||||
|
char *attname;
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
appendStringInfoString(&buf, ", ");
|
||||||
|
attname = get_relid_attribute_name(trigrec->tgrelid,
|
||||||
|
trigrec->tgattr.values[i]);
|
||||||
|
appendStringInfoString(&buf, quote_identifier(attname));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
|
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* by PostgreSQL
|
* by PostgreSQL
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.551 2009/10/12 23:41:43 tgl Exp $
|
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.552 2009/10/14 22:14:23 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -4419,6 +4419,18 @@ getTriggers(TableInfo tblinfo[], int numTables)
|
|||||||
if (i_tgdef >= 0)
|
if (i_tgdef >= 0)
|
||||||
{
|
{
|
||||||
tginfo[j].tgdef = strdup(PQgetvalue(res, j, i_tgdef));
|
tginfo[j].tgdef = strdup(PQgetvalue(res, j, i_tgdef));
|
||||||
|
|
||||||
|
/* remaining fields are not valid if we have tgdef */
|
||||||
|
tginfo[j].tgfname = NULL;
|
||||||
|
tginfo[j].tgtype = 0;
|
||||||
|
tginfo[j].tgnargs = 0;
|
||||||
|
tginfo[j].tgargs = NULL;
|
||||||
|
tginfo[j].tgisconstraint = false;
|
||||||
|
tginfo[j].tgdeferrable = false;
|
||||||
|
tginfo[j].tginitdeferred = false;
|
||||||
|
tginfo[j].tgconstrname = NULL;
|
||||||
|
tginfo[j].tgconstrrelid = InvalidOid;
|
||||||
|
tginfo[j].tgconstrrelname = NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.545 2009/10/12 18:10:51 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.546 2009/10/14 22:14:24 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 200910121
|
#define CATALOG_VERSION_NO 200910141
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.34 2009/07/28 02:56:31 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.35 2009/10/14 22:14:24 tgl Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* the genbki.sh script reads this file and generates .bki
|
* the genbki.sh script reads this file and generates .bki
|
||||||
@ -52,8 +52,8 @@ CATALOG(pg_trigger,2620)
|
|||||||
bool tginitdeferred; /* constraint trigger is deferred initially */
|
bool tginitdeferred; /* constraint trigger is deferred initially */
|
||||||
int2 tgnargs; /* # of extra arguments in tgargs */
|
int2 tgnargs; /* # of extra arguments in tgargs */
|
||||||
|
|
||||||
/* VARIABLE LENGTH FIELDS: */
|
/* VARIABLE LENGTH FIELDS (note: these are not supposed to be null) */
|
||||||
int2vector tgattr; /* reserved for column-specific triggers */
|
int2vector tgattr; /* column numbers, if trigger is on columns */
|
||||||
bytea tgargs; /* first\000second\000tgnargs\000 */
|
bytea tgargs; /* first\000second\000tgnargs\000 */
|
||||||
} FormData_pg_trigger;
|
} FormData_pg_trigger;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.409 2009/10/13 00:53:08 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.410 2009/10/14 22:14:24 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -1560,10 +1560,9 @@ typedef struct DropUserMappingStmt
|
|||||||
} DropUserMappingStmt;
|
} DropUserMappingStmt;
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
* Create/Drop TRIGGER Statements
|
* Create TRIGGER Statement
|
||||||
* ----------------------
|
* ----------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef struct CreateTrigStmt
|
typedef struct CreateTrigStmt
|
||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
@ -1575,6 +1574,7 @@ typedef struct CreateTrigStmt
|
|||||||
bool row; /* ROW/STATEMENT */
|
bool row; /* ROW/STATEMENT */
|
||||||
/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
|
/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
|
||||||
int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */
|
int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */
|
||||||
|
List *columns; /* column names, or NIL for all columns */
|
||||||
|
|
||||||
/* The following are used for constraint triggers (RI and unique checks) */
|
/* The following are used for constraint triggers (RI and unique checks) */
|
||||||
bool isconstraint; /* This is a constraint trigger */
|
bool isconstraint; /* This is a constraint trigger */
|
||||||
@ -1584,7 +1584,7 @@ typedef struct CreateTrigStmt
|
|||||||
} CreateTrigStmt;
|
} CreateTrigStmt;
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
* Create/Drop PROCEDURAL LANGUAGE Statement
|
* Create/Drop PROCEDURAL LANGUAGE Statements
|
||||||
* ----------------------
|
* ----------------------
|
||||||
*/
|
*/
|
||||||
typedef struct CreatePLangStmt
|
typedef struct CreatePLangStmt
|
||||||
|
@ -278,37 +278,37 @@ CREATE TABLE main_table (a int, b int);
|
|||||||
COPY main_table (a,b) FROM stdin;
|
COPY main_table (a,b) FROM stdin;
|
||||||
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
|
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
|
||||||
BEGIN
|
BEGIN
|
||||||
RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
|
RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
|
||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END;';
|
END;';
|
||||||
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
|
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
|
||||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
|
||||||
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
|
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
|
||||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
|
||||||
--
|
--
|
||||||
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
|
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
|
||||||
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
|
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
|
||||||
--
|
--
|
||||||
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
|
CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
|
||||||
EXECUTE PROCEDURE trigger_func();
|
EXECUTE PROCEDURE trigger_func('after_upd_stmt');
|
||||||
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
|
CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
|
||||||
FOR EACH ROW EXECUTE PROCEDURE trigger_func();
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
|
||||||
INSERT INTO main_table DEFAULT VALUES;
|
INSERT INTO main_table DEFAULT VALUES;
|
||||||
NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
|
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
|
||||||
NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
|
NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
|
||||||
UPDATE main_table SET a = a + 1 WHERE b < 30;
|
UPDATE main_table SET a = a + 1 WHERE b < 30;
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
|
NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
|
NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
|
NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
|
NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
|
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||||
-- UPDATE that effects zero rows should still call per-statement trigger
|
-- UPDATE that effects zero rows should still call per-statement trigger
|
||||||
UPDATE main_table SET a = a + 2 WHERE b > 100;
|
UPDATE main_table SET a = a + 2 WHERE b > 100;
|
||||||
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
|
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||||
-- COPY should fire per-row and per-statement INSERT triggers
|
-- COPY should fire per-row and per-statement INSERT triggers
|
||||||
COPY main_table (a, b) FROM stdin;
|
COPY main_table (a, b) FROM stdin;
|
||||||
NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
|
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
|
||||||
NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
|
NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
|
||||||
SELECT * FROM main_table ORDER BY a, b;
|
SELECT * FROM main_table ORDER BY a, b;
|
||||||
a | b
|
a | b
|
||||||
----+----
|
----+----
|
||||||
@ -322,6 +322,91 @@ SELECT * FROM main_table ORDER BY a, b;
|
|||||||
|
|
|
|
||||||
(8 rows)
|
(8 rows)
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row');
|
||||||
|
CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row');
|
||||||
|
CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
|
||||||
|
CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table
|
||||||
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt');
|
||||||
|
CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table
|
||||||
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt');
|
||||||
|
SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
|
||||||
|
pg_get_triggerdef
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row')
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
|
||||||
|
pg_get_triggerdef
|
||||||
|
---------------------------------------------------------
|
||||||
|
CREATE TRIGGER after_upd_a_b_row_trig
|
||||||
|
AFTER UPDATE OF a, b ON main_table
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE trigger_func('after_upd_a_b_row')
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE main_table SET a = 50;
|
||||||
|
NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||||
|
UPDATE main_table SET b = 10;
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||||
|
NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||||
|
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||||
|
-- bogus cases
|
||||||
|
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
|
||||||
|
ERROR: duplicate trigger events specified at or near "ON"
|
||||||
|
LINE 1: ...ER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_ta...
|
||||||
|
^
|
||||||
|
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');
|
||||||
|
ERROR: column "a" specified more than once
|
||||||
|
CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table
|
||||||
|
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
|
||||||
|
^
|
||||||
|
-- check dependency restrictions
|
||||||
|
ALTER TABLE main_table DROP COLUMN b;
|
||||||
|
ERROR: cannot drop table main_table column b because other objects depend on it
|
||||||
|
DETAIL: trigger after_upd_b_row_trig on table main_table depends on table main_table column b
|
||||||
|
trigger after_upd_a_b_row_trig on table main_table depends on table main_table column b
|
||||||
|
trigger after_upd_b_stmt_trig on table main_table depends on table main_table column b
|
||||||
|
HINT: Use DROP ... CASCADE to drop the dependent objects too.
|
||||||
|
-- this should succeed, but we'll roll it back to keep the triggers around
|
||||||
|
begin;
|
||||||
|
DROP TRIGGER after_upd_a_b_row_trig ON main_table;
|
||||||
|
DROP TRIGGER after_upd_b_row_trig ON main_table;
|
||||||
|
DROP TRIGGER after_upd_b_stmt_trig ON main_table;
|
||||||
|
ALTER TABLE main_table DROP COLUMN b;
|
||||||
|
rollback;
|
||||||
-- Test enable/disable triggers
|
-- Test enable/disable triggers
|
||||||
create table trigtest (i serial primary key);
|
create table trigtest (i serial primary key);
|
||||||
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
|
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
|
||||||
|
@ -220,25 +220,25 @@ COPY main_table (a,b) FROM stdin;
|
|||||||
|
|
||||||
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
|
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
|
||||||
BEGIN
|
BEGIN
|
||||||
RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
|
RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
|
||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END;';
|
END;';
|
||||||
|
|
||||||
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
|
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
|
||||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
|
||||||
|
|
||||||
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
|
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
|
||||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
|
||||||
|
|
||||||
--
|
--
|
||||||
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
|
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
|
||||||
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
|
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
|
||||||
--
|
--
|
||||||
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
|
CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
|
||||||
EXECUTE PROCEDURE trigger_func();
|
EXECUTE PROCEDURE trigger_func('after_upd_stmt');
|
||||||
|
|
||||||
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
|
CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
|
||||||
FOR EACH ROW EXECUTE PROCEDURE trigger_func();
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
|
||||||
|
|
||||||
INSERT INTO main_table DEFAULT VALUES;
|
INSERT INTO main_table DEFAULT VALUES;
|
||||||
|
|
||||||
@ -254,6 +254,45 @@ COPY main_table (a, b) FROM stdin;
|
|||||||
|
|
||||||
SELECT * FROM main_table ORDER BY a, b;
|
SELECT * FROM main_table ORDER BY a, b;
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row');
|
||||||
|
CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row');
|
||||||
|
CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
|
||||||
|
|
||||||
|
CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table
|
||||||
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt');
|
||||||
|
CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table
|
||||||
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt');
|
||||||
|
|
||||||
|
SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
|
||||||
|
SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
|
||||||
|
|
||||||
|
UPDATE main_table SET a = 50;
|
||||||
|
UPDATE main_table SET b = 10;
|
||||||
|
|
||||||
|
-- bogus cases
|
||||||
|
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
|
||||||
|
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');
|
||||||
|
|
||||||
|
-- check dependency restrictions
|
||||||
|
ALTER TABLE main_table DROP COLUMN b;
|
||||||
|
-- this should succeed, but we'll roll it back to keep the triggers around
|
||||||
|
begin;
|
||||||
|
DROP TRIGGER after_upd_a_b_row_trig ON main_table;
|
||||||
|
DROP TRIGGER after_upd_b_row_trig ON main_table;
|
||||||
|
DROP TRIGGER after_upd_b_stmt_trig ON main_table;
|
||||||
|
ALTER TABLE main_table DROP COLUMN b;
|
||||||
|
rollback;
|
||||||
|
|
||||||
-- Test enable/disable triggers
|
-- Test enable/disable triggers
|
||||||
|
|
||||||
create table trigtest (i serial primary key);
|
create table trigtest (i serial primary key);
|
||||||
|
Reference in New Issue
Block a user