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 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 TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs %type opt_lancompiler ! %type TriggerEvents TriggerOneEvent %type TriggerFuncArg %type relation_name copy_file_name --- 248,254 ---- %type TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs %type opt_lancompiler ! %type TriggerEvents TriggerOneEvent %type TriggerFuncArg %type 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);