Triggers on columns
Here is a patch to implement "Support triggers on columns" in our ToDo list.
The syntax is:
CREATE TRIGGER name
BEFORE UPDATE OF col1, col12, ...
ON tbl FOR EACH ROW EXECUTE PROCEDURE func();
I consulted the previous work following:
Column-level triggers (From: Greg Sabino Mullane, Date: 2005-07-04)
http://archives.postgresql.org/pgsql-patches/2005-07/msg00107.php
and completed some under-construction parts.
It's still arguable that we should add dependencies from column
triggers to referenced columns. In the present patch, dropeed
columns are just ignored and always considered as not-modified.
Please grep with "TODO: (TRIGGER)" to check the issue.
Comments welcome.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
column-trigger-20090903.patchapplication/octet-stream; name=column-trigger-20090903.patchDownload
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml 2009-09-03 10:27:51.483152714 +0900
--- column-trigger/doc/src/sgml/ref/create_trigger.sgml 2009-09-03 10:30:24.377498000 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 121,126 ****
--- 121,130 ----
<command>DELETE</command>, or <command>TRUNCATE</command>;
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
+ An optional list of comma-separated columns can be given after
+ the <command>UPDATE</command> command, by adding the word
+ <literal>OF</literal> after <command>UPDATE</command> and before
+ the list of columns.
</para>
</listitem>
</varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml 2009-09-03 10:27:51.436149664 +0900
--- column-trigger/doc/src/sgml/trigger.sgml 2009-09-03 10:29:18.860719000 +0900
***************
*** 39,45 ****
or once per <acronym>SQL</acronym> 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>
--- 39,46 ----
or once per <acronym>SQL</acronym> 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. In addition, UPDATE triggers can be set to only fire if certain
! columns are updated.
</para>
<para>
diff -cprN head/src/backend/access/heap/heapam.c column-trigger/src/backend/access/heap/heapam.c
*** head/src/backend/access/heap/heapam.c 2009-08-24 11:18:31.000000000 +0900
--- column-trigger/src/backend/access/heap/heapam.c 2009-09-03 09:24:46.158859461 +0900
*************** l2:
*** 2853,2859 ****
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! static bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
--- 2853,2859 ----
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
diff -cprN head/src/backend/commands/tablecmds.c column-trigger/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c 2009-08-24 04:23:41.000000000 +0900
--- column-trigger/src/backend/commands/tablecmds.c 2009-09-03 09:57:00.584926915 +0900
*************** ATExecDropColumn(List **wqueue, Relation
*** 4346,4351 ****
--- 4346,4354 ----
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* TODO: (TRIGGER) Don't drop if this column is being used by a trigger */
+ /* Possibly handled by dependency.c? */
+
ReleaseSysCache(tuple);
/*
*************** ATExecDropColumn(List **wqueue, Relation
*** 4384,4389 ****
--- 4387,4393 ----
if (recurse)
{
+ /* TODO: (TRIGGER) Drop column-based triggers */
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
diff -cprN head/src/backend/commands/trigger.c column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-08-05 01:08:36.000000000 +0900
--- column-trigger/src/backend/commands/trigger.c 2009-09-03 10:13:12.388599000 +0900
***************
*** 32,37 ****
--- 32,38 ----
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 66,71 ****
--- 67,74 ----
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
List *recheckIndexes);
+ static bool TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup,
+ HeapTuple newtup);
/*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 100,107 ----
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 336,343 ****
CStringGetDatum(""));
}
! /* tgattr is currently always a zero-length array */
! tgattr = buildint2vector(NULL, 0);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 341,379 ----
CStringGetDatum(""));
}
! /* build column references for UPDATE OF */
! ncolumns = list_length(stmt->columns);
! if (ncolumns == 0)
! columns = NULL;
! else
! {
! ListCell *cell;
! int x = 0;
!
! columns = (int2 *) palloc(ncolumns * sizeof(int2));
!
! foreach (cell, stmt->columns)
! {
! char *name = strVal(lfirst(cell));
! int attnum;
! int y;
!
! /* Lookup column name. System columns are not allowed. */
! attnum = attnameAttNum(rel, name, false);
!
! /* Check for duplicates */
! for (y = x - 1; y >= 0; y--)
! {
! if (columns[y] == attnum)
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_COLUMN),
! errmsg("column \"%s\" specified more than once", name)));
! }
!
! columns[x++] = attnum;
! }
! }
! tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 433,438 ****
--- 469,493 ----
Assert(!OidIsValid(indexOid));
}
+ /* TODO: (TRIGGER) Dependencies for columns. */
+ #ifdef NOT_USED
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.classId = TypeRelationId;
+ referenced.objectId = attrs[columns[i] - 1];
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+ #endif
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1625,1642 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1680,1688 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1684,1701 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1730,1738 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, newtuple))
! continue;
!
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1756,1773 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1793,1801 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1821,1838 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1849,1857 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1904,1921 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1923,1931 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1974,1991 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1984,1992 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, newtuple))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2056,2073 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 2057,2065 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3915,3933 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! /* Ignore disabled triggers */
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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
--- 3907,3914 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, rel, oldtup, newtup))
! continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4000,4002 ****
--- 3981,4031 ----
&new_event, &new_shared);
}
}
+
+ static bool
+ TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup, HeapTuple newtup)
+ {
+ 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-level triggers */
+ if (trigger->tgnattr > 0 &&
+ (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ {
+ int i;
+ bool modified;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ Assert(oldtup != NULL);
+ Assert(newtup != NULL);
+
+ modified = false;
+ for (i = 0; i < trigger->tgnattr; i++)
+ {
+ int n = trigger->tgattr[i];
+
+ if (n <= tupdesc->natts &&
+ !tupdesc->attrs[n - 1]->attisdropped &&
+ !heap_tuple_attr_equals(tupdesc, n, oldtup, newtup))
+ {
+ modified = true;
+ break;
+ }
+ }
+ if (!modified)
+ return false;
+ }
+
+ return true;
+ }
diff -cprN head/src/backend/parser/gram.y column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y 2009-08-19 08:40:20.000000000 +0900
--- column-trigger/src/backend/parser/gram.y 2009-09-03 09:29:24.482941918 +0900
*************** static TypeName *TableFuncTypeName(List
*** 248,254 ****
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <ival> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
--- 248,254 ----
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
*************** CreateTrigStmt:
*** 3160,3166 ****
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = $5;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
--- 3160,3167 ----
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = intVal(linitial($5));
! n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
*************** CreateTrigStmt:
*** 3180,3186 ****
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = $6;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
--- 3181,3188 ----
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = intVal(linitial($6));
! n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3199,3215 ****
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! if ($1 & $3)
parser_yyerror("duplicate trigger events specified");
! $$ = $1 | $3;
}
;
TriggerOneEvent:
! INSERT { $$ = TRIGGER_TYPE_INSERT; }
! | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
! | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
! | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
;
TriggerForSpec:
--- 3201,3222 ----
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! int events1 = intVal(linitial($1));
! int events2 = intVal(linitial($3));
!
! if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
! $$ = list_make2(makeInteger(events1 | events2),
! list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
! INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! | DELETE_P { $$ = 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:
diff -cprN head/src/bin/pg_dump/pg_dump.c column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c 2009-08-05 04:46:51.000000000 +0900
--- column-trigger/src/bin/pg_dump/pg_dump.c 2009-09-03 10:33:25.919870376 +0900
*************** static void check_sql_result(PGresult *r
*** 200,205 ****
--- 200,220 ----
ExecStatusType expected);
+ static char *
+ get_str_or_null(PGresult *res, int tup, int field)
+ {
+ char *value;
+
+ if (field < 0)
+ return NULL;
+
+ value = PQgetvalue(res, tup, field);
+ if (value && value[0])
+ return strdup(value);
+
+ return NULL;
+ }
+
int
main(int argc, char **argv)
{
*************** getTriggers(TableInfo tblinfo[], int num
*** 4261,4267 ****
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
--- 4276,4283 ----
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred,
! i_tgattr;
int ntups;
for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4281,4287 ****
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
--- 4297,4324 ----
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80500)
! {
! /*
! * We ignore triggers that are tied to a foreign-key constraint
! */
! appendPQExpBuffer(query,
! "SELECT tgname, "
! "tgfoid::pg_catalog.regproc AS tgfname, "
! "tgtype, tgnargs, tgargs, tgenabled, "
! "tgisconstraint, tgconstrname, tgdeferrable, "
! "tgconstrrelid, tginitdeferred, tableoid, oid, "
! "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname, "
! "pg_catalog.array_to_string((SELECT pg_catalog.array_agg(attname) "
! "FROM pg_catalog.pg_attribute, pg_catalog.unnest(tgattr) AS n "
! "WHERE attnum = n AND attrelid = tgrelid "
! "AND NOT attisdropped), ', ') AS tgattr "
! "FROM pg_catalog.pg_trigger t "
! "WHERE tgrelid = '%u'::pg_catalog.oid "
! "AND tgconstraint = 0",
! tbinfo->dobj.catId.oid);
! }
! else if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
*************** getTriggers(TableInfo tblinfo[], int num
*** 4368,4373 ****
--- 4405,4411 ----
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgattr = PQfnumber(res, "tgattr");
tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
*************** getTriggers(TableInfo tblinfo[], int num
*** 4388,4393 ****
--- 4426,4432 ----
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
+ tginfo[j].tgattr = get_str_or_null(res, j, i_tgattr);
if (tginfo[j].tgisconstraint)
{
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11066,11071 ****
--- 11105,11112 ----
appendPQExpBuffer(query, " OR UPDATE");
else
appendPQExpBuffer(query, " UPDATE");
+ if (tginfo->tgattr)
+ appendPQExpBuffer(query, " OF %s", tginfo->tgattr);
}
if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
{
diff -cprN head/src/bin/pg_dump/pg_dump.h column-trigger/src/bin/pg_dump/pg_dump.h
*** head/src/bin/pg_dump/pg_dump.h 2009-08-03 07:14:52.000000000 +0900
--- column-trigger/src/bin/pg_dump/pg_dump.h 2009-09-03 09:26:55.673887897 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ char *tgattr;
} TriggerInfo;
/*
diff -cprN head/src/include/access/heapam.h column-trigger/src/include/access/heapam.h
*** head/src/include/access/heapam.h 2009-08-24 11:18:32.000000000 +0900
--- column-trigger/src/include/access/heapam.h 2009-09-03 09:24:46.170875501 +0900
*************** extern bool heap_hot_search_buffer(ItemP
*** 86,91 ****
--- 86,93 ----
Snapshot snapshot, bool *all_dead);
extern bool heap_hot_search(ItemPointer tid, Relation relation,
Snapshot snapshot, bool *all_dead);
+ extern bool heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
+ HeapTuple tup1, HeapTuple tup2);
extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
ItemPointer tid);
diff -cprN head/src/include/catalog/pg_trigger.h column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h 2009-07-28 11:56:31.000000000 +0900
--- column-trigger/src/include/catalog/pg_trigger.h 2009-09-03 09:24:46.170875501 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* reserved for column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
--- 53,59 ----
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff -cprN head/src/include/nodes/parsenodes.h column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h 2009-08-03 07:14:53.000000000 +0900
--- column-trigger/src/include/nodes/parsenodes.h 2009-09-03 09:30:04.706842600 +0900
*************** typedef struct CreateTrigStmt
*** 1553,1558 ****
--- 1553,1559 ----
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff -cprN head/src/test/regress/expected/triggers.out column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/expected/triggers.out 2009-09-03 10:17:14.065898000 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
--- 278,314 ----
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,367 ----
|
(8 rows)
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+ UPDATE main_table SET a = 50;
+ 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_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_a_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_a_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_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ 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
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ UPDATE main_table SET a = 50;
+ NOTICE: trigger_func(after_upd_a_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_a_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_a_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(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/sql/triggers.sql 2009-09-03 10:16:55.136394000 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
--- 220,244 ----
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
INSERT INTO main_table DEFAULT VALUES;
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,279 ----
SELECT * FROM main_table ORDER BY a, b;
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+
+ UPDATE main_table SET a = 50;
+ UPDATE main_table SET b = 10;
+
+ 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');
+
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ UPDATE main_table SET a = 50;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
On Thu, Sep 03, 2009 at 10:52:09AM +0900, Itagaki Takahiro wrote:
Here is a patch to implement "Support triggers on columns" in our ToDo list.
The syntax is:
CREATE TRIGGER name
BEFORE UPDATE OF col1, col12, ...
ON tbl FOR EACH ROW EXECUTE PROCEDURE func();
Kudos!
I consulted the previous work following:
Column-level triggers (From: Greg Sabino Mullane, Date: 2005-07-04)
http://archives.postgresql.org/pgsql-patches/2005-07/msg00107.php
and completed some under-construction parts.It's still arguable that we should add dependencies from column
triggers to referenced columns.
+1 for adding the dependencies.
Cheers,
David.
In the present patch, dropeed
columns are just ignored and always considered as not-modified.
Please grep with "TODO: (TRIGGER)" to check the issue.Comments welcome.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
On Wed, Sep 2, 2009 at 9:52 PM, Itagaki
Takahiro<itagaki.takahiro@oss.ntt.co.jp> wrote:
Here is a patch to implement "Support triggers on columns" in our ToDo list.
The syntax is:
CREATE TRIGGER name
BEFORE UPDATE OF col1, col12, ...
ON tbl FOR EACH ROW EXECUTE PROCEDURE func();I consulted the previous work following:
Column-level triggers (From: Greg Sabino Mullane, Date: 2005-07-04)
http://archives.postgresql.org/pgsql-patches/2005-07/msg00107.php
and completed some under-construction parts.It's still arguable that we should add dependencies from column
triggers to referenced columns. In the present patch, dropeed
columns are just ignored and always considered as not-modified.
Please grep with "TODO: (TRIGGER)" to check the issue.Comments welcome.
Wow, so I wouldn't have to do this any more?
IF (TG_OP = 'UPDATE') THEN
IF (OLD.foo IS NOT DISTINCT FROM NEW.foo AND OLD.bar IS NOT
DISTINCT FROM NEW.bar
AND OLD.baz IS NOT DISTINCT FROM NEW.baz) THEN
RETURN NULL;
END IF;
END IF;
Apart from any possible gain in efficiency, the sheer savings in
typing sound quite awesome.
...Robert
Robert Haas <robertmhaas@gmail.com> wrote:
Wow, so I wouldn't have to do this any more?
IF (TG_OP = 'UPDATE') THEN
IF (OLD.foo IS NOT DISTINCT FROM NEW.foo AND OLD.bar IS NOT
DISTINCT FROM NEW.bar
AND OLD.baz IS NOT DISTINCT FROM NEW.baz) THEN
RETURN NULL;
END IF;
END IF;
Sure, and I found there might be difference between "UPDATE" and
"UPDATE OF {all-columns}" triggers. UPDATE trigger is always fired
when a row is updated even if none of the columns are actually
modified, but UPDATE OF {all-columns} trigger is fired only when
at least one of the columns is modified.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
David Fetter <david@fetter.org> wrote:
It's still arguable that we should add dependencies from column
triggers to referenced columns.+1 for adding the dependencies.
But how? First, I tried to use existing dependency mechanism:
ObjectAddress referenced;
referenced.classId = AttributeRelationId;
referenced.objectId = {relid};
referenced.objectSubId = {attnum};
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
but we don't use ObjectAddress with classId = AttributeRelationId
for now in any places. Does it work? or do I also need to modify
dependency.c to support dependency-to-columns?
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Itagaki Takahiro wrote:
David Fetter <david@fetter.org> wrote:
It's still arguable that we should add dependencies from column
triggers to referenced columns.+1 for adding the dependencies.
But how? First, I tried to use existing dependency mechanism:
ObjectAddress referenced;
referenced.classId = AttributeRelationId;
referenced.objectId = {relid};
referenced.objectSubId = {attnum};
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);but we don't use ObjectAddress with classId = AttributeRelationId
for now in any places. Does it work?
Well, apparently you've been tasked with making sure it works properly :-)
The only problem I see with it is the fact that the objectId is not the
attribute's OID, but it should be possible.
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
But how? First, I tried to use existing dependency mechanism:
ObjectAddress referenced;
referenced.classId = AttributeRelationId;
referenced.objectId = {relid};
referenced.objectSubId = {attnum};
This is just wrong. The correct representation of a column is
classId = RelationRelationId
objectId = relid
objectSubId = attnum
The column is a sub-object of a pg_class item, not an object in
its own right.
regards, tom lane
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
Sure, and I found there might be difference between "UPDATE" and
"UPDATE OF {all-columns}" triggers. UPDATE trigger is always fired
when a row is updated even if none of the columns are actually
modified, but UPDATE OF {all-columns} trigger is fired only when
at least one of the columns is modified.
I'm betraying the fact that I haven't read the patch, but ...
exactly how, and when, are you determining whether a column has
been "modified"? I can't count the number of times somebody
has proposed simplistic and incorrect solutions to that.
Usually they forget about BEFORE triggers changing the row.
regards, tom lane
Tom Lane wrote:
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
Sure, and I found there might be difference between "UPDATE" and
"UPDATE OF {all-columns}" triggers. UPDATE trigger is always fired
when a row is updated even if none of the columns are actually
modified, but UPDATE OF {all-columns} trigger is fired only when
at least one of the columns is modified.I'm betraying the fact that I haven't read the patch, but ...
exactly how, and when, are you determining whether a column has
been "modified"? I can't count the number of times somebody
has proposed simplistic and incorrect solutions to that.
Usually they forget about BEFORE triggers changing the row.
It uses heap_tuple_attr_equals() to check whether a certain
column is modified, or not.
Itagaki-san, isn't it more suitable to check rte->modifiedCols
than heap_tuple_attr_equals()? Although, this information is
not delivered to executor...
What is the correct behavior when UPDATE statement set a new
value but it was identical to the original value?
In this case, heap_tuple_attr_equals() cannot detect the column
is used as a target of the UPDATE.
Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>
Tom Lane <tgl@sss.pgh.pa.us> wrote:
exactly how, and when, are you determining whether a column has
been "modified"? I can't count the number of times somebody
has proposed simplistic and incorrect solutions to that.
Usually they forget about BEFORE triggers changing the row.
There are some approaches:
1. Just check conditions in alphabetical order. Ignore subsequent
modifications after the conditions are examined.
2. Recheck conditions if NEW values are modified, but triggers that
have been fired already are not executed twice.
3. Column triggers are called after non-conditional UPDATE triggers
and column triggers cannot modify NEW values.
I like approach 2. because it is the most user-friendly. There is a
possibility that another trigger changes NEW values to "unmodified"
state after some conditional triggers are executed, but it could be
admissible. The approach 3. seems to be the most strict, but hard to
use because of the restriction.
----
Just for reference:
- Oracle Database:
They support multiple triggers and UPDATE OF and WHEN clause and
can modify NEW values in trigger bodies. So they must have same
problems discussing here -- but I cannot find how they work around it...
http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/create_trigger.htm#i2064026
- MySQL:
They can modify NEW values, but no problem because they don't support
UPDATE OF, WHEN clause, nor multiple triggers for each event.
http://dev.mysql.com/doc/refman/5.4/en/create-trigger.html
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
KaiGai Kohei <kaigai@ak.jp.nec.com> wrote:
Itagaki-san, isn't it more suitable to check rte->modifiedCols
than heap_tuple_attr_equals()? Although, this information is
not delivered to executor...
I'd like to check conditions by comparing actual values but not
a target of UPDATE statement because I think almost user expects
the former behavior. Unmodified UPDATE-targets are common case
if we use a framework that generates SQL statements internally.
Anyway, we need to compare the actual values if we want to treat
NEW value modifed by another trigger correctly.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
On Thu, 2009-09-03 at 16:25 +0900, Itagaki Takahiro wrote:
I'd like to check conditions by comparing actual values but not
a target of UPDATE statement because I think almost user expects
the former behavior. Unmodified UPDATE-targets are common case
if we use a framework that generates SQL statements internally.
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.
On Sep 3, 2009, at 7:44 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
On Thu, 2009-09-03 at 16:25 +0900, Itagaki Takahiro wrote:
I'd like to check conditions by comparing actual values but not
a target of UPDATE statement because I think almost user expects
the former behavior. Unmodified UPDATE-targets are common case
if we use a framework that generates SQL statements internally.The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.
That is thorougly bizarre, IMO.
...Robert
Robert Haas wrote:
On Wed, Sep 2, 2009 at 9:52 PM, Itagaki
Takahiro<itagaki.takahiro@oss.ntt.co.jp> wrote:Here is a patch to implement "Support triggers on columns" in our ToDo list.
The syntax is:
CREATE TRIGGER name
BEFORE UPDATE OF col1, col12, ...
ON tbl FOR EACH ROW EXECUTE PROCEDURE func();I consulted the previous work following:
Column-level triggers (From: Greg Sabino Mullane, Date: 2005-07-04)
http://archives.postgresql.org/pgsql-patches/2005-07/msg00107.php
and completed some under-construction parts.It's still arguable that we should add dependencies from column
triggers to referenced columns. In the present patch, dropeed
columns are just ignored and always considered as not-modified.
Please grep with "TODO: (TRIGGER)" to check the issue.Comments welcome.
Wow, so I wouldn't have to do this any more?
IF (TG_OP = 'UPDATE') THEN
IF (OLD.foo IS NOT DISTINCT FROM NEW.foo AND OLD.bar IS NOT
DISTINCT FROM NEW.bar
AND OLD.baz IS NOT DISTINCT FROM NEW.baz) THEN
RETURN NULL;
END IF;
END IF;Apart from any possible gain in efficiency, the sheer savings in
typing sound quite awesome.
You could make it nicer with something like:
row(new.foo,new.bar,new.baz) is distinct from
row(old.foo,old.bar,old.baz)
couldn't you?
I'm actually having trouble thinking of a case where I'd find this
feature very useful.
cheers
andrew
On Thu, 2009-09-03 at 07:57 -0400, Robert Haas wrote:
On Sep 3, 2009, at 7:44 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.That is thorougly bizarre, IMO.
Well, if you find that bizarre, consider the existing behavior: Why
should an ON UPDATE row trigger fire when none of the values of the
row's columns actually change? I think if you read
TRIGGER ON UPDATE
as
TRIGER ON UPDATE OF <all columns>
then it makes some sense.
On Thu, Sep 3, 2009 at 9:51 AM, Peter Eisentraut<peter_e@gmx.net> wrote:
On Thu, 2009-09-03 at 07:57 -0400, Robert Haas wrote:
On Sep 3, 2009, at 7:44 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.That is thorougly bizarre, IMO.
Well, if you find that bizarre, consider the existing behavior: Why
should an ON UPDATE row trigger fire when none of the values of the
row's columns actually change? I think if you readTRIGGER ON UPDATE
as
TRIGER ON UPDATE OF <all columns>
then it makes some sense.
Not to me. I use triggers to maintain database invariants, such as:
CREATE TABLE foo (id serial, name varchar, number_of_bars integer not
null default 0, primary key (id));
CREATE TABLE bar (id serial, foo_id integer not null references foo (id));
By setting up INSERT, UPDATE, and DELETE triggers on bar, I can
maintain the invariant that number_of_bars for each foo is in fact the
number of bars where foo_id is the id of that foo. However, in order
to suppress unnecessary updates to the foo table, I have to have the
update trigger check whether OLD.foo_id = NEW.foo_id before it does
anything.
If TRIGGER ON UPDATE OF foo_id means whether the value actually
changed, then I can skip the check. If TRIGGER ON UPDATE OF foo_id
means whether the column was present in the update list, then it
doesn't. Perhaps there are some use cases where we can be certain
that we only care about whether the value was in the update list, and
not whether it was changed, but off the top of my head it seems like
0% of mine would fall into that category.
It also seems to me logically inconsistent that we would expose this
information via the CREATE TRIGGER interface but not to the trigger
function itself. From within the function, you can compare NEW and
OLD, but you get no visibility into which columns were actually
updated. And apparently now from within CREATE TRIGGER we'll have
just the opposite. Blech...
By the way, I completely agree that it would be useful to have a way
to suppress triggers from firing when no columns were actually
modified. But I also wouldn't argue that should be the only available
behavior. Sometimes it's useful to schedule a no-op update explicitly
for the purpose of firing triggers.
...Robert
On Thu, 2009-09-03 at 10:24 -0400, Robert Haas wrote:
If TRIGGER ON UPDATE OF foo_id means whether the value actually
changed, then I can skip the check. If TRIGGER ON UPDATE OF foo_id
means whether the column was present in the update list, then it
doesn't. Perhaps there are some use cases where we can be certain
that we only care about whether the value was in the update list, and
not whether it was changed, but off the top of my head it seems like
0% of mine would fall into that category.
Yeah, probably. I didn't make this up; I'm just reading the
standard. ;-)
But of course you can already do what you do, so you don't lose anything
if it turns out that this proposed feature ends up working the other
way.
It also seems to me logically inconsistent that we would expose this
information via the CREATE TRIGGER interface but not to the trigger
function itself. From within the function, you can compare NEW and
OLD, but you get no visibility into which columns were actually
updated. And apparently now from within CREATE TRIGGER we'll have
just the opposite. Blech...
Well, it might make sense to make this information available within the
trigger function through new variables.
Robert Haas <robertmhaas@gmail.com> wrote:
It also seems to me logically inconsistent that we would expose this
information via the CREATE TRIGGER interface but not to the trigger
function itself. From within the function, you can compare NEW and
OLD, but you get no visibility into which columns were actually
updated. And apparently now from within CREATE TRIGGER we'll have
just the opposite. Blech...
Sybase provides an "if update(columnname)" syntax to allow such tests.
Perhaps PostgreSQL could do something similar?
Sometimes it's useful to schedule a no-op update explicitly for the
purpose of firing triggers.
Yes. It's a less frequent need, but it does exist. The thing is, if
you only fire triggers if something was actually changed to a new
value, you can't get to that. If you fire on all updates you can test
whether there were actual changes. Of course, ideally, both would be
convenient.
-Kevin
Hi,
Robert Haas <robertmhaas@gmail.com> writes:
By the way, I completely agree that it would be useful to have a way
to suppress triggers from firing when no columns were actually
modified.
http://www.postgresql.org/docs/8.4/static/functions-trigger.html
Currently PostgreSQL provides one built in trigger function,
suppress_redundant_updates_trigger, which will prevent any update that
does not actually change the data in the row from taking place, in
contrast to the normal behaviour which always performs the update
regardless of whether or not the data has changed. (This normal
behaviour makes updates run faster, since no checking is required, and
is also useful in certain cases.)
...
The suppress_redundant_updates_trigger function can be added to a table like this:
CREATE TRIGGER z_min_update
BEFORE UPDATE ON tablename
FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
Regards,
--
dim
On Thu, Sep 3, 2009 at 10:37 AM, Peter Eisentraut<peter_e@gmx.net> wrote:
On Thu, 2009-09-03 at 10:24 -0400, Robert Haas wrote:
If TRIGGER ON UPDATE OF foo_id means whether the value actually
changed, then I can skip the check. If TRIGGER ON UPDATE OF foo_id
means whether the column was present in the update list, then it
doesn't. Perhaps there are some use cases where we can be certain
that we only care about whether the value was in the update list, and
not whether it was changed, but off the top of my head it seems like
0% of mine would fall into that category.Yeah, probably. I didn't make this up; I'm just reading the
standard. ;-)But of course you can already do what you do, so you don't lose anything
if it turns out that this proposed feature ends up working the other
way.
Sure, but I don't think it makes a lot of sense to spend a lot of time
implementing the standard behavior unless someone can provide a
plausible use case. If that means we have to give our non-standard
feature an incompatible syntax or whatever so as not to create
confusion with the "standard" behavior, then let's do that, because it
sounds WAY more useful.
...Robert
On Thu, 3 Sep 2009, Robert Haas wrote:
On Thu, Sep 3, 2009 at 9:51 AM, Peter Eisentraut<peter_e@gmx.net> wrote:
On Thu, 2009-09-03 at 07:57 -0400, Robert Haas wrote:
On Sep 3, 2009, at 7:44 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.That is thorougly bizarre, IMO.
Well, if you find that bizarre, consider the existing behavior: Why
should an ON UPDATE row trigger fire when none of the values of the
row's columns actually change? �I think if you readTRIGGER ON UPDATE
as
TRIGER ON UPDATE OF <all columns>
then it makes some sense.
Not to me. I use triggers to maintain database invariants, such as:
CREATE TABLE foo (id serial, name varchar, number_of_bars integer not
null default 0, primary key (id));
CREATE TABLE bar (id serial, foo_id integer not null references foo (id));By setting up INSERT, UPDATE, and DELETE triggers on bar, I can
maintain the invariant that number_of_bars for each foo is in fact the
number of bars where foo_id is the id of that foo. However, in order
to suppress unnecessary updates to the foo table, I have to have the
update trigger check whether OLD.foo_id = NEW.foo_id before it does
anything.If TRIGGER ON UPDATE OF foo_id means whether the value actually
changed, then I can skip the check. If TRIGGER ON UPDATE OF foo_id
means whether the column was present in the update list, then it
doesn't. Perhaps there are some use cases where we can be certain
that we only care about whether the value was in the update list, and
not whether it was changed, but off the top of my head it seems like
0% of mine would fall into that category.It also seems to me logically inconsistent that we would expose this
information via the CREATE TRIGGER interface but not to the trigger
function itself. From within the function, you can compare NEW and
OLD, but you get no visibility into which columns were actually
updated. And apparently now from within CREATE TRIGGER we'll have
just the opposite. Blech...By the way, I completely agree that it would be useful to have a way
to suppress triggers from firing when no columns were actually
modified. But I also wouldn't argue that should be the only available
behavior. Sometimes it's useful to schedule a no-op update explicitly
for the purpose of firing triggers.
A simple use case would be to update a timestamp column with CURRENT_TIMESTAMP
as instance.
...Robert
--
Guillaume (ioguix) de Rorthais
From pgsql-hackers-owner@postgresql.org Thu Sep 3 14:15:44 2009
Received: from localhost (unknown [200.46.208.211])
by mail.postgresql.org (Postfix) with ESMTP id 097A5634E22
for <pgsql-hackers-postgresql.org@mail.postgresql.org>; Thu, 3 Sep 2009 14:15:44 -0300 (ADT)
Received: from mail.postgresql.org ([200.46.204.86])
by localhost (mx1.hub.org [200.46.208.211]) (amavisd-maia, port 10024)
with ESMTP id 61422-03
for <pgsql-hackers-postgresql.org@mail.postgresql.org>;
Thu, 3 Sep 2009 17:15:29 +0000 (UTC)
X-Greylist: from auto-whitelisted by SQLgrey-1.7.6
Received: from fetter.org (start.fetter.org [66.92.188.65])
by mail.postgresql.org (Postfix) with ESMTP id 98BBA633CA1
for <pgsql-hackers@postgreSQL.org>; Thu, 3 Sep 2009 14:15:23 -0300 (ADT)
Received: by fetter.org (Postfix, from userid 500)
id 9123BFBCBF6; Thu, 3 Sep 2009 10:15:22 -0700 (PDT)
Date: Thu, 3 Sep 2009 10:15:22 -0700
From: David Fetter <david@fetter.org>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: pgsql-hackers@postgreSQL.org
Subject: Re: gcc versus division-by-zero traps
Message-ID: <20090903171522.GT8410@fetter.org>
References: <14006.1251987857@sss.pgh.pa.us>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
In-Reply-To: <14006.1251987857@sss.pgh.pa.us>
User-Agent: Mutt/1.5.19 (2009-01-05)
X-Virus-Scanned: Maia Mailguard 1.0.1
X-Spam-Status: No, hits=-1.579 tagged_above=-10 required=5 tests=AWL=1.020,
BAYES_00=-2.599
X-Spam-Level:
X-Archive-Number: 200909/198
X-Sequence-Number: 145004
On Thu, Sep 03, 2009 at 10:24:17AM -0400, Tom Lane wrote:
We have seen several previous reports of regression test failures
due to division by zero causing SIGFPE, even though the code should
never reach the division command:http://archives.postgresql.org/pgsql-bugs/2006-11/msg00180.php
http://archives.postgresql.org/pgsql-bugs/2007-11/msg00032.php
http://archives.postgresql.org/pgsql-bugs/2008-05/msg00148.php
http://archives.postgresql.org/pgsql-general/2009-05/msg00774.phpIt's always been on non-mainstream architectures so it was hard to
investigate. But I have finally been able to reproduce this:
https://bugzilla.redhat.com/show_bug.cgi?id=520916While s390x is still not quite mainstream, at least I can get
access to one ;-).
Do you also have access to z/OS with Unix System Services? IBM's
compiler, c89, is amazingly strict, and should help us flush out bugs. :)
What turns out to be the case is that
"simple" test cases like
if (y == 0)
single_function_call(...);
z = x / y;
do not show the problem; you need something pretty complex in the
if-command. Like, say, an ereport() construct. So that's why the gcc
boys haven't already had visits from mobs of villagers about this.I hope that the bug will get fixed in due course, but even if they
respond pretty quickly it will be years before the problem disappears
from every copy of gcc in the field. So I'm thinking that it would
behoove us to install a workaround, now that we've characterized the
problem sufficiently. What I am thinking is that in the three
functions known to exhibit the bug (int24div, int28div, int48div)
we should do something like this:if (arg2 == 0) + { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + /* ensure compiler realizes we don't reach the division */ + PG_RETURN_NULL(); + } /* No overflow is possible */ PG_RETURN_INT64((int64) arg1 / arg2);Thoughts?
How big would this change be? How would people know to use that
construct everywhere it's appropriate?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
ioguix@free.fr escribi�:
A simple use case would be to update a timestamp column with
CURRENT_TIMESTAMP as instance.
No, because you want to update the timestamp in all cases, whatever
columns the update actually updates.
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On tor, 2009-09-03 at 11:19 -0400, Robert Haas wrote:
Sure, but I don't think it makes a lot of sense to spend a lot of time
implementing the standard behavior unless someone can provide a
plausible use case.
One use case is porting Oracle applications. I see a lot of that used
there. The original proposer might had have other ideas.
On Thu, Sep 3, 2009 at 2:16 PM, Peter Eisentraut<peter_e@gmx.net> wrote:
On tor, 2009-09-03 at 11:19 -0400, Robert Haas wrote:
Sure, but I don't think it makes a lot of sense to spend a lot of time
implementing the standard behavior unless someone can provide a
plausible use case.One use case is porting Oracle applications. I see a lot of that used
there. The original proposer might had have other ideas.
Perhaps so, but his second post to the thread suggests that he didn't
have the interpretation you're proposing in mind.
...Robert
Peter Eisentraut <peter_e@gmx.net> writes:
On tor, 2009-09-03 at 11:19 -0400, Robert Haas wrote:
Sure, but I don't think it makes a lot of sense to spend a lot of time
implementing the standard behavior unless someone can provide a
plausible use case.
One use case is porting Oracle applications. I see a lot of that used
there. The original proposer might had have other ideas.
That's only a good argument if we are prepared to implement exactly
Oracle's semantics for the feature ... which, frankly, I have no reason
whatever to assume are exactly like the standard's :-(
regards, tom lane
Peter Eisentraut <peter_e@gmx.net> wrote:
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.
Hmmm, what does the SQL standard say about modification of NEW values?
Should we fire column triggers when their columns are mentioned in the
UPDATE statement, but modified by another triggers?
I believe we should fire them, but it is inconsistent because we will
make different decisions whether NEW values are modified or not.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Here is a updated version of column-trigger patch.
Changes from the previous patch:
* Add dependency of columns with recordDependencyOn().
Regression tests are also adjusted.
* Recheck columns if NEW values are modified, but each trigger
will be firec only one per row.
Peter Eisentraut <peter_e@gmx.net> wrote:
The SQL standard specifies that a trigger is fired if the column is
mentioned in the UPDATE statement, independent of whether the value is
actually changed through the update.
We are discussing how to determine modified columns
(UPDATE-target vs. changes of actual values), but in the patch
I used value-based checking. The reasons are:
1. Other triggers could modify referred columns even if the columns
are not specifed in UPDATE-target.
2. IMHO, almost users don't expect their triggers are not called
if the actual values are not modified.
3. Restriction of implementation; We don't have RTE in trigger
routine for now. The current patch doesn't modify codes a lot.
Comments welcome.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
column-trigger-20090907.patchapplication/octet-stream; name=column-trigger-20090907.patchDownload
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml 2008-11-14 19:22:46.000000000 +0900
--- column-trigger/doc/src/sgml/ref/create_trigger.sgml 2009-09-07 09:51:49.398449030 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 121,126 ****
--- 121,130 ----
<command>DELETE</command>, or <command>TRUNCATE</command>;
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
+ An optional list of comma-separated columns can be given after
+ the <command>UPDATE</command> command, by adding the word
+ <literal>OF</literal> after <command>UPDATE</command> and before
+ the list of columns.
</para>
</listitem>
</varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml 2009-08-05 07:04:37.000000000 +0900
--- column-trigger/doc/src/sgml/trigger.sgml 2009-09-07 09:51:49.398449030 +0900
***************
*** 39,45 ****
or once per <acronym>SQL</acronym> 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>
--- 39,46 ----
or once per <acronym>SQL</acronym> 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. In addition, UPDATE triggers can be set to only fire if certain
! columns are updated.
</para>
<para>
diff -cprN head/src/backend/access/heap/heapam.c column-trigger/src/backend/access/heap/heapam.c
*** head/src/backend/access/heap/heapam.c 2009-08-24 11:18:31.000000000 +0900
--- column-trigger/src/backend/access/heap/heapam.c 2009-09-07 09:51:49.399550520 +0900
*************** l2:
*** 2853,2859 ****
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! static bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
--- 2853,2859 ----
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
diff -cprN head/src/backend/commands/trigger.c column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-08-05 01:08:36.000000000 +0900
--- column-trigger/src/backend/commands/trigger.c 2009-09-07 09:54:38.469478147 +0900
***************
*** 32,37 ****
--- 32,38 ----
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 66,71 ****
--- 67,74 ----
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
List *recheckIndexes);
+ static bool TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup,
+ HeapTuple newtup);
/*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 100,107 ----
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 336,343 ****
CStringGetDatum(""));
}
! /* tgattr is currently always a zero-length array */
! tgattr = buildint2vector(NULL, 0);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 341,379 ----
CStringGetDatum(""));
}
! /* build column references for UPDATE OF */
! ncolumns = list_length(stmt->columns);
! if (ncolumns == 0)
! columns = NULL;
! else
! {
! ListCell *cell;
! int x = 0;
!
! columns = (int2 *) palloc(ncolumns * sizeof(int2));
!
! foreach (cell, stmt->columns)
! {
! char *name = strVal(lfirst(cell));
! int attnum;
! int y;
!
! /* Lookup column name. System columns are not allowed. */
! attnum = attnameAttNum(rel, name, false);
!
! /* Check for duplicates */
! for (y = x - 1; y >= 0; y--)
! {
! if (columns[y] == attnum)
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_COLUMN),
! errmsg("column \"%s\" specified more than once", name)));
! }
!
! columns[x++] = attnum;
! }
! }
! tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 433,438 ****
--- 469,491 ----
Assert(!OidIsValid(indexOid));
}
+ /* Add dependency on columns */
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1625,1642 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1678,1686 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1684,1701 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1728,1736 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, newtuple))
! continue;
!
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1756,1773 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1791,1799 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1821,1838 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1847,1855 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1904,1921 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1921,1929 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1953,1958 ****
--- 1961,1967 ----
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ bool *done;
trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
if (trigtuple == NULL)
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1970,1991 ****
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1979,1994 ----
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ done = (bool *) palloc0(sizeof(bool) * ntrigs);
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (done[i])
! continue;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, newtuple))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1996,2006 ****
--- 1999,2013 ----
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
+ done[i] = true;
if (oldtuple != newtuple && oldtuple != intuple)
heap_freetuple(oldtuple);
if (newtuple == NULL)
break;
+ if (oldtuple != newtuple)
+ i = 0; /* rewind if modifed to recheck column triggers. */
}
+ pfree(done);
heap_freetuple(trigtuple);
return newtuple;
}
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2056,2073 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 2063,2071 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3915,3933 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! /* Ignore disabled triggers */
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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
--- 3913,3920 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, rel, oldtup, newtup))
! continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4000,4002 ****
--- 3987,4042 ----
&new_event, &new_shared);
}
}
+
+ static bool
+ TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup, HeapTuple newtup)
+ {
+ 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-level triggers */
+ if (trigger->tgnattr > 0 &&
+ (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ {
+ int i;
+ bool modified;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+
+ Assert(oldtup != NULL);
+ Assert(newtup != NULL);
+
+ modified = false;
+ for (i = 0; i < trigger->tgnattr; i++)
+ {
+ int n = trigger->tgattr[i];
+
+ if (n <= tupdesc->natts &&
+ !tupdesc->attrs[n - 1]->attisdropped &&
+ #ifdef NOT_USED
+ bms_is_member(n, rte->modifiedCols) /* where can we get RTE? */
+ #else
+ !heap_tuple_attr_equals(tupdesc, n, oldtup, newtup)
+ #endif
+ )
+ {
+ modified = true;
+ break;
+ }
+ }
+ if (!modified)
+ return false;
+ }
+
+ return true;
+ }
diff -cprN head/src/backend/parser/gram.y column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y 2009-08-19 08:40:20.000000000 +0900
--- column-trigger/src/backend/parser/gram.y 2009-09-07 09:51:49.404507192 +0900
*************** static TypeName *TableFuncTypeName(List
*** 248,254 ****
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <ival> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
--- 248,254 ----
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
*************** CreateTrigStmt:
*** 3160,3166 ****
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = $5;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
--- 3160,3167 ----
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = intVal(linitial($5));
! n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
*************** CreateTrigStmt:
*** 3180,3186 ****
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = $6;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
--- 3181,3188 ----
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = intVal(linitial($6));
! n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3199,3215 ****
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! if ($1 & $3)
parser_yyerror("duplicate trigger events specified");
! $$ = $1 | $3;
}
;
TriggerOneEvent:
! INSERT { $$ = TRIGGER_TYPE_INSERT; }
! | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
! | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
! | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
;
TriggerForSpec:
--- 3201,3222 ----
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! int events1 = intVal(linitial($1));
! int events2 = intVal(linitial($3));
!
! if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
! $$ = list_make2(makeInteger(events1 | events2),
! list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
! INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! | DELETE_P { $$ = 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:
diff -cprN head/src/bin/pg_dump/pg_dump.c column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c 2009-08-05 04:46:51.000000000 +0900
--- column-trigger/src/bin/pg_dump/pg_dump.c 2009-09-07 09:51:49.408550111 +0900
*************** static void check_sql_result(PGresult *r
*** 200,205 ****
--- 200,220 ----
ExecStatusType expected);
+ static char *
+ get_str_or_null(PGresult *res, int tup, int field)
+ {
+ char *value;
+
+ if (field < 0)
+ return NULL;
+
+ value = PQgetvalue(res, tup, field);
+ if (value && value[0])
+ return strdup(value);
+
+ return NULL;
+ }
+
int
main(int argc, char **argv)
{
*************** getTriggers(TableInfo tblinfo[], int num
*** 4261,4267 ****
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
--- 4276,4283 ----
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred,
! i_tgattr;
int ntups;
for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4281,4287 ****
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
--- 4297,4324 ----
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80500)
! {
! /*
! * We ignore triggers that are tied to a foreign-key constraint
! */
! appendPQExpBuffer(query,
! "SELECT tgname, "
! "tgfoid::pg_catalog.regproc AS tgfname, "
! "tgtype, tgnargs, tgargs, tgenabled, "
! "tgisconstraint, tgconstrname, tgdeferrable, "
! "tgconstrrelid, tginitdeferred, tableoid, oid, "
! "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname, "
! "pg_catalog.array_to_string((SELECT pg_catalog.array_agg(attname) "
! "FROM pg_catalog.pg_attribute, pg_catalog.unnest(tgattr) AS n "
! "WHERE attnum = n AND attrelid = tgrelid "
! "AND NOT attisdropped), ', ') AS tgattr "
! "FROM pg_catalog.pg_trigger t "
! "WHERE tgrelid = '%u'::pg_catalog.oid "
! "AND tgconstraint = 0",
! tbinfo->dobj.catId.oid);
! }
! else if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
*************** getTriggers(TableInfo tblinfo[], int num
*** 4368,4373 ****
--- 4405,4411 ----
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgattr = PQfnumber(res, "tgattr");
tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
*************** getTriggers(TableInfo tblinfo[], int num
*** 4388,4393 ****
--- 4426,4432 ----
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
+ tginfo[j].tgattr = get_str_or_null(res, j, i_tgattr);
if (tginfo[j].tgisconstraint)
{
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11066,11071 ****
--- 11105,11112 ----
appendPQExpBuffer(query, " OR UPDATE");
else
appendPQExpBuffer(query, " UPDATE");
+ if (tginfo->tgattr)
+ appendPQExpBuffer(query, " OF %s", tginfo->tgattr);
}
if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
{
diff -cprN head/src/bin/pg_dump/pg_dump.h column-trigger/src/bin/pg_dump/pg_dump.h
*** head/src/bin/pg_dump/pg_dump.h 2009-08-03 07:14:52.000000000 +0900
--- column-trigger/src/bin/pg_dump/pg_dump.h 2009-09-07 09:51:49.408550111 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ char *tgattr;
} TriggerInfo;
/*
diff -cprN head/src/include/access/heapam.h column-trigger/src/include/access/heapam.h
*** head/src/include/access/heapam.h 2009-08-24 11:18:32.000000000 +0900
--- column-trigger/src/include/access/heapam.h 2009-09-07 09:51:49.409550205 +0900
*************** extern bool heap_hot_search_buffer(ItemP
*** 86,91 ****
--- 86,93 ----
Snapshot snapshot, bool *all_dead);
extern bool heap_hot_search(ItemPointer tid, Relation relation,
Snapshot snapshot, bool *all_dead);
+ extern bool heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
+ HeapTuple tup1, HeapTuple tup2);
extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
ItemPointer tid);
diff -cprN head/src/include/catalog/pg_trigger.h column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h 2009-07-28 11:56:31.000000000 +0900
--- column-trigger/src/include/catalog/pg_trigger.h 2009-09-07 09:51:49.409550205 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* reserved for column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
--- 53,59 ----
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff -cprN head/src/include/nodes/parsenodes.h column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h 2009-08-03 07:14:53.000000000 +0900
--- column-trigger/src/include/nodes/parsenodes.h 2009-09-07 09:51:49.409550205 +0900
*************** typedef struct CreateTrigStmt
*** 1553,1558 ****
--- 1553,1559 ----
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff -cprN head/src/test/regress/expected/triggers.out column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/expected/triggers.out 2009-09-07 09:51:49.410550443 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
--- 278,314 ----
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,363 ----
|
(8 rows)
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+ UPDATE main_table SET a = 50;
+ 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_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_a_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_a_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_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE: trigger_func(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ 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
+ 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_a_b_row_trig on table main_table depends on table main_table column b
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/sql/triggers.sql 2009-09-07 09:51:49.411483832 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
--- 220,244 ----
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
INSERT INTO main_table DEFAULT VALUES;
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,279 ----
SELECT * FROM main_table ORDER BY a, b;
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+
+ UPDATE main_table SET a = 50;
+ UPDATE main_table SET b = 10;
+
+ 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');
+
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
On Mon, 2009-09-07 at 11:20 +0900, Itagaki Takahiro wrote:
We are discussing how to determine modified columns
(UPDATE-target vs. changes of actual values), but in the patch
I used value-based checking. The reasons are:
If you implement a new feature using syntax from the standard, you have
to implement the semantics of the standard. If you don't like the
semantics of the standard, use a different syntax.
2. IMHO, almost users don't expect their triggers are not called
if the actual values are not modified.
Well, as we saw upthread, there can be different valid opinions on this.
But consider the following:
- Statement triggers are called even if the table was not actually
changed in a semantically significant way.
- Row triggers are called even if the row was not actually changed in a
semantically significant way.
Therefore, it cannot be completely unexpected if column triggers are
called even if the column was not actually changed in a semantically
significant way.
Peter Eisentraut <peter_e@gmx.net> wrote:
Therefore, it cannot be completely unexpected if column triggers are
called even if the column was not actually changed in a semantically
significant way.
Ok, the attached patch implements standard-compliant version of
column trigger.
Retrieving modified columns is not so difficult as I expected.
It is in:
rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols
and the information are passed from caller to trigger routines.
However, to be honest, I think standard-compliant column trigger is
useless... I'm thinking additional extension for triggers -- if we
want to check modifications of actual values, it could be defined as:
CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col <> OLD.col) EXECUTE PROCEDURE trigger_func();
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
standard-column-trigger-20090907.patchapplication/octet-stream; name=standard-column-trigger-20090907.patchDownload
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml 2008-11-14 19:22:46.000000000 +0900
--- standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml 2009-09-07 18:51:04.251299554 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 121,126 ****
--- 121,130 ----
<command>DELETE</command>, or <command>TRUNCATE</command>;
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
+ An optional list of comma-separated columns can be given after
+ the <command>UPDATE</command> command, by adding the word
+ <literal>OF</literal> after <command>UPDATE</command> and before
+ the list of columns.
</para>
</listitem>
</varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml standard-column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml 2009-08-05 07:04:37.000000000 +0900
--- standard-column-trigger/doc/src/sgml/trigger.sgml 2009-09-07 19:30:46.807632961 +0900
***************
*** 39,45 ****
or once per <acronym>SQL</acronym> 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>
--- 39,46 ----
or once per <acronym>SQL</acronym> 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. In addition, UPDATE triggers can be set to only fire if certain
! columns are mentioned in SET columns of the UPDATE statement.
</para>
<para>
diff -cprN head/src/backend/access/heap/heapam.c standard-column-trigger/src/backend/access/heap/heapam.c
*** head/src/backend/access/heap/heapam.c 2009-08-24 11:18:31.000000000 +0900
--- standard-column-trigger/src/backend/access/heap/heapam.c 2009-09-07 18:51:04.253299279 +0900
*************** l2:
*** 2853,2859 ****
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! static bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
--- 2853,2859 ----
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
! bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
HeapTuple tup1, HeapTuple tup2)
{
diff -cprN head/src/backend/commands/trigger.c standard-column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-08-05 01:08:36.000000000 +0900
--- standard-column-trigger/src/backend/commands/trigger.c 2009-09-07 19:22:52.650301301 +0900
***************
*** 30,37 ****
--- 30,40 ----
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+ #include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 65,71 ****
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes);
/*
--- 68,75 ----
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols);
! static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
/*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 101,108 ----
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 336,343 ****
CStringGetDatum(""));
}
! /* tgattr is currently always a zero-length array */
! tgattr = buildint2vector(NULL, 0);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 342,380 ----
CStringGetDatum(""));
}
! /* build column references for UPDATE OF */
! ncolumns = list_length(stmt->columns);
! if (ncolumns == 0)
! columns = NULL;
! else
! {
! ListCell *cell;
! int x = 0;
!
! columns = (int2 *) palloc(ncolumns * sizeof(int2));
!
! foreach (cell, stmt->columns)
! {
! char *name = strVal(lfirst(cell));
! int attnum;
! int y;
!
! /* Lookup column name. System columns are not allowed. */
! attnum = attnameAttNum(rel, name, false);
!
! /* Check for duplicates */
! for (y = x - 1; y >= 0; y--)
! {
! if (columns[y] == attnum)
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_COLUMN),
! errmsg("column \"%s\" specified more than once", name)));
! }
!
! columns[x++] = attnum;
! }
! }
! tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 433,438 ****
--- 470,492 ----
Assert(!OidIsValid(indexOid));
}
+ /* Add dependency on columns */
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1625,1642 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1679,1687 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASInsertTriggers(EState *estate, Res
*** 1658,1664 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1703,1709 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1684,1701 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1729,1737 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARInsertTriggers(EState *estate, Res
*** 1720,1726 ****
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes);
}
void
--- 1756,1762 ----
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes, NULL);
}
void
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1756,1773 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1792,1800 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASDeleteTriggers(EState *estate, Res
*** 1789,1795 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
--- 1816,1822 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL, NULL);
}
bool
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1821,1838 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1848,1856 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARDeleteTriggers(EState *estate, Res
*** 1866,1872 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
--- 1884,1890 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1904,1921 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1922,1930 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1937,1943 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1946,1952 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1953,1963 ****
--- 1962,1975 ----
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ Bitmapset *modifiedCols;
trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
if (trigtuple == NULL)
return NULL;
+ modifiedCols = rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols;
+
/*
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update.
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1974,1991 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1986,1994 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2018,2024 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes);
heap_freetuple(trigtuple);
}
}
--- 2021,2028 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes,
! rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols);
heap_freetuple(trigtuple);
}
}
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2056,2073 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 2060,2068 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASTruncateTriggers(EState *estate, R
*** 2089,2095 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
--- 2084,2090 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL, NULL);
}
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3813,3819 ****
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3808,3814 ----
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3915,3933 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! /* Ignore disabled triggers */
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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
--- 3910,3917 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4000,4002 ****
--- 3984,4026 ----
&new_event, &new_shared);
}
}
+
+ static bool
+ TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols)
+ {
+ 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-level triggers */
+ if (trigger->tgnattr > 0 && modifiedCols != NULL &&
+ (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ {
+ 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;
+ }
diff -cprN head/src/backend/parser/gram.y standard-column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y 2009-08-19 08:40:20.000000000 +0900
--- standard-column-trigger/src/backend/parser/gram.y 2009-09-07 18:51:04.257301684 +0900
*************** static TypeName *TableFuncTypeName(List
*** 248,254 ****
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <ival> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
--- 248,254 ----
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
*************** CreateTrigStmt:
*** 3160,3166 ****
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = $5;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
--- 3160,3167 ----
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = intVal(linitial($5));
! n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
*************** CreateTrigStmt:
*** 3180,3186 ****
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = $6;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
--- 3181,3188 ----
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = intVal(linitial($6));
! n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3199,3215 ****
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! if ($1 & $3)
parser_yyerror("duplicate trigger events specified");
! $$ = $1 | $3;
}
;
TriggerOneEvent:
! INSERT { $$ = TRIGGER_TYPE_INSERT; }
! | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
! | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
! | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
;
TriggerForSpec:
--- 3201,3222 ----
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! int events1 = intVal(linitial($1));
! int events2 = intVal(linitial($3));
!
! if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
! $$ = list_make2(makeInteger(events1 | events2),
! list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
! INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! | DELETE_P { $$ = 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:
diff -cprN head/src/bin/pg_dump/pg_dump.c standard-column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c 2009-08-05 04:46:51.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.c 2009-09-07 18:51:04.261297461 +0900
*************** static void check_sql_result(PGresult *r
*** 200,205 ****
--- 200,220 ----
ExecStatusType expected);
+ static char *
+ get_str_or_null(PGresult *res, int tup, int field)
+ {
+ char *value;
+
+ if (field < 0)
+ return NULL;
+
+ value = PQgetvalue(res, tup, field);
+ if (value && value[0])
+ return strdup(value);
+
+ return NULL;
+ }
+
int
main(int argc, char **argv)
{
*************** getTriggers(TableInfo tblinfo[], int num
*** 4261,4267 ****
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
--- 4276,4283 ----
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred,
! i_tgattr;
int ntups;
for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4281,4287 ****
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
--- 4297,4324 ----
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80500)
! {
! /*
! * We ignore triggers that are tied to a foreign-key constraint
! */
! appendPQExpBuffer(query,
! "SELECT tgname, "
! "tgfoid::pg_catalog.regproc AS tgfname, "
! "tgtype, tgnargs, tgargs, tgenabled, "
! "tgisconstraint, tgconstrname, tgdeferrable, "
! "tgconstrrelid, tginitdeferred, tableoid, oid, "
! "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname, "
! "pg_catalog.array_to_string((SELECT pg_catalog.array_agg(attname) "
! "FROM pg_catalog.pg_attribute, pg_catalog.unnest(tgattr) AS n "
! "WHERE attnum = n AND attrelid = tgrelid "
! "AND NOT attisdropped), ', ') AS tgattr "
! "FROM pg_catalog.pg_trigger t "
! "WHERE tgrelid = '%u'::pg_catalog.oid "
! "AND tgconstraint = 0",
! tbinfo->dobj.catId.oid);
! }
! else if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
*************** getTriggers(TableInfo tblinfo[], int num
*** 4368,4373 ****
--- 4405,4411 ----
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgattr = PQfnumber(res, "tgattr");
tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
*************** getTriggers(TableInfo tblinfo[], int num
*** 4388,4393 ****
--- 4426,4432 ----
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
+ tginfo[j].tgattr = get_str_or_null(res, j, i_tgattr);
if (tginfo[j].tgisconstraint)
{
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11066,11071 ****
--- 11105,11112 ----
appendPQExpBuffer(query, " OR UPDATE");
else
appendPQExpBuffer(query, " UPDATE");
+ if (tginfo->tgattr)
+ appendPQExpBuffer(query, " OF %s", tginfo->tgattr);
}
if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
{
diff -cprN head/src/bin/pg_dump/pg_dump.h standard-column-trigger/src/bin/pg_dump/pg_dump.h
*** head/src/bin/pg_dump/pg_dump.h 2009-08-03 07:14:52.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.h 2009-09-07 18:51:04.261297461 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ char *tgattr;
} TriggerInfo;
/*
diff -cprN head/src/include/access/heapam.h standard-column-trigger/src/include/access/heapam.h
*** head/src/include/access/heapam.h 2009-08-24 11:18:32.000000000 +0900
--- standard-column-trigger/src/include/access/heapam.h 2009-09-07 18:51:04.261297461 +0900
*************** extern bool heap_hot_search_buffer(ItemP
*** 86,91 ****
--- 86,93 ----
Snapshot snapshot, bool *all_dead);
extern bool heap_hot_search(ItemPointer tid, Relation relation,
Snapshot snapshot, bool *all_dead);
+ extern bool heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
+ HeapTuple tup1, HeapTuple tup2);
extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
ItemPointer tid);
diff -cprN head/src/include/catalog/pg_trigger.h standard-column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h 2009-07-28 11:56:31.000000000 +0900
--- standard-column-trigger/src/include/catalog/pg_trigger.h 2009-09-07 18:51:04.262312278 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* reserved for column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
--- 53,59 ----
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff -cprN head/src/include/nodes/parsenodes.h standard-column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h 2009-08-03 07:14:53.000000000 +0900
--- standard-column-trigger/src/include/nodes/parsenodes.h 2009-09-07 18:51:04.262312278 +0900
*************** typedef struct CreateTrigStmt
*** 1553,1558 ****
--- 1553,1559 ----
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff -cprN head/src/test/regress/expected/triggers.out standard-column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/expected/triggers.out 2009-09-07 19:26:05.637531000 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
--- 278,314 ----
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, 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
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,379 ----
|
(8 rows)
+ -- Column-level triggers should only fire on after row-level updates
+ 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');
+ UPDATE main_table SET a = 50;
+ 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_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ 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
+ 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
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql standard-column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/sql/triggers.sql 2009-09-07 19:15:03.211026000 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
--- 220,244 ----
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,282 ----
SELECT * FROM main_table ORDER BY a, b;
+ -- Column-level triggers should only fire on after row-level updates
+ 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');
+
+ UPDATE main_table SET a = 50;
+ UPDATE main_table SET b = 10;
+
+ 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');
+
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
On Mon, Sep 07, 2009 at 07:53:01PM +0900, Itagaki Takahiro wrote:
However, to be honest, I think standard-compliant column trigger is
useless... I'm thinking additional extension for triggers -- if we
want to check modifications of actual values, it could be defined
as:CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col <> OLD.col) EXECUTE PROCEDURE trigger_func();
That should probably read:
CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col IS DISTINCT FROM OLD.col) EXECUTE PROCEDURE trigger_func();
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
David Fetter <david@fetter.org> wrote:
CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col IS DISTINCT FROM OLD.col)
EXECUTE PROCEDURE trigger_func();
How much does that buy you versus including this at the start of
trigger_func:
IF (NEW.col IS NOT DISTINCT FROM OLD.col) THEN
RETURN NEW;
END IF;
What about the desire (mentioned up-thread) to test whether a column
was the target of an update SET list within the trigger function?
-Kevin
On Sep 8, 2009, at 7:38 AM, Kevin Grittner wrote:
David Fetter <david@fetter.org> wrote:
CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col IS DISTINCT FROM OLD.col)
EXECUTE PROCEDURE trigger_func();How much does that buy you versus including this at the start of
trigger_func:
On the face, it buys nothing, IMO. ISTM, looking at the examples, that
the above syntax would lead to redundant logic if the particular
trigger_func() were used by multiple TRIGGERs. That is, assuming the
precondition is necessary for proper functionality, it would have to
be repeated on all the TRIGGERs that trigger_func() would be executed
by.
[..moving away from the isolated use-case of the example]
However, if trigger_func() were a generalized trigger function, which
would likely be the case if it were used by multiple TRIGGERs[;)]. The
necessary precondition would probably be inconsistent across the
TRIGGERs, and thus the feature could be quite useful.
Currently, such a generalized trigger function *could* be crafted
using trigger arguments, but I'd be inclined to think that that would
require more exercise than it would be worth( that is, I'm imagining
something that would be dirty, and much less convenient than WHEN =).
Personally, I think WHEN () would be pretty sweet. =)
On Mon, Sep 7, 2009 at 6:53 AM, Itagaki
Takahiro<itagaki.takahiro@oss.ntt.co.jp> wrote:
Peter Eisentraut <peter_e@gmx.net> wrote:
Therefore, it cannot be completely unexpected if column triggers are
called even if the column was not actually changed in a semantically
significant way.Ok, the attached patch implements standard-compliant version of
column trigger.Retrieving modified columns is not so difficult as I expected.
It is in:
rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols
and the information are passed from caller to trigger routines.However, to be honest, I think standard-compliant column trigger is
useless... I'm thinking additional extension for triggers -- if we
want to check modifications of actual values, it could be defined as:CREATE TRIGGER trig BEFORE UPDATE ON tbl FOR EACH ROW
WHEN (NEW.col <> OLD.col) EXECUTE PROCEDURE trigger_func();
It might be better to use WHERE than WHEN. Unfortunately this still
has all the wordiness of doing it in the trigger function. How about
some more trivial syntax modification, like:
CREATE TRIGGER name
BEFORE UPDATE MODIFYING col1, col12, ...
ON tbl FOR EACH ROW EXECUTE PROCEDURE func();
...Robert
"Kevin Grittner" <Kevin.Grittner@wicourts.gov> wrote:
How much does that buy you versus including this at the start of
trigger_func:
One of the benefits is that it could handle tuple modifications
by another trigger, that is discussed here:
http://archives.postgresql.org/pgsql-hackers/2009-09/msg00161.php
| 2. Recheck conditions if NEW values are modified, but triggers that
| have been fired already are not executed twice.
In addition, some database developers think procedures of trigger
and its condition separately. They might use other DBMSs that supports
WHEN clause in other times [1]Oracle http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/create_trigger.htm[2]IBM DB2 http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/r0000931.htm[3]SQLite3 http://www.sqlite.org/lang_createtrigger.html. (All of them use "WHEN" for the
syntax; that's why I proposed "WHEN" but not "WHERE".)
Also, it would be useful if we reuse trigger bodies multiple times
with different conditions.
[1]: Oracle http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/create_trigger.htm
http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/create_trigger.htm
[2]: IBM DB2 http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/r0000931.htm
http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/r0000931.htm
[3]: SQLite3 http://www.sqlite.org/lang_createtrigger.html
http://www.sqlite.org/lang_createtrigger.html
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
On Sep 8, 2009, at 5:33 PM, Itagaki Takahiro wrote:
WHEN clause in other times [1][2][3]. (All of them use "WHEN" for the
syntax; that's why I proposed "WHEN" but not "WHERE".)
Well, looks like WHEN is, or is going to be standard:
<triggered action> ::=
[ FOREACH { ROW | STATEMENT } ]
[ WHEN<left paren><search condition> <right paren> ]
<triggered SQL statement>
(page 653 from 5CD2-02-Foundation-2006-01)
On Tue, Sep 08, 2009 at 06:28:36PM -0700, James Pye wrote:
On Sep 8, 2009, at 5:33 PM, Itagaki Takahiro wrote:
WHEN clause in other times [1][2][3]. (All of them use "WHEN" for
the syntax; that's why I proposed "WHEN" but not "WHERE".)Well, looks like WHEN is, or is going to be standard:
<triggered action> ::=
[ FOREACH { ROW | STATEMENT } ]
[ WHEN<left paren><search condition> <right paren> ]
<triggered SQL statement>(page 653 from 5CD2-02-Foundation-2006-01)
Page 674 of 6WD_02_Foundation_2007-12 has a similar thing:
<triggered action> ::=
[ FOR EACH { ROW | STATEMENT } ]
[ <triggered when clause> ]
<triggered SQL statement>
<triggered when clause> ::=
WHEN <left paren> <search condition> <right paren>
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> wrote:
Ok, the attached patch implements standard-compliant version of
column trigger.
Here is an updated version of column-level trigger patch.
I forgot to adjust pg_get_triggerdef() in the previous version.
pg_dump also uses pg_get_triggerdef() instead of building
CREATE TRIGGER statements to avoid duplicated codes if the
server version is 8.5 or later.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
standard-column-trigger-20090914.patchapplication/octet-stream; name=standard-column-trigger-20090914.patchDownload
diff -cprN head/doc/src/sgml/func.sgml standard-column-trigger/doc/src/sgml/func.sgml
*** head/doc/src/sgml/func.sgml 2009-08-17 04:55:21.000000000 +0900
--- standard-column-trigger/doc/src/sgml/func.sgml 2009-09-14 17:44:53.781713886 +0900
*************** SELECT pg_type_is_visible('myschema.widg
*** 12370,12375 ****
--- 12370,12380 ----
<entry>get <command>CREATE [ CONSTRAINT ] TRIGGER</> command for trigger</entry>
</row>
<row>
+ <entry><function>pg_get_triggerdef</function>(<parameter>trigger_oid</parameter>, <parameter>pretty_bool</>)</entry>
+ <entry><type>text</type></entry>
+ <entry>get <command>CREATE [ CONSTRAINT ] TRIGGER</> command for trigger</entry>
+ </row>
+ <row>
<entry><literal><function>pg_get_userbyid</function>(<parameter>role_oid</parameter>)</literal></entry>
<entry><type>name</type></entry>
<entry>get role name with given OID</entry>
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml 2008-11-14 19:22:46.000000000 +0900
--- standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml 2009-09-14 17:23:24.167839706 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 121,126 ****
--- 121,130 ----
<command>DELETE</command>, or <command>TRUNCATE</command>;
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
+ An optional list of comma-separated columns can be given after
+ the <command>UPDATE</command> command, by adding the word
+ <literal>OF</literal> after <command>UPDATE</command> and before
+ the list of columns.
</para>
</listitem>
</varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml standard-column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml 2009-08-05 07:04:37.000000000 +0900
--- standard-column-trigger/doc/src/sgml/trigger.sgml 2009-09-14 17:23:24.168760484 +0900
***************
*** 39,45 ****
or once per <acronym>SQL</acronym> 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>
--- 39,46 ----
or once per <acronym>SQL</acronym> 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. In addition, UPDATE triggers can be set to only fire if certain
! columns are mentioned in SET columns of the UPDATE statement.
</para>
<para>
diff -cprN head/src/backend/commands/trigger.c standard-column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-08-05 01:08:36.000000000 +0900
--- standard-column-trigger/src/backend/commands/trigger.c 2009-09-14 17:23:24.268741789 +0900
***************
*** 30,37 ****
--- 30,40 ----
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+ #include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 65,71 ****
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes);
/*
--- 68,75 ----
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols);
! static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
/*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 101,108 ----
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 336,343 ****
CStringGetDatum(""));
}
! /* tgattr is currently always a zero-length array */
! tgattr = buildint2vector(NULL, 0);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 342,380 ----
CStringGetDatum(""));
}
! /* build column references for UPDATE OF */
! ncolumns = list_length(stmt->columns);
! if (ncolumns == 0)
! columns = NULL;
! else
! {
! ListCell *cell;
! int x = 0;
!
! columns = (int2 *) palloc(ncolumns * sizeof(int2));
!
! foreach (cell, stmt->columns)
! {
! char *name = strVal(lfirst(cell));
! int attnum;
! int y;
!
! /* Lookup column name. System columns are not allowed. */
! attnum = attnameAttNum(rel, name, false);
!
! /* Check for duplicates */
! for (y = x - 1; y >= 0; y--)
! {
! if (columns[y] == attnum)
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_COLUMN),
! errmsg("column \"%s\" specified more than once", name)));
! }
!
! columns[x++] = attnum;
! }
! }
! tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 433,438 ****
--- 470,492 ----
Assert(!OidIsValid(indexOid));
}
+ /* Add dependency on columns */
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1625,1642 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1679,1687 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASInsertTriggers(EState *estate, Res
*** 1658,1664 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1703,1709 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1684,1701 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1729,1737 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARInsertTriggers(EState *estate, Res
*** 1720,1726 ****
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes);
}
void
--- 1756,1762 ----
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes, NULL);
}
void
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1756,1773 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1792,1800 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASDeleteTriggers(EState *estate, Res
*** 1789,1795 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
--- 1816,1822 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL, NULL);
}
bool
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1821,1838 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1848,1856 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARDeleteTriggers(EState *estate, Res
*** 1866,1872 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
--- 1884,1890 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1904,1921 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1922,1930 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1937,1943 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1946,1952 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1953,1963 ****
--- 1962,1975 ----
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ Bitmapset *modifiedCols;
trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
if (trigtuple == NULL)
return NULL;
+ modifiedCols = rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols;
+
/*
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update.
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1974,1991 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1986,1994 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2018,2024 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes);
heap_freetuple(trigtuple);
}
}
--- 2021,2028 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes,
! rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols);
heap_freetuple(trigtuple);
}
}
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2056,2073 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 2060,2068 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASTruncateTriggers(EState *estate, R
*** 2089,2095 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
--- 2084,2090 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL, NULL);
}
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3813,3819 ****
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3808,3814 ----
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3915,3933 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! /* Ignore disabled triggers */
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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
--- 3910,3917 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4000,4002 ****
--- 3984,4026 ----
&new_event, &new_shared);
}
}
+
+ static bool
+ TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols)
+ {
+ 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-level triggers */
+ if (trigger->tgnattr > 0 && modifiedCols != NULL &&
+ (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ {
+ 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;
+ }
diff -cprN head/src/backend/parser/gram.y standard-column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y 2009-08-19 08:40:20.000000000 +0900
--- standard-column-trigger/src/backend/parser/gram.y 2009-09-14 17:23:24.271734848 +0900
*************** static TypeName *TableFuncTypeName(List
*** 248,254 ****
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <ival> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
--- 248,254 ----
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <str> opt_lancompiler
! %type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
*************** CreateTrigStmt:
*** 3160,3166 ****
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = $5;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
--- 3160,3167 ----
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = intVal(linitial($5));
! n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
*************** CreateTrigStmt:
*** 3180,3186 ****
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = $6;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
--- 3181,3188 ----
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = intVal(linitial($6));
! n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3199,3215 ****
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! if ($1 & $3)
parser_yyerror("duplicate trigger events specified");
! $$ = $1 | $3;
}
;
TriggerOneEvent:
! INSERT { $$ = TRIGGER_TYPE_INSERT; }
! | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
! | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
! | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
;
TriggerForSpec:
--- 3201,3222 ----
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! int events1 = intVal(linitial($1));
! int events2 = intVal(linitial($3));
!
! if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
! $$ = list_make2(makeInteger(events1 | events2),
! list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
! INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! | DELETE_P { $$ = 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:
diff -cprN head/src/backend/utils/adt/ruleutils.c standard-column-trigger/src/backend/utils/adt/ruleutils.c
*** head/src/backend/utils/adt/ruleutils.c 2009-08-02 04:59:41.000000000 +0900
--- standard-column-trigger/src/backend/utils/adt/ruleutils.c 2009-09-14 17:59:36.415778604 +0900
*************** static char *deparse_expression_pretty(N
*** 139,144 ****
--- 139,145 ----
bool forceprefix, bool showimplicit,
int prettyFlags, int startIndent);
static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
+ static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
*************** Datum
*** 462,467 ****
--- 463,484 ----
pg_get_triggerdef(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, 0)));
+ }
+
+ Datum
+ pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
+ {
+ Oid trigid = PG_GETARG_OID(0);
+ bool pretty = PG_GETARG_BOOL(1);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, pretty)));
+ }
+
+ static char *
+ pg_get_triggerdef_worker(Oid trigid, bool pretty)
+ {
HeapTuple ht_trig;
Form_pg_trigger trigrec;
StringInfoData buf;
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 498,506 ****
initStringInfo(&buf);
tgname = NameStr(trigrec->tgname);
! appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
trigrec->tgisconstraint ? "CONSTRAINT " : "",
! quote_identifier(tgname));
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
--- 515,524 ----
initStringInfo(&buf);
tgname = NameStr(trigrec->tgname);
! appendStringInfo(&buf, "CREATE %sTRIGGER %s%s",
trigrec->tgisconstraint ? "CONSTRAINT " : "",
! quote_identifier(tgname),
! (pretty ? "\n " : " "));
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 525,530 ****
--- 543,562 ----
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
+ if (trigrec->tgattr.dim1 > 0)
+ {
+ int i;
+
+ appendStringInfoString(&buf, " OF ");
+ for (i = 0; i < trigrec->tgattr.dim1; i++)
+ {
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf,
+ quote_identifier(get_relid_attribute_name(
+ trigrec->tgrelid, trigrec->tgattr.values[i])));
+ }
+ }
}
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
{
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 533,561 ****
else
appendStringInfo(&buf, " TRUNCATE");
}
! appendStringInfo(&buf, " ON %s ",
! generate_relation_name(trigrec->tgrelid, NIL));
if (trigrec->tgisconstraint)
{
if (trigrec->tgconstrrelid != InvalidOid)
! appendStringInfo(&buf, "FROM %s ",
! generate_relation_name(trigrec->tgconstrrelid,
! NIL));
if (!trigrec->tgdeferrable)
appendStringInfo(&buf, "NOT ");
appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
if (trigrec->tginitdeferred)
! appendStringInfo(&buf, "DEFERRED ");
else
! appendStringInfo(&buf, "IMMEDIATE ");
!
}
if (TRIGGER_FOR_ROW(trigrec->tgtype))
! appendStringInfo(&buf, "FOR EACH ROW ");
else
! appendStringInfo(&buf, "FOR EACH STATEMENT ");
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0, NULL, NULL));
--- 565,595 ----
else
appendStringInfo(&buf, " TRUNCATE");
}
! appendStringInfo(&buf, " ON %s%s",
! generate_relation_name(trigrec->tgrelid, NIL),
! (pretty ? "\n " : " "));
if (trigrec->tgisconstraint)
{
if (trigrec->tgconstrrelid != InvalidOid)
! appendStringInfo(&buf, "FROM %s%s",
! generate_relation_name(trigrec->tgconstrrelid, NIL),
! (pretty ? "\n " : " "));
if (!trigrec->tgdeferrable)
appendStringInfo(&buf, "NOT ");
appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
if (trigrec->tginitdeferred)
! appendStringInfo(&buf, "DEFERRED");
else
! appendStringInfo(&buf, "IMMEDIATE");
! appendStringInfoString(&buf, pretty ? "\n " : " ");
}
if (TRIGGER_FOR_ROW(trigrec->tgtype))
! appendStringInfo(&buf, "FOR EACH ROW");
else
! appendStringInfo(&buf, "FOR EACH STATEMENT");
! appendStringInfoString(&buf, pretty ? "\n " : " ");
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0, NULL, NULL));
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 593,599 ****
heap_close(tgrel, AccessShareLock);
! PG_RETURN_TEXT_P(string_to_text(buf.data));
}
/* ----------
--- 627,633 ----
heap_close(tgrel, AccessShareLock);
! return buf.data;
}
/* ----------
diff -cprN head/src/bin/pg_dump/pg_dump.c standard-column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c 2009-09-12 04:17:04.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.c 2009-09-14 18:48:39.680720773 +0900
*************** getTriggers(TableInfo tblinfo[], int num
*** 4263,4269 ****
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
--- 4263,4270 ----
i_tgconstrrelname,
i_tgenabled,
i_tgdeferrable,
! i_tginitdeferred,
! i_tgdef;
int ntups;
for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4283,4289 ****
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
--- 4284,4302 ----
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
resetPQExpBuffer(query);
! if (g_fout->remoteVersion >= 80500)
! {
! appendPQExpBuffer(query,
! "SELECT tgname, "
! "tgfoid::pg_catalog.regproc AS tgfname, "
! "pg_catalog.pg_get_triggerdef(oid, true) AS tgdef, "
! "tgenabled, tableoid, oid "
! "FROM pg_catalog.pg_trigger t "
! "WHERE tgrelid = '%u'::pg_catalog.oid "
! "AND tgconstraint = 0",
! tbinfo->dobj.catId.oid);
! }
! else if (g_fout->remoteVersion >= 80300)
{
/*
* We ignore triggers that are tied to a foreign-key constraint
*************** getTriggers(TableInfo tblinfo[], int num
*** 4370,4375 ****
--- 4383,4389 ----
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgdef = PQfnumber(res, "tgdef");
tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
*************** getTriggers(TableInfo tblinfo[], int num
*** 4382,4419 ****
tginfo[j].dobj.name = strdup(PQgetvalue(res, j, i_tgname));
tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
tginfo[j].tgtable = tbinfo;
- tginfo[j].tgfname = strdup(PQgetvalue(res, j, i_tgfname));
- tginfo[j].tgtype = atoi(PQgetvalue(res, j, i_tgtype));
- tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs));
- tginfo[j].tgargs = strdup(PQgetvalue(res, j, i_tgargs));
- tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't';
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
! tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
! tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
! if (tginfo[j].tgisconstraint)
! {
! tginfo[j].tgconstrname = strdup(PQgetvalue(res, j, i_tgconstrname));
! tginfo[j].tgconstrrelid = atooid(PQgetvalue(res, j, i_tgconstrrelid));
! if (OidIsValid(tginfo[j].tgconstrrelid))
{
! if (PQgetisnull(res, j, i_tgconstrrelname))
{
! write_msg(NULL, "query produced null referenced table name for foreign key trigger \"%s\" on table \"%s\" (OID of table: %u)\n",
! tginfo[j].dobj.name, tbinfo->dobj.name,
! tginfo[j].tgconstrrelid);
! exit_nicely();
}
! tginfo[j].tgconstrrelname = strdup(PQgetvalue(res, j, i_tgconstrrelname));
}
else
tginfo[j].tgconstrrelname = NULL;
! }
! else
! {
! tginfo[j].tgconstrname = NULL;
! tginfo[j].tgconstrrelid = InvalidOid;
! tginfo[j].tgconstrrelname = NULL;
}
}
--- 4396,4442 ----
tginfo[j].dobj.name = strdup(PQgetvalue(res, j, i_tgname));
tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
tginfo[j].tgtable = tbinfo;
tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
! if (i_tgdef >= 0)
! {
! tginfo[j].tgdef = strdup(PQgetvalue(res, j, i_tgdef));
! }
! else
! {
! tginfo[j].tgdef = NULL;
!
! tginfo[j].tgfname = strdup(PQgetvalue(res, j, i_tgfname));
! tginfo[j].tgtype = atoi(PQgetvalue(res, j, i_tgtype));
! tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs));
! tginfo[j].tgargs = strdup(PQgetvalue(res, j, i_tgargs));
! tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't';
! tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
! tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
! if (tginfo[j].tgisconstraint)
{
! tginfo[j].tgconstrname = strdup(PQgetvalue(res, j, i_tgconstrname));
! tginfo[j].tgconstrrelid = atooid(PQgetvalue(res, j, i_tgconstrrelid));
! if (OidIsValid(tginfo[j].tgconstrrelid))
{
! if (PQgetisnull(res, j, i_tgconstrrelname))
! {
! write_msg(NULL, "query produced null referenced table name for foreign key trigger \"%s\" on table \"%s\" (OID of table: %u)\n",
! tginfo[j].dobj.name, tbinfo->dobj.name,
! tginfo[j].tgconstrrelid);
! exit_nicely();
! }
! tginfo[j].tgconstrrelname = strdup(PQgetvalue(res, j, i_tgconstrrelname));
}
! else
! tginfo[j].tgconstrrelname = NULL;
}
else
+ {
+ tginfo[j].tgconstrname = NULL;
+ tginfo[j].tgconstrrelid = InvalidOid;
tginfo[j].tgconstrrelname = NULL;
! }
}
}
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11031,11143 ****
appendPQExpBuffer(delqry, "%s;\n",
fmtId(tbinfo->dobj.name));
! if (tginfo->tgisconstraint)
{
! appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
! appendPQExpBufferStr(query, fmtId(tginfo->tgconstrname));
}
else
{
! appendPQExpBuffer(query, "CREATE TRIGGER ");
! appendPQExpBufferStr(query, fmtId(tginfo->dobj.name));
! }
! appendPQExpBuffer(query, "\n ");
!
! /* Trigger type */
! findx = 0;
! if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
! appendPQExpBuffer(query, "BEFORE");
! else
! appendPQExpBuffer(query, "AFTER");
! if (TRIGGER_FOR_INSERT(tginfo->tgtype))
! {
! appendPQExpBuffer(query, " INSERT");
! findx++;
! }
! if (TRIGGER_FOR_DELETE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR DELETE");
! else
! appendPQExpBuffer(query, " DELETE");
! findx++;
! }
! if (TRIGGER_FOR_UPDATE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR UPDATE");
else
! appendPQExpBuffer(query, " UPDATE");
! }
! if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR TRUNCATE");
else
! appendPQExpBuffer(query, " TRUNCATE");
! }
! appendPQExpBuffer(query, " ON %s\n",
! fmtId(tbinfo->dobj.name));
! if (tginfo->tgisconstraint)
! {
! if (OidIsValid(tginfo->tgconstrrelid))
{
! /* If we are using regclass, name is already quoted */
! if (g_fout->remoteVersion >= 70300)
! appendPQExpBuffer(query, " FROM %s\n ",
! tginfo->tgconstrrelname);
else
! appendPQExpBuffer(query, " FROM %s\n ",
! fmtId(tginfo->tgconstrrelname));
}
! if (!tginfo->tgdeferrable)
! appendPQExpBuffer(query, "NOT ");
! appendPQExpBuffer(query, "DEFERRABLE INITIALLY ");
! if (tginfo->tginitdeferred)
! appendPQExpBuffer(query, "DEFERRED\n");
else
! appendPQExpBuffer(query, "IMMEDIATE\n");
! }
! if (TRIGGER_FOR_ROW(tginfo->tgtype))
! appendPQExpBuffer(query, " FOR EACH ROW\n ");
! else
! appendPQExpBuffer(query, " FOR EACH STATEMENT\n ");
! /* In 7.3, result of regproc is already quoted */
! if (g_fout->remoteVersion >= 70300)
! appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! tginfo->tgfname);
! else
! appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! fmtId(tginfo->tgfname));
! tgargs = (char *) PQunescapeBytea((unsigned char *) tginfo->tgargs,
! &lentgargs);
! p = tgargs;
! for (findx = 0; findx < tginfo->tgnargs; findx++)
! {
! /* find the embedded null that terminates this trigger argument */
! size_t tlen = strlen(p);
!
! if (p + tlen >= tgargs + lentgargs)
! {
! /* hm, not found before end of bytea value... */
! write_msg(NULL, "invalid argument string (%s) for trigger \"%s\" on table \"%s\"\n",
! tginfo->tgargs,
! tginfo->dobj.name,
! tbinfo->dobj.name);
! exit_nicely();
! }
! if (findx > 0)
! appendPQExpBuffer(query, ", ");
! appendStringLiteralAH(query, p, fout);
! p += tlen + 1;
}
- free(tgargs);
- appendPQExpBuffer(query, ");\n");
if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O')
{
--- 11054,11173 ----
appendPQExpBuffer(delqry, "%s;\n",
fmtId(tbinfo->dobj.name));
! if (tginfo->tgdef)
{
! appendPQExpBuffer(query, "%s;\n", tginfo->tgdef);
}
else
{
! if (tginfo->tgisconstraint)
! {
! appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
! appendPQExpBufferStr(query, fmtId(tginfo->tgconstrname));
! }
else
! {
! appendPQExpBuffer(query, "CREATE TRIGGER ");
! appendPQExpBufferStr(query, fmtId(tginfo->dobj.name));
! }
! appendPQExpBuffer(query, "\n ");
!
! /* Trigger type */
! findx = 0;
! if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
! appendPQExpBuffer(query, "BEFORE");
else
! appendPQExpBuffer(query, "AFTER");
! if (TRIGGER_FOR_INSERT(tginfo->tgtype))
! {
! appendPQExpBuffer(query, " INSERT");
! findx++;
! }
! if (TRIGGER_FOR_DELETE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR DELETE");
! else
! appendPQExpBuffer(query, " DELETE");
! findx++;
! }
! if (TRIGGER_FOR_UPDATE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR UPDATE");
! else
! appendPQExpBuffer(query, " UPDATE");
! }
! if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
! {
! if (findx > 0)
! appendPQExpBuffer(query, " OR TRUNCATE");
! else
! appendPQExpBuffer(query, " TRUNCATE");
! }
! appendPQExpBuffer(query, " ON %s\n",
! fmtId(tbinfo->dobj.name));
! if (tginfo->tgisconstraint)
{
! if (OidIsValid(tginfo->tgconstrrelid))
! {
! /* If we are using regclass, name is already quoted */
! if (g_fout->remoteVersion >= 70300)
! appendPQExpBuffer(query, " FROM %s\n ",
! tginfo->tgconstrrelname);
! else
! appendPQExpBuffer(query, " FROM %s\n ",
! fmtId(tginfo->tgconstrrelname));
! }
! if (!tginfo->tgdeferrable)
! appendPQExpBuffer(query, "NOT ");
! appendPQExpBuffer(query, "DEFERRABLE INITIALLY ");
! if (tginfo->tginitdeferred)
! appendPQExpBuffer(query, "DEFERRED\n");
else
! appendPQExpBuffer(query, "IMMEDIATE\n");
}
!
! if (TRIGGER_FOR_ROW(tginfo->tgtype))
! appendPQExpBuffer(query, " FOR EACH ROW\n ");
else
! appendPQExpBuffer(query, " FOR EACH STATEMENT\n ");
! /* In 7.3, result of regproc is already quoted */
! if (g_fout->remoteVersion >= 70300)
! appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! tginfo->tgfname);
! else
! appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! fmtId(tginfo->tgfname));
! tgargs = (char *) PQunescapeBytea((unsigned char *) tginfo->tgargs,
! &lentgargs);
! p = tgargs;
! for (findx = 0; findx < tginfo->tgnargs; findx++)
! {
! /* find the embedded null that terminates this trigger argument */
! size_t tlen = strlen(p);
! if (p + tlen >= tgargs + lentgargs)
! {
! /* hm, not found before end of bytea value... */
! write_msg(NULL, "invalid argument string (%s) for trigger \"%s\" on table \"%s\"\n",
! tginfo->tgargs,
! tginfo->dobj.name,
! tbinfo->dobj.name);
! exit_nicely();
! }
! if (findx > 0)
! appendPQExpBuffer(query, ", ");
! appendStringLiteralAH(query, p, fout);
! p += tlen + 1;
! }
! free(tgargs);
! appendPQExpBuffer(query, ");\n");
}
if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O')
{
diff -cprN head/src/bin/pg_dump/pg_dump.h standard-column-trigger/src/bin/pg_dump/pg_dump.h
*** head/src/bin/pg_dump/pg_dump.h 2009-08-03 07:14:52.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.h 2009-09-14 18:38:58.859946528 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ char *tgdef;
} TriggerInfo;
/*
diff -cprN head/src/include/catalog/pg_proc.h standard-column-trigger/src/include/catalog/pg_proc.h
*** head/src/include/catalog/pg_proc.h 2009-09-01 11:54:52.000000000 +0900
--- standard-column-trigger/src/include/catalog/pg_proc.h 2009-09-14 17:27:45.399743291 +0900
*************** DATA(insert OID = 2599 ( pg_timezone_ab
*** 4082,4087 ****
--- 4082,4089 ----
DESCR("get the available time zone abbreviations");
DATA(insert OID = 2856 ( pg_timezone_names PGNSP PGUID 12 1 1000 0 f f f t t s 0 0 2249 "" "{25,25,1186,16}" "{o,o,o,o}" "{name,abbrev,utc_offset,is_dst}" _null_ pg_timezone_names _null_ _null_ _null_ ));
DESCR("get the available time zone names");
+ DATA(insert OID = 2730 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 f f f t f s 2 0 25 "26 16" _null_ _null_ _null_ _null_ pg_get_triggerdef_ext _null_ _null_ _null_ ));
+ DESCR("trigger description with pretty-print option");
/* non-persistent series generator */
DATA(insert OID = 1066 ( generate_series PGNSP PGUID 12 1 1000 0 f f f t t i 3 0 23 "23 23 23" _null_ _null_ _null_ _null_ generate_series_step_int4 _null_ _null_ _null_ ));
diff -cprN head/src/include/catalog/pg_trigger.h standard-column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h 2009-07-28 11:56:31.000000000 +0900
--- standard-column-trigger/src/include/catalog/pg_trigger.h 2009-09-14 17:23:24.277745679 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* reserved for column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
--- 53,59 ----
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff -cprN head/src/include/nodes/parsenodes.h standard-column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h 2009-08-03 07:14:53.000000000 +0900
--- standard-column-trigger/src/include/nodes/parsenodes.h 2009-09-14 17:23:24.277745679 +0900
*************** typedef struct CreateTrigStmt
*** 1553,1558 ****
--- 1553,1559 ----
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff -cprN head/src/include/utils/builtins.h standard-column-trigger/src/include/utils/builtins.h
*** head/src/include/utils/builtins.h 2009-09-10 04:00:09.000000000 +0900
--- standard-column-trigger/src/include/utils/builtins.h 2009-09-14 17:24:52.149745975 +0900
*************** extern Datum pg_get_indexdef_ext(PG_FUNC
*** 590,595 ****
--- 590,596 ----
extern char *pg_get_indexdef_string(Oid indexrelid);
extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
+ extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
extern char *pg_get_constraintdef_string(Oid constraintId);
diff -cprN head/src/test/regress/expected/triggers.out standard-column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/expected/triggers.out 2009-09-14 18:00:12.683748455 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
--- 278,314 ----
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, 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
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,394 ----
|
(8 rows)
+ -- Column-level triggers should only fire on after row-level updates
+ 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');
+ 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_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_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ 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
+ 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
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql standard-column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/sql/triggers.sql 2009-09-14 17:41:40.577757046 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
--- 220,244 ----
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,285 ----
SELECT * FROM main_table ORDER BY a, b;
+ -- Column-level triggers should only fire on after row-level updates
+ 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');
+
+ 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;
+
+ 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');
+
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
On Mon, 2009-09-14 at 18:58 +0900, Itagaki Takahiro wrote:
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> wrote:
Ok, the attached patch implements standard-compliant version of
column trigger.Here is an updated version of column-level trigger patch.
I forgot to adjust pg_get_triggerdef() in the previous version.
pg_dump also uses pg_get_triggerdef() instead of building
CREATE TRIGGER statements to avoid duplicated codes if the
server version is 8.5 or later.
What is the purpose of the new pg_get_triggerdef() variant? OK, the
parameter name "pretty_bool" gives a hint, but what does this have to do
with column triggers? Maybe you could try to explain this in more
detail. Ideally split the patch into two: one that deals with
pg_get_triggerdef(), and one that deals with column triggers.
If you want a "pretty" option on pg_get_triggerdef(), you could nowadays
also implement that via a parameter default value instead of a second
function.
Peter Eisentraut <peter_e@gmx.net> wrote:
What is the purpose of the new pg_get_triggerdef() variant? OK, the
parameter name "pretty_bool" gives a hint, but what does this have to do
with column triggers? Maybe you could try to explain this in more
detail. Ideally split the patch into two: one that deals with
pg_get_triggerdef(), and one that deals with column triggers.
It's for pg_dump. We can avoid duplicated codes if we use
pg_get_triggerdef() in pg_dump. So, I think column trigger and
the dump function for column trigger should be applied at once.
If you want a "pretty" option on pg_get_triggerdef(), you could nowadays
also implement that via a parameter default value instead of a second
function.
OK, I'll rewrite it to use default parameter.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Peter Eisentraut <peter_e@gmx.net> wrote:
If you want a "pretty" option on pg_get_triggerdef(), you could nowadays
also implement that via a parameter default value instead of a second
function.OK, I'll rewrite it to use default parameter.
I tried to remove pg_get_triggerdef_ext() and add a second argument
with default value to pg_get_triggerdef(), but there is a problem.
The definition of pg_get_triggerdef will be the following:
DATA(insert OID = 1662 (
pg_get_triggerdef PGNSP PGUID 12 1 0 0 f f f t f s 2 1 25 "26 16" _null_ _null_ _null_
"({CONST :consttype 16 :consttypmod -1 :constlen 1 :constbyval true :constisnull false :location 41 :constvalue 1 [ 0 0 0 0 0 0 0 0 ]})"
pg_get_triggerdef _null_ _null_ _null_ ));
The problem is in :constvalue part; It will be
":constvalue 1 [ 0 0 0 0 0 0 0 0 ]" on 64bit platform, but
":constvalue 1 [ 0 0 0 0 ]" on 32bit platform.
I think we should not use default values in functions listed on pg_proc.h,
so the previous patch is better than default value version.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
On Thu, 2009-10-01 at 10:40 +0900, Itagaki Takahiro wrote:
Peter Eisentraut <peter_e@gmx.net> wrote:
If you want a "pretty" option on pg_get_triggerdef(), you could nowadays
also implement that via a parameter default value instead of a second
function.OK, I'll rewrite it to use default parameter.
I tried to remove pg_get_triggerdef_ext() and add a second argument
with default value to pg_get_triggerdef(), but there is a problem.The definition of pg_get_triggerdef will be the following:
DATA(insert OID = 1662 (
pg_get_triggerdef PGNSP PGUID 12 1 0 0 f f f t f s 2 1 25 "26 16" _null_ _null_ _null_
"({CONST :consttype 16 :consttypmod -1 :constlen 1 :constbyval true :constisnull false :location 41 :constvalue 1 [ 0 0 0 0 0 0 0 0 ]})"
pg_get_triggerdef _null_ _null_ _null_ ));The problem is in :constvalue part; It will be
":constvalue 1 [ 0 0 0 0 0 0 0 0 ]" on 64bit platform, but
":constvalue 1 [ 0 0 0 0 ]" on 32bit platform.
I think we should not use default values in functions listed on pg_proc.h,
so the previous patch is better than default value version.
OK, but what you can do is point both variants to the same C function
and check with PG_NARGS() with how many arguments you were called. That
would save some of the indirections.
Peter Eisentraut <peter_e@gmx.net> wrote:
OK, but what you can do is point both variants to the same C function
and check with PG_NARGS() with how many arguments you were called. That
would save some of the indirections.
The regressiontest 'opr_sanity' failed if do so. Should we remove this
check only for pg_get_triggerdef? If we cannot do that, the first version
of patch is still the best solution.
-- Considering only built-in procs (prolang = 12), look for multiple uses
-- of the same internal function (ie, matching prosrc fields). It's OK to
-- have several entries with different pronames for the same internal function,
-- but conflicts in the number of arguments and other critical items should
-- be complained of. (We don't check data types here; see next query.)
-- Note: ignore aggregate functions here, since they all point to the same
-- dummy built-in function.
oid | proname | oid | proname
! ------+-------------------+------+-------------------
! 1662 | pg_get_triggerdef | 2730 | pg_get_triggerdef
! (1 row)
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
Peter Eisentraut <peter_e@gmx.net> wrote:
OK, but what you can do is point both variants to the same C function
and check with PG_NARGS() with how many arguments you were called. That
would save some of the indirections.
The regressiontest 'opr_sanity' failed if do so. Should we remove this
check only for pg_get_triggerdef? If we cannot do that, the first version
of patch is still the best solution.
I have always been of the opinion that V1 functions should be written
in the style
foo(PG_FUNCTION_ARGS)
{
type1 arg1 = PG_GETARG_whatever(0);
type2 arg2 = PG_GETARG_whatever(1);
type3 arg3 = PG_GETARG_whatever(2);
as much as possible. The V1 protocol is already a big hit to
readability compared to plain-vanilla C functions, and one of the main
reasons is that you can't instantly see what arguments a function is
expecting. Sticking to the above style ameliorates that. Cute tricks
like conditionally grabbing arguments depending on PG_NARGS do far more
damage to readability than they can ever repay in any other metric.
In short: while I haven't looked at the patch, I think Peter may be
steering you in the wrong direction.
In cases where you do have related functions, I suggest having
SQL-callable V1 functions that absorb their arguments in this
style, and then have them call a common subroutine that's a plain
C function.
regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> writes:
In cases where you do have related functions, I suggest having
SQL-callable V1 functions that absorb their arguments in this
style, and then have them call a common subroutine that's a plain
C function.
Unless you have high performance requirements, IME. Avoiding the SQL
function call is indeed measurable, even if very low in the radar.
Regards,
--
dim
You have a nice quote about the sins we'd accept/follow in the name of
performance, but google will about only find sin/cos etc material...
On Sun, 2009-10-04 at 22:07 -0400, Tom Lane wrote:
In short: while I haven't looked at the patch, I think Peter may be
steering you in the wrong direction.In cases where you do have related functions, I suggest having
SQL-callable V1 functions that absorb their arguments in this
style, and then have them call a common subroutine that's a plain
C function.
Yeah, that's what he did. So forget what I said. :-)
2009/10/7 Peter Eisentraut <peter_e@gmx.net>:
On Sun, 2009-10-04 at 22:07 -0400, Tom Lane wrote:
In short: while I haven't looked at the patch, I think Peter may be
steering you in the wrong direction.In cases where you do have related functions, I suggest having
SQL-callable V1 functions that absorb their arguments in this
style, and then have them call a common subroutine that's a plain
C function.Yeah, that's what he did. So forget what I said. :-)
What's the current status of this patch in the commitfest process? I
see the last version posted by the author was on 14 Sep and there's
been some discussion since then, but no new patch version has
surfaced. Are we waiting on a new version of the patch, or is there
more review of the existing version yet to be done? The status of the
patch on the commitfest app is still "Needs Review", but I'm not sure
whether that is accurate.
Cheers,
BJ
On Thu, 2009-10-08 at 08:57 +1100, Brendan Jurd wrote:
What's the current status of this patch in the commitfest process?
I'm working on it.
On Mon, 2009-09-14 at 18:58 +0900, Itagaki Takahiro wrote:
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> wrote:
Ok, the attached patch implements standard-compliant version of
column trigger.Here is an updated version of column-level trigger patch.
I forgot to adjust pg_get_triggerdef() in the previous version.
pg_dump also uses pg_get_triggerdef() instead of building
CREATE TRIGGER statements to avoid duplicated codes if the
server version is 8.5 or later.
I have committed the parts involving pg_get_triggerdef and pg_dump. I
will get to the actual column trigger functionality next.
On Sat, 2009-10-10 at 00:04 +0300, Peter Eisentraut wrote:
On Mon, 2009-09-14 at 18:58 +0900, Itagaki Takahiro wrote:
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> wrote:
Ok, the attached patch implements standard-compliant version of
column trigger.Here is an updated version of column-level trigger patch.
I forgot to adjust pg_get_triggerdef() in the previous version.
pg_dump also uses pg_get_triggerdef() instead of building
CREATE TRIGGER statements to avoid duplicated codes if the
server version is 8.5 or later.I have committed the parts involving pg_get_triggerdef and pg_dump. I
will get to the actual column trigger functionality next.
Attached is a merged up patch with some slightly improved documentation.
I think the patch is almost ready now. One remaining issue is, in
TriggerEnabled() you apparently check the column list only if it is a
row trigger. But columns are supported for statement triggers as well
per SQL standard. Check please.
Btw., I might not get a chance to commit this within the next 48 hours.
If someone else wants to, go ahead.
Attachments:
column-trigger.patchtext/x-patch; charset=UTF-8; name=column-trigger.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 6185a7d..f656cbf 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -122,6 +122,16 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
</para>
+
+ <para>
+ For <command>UPDATE</command> triggers, it is possible to
+ specify a list of columns using this syntax:
+<synopsis>
+UPDATE OF <replaceable>colname1</replaceable> [, <replaceable>colname2</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>
</varlistentry>
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 36bf050..ed956fe 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -36,10 +36,13 @@
performed. Triggers can be defined to execute either before or after any
<command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command> operation, either once per modified row,
- or once per <acronym>SQL</acronym> 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.
+ or once per <acronym>SQL</acronym>
+ statement. <command>UPDATE</command> triggers can moreover be set
+ to only fire if certain columns are mentioned in
+ the <literal>SET</literal> clause of the <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>
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 790cbdc..1af0c61 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -30,8 +30,11 @@
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+#include "parser/parse_relation.h"
+#include "parser/parsetree.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
@@ -66,7 +69,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
- List *recheckIndexes);
+ List *recheckIndexes, Bitmapset *modifiedCols);
+static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
/*
@@ -98,6 +102,8 @@ CreateTrigger(CreateTrigStmt *stmt,
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
@@ -337,8 +343,39 @@ CreateTrigger(CreateTrigStmt *stmt,
CStringGetDatum(""));
}
- /* tgattr is currently always a zero-length array */
- tgattr = buildint2vector(NULL, 0);
+ /* build column references for UPDATE OF */
+ ncolumns = list_length(stmt->columns);
+ if (ncolumns == 0)
+ columns = NULL;
+ else
+ {
+ ListCell *cell;
+ int x = 0;
+
+ columns = (int2 *) palloc(ncolumns * sizeof(int2));
+
+ foreach (cell, stmt->columns)
+ {
+ char *name = strVal(lfirst(cell));
+ int attnum;
+ int y;
+
+ /* Lookup column name. System columns are not allowed. */
+ attnum = attnameAttNum(rel, name, false);
+
+ /* Check for duplicates */
+ for (y = x - 1; y >= 0; y--)
+ {
+ if (columns[y] == attnum)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" specified more than once", name)));
+ }
+
+ columns[x++] = attnum;
+ }
+ }
+ tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
@@ -434,6 +471,23 @@ CreateTrigger(CreateTrigStmt *stmt,
Assert(!OidIsValid(indexOid));
}
+ /* Add dependency on columns */
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
@@ -1626,18 +1680,9 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1659,7 +1704,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
HeapTuple
@@ -1685,18 +1730,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
@@ -1721,7 +1757,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- true, NULL, trigtuple, recheckIndexes);
+ true, NULL, trigtuple, recheckIndexes, NULL);
}
void
@@ -1757,18 +1793,9 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1790,7 +1817,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
bool
@@ -1824,18 +1851,9 @@ ExecBRDeleteTriggers(EState *estate, PlanState *subplanstate,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
@@ -1869,7 +1887,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- true, trigtuple, NULL, NIL);
+ true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
@@ -1907,18 +1925,9 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1940,7 +1949,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
HeapTuple
@@ -1957,12 +1966,15 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ Bitmapset *modifiedCols;
trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
&newSlot);
if (trigtuple == NULL)
return NULL;
+ modifiedCols = rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols;
+
/*
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update. In that case we have a raw subplan
@@ -1980,18 +1992,9 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, modifiedCols))
+ continue;
+
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
@@ -2024,7 +2027,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- true, trigtuple, newtuple, recheckIndexes);
+ true, trigtuple, newtuple, recheckIndexes,
+ rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols);
heap_freetuple(trigtuple);
}
}
@@ -2062,18 +2066,9 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -2095,7 +2090,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
@@ -3825,7 +3820,7 @@ AfterTriggerPendingOnRel(Oid relid)
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
- List *recheckIndexes)
+ List *recheckIndexes, Bitmapset *modifiedCols)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -3927,19 +3922,8 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- /* Ignore disabled triggers */
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- 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 (!TriggerEnabled(trigger, modifiedCols))
+ continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
@@ -4012,3 +3996,43 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
&new_event, &new_shared);
}
}
+
+static bool
+TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols)
+{
+ 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-level triggers */
+ if (trigger->tgnattr > 0 && modifiedCols != NULL &&
+ (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ {
+ 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;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d4acf60..9a2da19 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -251,7 +251,7 @@ static TypeName *TableFuncTypeName(List *columns);
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
-%type <ival> TriggerEvents TriggerOneEvent
+%type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
@@ -3240,7 +3240,8 @@ CreateTrigStmt:
n->args = $13;
n->before = $4;
n->row = $8;
- n->events = $5;
+ n->events = intVal(linitial($5));
+ n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
@@ -3260,7 +3261,8 @@ CreateTrigStmt:
n->args = $18;
n->before = FALSE;
n->row = TRUE;
- n->events = $6;
+ n->events = intVal(linitial($6));
+ n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
@@ -3279,17 +3281,22 @@ TriggerEvents:
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
- if ($1 & $3)
+ int events1 = intVal(linitial($1));
+ int events2 = intVal(linitial($3));
+
+ if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
- $$ = $1 | $3;
+ $$ = list_make2(makeInteger(events1 | events2),
+ list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
- INSERT { $$ = TRIGGER_TYPE_INSERT; }
- | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
- | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
- | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
+ INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
+ | DELETE_P { $$ = 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:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d20c893..529056d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -543,6 +543,20 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
+ if (trigrec->tgattr.dim1 > 0)
+ {
+ int i;
+
+ appendStringInfoString(&buf, " OF ");
+ for (i = 0; i < trigrec->tgattr.dim1; i++)
+ {
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf,
+ quote_identifier(get_relid_attribute_name(
+ trigrec->tgrelid, trigrec->tgattr.values[i])));
+ }
+ }
}
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
{
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index 64ca26f..17198a9 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -53,7 +53,7 @@ CATALOG(pg_trigger,2620)
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
- int2vector tgattr; /* reserved for column-specific triggers */
+ int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c629aca..bbd903f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1575,6 +1575,7 @@ typedef struct CreateTrigStmt
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c0b7645..3e8c599 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -278,37 +278,37 @@ CREATE TABLE main_table (a int, b int);
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
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;
END;';
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
-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,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
-CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
-EXECUTE PROCEDURE trigger_func();
-CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
-FOR EACH ROW EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
+EXECUTE PROCEDURE trigger_func('after_upd_stmt');
+CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
+FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
-NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
-NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
+NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
-NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
-NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
-NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
-NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
-NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
+NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
+NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
+NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, 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
-- UPDATE that effects zero rows should still call per-statement trigger
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 main_table (a, b) FROM stdin;
-NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
-NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
+NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
@@ -322,6 +322,73 @@ SELECT * FROM main_table ORDER BY a, b;
|
(8 rows)
+-- Column-level triggers should only fire on after row-level updates
+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');
+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_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_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+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
+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
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+DROP TRIGGER after_upd_b_row_trig ON main_table;
+ALTER TABLE main_table DROP COLUMN b;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 878adbb..ebef805 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -220,25 +220,25 @@ COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
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;
END;';
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
-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,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
-CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
-EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
+EXECUTE PROCEDURE trigger_func('after_upd_stmt');
-CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
-FOR EACH ROW EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
+FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
@@ -254,6 +254,32 @@ COPY main_table (a, b) FROM stdin;
SELECT * FROM main_table ORDER BY a, b;
+-- Column-level triggers should only fire on after row-level updates
+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');
+
+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;
+
+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');
+
+ALTER TABLE main_table DROP COLUMN b;
+DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+DROP TRIGGER after_upd_b_row_trig ON main_table;
+ALTER TABLE main_table DROP COLUMN b;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
Peter Eisentraut <peter_e@gmx.net> wrote:
I think the patch is almost ready now. One remaining issue is, in
TriggerEnabled() you apparently check the column list only if it is a
row trigger. But columns are supported for statement triggers as well
per SQL standard. Check please.
I added it. I've missed it because I tried to implement column
trigger based on value-comparison, but now we check conditions
based on a target-list. Statement column triggers are reasonable.
diff-of-column-trigger.patch is a diff from the previous column-trigger.patch.
column-trigger_20091014.patch is a full patch to the HEAD.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
column-trigger_20091014.patchapplication/octet-stream; name=column-trigger_20091014.patchDownload
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml work/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml 2009-09-19 19:23:27.000000000 +0900
--- work/doc/src/sgml/ref/create_trigger.sgml 2009-10-14 12:15:45.343050314 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 122,127 ****
--- 122,137 ----
this specifies the event that will fire the trigger. Multiple
events can be specified using <literal>OR</literal>.
</para>
+
+ <para>
+ For <command>UPDATE</command> triggers, it is possible to
+ specify a list of columns using this syntax:
+ <synopsis>
+ UPDATE OF <replaceable>colname1</replaceable> [, <replaceable>colname2</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>
</varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml work/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml 2009-08-05 07:04:37.000000000 +0900
--- work/doc/src/sgml/trigger.sgml 2009-10-14 12:15:45.343050314 +0900
***************
*** 36,45 ****
performed. Triggers can be defined to execute either before or after any
<command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command> operation, either once per modified row,
! or once per <acronym>SQL</acronym> 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>
--- 36,48 ----
performed. Triggers can be defined to execute either before or after any
<command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command> operation, either once per modified row,
! or once per <acronym>SQL</acronym>
! statement. <command>UPDATE</command> triggers can moreover be set
! to only fire if certain columns are mentioned in
! the <literal>SET</literal> clause of the <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>
diff -cprN head/src/backend/commands/trigger.c work/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-10-10 10:43:45.000000000 +0900
--- work/src/backend/commands/trigger.c 2009-10-14 13:18:14.881976192 +0900
***************
*** 30,37 ****
--- 30,40 ----
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+ #include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 66,72 ****
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes);
/*
--- 69,79 ----
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols);
! static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
!
! #define GetModifiedColumns(relinfo, estate) \
! (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
/*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 98,103 ****
--- 105,112 ----
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 337,344 ****
CStringGetDatum(""));
}
! /* tgattr is currently always a zero-length array */
! tgattr = buildint2vector(NULL, 0);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 346,384 ----
CStringGetDatum(""));
}
! /* build column references for UPDATE OF */
! ncolumns = list_length(stmt->columns);
! if (ncolumns == 0)
! columns = NULL;
! else
! {
! ListCell *cell;
! int x = 0;
!
! columns = (int2 *) palloc(ncolumns * sizeof(int2));
!
! foreach (cell, stmt->columns)
! {
! char *name = strVal(lfirst(cell));
! int attnum;
! int y;
!
! /* Lookup column name. System columns are not allowed. */
! attnum = attnameAttNum(rel, name, false);
!
! /* Check for duplicates */
! for (y = x - 1; y >= 0; y--)
! {
! if (columns[y] == attnum)
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_COLUMN),
! errmsg("column \"%s\" specified more than once", name)));
! }
!
! columns[x++] = attnum;
! }
! }
! tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 434,439 ****
--- 474,496 ----
Assert(!OidIsValid(indexOid));
}
+ /* Add dependency on columns */
+ if (columns != NULL)
+ {
+ int i;
+ Form_pg_attribute *attrs;
+
+ attrs = RelationGetDescr(rel)->attrs;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1626,1643 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1683,1691 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASInsertTriggers(EState *estate, Res
*** 1659,1665 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1707,1713 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1685,1702 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1733,1741 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARInsertTriggers(EState *estate, Res
*** 1721,1727 ****
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes);
}
void
--- 1760,1766 ----
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, recheckIndexes, NULL);
}
void
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1757,1774 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1796,1804 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASDeleteTriggers(EState *estate, Res
*** 1790,1796 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
--- 1820,1826 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL, NULL);
}
bool
*************** ExecBRDeleteTriggers(EState *estate, Pla
*** 1824,1841 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
--- 1854,1862 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
*************** ExecARDeleteTriggers(EState *estate, Res
*** 1869,1875 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
--- 1890,1896 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1882,1887 ****
--- 1903,1909 ----
int *tgindx;
int i;
TriggerData LocTriggerData;
+ Bitmapset *modifiedCols;
trigdesc = relinfo->ri_TrigDesc;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1894,1899 ****
--- 1916,1923 ----
if (ntrigs == 0)
return;
+ modifiedCols = GetModifiedColumns(relinfo, estate);
+
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1907,1924 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 1931,1939 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1940,1946 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
--- 1955,1962 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL,
! GetModifiedColumns(relinfo, estate));
}
HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Pla
*** 1957,1968 ****
--- 1973,1987 ----
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ Bitmapset *modifiedCols;
trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
&newSlot);
if (trigtuple == NULL)
return NULL;
+ modifiedCols = GetModifiedColumns(relinfo, estate);
+
/*
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update. In that case we have a raw subplan
*************** ExecBRUpdateTriggers(EState *estate, Pla
*** 1980,1997 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1999,2007 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
!
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2024,2030 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes);
heap_freetuple(trigtuple);
}
}
--- 2034,2041 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, recheckIndexes,
! GetModifiedColumns(relinfo, estate));
heap_freetuple(trigtuple);
}
}
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2062,2079 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
--- 2073,2081 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
! continue;
!
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
*************** ExecASTruncateTriggers(EState *estate, R
*** 2095,2101 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
--- 2097,2103 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL, NULL);
}
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3825,3831 ****
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3827,3833 ----
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
! List *recheckIndexes, Bitmapset *modifiedCols)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3927,3945 ****
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! /* Ignore disabled triggers */
! if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! {
! 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
--- 3929,3936 ----
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
! if (!TriggerEnabled(trigger, modifiedCols))
! continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4012,4014 ****
--- 4003,4044 ----
&new_event, &new_shared);
}
}
+
+ static bool
+ TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols)
+ {
+ 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-level triggers */
+ if (trigger->tgnattr > 0 && modifiedCols != NULL &&
+ (trigger->tgtype & TRIGGER_TYPE_UPDATE) != 0)
+ {
+ 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;
+ }
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y 2009-10-13 08:41:43.000000000 +0900
--- work/src/backend/parser/gram.y 2009-10-14 12:15:45.348019975 +0900
*************** static TypeName *TableFuncTypeName(List
*** 251,257 ****
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
! %type <ival> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
--- 251,257 ----
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
! %type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <str> relation_name copy_file_name
*************** CreateTrigStmt:
*** 3240,3246 ****
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = $5;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
--- 3240,3247 ----
n->args = $13;
n->before = $4;
n->row = $8;
! n->events = intVal(linitial($5));
! n->columns = llast($5);
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
*************** CreateTrigStmt:
*** 3260,3266 ****
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = $6;
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
--- 3261,3268 ----
n->args = $18;
n->before = FALSE;
n->row = TRUE;
! n->events = intVal(linitial($6));
! n->columns = llast($6);
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3279,3295 ****
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! if ($1 & $3)
parser_yyerror("duplicate trigger events specified");
! $$ = $1 | $3;
}
;
TriggerOneEvent:
! INSERT { $$ = TRIGGER_TYPE_INSERT; }
! | DELETE_P { $$ = TRIGGER_TYPE_DELETE; }
! | UPDATE { $$ = TRIGGER_TYPE_UPDATE; }
! | TRUNCATE { $$ = TRIGGER_TYPE_TRUNCATE; }
;
TriggerForSpec:
--- 3281,3302 ----
{ $$ = $1; }
| TriggerEvents OR TriggerOneEvent
{
! int events1 = intVal(linitial($1));
! int events2 = intVal(linitial($3));
!
! if (events1 & events2)
parser_yyerror("duplicate trigger events specified");
! $$ = list_make2(makeInteger(events1 | events2),
! list_concat(llast($1), llast($3)));
}
;
TriggerOneEvent:
! INSERT { $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! | DELETE_P { $$ = 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:
diff -cprN head/src/backend/utils/adt/ruleutils.c work/src/backend/utils/adt/ruleutils.c
*** head/src/backend/utils/adt/ruleutils.c 2009-10-10 10:43:49.000000000 +0900
--- work/src/backend/utils/adt/ruleutils.c 2009-10-14 12:15:45.350019985 +0900
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 543,548 ****
--- 543,562 ----
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
+ if (trigrec->tgattr.dim1 > 0)
+ {
+ int i;
+
+ appendStringInfoString(&buf, " OF ");
+ for (i = 0; i < trigrec->tgattr.dim1; i++)
+ {
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf,
+ quote_identifier(get_relid_attribute_name(
+ trigrec->tgrelid, trigrec->tgattr.values[i])));
+ }
+ }
}
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
{
diff -cprN head/src/include/catalog/pg_trigger.h work/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h 2009-07-28 11:56:31.000000000 +0900
--- work/src/include/catalog/pg_trigger.h 2009-10-14 12:15:45.350019985 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* reserved for column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
--- 53,59 ----
int2 tgnargs; /* # of extra arguments in tgargs */
/* VARIABLE LENGTH FIELDS: */
! int2vector tgattr; /* column-specific triggers */
bytea tgargs; /* first\000second\000tgnargs\000 */
} FormData_pg_trigger;
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h 2009-10-13 09:53:08.000000000 +0900
--- work/src/include/nodes/parsenodes.h 2009-10-14 12:15:45.351020361 +0900
*************** typedef struct CreateTrigStmt
*** 1575,1580 ****
--- 1575,1581 ----
bool row; /* ROW/STATEMENT */
/* 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 */
/* The following are used for constraint triggers (RI and unique checks) */
bool isconstraint; /* This is a constraint trigger */
diff -cprN head/src/test/regress/expected/triggers.out work/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2009-10-08 01:27:18.000000000 +0900
--- work/src/test/regress/expected/triggers.out 2009-10-14 13:25:21.409894000 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
--- 278,314 ----
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, 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;
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, 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
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
! NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a, b;
a | b
----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,402 ----
|
(8 rows)
+ -- Column-level triggers should only fire on after row-level updates
+ 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
+ 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
+ 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.
+ 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;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
NOTICE: CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql work/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2009-10-08 01:27:18.000000000 +0900
--- work/src/test/regress/sql/triggers.sql 2009-10-14 13:23:27.437208181 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
--- 220,244 ----
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
! RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,291 ----
SELECT * FROM main_table ORDER BY a, b;
+ -- Column-level triggers should only fire on after row-level updates
+ 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;
+
+ 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');
+
+ ALTER TABLE main_table DROP COLUMN b;
+ 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;
+
-- Test enable/disable triggers
create table trigtest (i serial primary key);
diff-of-column-trigger.patchapplication/octet-stream; name=diff-of-column-trigger.patchDownload
diff -cprN head/src/backend/commands/trigger.c work/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c 2009-10-14 13:28:32.297915641 +0900
--- work/src/backend/commands/trigger.c 2009-10-14 13:18:14.881976192 +0900
*************** static void AfterTriggerSaveEvent(Result
*** 72,77 ****
--- 72,80 ----
List *recheckIndexes, Bitmapset *modifiedCols);
static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
+ #define GetModifiedColumns(relinfo, estate) \
+ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
+
/*
* Create a trigger. Returns the OID of the created trigger.
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1900,1905 ****
--- 1903,1909 ----
int *tgindx;
int i;
TriggerData LocTriggerData;
+ Bitmapset *modifiedCols;
trigdesc = relinfo->ri_TrigDesc;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1912,1917 ****
--- 1916,1923 ----
if (ntrigs == 0)
return;
+ modifiedCols = GetModifiedColumns(relinfo, estate);
+
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1925,1931 ****
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, NULL))
continue;
LocTriggerData.tg_trigger = trigger;
--- 1931,1937 ----
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
! if (!TriggerEnabled(trigger, modifiedCols))
continue;
LocTriggerData.tg_trigger = trigger;
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1949,1955 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL, NULL);
}
HeapTuple
--- 1955,1962 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL,
! GetModifiedColumns(relinfo, estate));
}
HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Pla
*** 1973,1979 ****
if (trigtuple == NULL)
return NULL;
! modifiedCols = rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols;
/*
* In READ COMMITTED isolation level it's possible that newtuple was
--- 1980,1986 ----
if (trigtuple == NULL)
return NULL;
! modifiedCols = GetModifiedColumns(relinfo, estate);
/*
* In READ COMMITTED isolation level it's possible that newtuple was
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2028,2034 ****
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple, recheckIndexes,
! rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols);
heap_freetuple(trigtuple);
}
}
--- 2035,2041 ----
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple, recheckIndexes,
! GetModifiedColumns(relinfo, estate));
heap_freetuple(trigtuple);
}
}
*************** TriggerEnabled(Trigger *trigger, Bitmaps
*** 4015,4022 ****
/* Check for column-level triggers */
if (trigger->tgnattr > 0 && modifiedCols != NULL &&
! (trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
! (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
{
int i;
bool modified;
--- 4022,4028 ----
/* Check for column-level triggers */
if (trigger->tgnattr > 0 && modifiedCols != NULL &&
! (trigger->tgtype & TRIGGER_TYPE_UPDATE) != 0)
{
int i;
bool modified;
diff -cprN head/src/test/regress/expected/triggers.out work/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out 2009-10-14 13:28:32.304917221 +0900
--- work/src/test/regress/expected/triggers.out 2009-10-14 13:25:21.409894000 +0900
*************** CREATE TRIGGER after_upd_b_row_trig AFTE
*** 330,335 ****
--- 330,339 ----
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
-------------------------------------------------------------------------------------------------------------------------------------------
*************** SELECT pg_get_triggerdef(oid, true) FROM
*** 346,351 ****
--- 350,356 ----
(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(after_upd_a_b_row)
*** 372,377 ****
--- 377,383 ----
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
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');
*************** ALTER TABLE main_table DROP COLUMN b;
*** 385,393 ****
--- 391,401 ----
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.
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;
-- Test enable/disable triggers
create table trigtest (i serial primary key);
diff -cprN head/src/test/regress/sql/triggers.sql work/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql 2009-10-14 13:28:32.305925057 +0900
--- work/src/test/regress/sql/triggers.sql 2009-10-14 13:23:27.437208181 +0900
*************** FOR EACH ROW EXECUTE PROCEDURE trigger_f
*** 264,269 ****
--- 264,274 ----
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';
*************** FOR EACH ROW EXECUTE PROCEDURE trigger_f
*** 278,283 ****
--- 283,289 ----
ALTER TABLE main_table DROP COLUMN b;
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;
-- Test enable/disable triggers
Peter Eisentraut <peter_e@gmx.net> writes:
Btw., I might not get a chance to commit this within the next 48 hours.
If someone else wants to, go ahead.
I will take it.
regards, tom lane
Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
column-trigger_20091014.patch is a full patch to the HEAD.
Applied with assorted corrections, mostly cosmetic but not entirely.
One thing you really should have caught was updating
copyfuncs/equalfuncs for the parsenode field addition.
Next time, try grepping for every reference to a struct type you
change ...
regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> wrote:
Applied with assorted corrections, mostly cosmetic but not entirely.
One thing you really should have caught was updating
copyfuncs/equalfuncs for the parsenode field addition.
Next time, try grepping for every reference to a struct type you
change ...
Thanks for the fix. I'll be careful about it.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center