Index: doc/src/sgml/ref/create_trigger.sgml =================================================================== RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v retrieving revision 1.43 diff -c -p -r1.43 create_trigger.sgml *** doc/src/sgml/ref/create_trigger.sgml 9 Dec 2005 19:39:41 -0000 1.43 --- doc/src/sgml/ref/create_trigger.sgml 7 Jul 2006 21:58:04 -0000 *************** PostgreSQL documentation *** 18,27 **** --- 18,32 ---- CREATE TRIGGER + + WHEN + + CREATE TRIGGER name { BEFORE | AFTER } { event [ OR ... ] } ON table [ FOR [ EACH ] { ROW | STATEMENT } ] + [ WHEN ( expr ) ] EXECUTE PROCEDURE funcname ( arguments ) *************** CREATE TRIGGER expr + + + An SQL expression which returns a boolean result. + + + + INSERT triggers may refer only to the + NEW table. DELETE triggers + may only refer to the OLD table. + UPDATE triggers may refer to both. The + expression may not refer to any other tables. + + + + This feature is not supported on FOR STATEMENT triggers. + + + + + funcname Index: src/backend/commands/trigger.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/commands/trigger.c,v retrieving revision 1.203 diff -c -p -r1.203 trigger.c *** src/backend/commands/trigger.c 16 Jun 2006 20:23:44 -0000 1.203 --- src/backend/commands/trigger.c 8 Jul 2006 10:12:46 -0000 *************** *** 31,37 **** --- 31,39 ---- #include "executor/instrument.h" #include "miscadmin.h" #include "nodes/makefuncs.h" + #include "optimizer/clauses.h" #include "parser/parse_func.h" + #include "rewrite/rewriteManip.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** static HeapTuple ExecCallTriggerFunc(Tri *** 55,60 **** --- 57,66 ---- MemoryContext per_tuple_context); static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup); + static void setup_trigger_quals(ResultRelInfo *ri, EState *estate, + bool before, int event); + static bool test_trig_qual(EState *estate, Relation rel, HeapTuple oldtuple, + HeapTuple newtuple, List *qual, int event); /* *************** CreateTrigger(CreateTrigStmt *stmt, bool *** 182,187 **** --- 188,203 ---- } /* + * SQL:2003 doesn't specifically prohibit WHEN clauses for statement-level + * triggers, but Oracle doesn't allow them, and it's not clear they'd be + * useful anyway. Therefore, disallow them for now. + */ + if (!stmt->row && stmt->when != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WHEN clause cannot be specified for statement-level triggers"))); + + /* * Generate the trigger's OID now, so that we can use it in the name if * needed. */ *************** CreateTrigger(CreateTrigStmt *stmt, bool *** 315,320 **** --- 331,338 ---- values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid); values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable); values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred); + values[Anum_pg_trigger_tgqual - 1] = DirectFunctionCall1(textin, + CStringGetDatum(nodeToString(stmt->when))); if (stmt->args) { *************** CreateTrigger(CreateTrigStmt *stmt, bool *** 442,447 **** --- 460,473 ---- } } + if (stmt->when) + { + ChangeVarNodes(stmt->when, TRIG_OLD_VARNO, 1, 0); + ChangeVarNodes(stmt->when, TRIG_NEW_VARNO, 2, 0); + recordDependencyOnExpr(&myself, stmt->when, stmt->rtable, + DEPENDENCY_NORMAL); + } + /* Keep lock on target rel until end of xact */ heap_close(rel, NoLock); *************** RelationBuildTriggers(Relation relation) *** 875,880 **** --- 901,909 ---- { Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup); Trigger *build; + Datum tmp; + bool isnull; + char *when_str; if (found >= ntrigs) elog(ERROR, "too many trigger records found for relation \"%s\"", *************** RelationBuildTriggers(Relation relation) *** 927,932 **** --- 956,979 ---- else build->tgargs = NULL; + /* get the trigger's WHEN clause, if any */ + tmp = heap_getattr(htup, Anum_pg_trigger_tgqual, + RelationGetDescr(tgrel), &isnull); + Assert(!isnull); + + when_str = DatumGetCString(DirectFunctionCall1(textout, + PointerGetDatum(tmp))); + + /* + * XXX: we leak the node here because FreeTriggerDesc() has no + * ability to do a deep free of a Node. + * + * Ideally, the Node would be created in its own context which + * we could just reset. Since we create the node in the + * CacheMemoryContext the effect of this leak will be long lived + */ + + build->when = (Node *) stringToNode(when_str); found++; } *************** CopyTriggerDesc(TriggerDesc *trigdesc) *** 1074,1079 **** --- 1121,1127 ---- newargs[j] = pstrdup(trigger->tgargs[j]); trigger->tgargs = newargs; } + trigger->when = copyObject(trigdesc->triggers[i].when); trigger++; } *************** FreeTriggerDesc(TriggerDesc *trigdesc) *** 1175,1180 **** --- 1223,1229 ---- pfree(trigger->tgargs[trigger->tgnargs]); pfree(trigger->tgargs); } + /* XXX: leaks trigger->when */ trigger++; } pfree(trigdesc->triggers); *************** equalTriggerDescs(TriggerDesc *trigdesc1 *** 1232,1237 **** --- 1281,1288 ---- return false; if (trig1->tgnattr != trig2->tgnattr) return false; + if (!equal(trig1->when, trig2->when)) + return false; if (trig1->tgnattr > 0 && memcmp(trig1->tgattr, trig2->tgattr, trig1->tgnattr * sizeof(int2)) != 0) *************** ExecBRInsertTriggers(EState *estate, Res *** 1389,1399 **** --- 1440,1454 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; int ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_INSERT]; int *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_INSERT]; + TrigQualState *qual_state; HeapTuple newtuple = trigtuple; HeapTuple oldtuple; TriggerData LocTriggerData; int i; + setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_INSERT); + qual_state = relinfo->ri_TrigQuals; + LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | *************** ExecBRInsertTriggers(EState *estate, Res *** 1401,1412 **** --- 1456,1481 ---- 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 (!trigger->tgenabled) continue; + + /* Check the trigger's WHEN clause, if any */ + if (trigger->when) + { + bool res; + + res = test_trig_qual(estate, relinfo->ri_RelationDesc, + NULL, trigtuple, qual_state->quals[tgindx[i]], + TRIGGER_EVENT_INSERT); + if (!res) + continue; + } + LocTriggerData.tg_trigtuple = oldtuple = newtuple; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; *************** ExecBRDeleteTriggers(EState *estate, Res *** 1501,1506 **** --- 1570,1576 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; int ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_DELETE]; int *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_DELETE]; + TrigQualState *qual_state; bool result = true; TriggerData LocTriggerData; HeapTuple trigtuple; *************** ExecBRDeleteTriggers(EState *estate, Res *** 1512,1517 **** --- 1582,1590 ---- if (trigtuple == NULL) return false; + setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_DELETE); + qual_state = relinfo->ri_TrigQuals; + LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | *************** ExecBRDeleteTriggers(EState *estate, Res *** 1525,1530 **** --- 1598,1615 ---- if (!trigger->tgenabled) continue; + + /* Check the trigger's WHEN clause, if any */ + if (trigger->when) + { + bool res; + + res = test_trig_qual(estate, relinfo->ri_RelationDesc, + trigtuple, NULL, qual_state->quals[tgindx[i]], + TRIGGER_EVENT_DELETE); + if (!res) + continue; + } LocTriggerData.tg_trigtuple = trigtuple; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; *************** ExecBRUpdateTriggers(EState *estate, Res *** 1632,1637 **** --- 1717,1723 ---- TriggerDesc *trigdesc = relinfo->ri_TrigDesc; int ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_UPDATE]; int *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE]; + TrigQualState *qual_state; TriggerData LocTriggerData; HeapTuple trigtuple; HeapTuple oldtuple; *************** ExecBRUpdateTriggers(EState *estate, Res *** 1643,1648 **** --- 1729,1737 ---- if (trigtuple == NULL) return NULL; + setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_UPDATE); + qual_state = relinfo->ri_TrigQuals; + /* * In READ COMMITTED isolation level it's possible that newtuple was * changed due to concurrent update. *************** ExecBRUpdateTriggers(EState *estate, Res *** 1661,1666 **** --- 1750,1768 ---- if (!trigger->tgenabled) continue; + + /* Check the trigger's WHEN clause, if any */ + if (trigger->when) + { + bool res; + + res = test_trig_qual(estate, relinfo->ri_RelationDesc, + trigtuple, newtuple, qual_state->quals[tgindx[i]], + TRIGGER_EVENT_UPDATE); + if (!res) + continue; + } + LocTriggerData.tg_trigtuple = trigtuple; LocTriggerData.tg_newtuple = oldtuple = newtuple; LocTriggerData.tg_trigtuplebuf = InvalidBuffer; *************** AfterTriggerSaveEvent(ResultRelInfo *rel *** 3262,3264 **** --- 3364,3457 ---- afterTriggerAddEvent(new_event); } } + + /* + * There is some inefficiency here. Subsequent calls to setup_trigger_quals() + * during the same command will end up iterating through the array of + * triggers. Ideally, this redundant work should be avoided. + */ + + static void + setup_trigger_quals(ResultRelInfo *ri, EState *estate, bool before, int event) + { + TriggerDesc *trigdesc = ri->ri_TrigDesc; + MemoryContext old_cxt; + TrigQualState *qual_state; + int i; + int ntrigs; + int *tgindx; + + old_cxt = MemoryContextSwitchTo(estate->es_query_cxt); + if (ri->ri_TrigQuals == NULL) + { + ri->ri_TrigQuals = palloc(sizeof(TrigQualState)); + ri->ri_TrigQuals->quals = palloc0(trigdesc->numtriggers * sizeof(List *)); + } + + qual_state = ri->ri_TrigQuals; + + if (before) + { + ntrigs = trigdesc->n_before_row[event]; + tgindx = trigdesc->tg_before_row[event]; + } + else + { + ntrigs = trigdesc->n_after_row[event]; + tgindx = trigdesc->tg_after_row[event]; + } + + for (i = 0; i < ntrigs; i++) + { + Trigger *trigger; + int trig_idx; + List *qual; + + trig_idx = tgindx[i]; + trigger = &trigdesc->triggers[trig_idx]; + + if (!trigger->when) + continue; + + if (qual_state->quals[trig_idx]) + continue; + + qual = make_ands_implicit((Expr *) trigger->when); + qual_state->quals[trig_idx] = (List *) ExecPrepareExpr((Expr *) qual, + estate); + } + + MemoryContextSwitchTo(old_cxt); + } + + /* + * Test if a trigger qualification evaluates true for the + * input tuple(s) + */ + static bool + test_trig_qual(EState *estate, Relation rel, HeapTuple oldtuple, + HeapTuple newtuple, List *qual, int event) + { + ExprContext *econtext = GetPerTupleExprContext(estate); + TupleDesc tupdesc = RelationGetDescr(rel); + + if (event == TRIGGER_EVENT_INSERT || + event == TRIGGER_EVENT_UPDATE) + { + if (econtext->ecxt_newtuple == NULL) + econtext->ecxt_newtuple = MakeSingleTupleTableSlot(tupdesc); + ExecClearTuple(econtext->ecxt_newtuple); + ExecStoreTuple(newtuple, econtext->ecxt_newtuple, + InvalidBuffer, false); + } + if (event == TRIGGER_EVENT_UPDATE || + event == TRIGGER_EVENT_DELETE) + { + if (econtext->ecxt_oldtuple == NULL) + econtext->ecxt_oldtuple = MakeSingleTupleTableSlot(tupdesc); + ExecClearTuple(econtext->ecxt_oldtuple); + ExecStoreTuple(oldtuple, econtext->ecxt_oldtuple, InvalidBuffer, false); + } + + return ExecQual(qual, econtext, false); + } Index: src/backend/executor/execMain.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execMain.c,v retrieving revision 1.273 diff -c -p -r1.273 execMain.c *** src/backend/executor/execMain.c 3 Jul 2006 22:45:38 -0000 1.273 --- src/backend/executor/execMain.c 7 Jul 2006 18:55:35 -0000 *************** initResultRelInfo(ResultRelInfo *resultR *** 914,919 **** --- 914,920 ---- resultRelInfo->ri_TrigFunctions = NULL; resultRelInfo->ri_TrigInstrument = NULL; } + resultRelInfo->ri_TrigQuals = NULL; resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_junkFilter = NULL; Index: src/backend/executor/execQual.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execQual.c,v retrieving revision 1.191 diff -c -p -r1.191 execQual.c *** src/backend/executor/execQual.c 16 Jun 2006 18:42:21 -0000 1.191 --- src/backend/executor/execQual.c 7 Jul 2006 19:32:34 -0000 *************** ExecEvalVar(ExprState *exprstate, ExprCo *** 459,464 **** --- 459,472 ---- Assert(attnum > 0); break; + case TRIG_OLD_VARNO: /* old tuple in trigger context */ + slot = econtext->ecxt_oldtuple; + break; + + case TRIG_NEW_VARNO: /* new tuple in trigger context */ + slot = econtext->ecxt_newtuple; + break; + default: /* get the tuple from the relation being * scanned */ slot = econtext->ecxt_scantuple; Index: src/backend/executor/execUtils.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execUtils.c,v retrieving revision 1.135 diff -c -p -r1.135 execUtils.c *** src/backend/executor/execUtils.c 16 Jun 2006 18:42:22 -0000 1.135 --- src/backend/executor/execUtils.c 7 Jul 2006 13:04:01 -0000 *************** CreateExprContext(EState *estate) *** 296,301 **** --- 296,303 ---- econtext->ecxt_scantuple = NULL; econtext->ecxt_innertuple = NULL; econtext->ecxt_outertuple = NULL; + econtext->ecxt_oldtuple = NULL; + econtext->ecxt_newtuple = NULL; econtext->ecxt_per_query_memory = estate->es_query_cxt; *************** FreeExprContext(ExprContext *econtext) *** 356,361 **** --- 358,371 ---- /* Call any registered callbacks */ ShutdownExprContext(econtext); + + /* Clean up special slots for NEW and OLD trigger tuples */ + if (econtext->ecxt_newtuple) + ExecDropSingleTupleTableSlot(econtext->ecxt_newtuple); + + if (econtext->ecxt_oldtuple) + ExecDropSingleTupleTableSlot(econtext->ecxt_oldtuple); + /* And clean up the memory used */ MemoryContextDelete(econtext->ecxt_per_tuple_memory); /* Unlink self from owning EState */ Index: src/backend/nodes/copyfuncs.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v retrieving revision 1.342 diff -c -p -r1.342 copyfuncs.c *** src/backend/nodes/copyfuncs.c 3 Jul 2006 22:45:38 -0000 1.342 --- src/backend/nodes/copyfuncs.c 7 Jul 2006 13:04:02 -0000 *************** _copyCreateTrigStmt(CreateTrigStmt *from *** 2441,2446 **** --- 2441,2448 ---- COPY_SCALAR_FIELD(deferrable); COPY_SCALAR_FIELD(initdeferred); COPY_NODE_FIELD(constrrel); + COPY_NODE_FIELD(when); + COPY_NODE_FIELD(rtable); return newnode; } Index: src/backend/nodes/equalfuncs.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v retrieving revision 1.276 diff -c -p -r1.276 equalfuncs.c *** src/backend/nodes/equalfuncs.c 3 Jul 2006 22:45:38 -0000 1.276 --- src/backend/nodes/equalfuncs.c 7 Jul 2006 13:04:03 -0000 *************** _equalCreateTrigStmt(CreateTrigStmt *a, *** 1306,1311 **** --- 1306,1313 ---- COMPARE_SCALAR_FIELD(deferrable); COMPARE_SCALAR_FIELD(initdeferred); COMPARE_NODE_FIELD(constrrel); + COMPARE_NODE_FIELD(when); + COMPARE_NODE_FIELD(rtable); return true; } Index: src/backend/parser/analyze.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/parser/analyze.c,v retrieving revision 1.338 diff -c -p -r1.338 analyze.c *** src/backend/parser/analyze.c 3 Jul 2006 22:45:39 -0000 1.338 --- src/backend/parser/analyze.c 7 Jul 2006 19:45:06 -0000 *************** static Query *transformInsertStmt(ParseS *** 106,111 **** --- 106,112 ---- static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, List **extras_before, List **extras_after); + static Query *transformCreateTrigStmt(ParseState *pstate, CreateTrigStmt *stmt); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); *************** transformStmt(ParseState *pstate, Node * *** 324,329 **** --- 325,335 ---- extras_before, extras_after); break; + case T_CreateTrigStmt: + result = transformCreateTrigStmt(pstate, + (CreateTrigStmt *) parseTree); + break; + case T_ViewStmt: result = transformViewStmt(pstate, (ViewStmt *) parseTree, extras_before, extras_after); *************** transformRuleStmt(ParseState *pstate, Ru *** 1852,1857 **** --- 1858,1965 ---- return qry; } + /* + * transformCreateTrigStmt - + * transform a CREATE TRIGGER statement. Most of the work we do is + * transforming the statement's WHEN clause, if any. + */ + static Query * + transformCreateTrigStmt(ParseState *pstate, CreateTrigStmt *stmt) + { + Query *qry; + Relation rel; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + int i; + + qry = makeNode(Query); + qry->commandType = CMD_UTILITY; + qry->utilityStmt = (Node *) stmt; + + /* If there's no WHEN clause, we're done. */ + if (!stmt->when) + return qry; + + /* + * Note that we acquire and keep an exclusive lock on the target table + * only if there's a WHEN clause; if there's no WHEN, we acquire the same + * lock in CreateTrigger(), so the effect should be the same. + */ + rel = heap_openrv(stmt->relation, AccessExclusiveLock); + + /* + * Setup RTEs for the NEW and OLD relations in the main pstate, for use in + * parsing the trigger qualification. We initially add "OLD" with RT index + * 1, and "NEW" with RT index 2, and then change them to use the correct + * varnos below. + */ + Assert(pstate->p_rtable == NIL); + oldrte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("*OLD*", NIL), + false, false); + newrte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("*NEW*", NIL), + false, false); + + for (i = 0; stmt->actions[i] != '\0'; i++) + { + if (stmt->actions[i] == 'd' || stmt->actions[i] == 'u') + { + addRTEtoQuery(pstate, oldrte, false, true, true); + break; + } + } + + for (i = 0; stmt->actions[i] != '\0'; i++) + { + if (stmt->actions[i] == 'i' || stmt->actions[i] == 'u') + { + addRTEtoQuery(pstate, newrte, false, true, true); + break; + } + } + + /* process WHEN clause as though it was a WHERE clause */ + stmt->when = transformWhereClause(pstate, stmt->when, "WHEN"); + + if (list_length(pstate->p_rtable) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("trigger WHEN condition may not contain " + "references to other relations"))); + + stmt->rtable = list_copy(pstate->p_rtable); + + /* aggregates not allowed */ + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("trigger WHEN condition may not contain aggregate functions"))); + + /* subselects are not allowed either, at least for now */ + if (pstate->p_hasSubLinks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("trigger WHEN condition may not contain subqueries"))); + + /* + * Rewrite the WHEN expression to give the right varno to the NEW and OLD + * relations, so that these relations can be treated specially by the + * executor. + * + * We could avoid doing this by having the executor code do it for us when + * we initialise the expressions out of pg_trigger. This seems like a + * better place to do it, especially if you consider very complex + * expressions. + */ + ChangeVarNodes(stmt->when, 1, TRIG_OLD_VARNO, 0); + ChangeVarNodes(stmt->when, 2, TRIG_NEW_VARNO, 0); + + /* Close relation, but keep the exclusive lock */ + heap_close(rel, NoLock); + + return qry; + } /* * transformSelectStmt - Index: src/backend/parser/gram.y =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/parser/gram.y,v retrieving revision 2.551 diff -c -p -r2.551 gram.y *** src/backend/parser/gram.y 3 Jul 2006 22:45:39 -0000 2.551 --- src/backend/parser/gram.y 7 Jul 2006 20:20:40 -0000 *************** *** 79,85 **** extern List *parsetree; /* final parse result is delivered here */ ! static bool QueryIsRule = FALSE; /* * If you need access to certain yacc-generated variables and find that --- 79,85 ---- extern List *parsetree; /* final parse result is delivered here */ ! static bool QueryIsRuleOrTrigger = FALSE; /* * If you need access to certain yacc-generated variables and find that *************** static void doNegateFloat(Value *v); *** 169,175 **** UnlistenStmt UpdateStmt VacuumStmt VariableResetStmt VariableSetStmt VariableShowStmt ViewStmt CheckPointStmt CreateConversionStmt ! DeallocateStmt PrepareStmt ExecuteStmt DropOwnedStmt ReassignOwnedStmt %type select_no_parens select_with_parens select_clause --- 169,175 ---- UnlistenStmt UpdateStmt VacuumStmt VariableResetStmt VariableSetStmt VariableShowStmt ViewStmt CheckPointStmt CreateConversionStmt ! DeallocateStmt PrepareStmt ExecuteStmt TriggerWhen DropOwnedStmt ReassignOwnedStmt %type select_no_parens select_with_parens select_clause *************** DropTableSpaceStmt: DROP TABLESPACE name *** 2519,2540 **** *****************************************************************************/ 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; ! memcpy(n->actions, $5, 4); n->isconstraint = FALSE; n->deferrable = FALSE; n->initdeferred = FALSE; n->constrrel = NULL; $$ = (Node *)n; } | CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON --- 2519,2545 ---- *****************************************************************************/ CreateTrigStmt: ! CREATE TRIGGER name ! { QueryIsRuleOrTrigger=TRUE; } ! TriggerActionTime TriggerEvents ON ! qualified_name TriggerForSpec TriggerWhen EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); n->trigname = $3; ! n->relation = $8; ! n->funcname = $13; ! n->args = $15; ! n->before = $5; ! n->row = $9; ! memcpy(n->actions, $6, 4); n->isconstraint = FALSE; n->deferrable = FALSE; n->initdeferred = FALSE; n->constrrel = NULL; + n->when = $10; + n->rtable = NIL; + QueryIsRuleOrTrigger = FALSE; $$ = (Node *)n; } | CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON *************** CreateTrigStmt: *** 2554,2560 **** n->isconstraint = TRUE; n->deferrable = ($10 & 1) != 0; n->initdeferred = ($10 & 2) != 0; ! n->constrrel = $9; $$ = (Node *)n; } --- 2559,2566 ---- n->isconstraint = TRUE; n->deferrable = ($10 & 1) != 0; n->initdeferred = ($10 & 2) != 0; ! n->when = NULL; ! n->rtable = NIL; n->constrrel = $9; $$ = (Node *)n; } *************** TriggerForType: *** 2617,2622 **** --- 2623,2633 ---- | STATEMENT { $$ = FALSE; } ; + TriggerWhen: + WHEN '(' a_expr ')' { $$ = $3; } + | /*EMPTY*/ { $$ = NULL; } + ; + TriggerFuncArgs: TriggerFuncArg { $$ = list_make1($1); } | TriggerFuncArgs ',' TriggerFuncArg { $$ = lappend($1, $3); } *************** AlterOwnerStmt: ALTER AGGREGATE func_nam *** 4480,4486 **** *****************************************************************************/ RuleStmt: CREATE opt_or_replace RULE name AS ! { QueryIsRule=TRUE; } ON event TO qualified_name where_clause DO opt_instead RuleActionList { --- 4491,4497 ---- *****************************************************************************/ RuleStmt: CREATE opt_or_replace RULE name AS ! { QueryIsRuleOrTrigger=TRUE; } ON event TO qualified_name where_clause DO opt_instead RuleActionList { *************** RuleStmt: CREATE opt_or_replace RULE nam *** 4493,4499 **** n->instead = $13; n->actions = $14; $$ = (Node *)n; ! QueryIsRule=FALSE; } ; --- 4504,4510 ---- n->instead = $13; n->actions = $14; $$ = (Node *)n; ! QueryIsRuleOrTrigger=FALSE; } ; *************** reserved_keyword: *** 8851,8857 **** SpecialRuleRelation: OLD { ! if (QueryIsRule) $$ = "*OLD*"; else ereport(ERROR, --- 8862,8868 ---- SpecialRuleRelation: OLD { ! if (QueryIsRuleOrTrigger) $$ = "*OLD*"; else ereport(ERROR, *************** SpecialRuleRelation: *** 8860,8866 **** } | NEW { ! if (QueryIsRule) $$ = "*NEW*"; else ereport(ERROR, --- 8871,8877 ---- } | NEW { ! if (QueryIsRuleOrTrigger) $$ = "*NEW*"; else ereport(ERROR, *************** SystemTypeName(char *name) *** 9238,9244 **** void parser_init(void) { ! QueryIsRule = FALSE; } /* exprIsNullConstant() --- 9249,9255 ---- void parser_init(void) { ! QueryIsRuleOrTrigger = FALSE; } /* exprIsNullConstant() Index: src/backend/utils/adt/ruleutils.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v retrieving revision 1.227 diff -c -p -r1.227 ruleutils.c *** src/backend/utils/adt/ruleutils.c 4 Jul 2006 04:35:49 -0000 1.227 --- src/backend/utils/adt/ruleutils.c 7 Jul 2006 13:04:13 -0000 *************** *** 38,43 **** --- 38,44 ---- #include "parser/parse_oper.h" #include "parser/parse_type.h" #include "parser/parsetree.h" + #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSupport.h" *************** pg_get_triggerdef(PG_FUNCTION_ARGS) *** 435,440 **** --- 436,445 ---- SysScanDesc tgscan; int findx = 0; char *tgname; + Datum when_text; + char *when_str; + Node *node; + bool isnull; /* * Fetch the pg_trigger tuple by the Oid of the trigger *************** pg_get_triggerdef(PG_FUNCTION_ARGS) *** 514,519 **** --- 519,570 ---- else appendStringInfo(&buf, "FOR EACH STATEMENT "); + /* handle WHEN clause */ + when_text = heap_getattr(ht_trig, Anum_pg_trigger_tgqual, + RelationGetDescr(tgrel), &isnull); + Assert(!isnull); + + when_str = DatumGetCString(DirectFunctionCall1(textout, when_text)); + node = (Node *) stringToNode(when_str); + + if (node) + { + Relation rel; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + deparse_context context; + deparse_namespace dpns; + + rel = heap_open(trigrec->tgrelid, AccessShareLock); + + appendStringInfo(&buf, "WHEN "); + + oldrte = addRangeTableEntryForRelation(NULL, rel, + makeAlias("*OLD*", NIL), + false, false); + + newrte = addRangeTableEntryForRelation(NULL, rel, + makeAlias("*NEW*", NIL), + false, false); + + ChangeVarNodes(node, TRIG_OLD_VARNO, 1, 0); + ChangeVarNodes(node, TRIG_NEW_VARNO, 2, 0); + + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.varprefix = true; + context.prettyFlags = 0; + context.indentLevel = PRETTYINDENT_STD; + + dpns.rtable = list_make2(oldrte, newrte); + dpns.outer_varno = dpns.inner_varno = 0; + dpns.outer_rte = dpns.inner_rte = NULL; + + get_rule_expr(node, &context, false); + appendStringInfo(&buf, " "); + heap_close(rel, AccessShareLock); + } + appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", generate_function_name(trigrec->tgfoid, 0, NULL)); Index: src/bin/pg_dump/pg_dump.c =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v retrieving revision 1.439 diff -c -p -r1.439 pg_dump.c *** src/bin/pg_dump/pg_dump.c 2 Jul 2006 02:23:21 -0000 1.439 --- src/bin/pg_dump/pg_dump.c 7 Jul 2006 13:04:21 -0000 *************** dumpSequence(Archive *fout, TableInfo *t *** 8048,8077 **** } static void ! dumpTrigger(Archive *fout, TriggerInfo *tginfo) { TableInfo *tbinfo = tginfo->tgtable; - PQExpBuffer query; - PQExpBuffer delqry; const char *p; int findx; - if (dataOnly) - return; - - query = createPQExpBuffer(); - delqry = createPQExpBuffer(); - - /* - * DROP must be fully qualified in case same name appears in pg_catalog - */ - appendPQExpBuffer(delqry, "DROP TRIGGER %s ", - fmtId(tginfo->dobj.name)); - appendPQExpBuffer(delqry, "ON %s.", - fmtId(tbinfo->dobj.namespace->dobj.name)); - appendPQExpBuffer(delqry, "%s;\n", - fmtId(tbinfo->dobj.name)); - if (tginfo->tgisconstraint) { appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER "); --- 8048,8059 ---- } static void ! dumpTriggerManually(Archive *fout, TriggerInfo *tginfo, PQExpBuffer query) { TableInfo *tbinfo = tginfo->tgtable; const char *p; int findx; if (tginfo->tgisconstraint) { appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER "); *************** dumpTrigger(Archive *fout, TriggerInfo * *** 8194,8199 **** --- 8176,8234 ---- p = p + 4; } appendPQExpBuffer(query, ");\n"); + } + + static void + dumpTrigger(Archive *fout, TriggerInfo *tginfo) + { + TableInfo *tbinfo = tginfo->tgtable; + PQExpBuffer query; + PQExpBuffer delqry; + + if (dataOnly) + return; + + query = createPQExpBuffer(); + delqry = createPQExpBuffer(); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + appendPQExpBuffer(delqry, "DROP TRIGGER %s ", + fmtId(tginfo->dobj.name)); + appendPQExpBuffer(delqry, "ON %s.", + fmtId(tbinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delqry, "%s;\n", + fmtId(tbinfo->dobj.name)); + + /* + * If pg_get_triggerdef() is available, use it. Otherwise, reconstruct the + * CREATE TRIGGER command manually. + */ + if (g_fout->remoteVersion >= 70300) + { + PQExpBuffer def_query; + PGresult *res; + + def_query = createPQExpBuffer(); + appendPQExpBuffer(def_query, + "SELECT pg_catalog.pg_get_triggerdef('%u'::pg_catalog.oid) AS definition", + tginfo->dobj.catId.oid); + res = PQexec(g_conn, def_query->data); + check_sql_result(res, g_conn, def_query->data, PGRES_TUPLES_OK); + + if (PQntuples(res) != 1) + { + write_msg(NULL, "query to get trigger \"%s\" for table \"%s\" failed: wrong number of rows returned", + tginfo->dobj.name, tbinfo->dobj.name); + exit_nicely(); + } + + appendPQExpBuffer(query, "%s;\n", PQgetvalue(res, 0, 0)); + destroyPQExpBuffer(def_query); + } + else + dumpTriggerManually(fout, tginfo, query); if (!tginfo->tgenabled) { Index: src/include/catalog/pg_trigger.h =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/include/catalog/pg_trigger.h,v retrieving revision 1.25 diff -c -p -r1.25 pg_trigger.h *** src/include/catalog/pg_trigger.h 11 Mar 2006 04:38:38 -0000 1.25 --- src/include/catalog/pg_trigger.h 5 Jul 2006 17:37:22 -0000 *************** CATALOG(pg_trigger,2620) *** 48,53 **** --- 48,54 ---- /* VARIABLE LENGTH FIELDS: */ int2vector tgattr; /* reserved for column-specific triggers */ bytea tgargs; /* first\000second\000tgnargs\000 */ + text tgqual; /* string form of qualification clause */ } FormData_pg_trigger; /* ---------------- *************** typedef FormData_pg_trigger *Form_pg_tri *** 61,67 **** * compiler constants for pg_trigger * ---------------- */ ! #define Natts_pg_trigger 13 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 --- 62,68 ---- * compiler constants for pg_trigger * ---------------- */ ! #define Natts_pg_trigger 14 #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 *** 75,80 **** --- 76,82 ---- #define Anum_pg_trigger_tgnargs 11 #define Anum_pg_trigger_tgattr 12 #define Anum_pg_trigger_tgargs 13 + #define Anum_pg_trigger_tgqual 14 #define TRIGGER_TYPE_ROW (1 << 0) #define TRIGGER_TYPE_BEFORE (1 << 1) Index: src/include/nodes/execnodes.h =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/execnodes.h,v retrieving revision 1.152 diff -c -p -r1.152 execnodes.h *** src/include/nodes/execnodes.h 28 Jun 2006 19:40:52 -0000 1.152 --- src/include/nodes/execnodes.h 7 Jul 2006 19:03:02 -0000 *************** typedef struct ExprContext *** 100,105 **** --- 100,107 ---- TupleTableSlot *ecxt_scantuple; TupleTableSlot *ecxt_innertuple; TupleTableSlot *ecxt_outertuple; + TupleTableSlot *ecxt_oldtuple; /* OLD tuple in trigger context */ + TupleTableSlot *ecxt_newtuple; /* NEW tuple in trigger context */ /* Memory contexts for expression evaluation --- see notes above */ MemoryContext ecxt_per_query_memory; *************** typedef struct JunkFilter *** 247,252 **** --- 249,269 ---- TupleTableSlot *jf_resultSlot; } JunkFilter; + /* + * State information for trigger WHEN clauses. Since ExecPrepareExpr() could + * be expensive, we want to initialise each expression once and + * reuse it again and again + * + * "quals" is an array of lists, with one array element for each trigger (in + * the same order as the array of Triggers kept in the TriggerDesc). Each + * List represents an Expr, in "ANDs implicit" format. + */ + typedef struct TrigQualState + { + List **quals; + } TrigQualState; + + /* ---------------- * ResultRelInfo information * *************** typedef struct JunkFilter *** 262,267 **** --- 279,285 ---- * IndexRelationInfo array of key/attr info for indices * TrigDesc triggers to be fired, if any * TrigFunctions cached lookup info for trigger functions + * TrigQuals state for trigger WHEN clauses * TrigInstrument optional runtime measurements for triggers * ConstraintExprs array of constraint-checking expr states * junkFilter for removing junk attributes from tuples *************** typedef struct ResultRelInfo *** 277,282 **** --- 295,301 ---- IndexInfo **ri_IndexRelationInfo; TriggerDesc *ri_TrigDesc; FmgrInfo *ri_TrigFunctions; + TrigQualState *ri_TrigQuals; struct Instrumentation *ri_TrigInstrument; List **ri_ConstraintExprs; JunkFilter *ri_junkFilter; Index: src/include/nodes/parsenodes.h =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/parsenodes.h,v retrieving revision 1.315 diff -c -p -r1.315 parsenodes.h *** src/include/nodes/parsenodes.h 3 Jul 2006 22:45:40 -0000 1.315 --- src/include/nodes/parsenodes.h 7 Jul 2006 13:04:27 -0000 *************** typedef struct CreateTrigStmt *** 1162,1167 **** --- 1162,1169 ---- bool deferrable; /* [NOT] DEFERRABLE */ bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ RangeVar *constrrel; /* opposite relation */ + Node *when; /* WHEN clause qual */ + List *rtable; /* range table for interpreting WHEN expr */ } CreateTrigStmt; /* ---------------------- Index: src/include/nodes/primnodes.h =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/primnodes.h,v retrieving revision 1.113 diff -c -p -r1.113 primnodes.h *** src/include/nodes/primnodes.h 22 Apr 2006 01:26:01 -0000 1.113 --- src/include/nodes/primnodes.h 7 Jul 2006 19:05:51 -0000 *************** typedef struct Expr *** 101,108 **** * The code doesn't really need varnoold/varoattno, but they are very useful * for debugging and interpreting completed plans, so we keep them around. */ ! #define INNER 65000 ! #define OUTER 65001 #define PRS2_OLD_VARNO 1 #define PRS2_NEW_VARNO 2 --- 101,110 ---- * The code doesn't really need varnoold/varoattno, but they are very useful * for debugging and interpreting completed plans, so we keep them around. */ ! #define INNER 65000 ! #define OUTER 65001 ! #define TRIG_OLD_VARNO 65002 ! #define TRIG_NEW_VARNO 65003 #define PRS2_OLD_VARNO 1 #define PRS2_NEW_VARNO 2 Index: src/include/utils/rel.h =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/include/utils/rel.h,v retrieving revision 1.91 diff -c -p -r1.91 rel.h *** src/include/utils/rel.h 3 Jul 2006 22:45:41 -0000 1.91 --- src/include/utils/rel.h 7 Jul 2006 13:04:30 -0000 *************** typedef struct Trigger *** 62,67 **** --- 62,68 ---- int16 tgnattr; int16 *tgattr; char **tgargs; + Node *when; } Trigger; typedef struct TriggerDesc Index: src/test/regress/expected/triggers.out =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/test/regress/expected/triggers.out,v retrieving revision 1.23 diff -c -p -r1.23 triggers.out *** src/test/regress/expected/triggers.out 26 Jun 2006 17:24:41 -0000 1.23 --- src/test/regress/expected/triggers.out 7 Jul 2006 20:48:15 -0000 *************** NOTICE: row 1 not changed *** 537,539 **** --- 537,562 ---- NOTICE: row 2 not changed DROP TABLE trigger_test; DROP FUNCTION mytrigger(); + -- test the WHEN clause for trigger definitions + CREATE OR REPLACE FUNCTION notify_trig() RETURNS trigger LANGUAGE plpgsql AS $$ + begin + raise notice '%s invoked: new.a = %, new.b = %', tg_name, new.a, new.b; + return new; + end$$; + CREATE TABLE when_test (a int, b int); + CREATE TRIGGER t1 BEFORE INSERT ON when_test FOR EACH ROW + WHEN (new.a > 5) + EXECUTE PROCEDURE notify_trig(); + INSERT INTO when_test VALUES (NULL, 0); -- shouldn't fire + INSERT INTO when_test VALUES (10, 100); -- should fire + NOTICE: t1s invoked: new.a = 10, new.b = 100 + CREATE TRIGGER t2 BEFORE UPDATE ON when_test FOR EACH ROW + WHEN (new.b > 50) + EXECUTE PROCEDURE notify_trig(); + UPDATE when_test SET b = b + 50; -- should fire once + NOTICE: t2s invoked: new.a = 10, new.b = 150 + UPDATE when_test SET b = b + 1; -- should fire twice + NOTICE: t2s invoked: new.a = , new.b = 51 + NOTICE: t2s invoked: new.a = 10, new.b = 151 + DROP TABLE when_test; + DROP FUNCTION notify_trig(); Index: src/test/regress/sql/triggers.sql =================================================================== RCS file: /usr/local/cvsroot/pgsql/src/test/regress/sql/triggers.sql,v retrieving revision 1.13 diff -c -p -r1.13 triggers.sql *** src/test/regress/sql/triggers.sql 26 Jun 2006 17:24:41 -0000 1.13 --- src/test/regress/sql/triggers.sql 7 Jul 2006 13:15:22 -0000 *************** UPDATE trigger_test SET f3 = NULL; *** 415,417 **** --- 415,444 ---- DROP TABLE trigger_test; DROP FUNCTION mytrigger(); + + -- test the WHEN clause for trigger definitions + CREATE OR REPLACE FUNCTION notify_trig() RETURNS trigger LANGUAGE plpgsql AS $$ + begin + raise notice '%s invoked: new.a = %, new.b = %', tg_name, new.a, new.b; + return new; + end$$; + + CREATE TABLE when_test (a int, b int); + + CREATE TRIGGER t1 BEFORE INSERT ON when_test FOR EACH ROW + WHEN (new.a > 5) + EXECUTE PROCEDURE notify_trig(); + + INSERT INTO when_test VALUES (NULL, 0); -- shouldn't fire + INSERT INTO when_test VALUES (10, 100); -- should fire + + CREATE TRIGGER t2 BEFORE UPDATE ON when_test FOR EACH ROW + WHEN (new.b > 50) + EXECUTE PROCEDURE notify_trig(); + + UPDATE when_test SET b = b + 50; -- should fire once + UPDATE when_test SET b = b + 1; -- should fire twice + + DROP TABLE when_test; + DROP FUNCTION notify_trig(); +