diff options
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r-- | src/backend/commands/trigger.c | 311 |
1 files changed, 287 insertions, 24 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 885c34d8347..b1f8a617484 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.256 2009/10/27 20:14:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.257 2009/11/20 20:38:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,10 +32,14 @@ #include "miscadmin.h" #include "nodes/bitmapset.h" #include "nodes/makefuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/var.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" @@ -65,21 +69,27 @@ static HeapTuple GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo, ItemPointer tid, TupleTableSlot **newSlot); -static bool TriggerEnabled(Trigger *trigger, TriggerEvent event, - Bitmapset *modifiedCols); +static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo, + Trigger *trigger, TriggerEvent event, + Bitmapset *modifiedCols, + HeapTuple oldtup, HeapTuple newtup); 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, +static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, + int event, bool row_trigger, + HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols); /* * Create a trigger. Returns the OID of the created trigger. * + * queryString is the source text of the CREATE TRIGGER command. + * This must be supplied if a whenClause is specified, else it can be NULL. + * * constraintOid, if nonzero, says that this trigger is being created * internally to implement that constraint. A suitable pg_depend entry will * be made to link the trigger to that constraint. constraintOid is zero when @@ -101,7 +111,7 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, * but a foreign-key constraint. This is a kluge for backwards compatibility. */ Oid -CreateTrigger(CreateTrigStmt *stmt, +CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions) { @@ -109,6 +119,9 @@ CreateTrigger(CreateTrigStmt *stmt, int ncolumns; int2 *columns; int2vector *tgattr; + Node *whenClause; + List *whenRtable; + char *qual; Datum values[Natts_pg_trigger]; bool nulls[Natts_pg_trigger]; Relation rel; @@ -180,6 +193,120 @@ CreateTrigger(CreateTrigStmt *stmt, errmsg("TRUNCATE FOR EACH ROW triggers are not supported"))); /* + * Parse the WHEN clause, if any + */ + if (stmt->whenClause) + { + ParseState *pstate; + RangeTblEntry *rte; + List *varList; + ListCell *lc; + + /* Set up a pstate to parse with */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Set up RTEs for OLD and NEW references. + * + * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2. + */ + rte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("old", NIL), + false, false); + addRTEtoQuery(pstate, rte, false, true, true); + rte = addRangeTableEntryForRelation(pstate, rel, + makeAlias("new", NIL), + false, false); + addRTEtoQuery(pstate, rte, false, true, true); + + /* Transform expression. Copy to be sure we don't modify original */ + whenClause = transformWhereClause(pstate, + copyObject(stmt->whenClause), + "WHEN"); + + /* + * No subplans or aggregates, please + */ + if (pstate->p_hasSubLinks) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use subquery in trigger WHEN condition"))); + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in trigger WHEN condition"))); + if (pstate->p_hasWindowFuncs) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("cannot use window function in trigger WHEN condition"))); + + /* + * Check for disallowed references to OLD/NEW. + * + * NB: pull_var_clause is okay here only because we don't allow + * subselects in WHEN clauses; it would fail to examine the contents + * of subselects. + */ + varList = pull_var_clause(whenClause, PVC_REJECT_PLACEHOLDERS); + foreach(lc, varList) + { + Var *var = (Var *) lfirst(lc); + + switch (var->varno) + { + case PRS2_OLD_VARNO: + if (!TRIGGER_FOR_ROW(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("statement trigger's WHEN condition cannot reference column values"), + parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_INSERT(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("INSERT trigger's WHEN condition cannot reference OLD values"), + parser_errposition(pstate, var->location))); + /* system columns are okay here */ + break; + case PRS2_NEW_VARNO: + if (!TRIGGER_FOR_ROW(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("statement trigger's WHEN condition cannot reference column values"), + parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_DELETE(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("DELETE trigger's WHEN condition cannot reference NEW values"), + parser_errposition(pstate, var->location))); + if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"), + parser_errposition(pstate, var->location))); + break; + default: + /* can't happen without add_missing_from, so just elog */ + elog(ERROR, "trigger WHEN condition cannot contain references to other relations"); + break; + } + } + + /* we'll need the rtable for recordDependencyOnExpr */ + whenRtable = pstate->p_rtable; + + qual = nodeToString(whenClause); + + free_parsestate(pstate); + } + else + { + whenClause = NULL; + whenRtable = NIL; + qual = NULL; + } + + /* * Find and validate the trigger function. */ funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false); @@ -387,6 +514,12 @@ CreateTrigger(CreateTrigStmt *stmt, 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 */ @@ -495,6 +628,14 @@ CreateTrigger(CreateTrigStmt *stmt, } } + /* + * If it has a WHEN clause, add dependencies on objects mentioned in + * the expression (eg, functions, as well as any columns used). + */ + if (whenClause != NULL) + recordDependencyOnExpr(&myself, whenClause, whenRtable, + DEPENDENCY_NORMAL); + /* Keep lock on target rel until end of xact */ heap_close(rel, NoLock); @@ -1184,6 +1325,8 @@ RelationBuildTriggers(Relation relation) { Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup); Trigger *build; + Datum datum; + bool isnull; if (numtrigs >= maxtrigs) { @@ -1218,7 +1361,6 @@ RelationBuildTriggers(Relation relation) if (build->tgnargs > 0) { bytea *val; - bool isnull; char *p; val = DatumGetByteaP(fastgetattr(htup, @@ -1237,6 +1379,12 @@ RelationBuildTriggers(Relation relation) } else build->tgargs = NULL; + datum = fastgetattr(htup, Anum_pg_trigger_tgqual, + tgrel->rd_att, &isnull); + if (!isnull) + build->tgqual = TextDatumGetCString(datum); + else + build->tgqual = NULL; numtrigs++; } @@ -1396,6 +1544,8 @@ CopyTriggerDesc(TriggerDesc *trigdesc) newargs[j] = pstrdup(trigger->tgargs[j]); trigger->tgargs = newargs; } + if (trigger->tgqual) + trigger->tgqual = pstrdup(trigger->tgqual); trigger++; } @@ -1497,6 +1647,8 @@ FreeTriggerDesc(TriggerDesc *trigdesc) pfree(trigger->tgargs[trigger->tgnargs]); pfree(trigger->tgargs); } + if (trigger->tgqual) + pfree(trigger->tgqual); trigger++; } pfree(trigdesc->triggers); @@ -1520,6 +1672,11 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) * * As of 7.3 we assume trigger set ordering is significant in the * comparison; so we just compare corresponding slots of the two sets. + * + * Note: comparing the stringToNode forms of the WHEN clauses means that + * parse column locations will affect the result. This is okay as long + * as this function is only used for detecting exact equality, as for + * example in checking for staleness of a cache entry. */ if (trigdesc1 != NULL) { @@ -1565,6 +1722,12 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) for (j = 0; j < trig1->tgnargs; j++) if (strcmp(trig1->tgargs[j], trig2->tgargs[j]) != 0) return false; + if (trig1->tgqual == NULL && trig2->tgqual == NULL) + /* ok */ ; + else if (trig1->tgqual == NULL || trig2->tgqual == NULL) + return false; + else if (strcmp(trig1->tgqual, trig2->tgqual) != 0) + return false; } } else if (trigdesc2 != NULL) @@ -1687,7 +1850,8 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; @@ -1710,7 +1874,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0) - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, false, NULL, NULL, NIL, NULL); } @@ -1737,7 +1901,8 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, NULL, newtuple)) continue; LocTriggerData.tg_trigtuple = oldtuple = newtuple; @@ -1763,7 +1928,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, true, NULL, trigtuple, recheckIndexes, NULL); } @@ -1800,7 +1965,8 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; @@ -1823,7 +1989,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0) - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, false, NULL, NULL, NIL, NULL); } @@ -1858,7 +2024,8 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, trigtuple, NULL)) continue; LocTriggerData.tg_trigtuple = trigtuple; @@ -1893,7 +2060,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, true, trigtuple, NULL, NIL, NULL); heap_freetuple(trigtuple); } @@ -1935,7 +2102,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + modifiedCols, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; @@ -1958,7 +2126,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0) - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, GetModifiedColumns(relinfo, estate)); } @@ -2003,7 +2171,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + modifiedCols, trigtuple, newtuple)) continue; LocTriggerData.tg_trigtuple = trigtuple; @@ -2037,7 +2206,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo, tupleid, NULL); - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, trigtuple, newtuple, recheckIndexes, GetModifiedColumns(relinfo, estate)); heap_freetuple(trigtuple); @@ -2077,7 +2246,8 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo) Trigger *trigger = &trigdesc->triggers[tgindx[i]]; HeapTuple newtuple; - if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL)) + if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, + NULL, NULL, NULL)) continue; LocTriggerData.tg_trigger = trigger; @@ -2100,7 +2270,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0) - AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE, + AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE, false, NULL, NULL, NIL, NULL); } @@ -2219,7 +2389,10 @@ ltrmark:; * Is trigger enabled to fire? */ static bool -TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols) +TriggerEnabled(EState *estate, ResultRelInfo *relinfo, + Trigger *trigger, TriggerEvent event, + Bitmapset *modifiedCols, + HeapTuple oldtup, HeapTuple newtup) { /* Check replication-role-dependent enable state */ if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) @@ -2258,6 +2431,94 @@ TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols) return false; } + /* Check for WHEN clause */ + if (trigger->tgqual) + { + TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); + List **predicate; + ExprContext *econtext; + TupleTableSlot *oldslot = NULL; + TupleTableSlot *newslot = NULL; + MemoryContext oldContext; + int i; + + Assert(estate != NULL); + + /* + * trigger is an element of relinfo->ri_TrigDesc->triggers[]; + * find the matching element of relinfo->ri_TrigWhenExprs[] + */ + i = trigger - relinfo->ri_TrigDesc->triggers; + predicate = &relinfo->ri_TrigWhenExprs[i]; + + /* + * If first time through for this WHEN expression, build expression + * nodetrees for it. Keep them in the per-query memory context so + * they'll survive throughout the query. + */ + if (*predicate == NIL) + { + Node *tgqual; + + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + tgqual = stringToNode(trigger->tgqual); + /* Change references to OLD and NEW to INNER and OUTER */ + ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER, 0); + ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER, 0); + /* ExecQual wants implicit-AND form */ + tgqual = (Node *) make_ands_implicit((Expr *) tgqual); + *predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate); + MemoryContextSwitchTo(oldContext); + } + + /* + * We will use the EState's per-tuple context for evaluating WHEN + * expressions (creating it if it's not already there). + */ + econtext = GetPerTupleExprContext(estate); + + /* + * Put OLD and NEW tuples into tupleslots for expression evaluation. + * These slots can be shared across the whole estate, but be careful + * that they have the current resultrel's tupdesc. + */ + if (HeapTupleIsValid(oldtup)) + { + if (estate->es_trig_oldtup_slot == NULL) + { + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate); + MemoryContextSwitchTo(oldContext); + } + oldslot = estate->es_trig_oldtup_slot; + if (oldslot->tts_tupleDescriptor != tupdesc) + ExecSetSlotDescriptor(oldslot, tupdesc); + ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false); + } + if (HeapTupleIsValid(newtup)) + { + if (estate->es_trig_tuple_slot == NULL) + { + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate); + MemoryContextSwitchTo(oldContext); + } + newslot = estate->es_trig_tuple_slot; + if (newslot->tts_tupleDescriptor != tupdesc) + ExecSetSlotDescriptor(newslot, tupdesc); + ExecStoreTuple(newtup, newslot, InvalidBuffer, false); + } + + /* + * Finally evaluate the expression, making the old and/or new tuples + * available as INNER/OUTER respectively. + */ + econtext->ecxt_innertuple = oldslot; + econtext->ecxt_outertuple = newslot; + if (!ExecQual(*predicate, econtext, false)) + return false; + } + return true; } @@ -3883,7 +4144,8 @@ AfterTriggerPendingOnRel(Oid relid) * ---------- */ static void -AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, +AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, + int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, List *recheckIndexes, Bitmapset *modifiedCols) { @@ -3993,7 +4255,8 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, { Trigger *trigger = &trigdesc->triggers[tgindx[i]]; - if (!TriggerEnabled(trigger, event, modifiedCols)) + if (!TriggerEnabled(estate, relinfo, trigger, event, + modifiedCols, oldtup, newtup)) continue; /* |