diff -cprN head/doc/src/sgml/catalogs.sgml work/doc/src/sgml/catalogs.sgml *** head/doc/src/sgml/catalogs.sgml 2009-10-15 07:14:21.000000000 +0900 --- work/doc/src/sgml/catalogs.sgml 2009-11-18 16:29:02.215614422 +0900 *************** *** 4756,4761 **** --- 4756,4770 ---- Argument strings to pass to trigger, each NULL-terminated + + + tgqual + text + + Expression tree (in the form of a nodeToString() + representation) for the trigger's qualifying condition, or NULL + if the trigger has no WHEN clause. + 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-10-15 07:14:21.000000000 +0900 --- work/doc/src/sgml/ref/create_trigger.sgml 2009-11-18 17:42:57.312988000 +0900 *************** PostgreSQL documentation *** 23,28 **** --- 23,29 ---- CREATE TRIGGER name { BEFORE | AFTER } { event [ OR ... ] } ON table [ FOR [ EACH ] { ROW | STATEMENT } ] + WHEN ( condition ) EXECUTE PROCEDURE function_name ( arguments ) *************** UPDATE OF column_name1 + condition + + + Any SQL conditional expression (returning + boolean). Only FOR EACH ROW triggers + can refer NEW and OLD tuples. + INSERT trigger can refer NEW, + DELETE trigger can refer OLD, + and UPDATE trigger can refer both of them. + Note that if a trigger has multiple events, it can refer only tuples + that can be referred in all of the events. For example, + INSERT OR DELETE trigger cannot + refer neither NEW nor OLD tuples. + + + + System columns are not available in the WHEN clause + because those values are initialized after triggers are called. + They might return wrong values if they used in expressions of the clause. + + + + + function_name diff -cprN head/src/backend/catalog/index.c work/src/backend/catalog/index.c *** head/src/backend/catalog/index.c 2009-10-15 07:14:21.000000000 +0900 --- work/src/backend/catalog/index.c 2009-11-18 16:29:02.216972882 +0900 *************** index_create(Oid heapRelationId, *** 798,804 **** trigger->initdeferred = initdeferred; trigger->constrrel = NULL; ! (void) CreateTrigger(trigger, conOid, indexRelationId, isprimary ? "PK_ConstraintTrigger" : "Unique_ConstraintTrigger", false); --- 798,804 ---- trigger->initdeferred = initdeferred; trigger->constrrel = NULL; ! (void) CreateTrigger(trigger, NULL, conOid, indexRelationId, isprimary ? "PK_ConstraintTrigger" : "Unique_ConstraintTrigger", false); diff -cprN head/src/backend/commands/copy.c work/src/backend/commands/copy.c *** head/src/backend/commands/copy.c 2009-09-22 05:10:21.000000000 +0900 --- work/src/backend/commands/copy.c 2009-11-18 17:01:00.624558839 +0900 *************** CopyFrom(CopyState cstate) *** 1810,1816 **** estate->es_result_relation_info = resultRelInfo; /* Set up a tuple slot too */ ! slot = MakeSingleTupleTableSlot(tupDesc); econtext = GetPerTupleExprContext(estate); --- 1810,1817 ---- estate->es_result_relation_info = resultRelInfo; /* Set up a tuple slot too */ ! slot = ExecInitExtraTupleSlot(estate); ! ExecSetSlotDescriptor(slot, tupDesc); econtext = GetPerTupleExprContext(estate); *************** CopyFrom(CopyState cstate) *** 2198,2204 **** pfree(defmap); pfree(defexprs); ! ExecDropSingleTupleTableSlot(slot); ExecCloseIndices(resultRelInfo); --- 2199,2205 ---- pfree(defmap); pfree(defexprs); ! ExecResetTupleTable(estate->es_tupleTable, true); ExecCloseIndices(resultRelInfo); diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c *** head/src/backend/commands/tablecmds.c 2009-11-04 21:24:23.000000000 +0900 --- work/src/backend/commands/tablecmds.c 2009-11-18 16:29:02.217919047 +0900 *************** CreateFKCheckTrigger(RangeVar *myRel, Co *** 5482,5488 **** fk_trigger->constrrel = fkconstraint->pktable; fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, constraintOid, indexOid, "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ --- 5482,5488 ---- fk_trigger->constrrel = fkconstraint->pktable; fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ *************** createForeignKeyTriggers(Relation rel, C *** 5523,5528 **** --- 5523,5529 ---- fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->conname; fk_trigger->relation = fkconstraint->pktable; + fk_trigger->whenClause = NULL; fk_trigger->before = false; fk_trigger->row = true; fk_trigger->events = TRIGGER_TYPE_DELETE; *************** createForeignKeyTriggers(Relation rel, C *** 5563,5569 **** } fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, constraintOid, indexOid, "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ --- 5564,5570 ---- } fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ *************** createForeignKeyTriggers(Relation rel, C *** 5576,5581 **** --- 5577,5583 ---- fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->conname; fk_trigger->relation = fkconstraint->pktable; + fk_trigger->whenClause = NULL; fk_trigger->before = false; fk_trigger->row = true; fk_trigger->events = TRIGGER_TYPE_UPDATE; *************** createForeignKeyTriggers(Relation rel, C *** 5616,5622 **** } fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, constraintOid, indexOid, "RI_ConstraintTrigger", false); } --- 5618,5624 ---- } fk_trigger->args = NIL; ! (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, "RI_ConstraintTrigger", false); } diff -cprN head/src/backend/commands/trigger.c work/src/backend/commands/trigger.c *** head/src/backend/commands/trigger.c 2009-10-28 05:14:27.000000000 +0900 --- work/src/backend/commands/trigger.c 2009-11-18 16:32:15.510633752 +0900 *************** *** 32,41 **** --- 32,43 ---- #include "miscadmin.h" #include "nodes/bitmapset.h" #include "nodes/makefuncs.h" + #include "parser/parse_clause.h" #include "parser/parse_func.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "pgstat.h" + #include "rewrite/rewriteManip.h" #include "storage/bufmgr.h" #include "tcop/utility.h" #include "utils/acl.h" *************** static HeapTuple GetTupleForTrigger(ESta *** 66,79 **** ItemPointer tid, TupleTableSlot **newSlot); static bool TriggerEnabled(Trigger *trigger, TriggerEvent event, ! Bitmapset *modifiedCols); static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, int tgindx, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context); ! static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, ! bool row_trigger, HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols); --- 68,83 ---- ItemPointer tid, TupleTableSlot **newSlot); static bool TriggerEnabled(Trigger *trigger, TriggerEvent event, ! Bitmapset *modifiedCols, EState *estate, ! TupleDesc tupdesc, HeapTuple oldtup, HeapTuple newtup); ! static void ClearTriggerTableSlots(EState *estate); static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, int tgindx, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context); ! static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ! int event, HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols); *************** static void AfterTriggerSaveEvent(Result *** 101,107 **** * but a foreign-key constraint. This is a kluge for backwards compatibility. */ Oid ! CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions) { --- 105,111 ---- * but a foreign-key constraint. This is a kluge for backwards compatibility. */ Oid ! CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions) { *************** CreateTrigger(CreateTrigStmt *stmt, *** 128,133 **** --- 132,138 ---- Oid constrrelid = InvalidOid; ObjectAddress myself, referenced; + char *qual; rel = heap_openrv(stmt->relation, AccessExclusiveLock); *************** CreateTrigger(CreateTrigStmt *stmt, *** 224,229 **** --- 229,296 ---- return InvalidOid; } + if (stmt->whenClause == NULL) + qual = NULL; + else + { + ParseState *pstate; + Node *whenClause; + int num_rte; + + /* Set up pstate */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2. + * Set up their RTEs in the main pstate for use in parsing the rule + * qualification. + */ + if (TRIGGER_FOR_ROW(tgtype)) + { + RangeTblEntry *rte; + + if ((TRIGGER_FOR_DELETE(tgtype) || TRIGGER_FOR_UPDATE(tgtype)) && + !TRIGGER_FOR_INSERT(tgtype)) + { + rte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("old", NIL), false, false); + rte->requiredPerms = 0; + addRTEtoQuery(pstate, rte, false, true, true); + } + + if ((TRIGGER_FOR_INSERT(tgtype) || TRIGGER_FOR_UPDATE(tgtype)) && + !TRIGGER_FOR_DELETE(tgtype)) + { + rte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("new", NIL), false, false); + rte->requiredPerms = 0; + addRTEtoQuery(pstate, rte, false, true, true); + } + } + num_rte = list_length(pstate->p_rtable); + + /* take care of the when clause */ + whenClause = transformWhereClause(pstate, + (Node *) copyObject(stmt->whenClause), "WHEN"); + + if (list_length(pstate->p_rtable) != num_rte) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("trigger WHEN condition cannot contain references to other relations"))); + + /* + * adjust varno of NEW reference to PRS2_NEW_VARNO. + * XXX: Are there any better ways? + */ + if (num_rte == 1 && TRIGGER_FOR_INSERT(tgtype)) + ChangeVarNodes(whenClause, 1, PRS2_NEW_VARNO, 0); + + free_parsestate(pstate); + + qual = nodeToString(whenClause); + } + /* * Generate the trigger's OID now, so that we can use it in the name if * needed. *************** CreateTrigger(CreateTrigStmt *stmt, *** 387,392 **** --- 454,465 ---- tgattr = buildint2vector(columns, ncolumns); values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr); + /* set tgqual if trigger has WHEN clause */ + if (qual) + values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(qual); + else + nulls[Anum_pg_trigger_tgqual - 1] = true; + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); /* force tuple to have the desired OID */ *************** RelationBuildTriggers(Relation relation) *** 1184,1189 **** --- 1257,1264 ---- { Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup); Trigger *build; + Datum qual_datum; + bool isnull; if (numtrigs >= maxtrigs) { *************** RelationBuildTriggers(Relation relation) *** 1238,1243 **** --- 1313,1334 ---- else build->tgargs = NULL; + qual_datum = heap_getattr(htup, Anum_pg_trigger_tgqual, + RelationGetDescr(tgrel), &isnull); + if (!isnull) + { + char *qual_str = TextDatumGetCString(qual_datum); + + /* Fix references to OLD and NEW to INNER and OUTER */ + build->tgqual = stringToNode(qual_str); + ChangeVarNodes(build->tgqual, PRS2_OLD_VARNO, INNER, 0); + ChangeVarNodes(build->tgqual, PRS2_NEW_VARNO, OUTER, 0); + + pfree(qual_str); + } + else + build->tgqual = NULL; + numtrigs++; } *************** CopyTriggerDesc(TriggerDesc *trigdesc) *** 1396,1401 **** --- 1487,1493 ---- newargs[j] = pstrdup(trigger->tgargs[j]); trigger->tgargs = newargs; } + trigger->tgqual = copyObject(trigger->tgqual); trigger++; } *************** ExecBSInsertTriggers(EState *estate, Res *** 1682,1693 **** LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) continue; LocTriggerData.tg_trigger = trigger; --- 1774,1787 ---- LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL, ! estate, NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; *************** ExecASInsertTriggers(EState *estate, Res *** 1710,1717 **** TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0) ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, ! false, NULL, NULL, NIL, NULL); } HeapTuple --- 1804,1811 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0) ! AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, ! NULL, NULL, NIL, NULL); } HeapTuple *************** ExecBRInsertTriggers(EState *estate, Res *** 1733,1743 **** LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) continue; LocTriggerData.tg_trigtuple = oldtuple = newtuple; --- 1827,1840 ---- LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL, ! estate, RelationGetDescr(relinfo->ri_RelationDesc), ! NULL, newtuple)) continue; LocTriggerData.tg_trigtuple = oldtuple = newtuple; *************** ExecARInsertTriggers(EState *estate, Res *** 1763,1770 **** TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, ! true, NULL, trigtuple, recheckIndexes, NULL); } void --- 1860,1868 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) ! AfterTriggerSaveEvent(estate, relinfo, ! TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW, ! NULL, trigtuple, recheckIndexes, NULL); } void *************** ExecBSDeleteTriggers(EState *estate, Res *** 1795,1806 **** LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) continue; LocTriggerData.tg_trigger = trigger; --- 1893,1906 ---- LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL, ! estate, NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; *************** ExecASDeleteTriggers(EState *estate, Res *** 1823,1830 **** TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0) ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, ! false, NULL, NULL, NIL, NULL); } bool --- 1923,1930 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0) ! AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, ! NULL, NULL, NIL, NULL); } bool *************** ExecBRDeleteTriggers(EState *estate, EPQ *** 1854,1864 **** LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) continue; LocTriggerData.tg_trigtuple = trigtuple; --- 1954,1967 ---- LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL, ! estate, RelationGetDescr(relinfo->ri_RelationDesc), ! trigtuple, NULL)) continue; LocTriggerData.tg_trigtuple = trigtuple; *************** ExecARDeleteTriggers(EState *estate, Res *** 1893,1900 **** HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, ! true, trigtuple, NULL, NIL, NULL); heap_freetuple(trigtuple); } } --- 1996,2004 ---- HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); ! AfterTriggerSaveEvent(estate, relinfo, ! TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW, ! trigtuple, NULL, NIL, NULL); heap_freetuple(trigtuple); } } *************** ExecBSUpdateTriggers(EState *estate, Res *** 1930,1941 **** LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols)) continue; LocTriggerData.tg_trigger = trigger; --- 2034,2047 ---- LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols, ! estate, NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; *************** ExecASUpdateTriggers(EState *estate, Res *** 1958,1965 **** TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0) ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, ! false, NULL, NULL, NIL, GetModifiedColumns(relinfo, estate)); } --- 2064,2071 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0) ! AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, ! NULL, NULL, NIL, GetModifiedColumns(relinfo, estate)); } *************** ExecBRUpdateTriggers(EState *estate, EPQ *** 1999,2009 **** 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 (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols)) continue; LocTriggerData.tg_trigtuple = trigtuple; --- 2105,2118 ---- TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols, ! estate, RelationGetDescr(relinfo->ri_RelationDesc), ! trigtuple, newtuple)) continue; LocTriggerData.tg_trigtuple = trigtuple; *************** ExecARUpdateTriggers(EState *estate, Res *** 2037,2044 **** HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, ! true, trigtuple, newtuple, recheckIndexes, GetModifiedColumns(relinfo, estate)); heap_freetuple(trigtuple); } --- 2146,2154 ---- HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); ! AfterTriggerSaveEvent(estate, relinfo, ! TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW, ! trigtuple, newtuple, recheckIndexes, GetModifiedColumns(relinfo, estate)); heap_freetuple(trigtuple); } *************** ExecBSTruncateTriggers(EState *estate, R *** 2072,2083 **** LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) continue; LocTriggerData.tg_trigger = trigger; --- 2182,2195 ---- LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ClearTriggerTableSlots(estate); for (i = 0; i < ntrigs; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; ! if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL, ! estate, NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; *************** ExecASTruncateTriggers(EState *estate, R *** 2100,2107 **** TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0) ! AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE, ! false, NULL, NULL, NIL, NULL); } --- 2212,2219 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0) ! AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE, ! NULL, NULL, NIL, NULL); } *************** ltrmark:; *** 2219,2225 **** * Is trigger enabled to fire? */ static bool ! TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols) { /* Check replication-role-dependent enable state */ if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) --- 2331,2338 ---- * Is trigger enabled to fire? */ static bool ! TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols, ! EState *estate, TupleDesc tupdesc, HeapTuple oldtup, HeapTuple newtup) { /* Check replication-role-dependent enable state */ if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) *************** TriggerEnabled(Trigger *trigger, Trigger *** 2258,2266 **** --- 2371,2443 ---- return false; } + /* Check for WHEN clause */ + if (trigger->tgqual) + { + ExprContext *econtext; + List *predicate; + TupleTableSlot *oldslot = NULL; + TupleTableSlot *newslot = NULL; + + Assert(estate != NULL); + + econtext = GetPerTupleExprContext(estate); + + predicate = list_make1(ExecPrepareExpr((Expr *) trigger->tgqual, estate)); + + /* set OLD and NEW tuples into tupleslots */ + if (TRIGGER_FIRED_FOR_ROW(event)) + { + Assert(tupdesc != NULL); + + if (TRIGGER_FIRED_BY_DELETE(event) || TRIGGER_FIRED_BY_UPDATE(event)) + { + Assert(oldtup != NULL); + + if (estate->es_trig_oldtup_slot == NULL) + estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate); + + oldslot = estate->es_trig_oldtup_slot; + if (oldslot->tts_tupleDescriptor != tupdesc) + ExecSetSlotDescriptor(oldslot, tupdesc); + if (oldslot->tts_tuple != oldtup) + ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false); + + } + if (TRIGGER_FIRED_BY_INSERT(event) || TRIGGER_FIRED_BY_UPDATE(event)) + { + Assert(newtup != NULL); + + if (estate->es_trig_tuple_slot == NULL) + estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate); + + newslot = estate->es_trig_tuple_slot; + if (newslot->tts_tupleDescriptor != tupdesc) + ExecSetSlotDescriptor(newslot, tupdesc); + if (newslot->tts_tuple != newtup) + ExecStoreTuple(newtup, newslot, InvalidBuffer, false); + } + } + + econtext->ecxt_innertuple = oldslot; + econtext->ecxt_outertuple = newslot; + if (!ExecQual(predicate, econtext, false)) + return false; + } + return true; } + /* clear slots used by trigger to avoid dangling-reference to tuples */ + static void + ClearTriggerTableSlots(EState *estate) + { + if (estate->es_trig_tuple_slot) + ExecClearTuple(estate->es_trig_tuple_slot); + if (estate->es_trig_oldtup_slot) + ExecClearTuple(estate->es_trig_oldtup_slot); + } + /* ---------- * After-trigger stuff *************** AfterTriggerPendingOnRel(Oid relid) *** 3883,3889 **** * ---------- */ static void ! AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols) { --- 4060,4066 ---- * ---------- */ static void ! AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols) { *************** AfterTriggerSaveEvent(ResultRelInfo *rel *** 3894,3899 **** --- 4071,4087 ---- int i; int ntriggers; int *tgindx; + int offset; + bool row_trigger; + + Assert((event & ~(TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW)) == 0); + + /* + * The event code will be used both as a bitmask and an array offset, so + * split it into offset of our array and row trigger flag. + */ + offset = (event & TRIGGER_EVENT_OPMASK); + row_trigger = TRIGGER_FIRED_FOR_ROW(event); /* * Check state. We use normal tests not Asserts because it is possible *************** AfterTriggerSaveEvent(ResultRelInfo *rel *** 3907,3919 **** /* * Validate the event code and collect the associated tuple CTIDs. - * - * The event code will be used both as a bitmask and an array offset, so - * validation is important to make sure we don't walk off the edge of our - * arrays. */ new_event.ate_flags = 0; ! switch (event) { case TRIGGER_EVENT_INSERT: if (row_trigger) --- 4095,4103 ---- /* * Validate the event code and collect the associated tuple CTIDs. */ new_event.ate_flags = 0; ! switch (offset) { case TRIGGER_EVENT_INSERT: if (row_trigger) *************** AfterTriggerSaveEvent(ResultRelInfo *rel *** 3980,3999 **** */ if (row_trigger) { ! ntriggers = trigdesc->n_after_row[event]; ! tgindx = trigdesc->tg_after_row[event]; } else { ! ntriggers = trigdesc->n_after_statement[event]; ! tgindx = trigdesc->tg_after_statement[event]; } for (i = 0; i < ntriggers; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, event, modifiedCols)) continue; /* --- 4164,4186 ---- */ if (row_trigger) { ! ntriggers = trigdesc->n_after_row[offset]; ! tgindx = trigdesc->tg_after_row[offset]; } else { ! ntriggers = trigdesc->n_after_statement[offset]; ! tgindx = trigdesc->tg_after_statement[offset]; } + ClearTriggerTableSlots(estate); for (i = 0; i < ntriggers; i++) { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; ! if (!TriggerEnabled(trigger, event, modifiedCols, ! estate, RelationGetDescr(relinfo->ri_RelationDesc), ! oldtup, newtup)) continue; /* *************** AfterTriggerSaveEvent(ResultRelInfo *rel *** 4054,4062 **** /* * Fill in event structure and add it to the current query's queue. */ ! new_shared.ats_event = ! (event & TRIGGER_EVENT_OPMASK) | ! (row_trigger ? TRIGGER_EVENT_ROW : 0) | (trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) | (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0); new_shared.ats_tgoid = trigger->tgoid; --- 4241,4247 ---- /* * Fill in event structure and add it to the current query's queue. */ ! new_shared.ats_event = event | (trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) | (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0); new_shared.ats_tgoid = trigger->tgoid; diff -cprN head/src/backend/executor/execMain.c work/src/backend/executor/execMain.c *** head/src/backend/executor/execMain.c 2009-10-26 11:26:29.000000000 +0900 --- work/src/backend/executor/execMain.c 2009-11-18 16:29:02.219689260 +0900 *************** InitPlan(QueryDesc *queryDesc, int eflag *** 752,757 **** --- 752,758 ---- */ estate->es_tupleTable = NIL; estate->es_trig_tuple_slot = NULL; + estate->es_trig_oldtup_slot = NULL; /* mark EvalPlanQual not active */ estate->es_epqTuple = NULL; diff -cprN head/src/backend/executor/execQual.c work/src/backend/executor/execQual.c *** head/src/backend/executor/execQual.c 2009-11-05 07:26:05.000000000 +0900 --- work/src/backend/executor/execQual.c 2009-11-18 16:29:02.220969834 +0900 *************** static Datum ExecEvalArrayCoerceExpr(Arr *** 170,175 **** --- 170,176 ---- bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); + static TupleTableSlot *get_slot(ExprContext *econtext, Index varno); /* ---------------------------------------------------------------- *************** ExecEvalVar(ExprState *exprstate, ExprCo *** 501,523 **** */ attnum = variable->varattno; ! switch (variable->varno) ! { ! case INNER: /* get the tuple from the inner node */ ! slot = econtext->ecxt_innertuple; ! Assert(attnum > 0); ! break; ! ! case OUTER: /* get the tuple from the outer node */ ! slot = econtext->ecxt_outertuple; ! Assert(attnum > 0); ! break; ! ! default: /* get the tuple from the relation being ! * scanned */ ! slot = econtext->ecxt_scantuple; ! break; ! } if (attnum != InvalidAttrNumber) { --- 502,508 ---- */ attnum = variable->varattno; ! slot = get_slot(econtext, variable->varno); if (attnum != InvalidAttrNumber) { *************** ExecEvalScalarVar(ExprState *exprstate, *** 682,702 **** *isDone = ExprSingleResult; /* Get the input slot and attribute number we want */ ! switch (variable->varno) ! { ! case INNER: /* get the tuple from the inner node */ ! slot = econtext->ecxt_innertuple; ! break; ! ! case OUTER: /* get the tuple from the outer node */ ! slot = econtext->ecxt_outertuple; ! break; ! ! default: /* get the tuple from the relation being ! * scanned */ ! slot = econtext->ecxt_scantuple; ! break; ! } attnum = variable->varattno; --- 667,673 ---- *isDone = ExprSingleResult; /* Get the input slot and attribute number we want */ ! slot = get_slot(econtext, variable->varno); attnum = variable->varattno; *************** ExecEvalWholeRowVar(ExprState *exprstate *** 715,721 **** bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; ! TupleTableSlot *slot = econtext->ecxt_scantuple; HeapTuple tuple; TupleDesc tupleDesc; HeapTupleHeader dtuple; --- 686,692 ---- bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; ! TupleTableSlot *slot = get_slot(econtext, variable->varno); HeapTuple tuple; TupleDesc tupleDesc; HeapTupleHeader dtuple; *************** ExecEvalWholeRowSlow(ExprState *exprstat *** 766,772 **** bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; ! TupleTableSlot *slot = econtext->ecxt_scantuple; HeapTuple tuple; TupleDesc var_tupdesc; HeapTupleHeader dtuple; --- 737,743 ---- bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; ! TupleTableSlot *slot = get_slot(econtext, variable->varno); HeapTuple tuple; TupleDesc var_tupdesc; HeapTupleHeader dtuple; *************** ExecProject(ProjectionInfo *projInfo, Ex *** 5179,5181 **** --- 5150,5169 ---- */ return ExecStoreVirtualTuple(slot); } + + static TupleTableSlot * + get_slot(ExprContext *econtext, Index varno) + { + switch (varno) + { + case INNER: /* get the tuple from the inner node */ + Assert(econtext->ecxt_innertuple != NULL); + return econtext->ecxt_innertuple; + case OUTER: /* get the tuple from the outer node */ + Assert(econtext->ecxt_outertuple != NULL); + return econtext->ecxt_outertuple; + default: /* get the tuple from the relation being scanned */ + Assert(econtext->ecxt_scantuple != NULL); + return econtext->ecxt_scantuple; + } + } diff -cprN head/src/backend/executor/execUtils.c work/src/backend/executor/execUtils.c *** head/src/backend/executor/execUtils.c 2009-10-26 11:26:29.000000000 +0900 --- work/src/backend/executor/execUtils.c 2009-11-18 16:29:02.221857932 +0900 *************** CreateExecutorState(void) *** 117,122 **** --- 117,123 ---- estate->es_trig_target_relations = NIL; estate->es_trig_tuple_slot = NULL; + estate->es_trig_oldtup_slot = NULL; estate->es_param_list_info = NULL; estate->es_param_exec_vals = NULL; diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c *** head/src/backend/nodes/copyfuncs.c 2009-11-17 06:32:06.000000000 +0900 --- work/src/backend/nodes/copyfuncs.c 2009-11-18 16:29:02.221857932 +0900 *************** _copyCreateTrigStmt(CreateTrigStmt *from *** 3180,3185 **** --- 3180,3186 ---- COPY_SCALAR_FIELD(row); COPY_SCALAR_FIELD(events); COPY_NODE_FIELD(columns); + COPY_NODE_FIELD(whenClause); COPY_SCALAR_FIELD(isconstraint); COPY_SCALAR_FIELD(deferrable); COPY_SCALAR_FIELD(initdeferred); diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c *** head/src/backend/nodes/equalfuncs.c 2009-11-17 06:32:06.000000000 +0900 --- work/src/backend/nodes/equalfuncs.c 2009-11-18 16:29:02.222689821 +0900 *************** _equalCreateTrigStmt(CreateTrigStmt *a, *** 1670,1675 **** --- 1670,1676 ---- COMPARE_SCALAR_FIELD(row); COMPARE_SCALAR_FIELD(events); COMPARE_NODE_FIELD(columns); + COMPARE_NODE_FIELD(whenClause); COMPARE_SCALAR_FIELD(isconstraint); COMPARE_SCALAR_FIELD(deferrable); COMPARE_SCALAR_FIELD(initdeferred); diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y *** head/src/backend/parser/gram.y 2009-11-17 06:32:06.000000000 +0900 --- work/src/backend/parser/gram.y 2009-11-18 16:29:02.223620775 +0900 *************** static TypeName *TableFuncTypeName(List *** 249,254 **** --- 249,255 ---- %type TriggerEvents TriggerOneEvent %type TriggerFuncArg + %type TriggerWhen %type copy_file_name database_name access_method_clause access_method attr_name *************** AlterUserMappingStmt: ALTER USER MAPPING *** 3227,3240 **** CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON ! qualified_name TriggerForSpec EXECUTE PROCEDURE ! func_name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); n->trigname = $3; n->relation = $7; ! n->funcname = $11; ! n->args = $13; n->before = $4; n->row = $8; n->events = intVal(linitial($5)); --- 3228,3242 ---- CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON ! qualified_name TriggerForSpec ! TriggerWhen EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); n->trigname = $3; n->relation = $7; ! n->whenClause = $9; ! n->funcname = $12; ! n->args = $14; n->before = $4; n->row = $8; n->events = intVal(linitial($5)); *************** TriggerFuncArg: *** 3355,3360 **** --- 3357,3367 ---- | ColId { $$ = makeString($1); } ; + TriggerWhen: + WHEN '(' a_expr ')' { $$ = $3; } + | /*EMPTY*/ { $$ = NULL; } + ; + OptConstrFromTable: FROM qualified_name { $$ = $2; } | /*EMPTY*/ { $$ = NULL; } diff -cprN head/src/backend/tcop/utility.c work/src/backend/tcop/utility.c *** head/src/backend/tcop/utility.c 2009-11-17 06:32:07.000000000 +0900 --- work/src/backend/tcop/utility.c 2009-11-18 16:29:02.224641175 +0900 *************** ProcessUtility(Node *parsetree, *** 939,945 **** break; case T_CreateTrigStmt: ! CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid, InvalidOid, NULL, true); break; --- 939,945 ---- break; case T_CreateTrigStmt: ! CreateTrigger((CreateTrigStmt *) parsetree, queryString, InvalidOid, InvalidOid, NULL, true); break; diff -cprN head/src/backend/utils/adt/ruleutils.c work/src/backend/utils/adt/ruleutils.c *** head/src/backend/utils/adt/ruleutils.c 2009-11-06 08:24:25.000000000 +0900 --- work/src/backend/utils/adt/ruleutils.c 2009-11-18 16:29:02.225607111 +0900 *************** *** 42,47 **** --- 42,48 ---- #include "parser/keywords.h" #include "parser/parse_func.h" #include "parser/parse_oper.h" + #include "parser/parse_relation.h" #include "parser/parser.h" #include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" *************** pg_get_triggerdef_worker(Oid trigid, boo *** 487,492 **** --- 488,495 ---- SysScanDesc tgscan; int findx = 0; char *tgname; + Datum value; + bool isnull; /* * Fetch the pg_trigger tuple by the Oid of the trigger *************** pg_get_triggerdef_worker(Oid trigid, boo *** 596,618 **** appendStringInfo(&buf, "FOR EACH STATEMENT"); appendStringInfoString(&buf, pretty ? "\n " : " "); appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", generate_function_name(trigrec->tgfoid, 0, NIL, NULL, NULL)); if (trigrec->tgnargs > 0) { - bytea *val; - bool isnull; char *p; int i; ! val = DatumGetByteaP(fastgetattr(ht_trig, ! Anum_pg_trigger_tgargs, ! tgrel->rd_att, &isnull)); if (isnull) elog(ERROR, "tgargs is null for trigger %u", trigid); ! p = (char *) VARDATA(val); for (i = 0; i < trigrec->tgnargs; i++) { if (i > 0) --- 599,663 ---- appendStringInfo(&buf, "FOR EACH STATEMENT"); appendStringInfoString(&buf, pretty ? "\n " : " "); + /* If the trigger has an event qualification, add it */ + value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual, + tgrel->rd_att, &isnull); + if (!isnull) + { + Node *qual; + deparse_context context; + deparse_namespace dpns; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + + appendStringInfo(&buf, "WHEN ("); + + qual = stringToNode(TextDatumGetCString(value)); + + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = true; + context.prettyFlags = PRETTYFLAG_PAREN | (pretty ? PRETTYFLAG_INDENT : 0); + context.indentLevel = PRETTYINDENT_STD; + + /* Build a minimal NEW and OLD RTEs for the rel */ + oldrte = makeNode(RangeTblEntry); + oldrte->rtekind = RTE_RELATION; + oldrte->alias = oldrte->eref = makeAlias("old", NIL); + oldrte->relid = trigrec->tgrelid; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_RELATION; + newrte->alias = newrte->eref = makeAlias("new", NIL); + newrte->relid = trigrec->tgrelid; + + /* Build two-elements rtable */ + dpns.rtable = list_make2(oldrte, newrte); + dpns.ctes = NIL; + dpns.subplans = NIL; + dpns.outer_plan = dpns.inner_plan = NULL; + + get_rule_expr(qual, &context, false); + + appendStringInfo(&buf, ")%s", pretty ? "\n " : " "); + } + appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", generate_function_name(trigrec->tgfoid, 0, NIL, NULL, NULL)); if (trigrec->tgnargs > 0) { char *p; int i; ! value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs, ! tgrel->rd_att, &isnull); if (isnull) elog(ERROR, "tgargs is null for trigger %u", trigid); ! p = (char *) VARDATA(DatumGetByteaP(value)); for (i = 0; i < trigrec->tgnargs; i++) { if (i > 0) diff -cprN head/src/include/catalog/pg_trigger.h work/src/include/catalog/pg_trigger.h *** head/src/include/catalog/pg_trigger.h 2009-10-15 07:14:24.000000000 +0900 --- work/src/include/catalog/pg_trigger.h 2009-11-18 16:29:02.225607111 +0900 *************** CATALOG(pg_trigger,2620) *** 52,60 **** bool tginitdeferred; /* constraint trigger is deferred initially */ int2 tgnargs; /* # of extra arguments in tgargs */ ! /* VARIABLE LENGTH FIELDS (note: these are not supposed to be null) */ int2vector tgattr; /* column numbers, if trigger is on columns */ bytea tgargs; /* first\000second\000tgnargs\000 */ } FormData_pg_trigger; /* ---------------- --- 52,61 ---- bool tginitdeferred; /* constraint trigger is deferred initially */ int2 tgnargs; /* # of extra arguments in tgargs */ ! /* VARIABLE LENGTH FIELDS (note: tgattr and tgargs are not supposed to be null) */ int2vector tgattr; /* column numbers, if trigger is on columns */ bytea tgargs; /* first\000second\000tgnargs\000 */ + text tgqual; /* expression tree of WHEN clause, or NULL */ } FormData_pg_trigger; /* ---------------- *************** typedef FormData_pg_trigger *Form_pg_tri *** 68,74 **** * compiler constants for pg_trigger * ---------------- */ ! #define Natts_pg_trigger 15 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 --- 69,75 ---- * compiler constants for pg_trigger * ---------------- */ ! #define Natts_pg_trigger 16 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 *************** typedef FormData_pg_trigger *Form_pg_tri *** 84,89 **** --- 85,91 ---- #define Anum_pg_trigger_tgnargs 13 #define Anum_pg_trigger_tgattr 14 #define Anum_pg_trigger_tgargs 15 + #define Anum_pg_trigger_tgqual 16 /* Bits within tgtype */ #define TRIGGER_TYPE_ROW (1 << 0) diff -cprN head/src/include/catalog/toasting.h work/src/include/catalog/toasting.h *** head/src/include/catalog/toasting.h 2009-10-08 07:14:25.000000000 +0900 --- work/src/include/catalog/toasting.h 2009-11-18 16:48:52.390591559 +0900 *************** DECLARE_TOAST(pg_description, 2834, 2835 *** 47,52 **** --- 47,53 ---- DECLARE_TOAST(pg_proc, 2836, 2837); DECLARE_TOAST(pg_rewrite, 2838, 2839); DECLARE_TOAST(pg_statistic, 2840, 2841); + DECLARE_TOAST(pg_trigger, 2336, 2337); /* shared catalogs */ DECLARE_TOAST(pg_authid, 2842, 2843); diff -cprN head/src/include/commands/trigger.h work/src/include/commands/trigger.h *** head/src/include/commands/trigger.h 2009-10-26 11:26:41.000000000 +0900 --- work/src/include/commands/trigger.h 2009-11-18 16:29:02.225607111 +0900 *************** extern PGDLLIMPORT int SessionReplicatio *** 104,110 **** #define TRIGGER_FIRES_ON_REPLICA 'R' #define TRIGGER_DISABLED 'D' ! extern Oid CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions); --- 104,110 ---- #define TRIGGER_FIRES_ON_REPLICA 'R' #define TRIGGER_DISABLED 'D' ! extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions); diff -cprN head/src/include/nodes/execnodes.h work/src/include/nodes/execnodes.h *** head/src/include/nodes/execnodes.h 2009-10-26 11:26:41.000000000 +0900 --- work/src/include/nodes/execnodes.h 2009-11-18 16:29:02.226604133 +0900 *************** typedef struct EState *** 345,351 **** /* Stuff used for firing triggers: */ List *es_trig_target_relations; /* trigger-only ResultRelInfos */ ! TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */ /* Parameter info: */ ParamListInfo es_param_list_info; /* values of external params */ --- 345,352 ---- /* Stuff used for firing triggers: */ List *es_trig_target_relations; /* trigger-only ResultRelInfos */ ! TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */ ! TupleTableSlot *es_trig_oldtup_slot; /* for trigger old tuples */ /* Parameter info: */ ParamListInfo es_param_list_info; /* values of external params */ diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h *** head/src/include/nodes/parsenodes.h 2009-11-17 06:32:07.000000000 +0900 --- work/src/include/nodes/parsenodes.h 2009-11-18 16:29:02.226604133 +0900 *************** typedef struct CreateTrigStmt *** 1571,1576 **** --- 1571,1577 ---- /* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */ int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */ List *columns; /* column names, or NIL for all columns */ + Node *whenClause; /* qualifications */ /* The following are used for constraint triggers (RI and unique checks) */ bool isconstraint; /* This is a constraint trigger */ diff -cprN head/src/include/utils/rel.h work/src/include/utils/rel.h *** head/src/include/utils/rel.h 2009-07-28 11:56:31.000000000 +0900 --- work/src/include/utils/rel.h 2009-11-18 16:29:02.227553816 +0900 *************** typedef struct Trigger *** 66,71 **** --- 66,72 ---- int16 tgnattr; int16 *tgattr; char **tgargs; + Node *tgqual; } Trigger; typedef struct TriggerDesc 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-15 07:14:25.000000000 +0900 --- work/src/test/regress/expected/triggers.out 2009-11-18 17:01:24.539549414 +0900 *************** SELECT * FROM main_table ORDER BY a, b; *** 322,327 **** --- 322,403 ---- | (8 rows) + -- Trigger with WHEN clause + CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a'); + CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table + FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any'); + CREATE TRIGGER insert_a AFTER INSERT ON main_table + FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a'); + CREATE TRIGGER delete_a AFTER DELETE ON main_table + FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a'); + CREATE TRIGGER insert_when BEFORE INSERT ON main_table + FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when'); + CREATE TRIGGER delete_when AFTER DELETE ON main_table + FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when'); + INSERT INTO main_table (a) VALUES (123), (456); + NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT + NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT + NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW + NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT + COPY main_table FROM stdin; + NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT + NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT + NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW + NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT + DELETE FROM main_table WHERE a IN (123, 456); + NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW + NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW + NOTICE: trigger_func(delete_when) called: action = DELETE, when = AFTER, level = STATEMENT + UPDATE main_table SET a = 50, b = 60; + NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW + NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW + NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT + SELECT * FROM main_table ORDER BY a, b; + a | b + ----+---- + 6 | 10 + 21 | 20 + 30 | 40 + 31 | 10 + 50 | 35 + 50 | 60 + 81 | 15 + | + (8 rows) + + SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; + pg_get_triggerdef + -------------------------------------------------- + CREATE TRIGGER modified_a + BEFORE UPDATE OF a ON main_table + FOR EACH ROW + WHEN (old.a <> new.a) + EXECUTE PROCEDURE trigger_func('modified_a') + (1 row) + + SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any'; + pg_get_triggerdef + ---------------------------------------------------- + CREATE TRIGGER modified_any + BEFORE UPDATE OF a ON main_table + FOR EACH ROW + WHEN (old.* IS DISTINCT FROM new.*) + EXECUTE PROCEDURE trigger_func('modified_any') + (1 row) + + DROP TRIGGER modified_a ON main_table; + DROP TRIGGER modified_any ON main_table; + DROP TRIGGER insert_a ON main_table; + DROP TRIGGER delete_a ON main_table; + DROP TRIGGER insert_when ON main_table; + DROP TRIGGER delete_when ON main_table; -- Test column-level triggers DROP TRIGGER after_upd_row_trig ON main_table; CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table *************** FOR EACH ROW EXECUTE PROCEDURE trigger_f *** 393,398 **** --- 469,489 ---- ERROR: syntax error at or near "OF" LINE 1: CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table ^ + CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_ins_when'); + ERROR: missing FROM-clause entry for table "old" + LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger... + ^ + CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_del_when'); + ERROR: missing FROM-clause entry for table "new" + LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger... + ^ + CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table + FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('error_stmt_when'); + ERROR: missing FROM-clause entry for table "old" + LINE 2: FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECU... + ^ -- check dependency restrictions ALTER TABLE main_table DROP COLUMN b; ERROR: cannot drop table main_table column b because other objects depend on it 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-15 07:14:25.000000000 +0900 --- work/src/test/regress/sql/triggers.sql 2009-11-18 16:34:10.518923028 +0900 *************** COPY main_table (a, b) FROM stdin; *** 254,259 **** --- 254,289 ---- SELECT * FROM main_table ORDER BY a, b; + -- Trigger with WHEN clause + CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a'); + CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table + FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any'); + CREATE TRIGGER insert_a AFTER INSERT ON main_table + FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a'); + CREATE TRIGGER delete_a AFTER DELETE ON main_table + FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a'); + CREATE TRIGGER insert_when BEFORE INSERT ON main_table + FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when'); + CREATE TRIGGER delete_when AFTER DELETE ON main_table + FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when'); + INSERT INTO main_table (a) VALUES (123), (456); + COPY main_table FROM stdin; + 123 999 + 456 999 + \. + DELETE FROM main_table WHERE a IN (123, 456); + UPDATE main_table SET a = 50, b = 60; + SELECT * FROM main_table ORDER BY a, b; + SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; + SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any'; + DROP TRIGGER modified_a ON main_table; + DROP TRIGGER modified_any ON main_table; + DROP TRIGGER insert_a ON main_table; + DROP TRIGGER delete_a ON main_table; + DROP TRIGGER insert_when ON main_table; + DROP TRIGGER delete_when ON main_table; + -- Test column-level triggers DROP TRIGGER after_upd_row_trig ON main_table; *************** FOR EACH ROW EXECUTE PROCEDURE trigger_f *** 283,288 **** --- 313,325 ---- CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a'); + CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_ins_when'); + CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table + FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_del_when'); + CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table + FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('error_stmt_when'); + -- check dependency restrictions ALTER TABLE main_table DROP COLUMN b; -- this should succeed, but we'll roll it back to keep the triggers around