aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/trigger.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2009-10-14 22:14:25 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2009-10-14 22:14:25 +0000
commitb2734a0d792df710aeeab21242cfa21ab470c773 (patch)
treefea1d5961054f413f63995339f1aa6037d825b9e /src/backend/commands/trigger.c
parentbe922e8555a87263973a038c54171f2db833810d (diff)
downloadpostgresql-b2734a0d792df710aeeab21242cfa21ab470c773.tar.gz
postgresql-b2734a0d792df710aeeab21242cfa21ab470c773.zip
Support SQL-compliant triggers on columns, ie fire only if certain columns
are named in the UPDATE's SET list. Note: the schema of pg_trigger has not actually changed; we've just started to use a column that was there all along. catversion bumped anyway so that this commit is included in the history of potentially interesting changes to system catalog contents. Itagaki Takahiro
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r--src/backend/commands/trigger.c261
1 files changed, 151 insertions, 110 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 47cf0c470d6..dd526f6db19 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.253 2009/10/10 01:43:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.254 2009/10/14 22:14:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -30,8 +30,11 @@
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
+#include "parser/parse_relation.h"
+#include "parser/parsetree.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "tcop/utility.h"
@@ -51,6 +54,9 @@
int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
+#define GetModifiedColumns(relinfo, estate) \
+ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
+
/* Local function prototypes */
static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid);
static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
@@ -59,6 +65,8 @@ static HeapTuple GetTupleForTrigger(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tid,
TupleTableSlot **newSlot);
+static bool TriggerEnabled(Trigger *trigger, TriggerEvent event,
+ Bitmapset *modifiedCols);
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
int tgindx,
FmgrInfo *finfo,
@@ -66,7 +74,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
- List *recheckIndexes);
+ List *recheckIndexes, Bitmapset *modifiedCols);
/*
@@ -98,6 +106,8 @@ CreateTrigger(CreateTrigStmt *stmt,
bool checkPermissions)
{
int16 tgtype;
+ int ncolumns;
+ int2 *columns;
int2vector *tgattr;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
@@ -337,8 +347,44 @@ CreateTrigger(CreateTrigStmt *stmt,
CStringGetDatum(""));
}
- /* tgattr is currently always a zero-length array */
- tgattr = buildint2vector(NULL, 0);
+ /* build column number array if it's a column-specific trigger */
+ ncolumns = list_length(stmt->columns);
+ if (ncolumns == 0)
+ columns = NULL;
+ else
+ {
+ ListCell *cell;
+ int i = 0;
+
+ columns = (int2 *) palloc(ncolumns * sizeof(int2));
+ foreach(cell, stmt->columns)
+ {
+ char *name = strVal(lfirst(cell));
+ int2 attnum;
+ int j;
+
+ /* Lookup column name. System columns are not allowed */
+ attnum = attnameAttNum(rel, name, false);
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ name, RelationGetRelationName(rel))));
+
+ /* Check for duplicates */
+ for (j = i - 1; j >= 0; j--)
+ {
+ if (columns[j] == attnum)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" specified more than once",
+ name)));
+ }
+
+ columns[i++] = attnum;
+ }
+ }
+ tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
@@ -358,6 +404,7 @@ CreateTrigger(CreateTrigStmt *stmt,
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
+ pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
/*
* Update relation's pg_class entry. Crucial side-effect: other backends
@@ -434,6 +481,20 @@ CreateTrigger(CreateTrigStmt *stmt,
Assert(!OidIsValid(indexOid));
}
+ /* If column-specific trigger, add normal dependencies on columns */
+ if (columns != NULL)
+ {
+ int i;
+
+ referenced.classId = RelationRelationId;
+ referenced.objectId = RelationGetRelid(rel);
+ for (i = 0; i < ncolumns; i++)
+ {
+ referenced.objectSubId = columns[i];
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ }
+
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
@@ -1626,18 +1687,9 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1659,7 +1711,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
HeapTuple
@@ -1685,18 +1737,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
+ continue;
+
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
@@ -1721,7 +1764,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- true, NULL, trigtuple, recheckIndexes);
+ true, NULL, trigtuple, recheckIndexes, NULL);
}
void
@@ -1757,18 +1800,9 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1790,7 +1824,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
bool
@@ -1824,18 +1858,9 @@ ExecBRDeleteTriggers(EState *estate, PlanState *subplanstate,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
+ continue;
+
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
@@ -1869,7 +1894,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- true, trigtuple, NULL, NIL);
+ true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
@@ -1882,6 +1907,7 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
int *tgindx;
int i;
TriggerData LocTriggerData;
+ Bitmapset *modifiedCols;
trigdesc = relinfo->ri_TrigDesc;
@@ -1894,6 +1920,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
if (ntrigs == 0)
return;
+ modifiedCols = GetModifiedColumns(relinfo, estate);
+
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
@@ -1907,18 +1935,9 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -1940,7 +1959,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL,
+ GetModifiedColumns(relinfo, estate));
}
HeapTuple
@@ -1957,6 +1977,7 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
HeapTuple intuple = newtuple;
TupleTableSlot *newSlot;
int i;
+ Bitmapset *modifiedCols;
trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
&newSlot);
@@ -1971,6 +1992,8 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
if (newSlot != NULL)
intuple = newtuple = ExecRemoveJunk(relinfo->ri_junkFilter, newSlot);
+ modifiedCols = GetModifiedColumns(relinfo, estate);
+
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_ROW |
@@ -1980,18 +2003,9 @@ ExecBRUpdateTriggers(EState *estate, PlanState *subplanstate,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
+ continue;
+
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
@@ -2024,7 +2038,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- true, trigtuple, newtuple, recheckIndexes);
+ true, trigtuple, newtuple, recheckIndexes,
+ GetModifiedColumns(relinfo, estate));
heap_freetuple(trigtuple);
}
}
@@ -2062,18 +2077,9 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
+ continue;
+
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx[i],
@@ -2095,7 +2101,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
- false, NULL, NULL, NIL);
+ false, NULL, NULL, NIL, NULL);
}
@@ -2201,6 +2207,52 @@ ltrmark:;
return result;
}
+/*
+ * 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)
+ {
+ if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
+ trigger->tgenabled == TRIGGER_DISABLED)
+ return false;
+ }
+ else /* ORIGIN or LOCAL role */
+ {
+ if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
+ trigger->tgenabled == TRIGGER_DISABLED)
+ return false;
+ }
+
+ /*
+ * Check for column-specific trigger (only possible for UPDATE, and in
+ * fact we *must* ignore tgattr for other event types)
+ */
+ if (trigger->tgnattr > 0 && TRIGGER_FIRED_BY_UPDATE(event))
+ {
+ int i;
+ bool modified;
+
+ modified = false;
+ for (i = 0; i < trigger->tgnattr; i++)
+ {
+ if (bms_is_member(trigger->tgattr[i] - FirstLowInvalidHeapAttributeNumber,
+ modifiedCols))
+ {
+ modified = true;
+ break;
+ }
+ }
+ if (!modified)
+ return false;
+ }
+
+ return true;
+}
+
/* ----------
* After-trigger stuff
@@ -3825,7 +3877,7 @@ AfterTriggerPendingOnRel(Oid relid)
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
- List *recheckIndexes)
+ List *recheckIndexes, Bitmapset *modifiedCols)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -3927,26 +3979,15 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
- /* Ignore disabled triggers */
- if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
- else /* ORIGIN or LOCAL role */
- {
- if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
- trigger->tgenabled == TRIGGER_DISABLED)
- continue;
- }
+ if (!TriggerEnabled(trigger, event, modifiedCols))
+ continue;
/*
* If this is an UPDATE of a PK table or FK table that does not change
* the PK or FK respectively, we can skip queuing the event: there is
* no need to fire the trigger.
*/
- if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
+ if (TRIGGER_FIRED_BY_UPDATE(event))
{
switch (RI_FKey_trigger_type(trigger->tgfoid))
{