aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Grittner <kgrittn@postgresql.org>2016-11-04 10:49:50 -0500
committerKevin Grittner <kgrittn@postgresql.org>2016-11-04 10:49:50 -0500
commit8c48375e5f43ebd832f93c9166d1fe0e639ff806 (patch)
tree5115baa716b278c4dcabcf6b22fd446a17eb36d8 /src
parent69d590fffbdcfb50a31a8c78ce87e602002a869f (diff)
downloadpostgresql-8c48375e5f43ebd832f93c9166d1fe0e639ff806.tar.gz
postgresql-8c48375e5f43ebd832f93c9166d1fe0e639ff806.zip
Implement syntax for transition tables in AFTER triggers.
This is infrastructure for the complete SQL standard feature. No support is included at this point for execution nodes or PLs. The intent is to add that soon. As this patch leaves things, standard syntax can create tuplestores to contain old and/or new versions of rows affected by a statement. References to these tuplestores are in the TriggerData structure. C triggers can access the tuplestores directly, so they are usable, but they cannot yet be referenced within a SQL statement.
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/tablecmds.c5
-rw-r--r--src/backend/commands/trigger.c327
-rw-r--r--src/backend/nodes/copyfuncs.c16
-rw-r--r--src/backend/nodes/equalfuncs.c14
-rw-r--r--src/backend/nodes/outfuncs.c13
-rw-r--r--src/backend/parser/gram.y70
-rw-r--r--src/backend/utils/adt/ruleutils.c23
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/pg_trigger.h13
-rw-r--r--src/include/commands/trigger.h2
-rw-r--r--src/include/nodes/nodes.h1
-rw-r--r--src/include/nodes/parsenodes.h17
-rw-r--r--src/include/parser/kwlist.h3
-rw-r--r--src/include/utils/reltrigger.h7
14 files changed, 488 insertions, 25 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2137372c234..f97bee5b0e4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7430,7 +7430,7 @@ validateForeignKeyConstraint(char *conname,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
- /* we needn't fill in tgargs or tgqual */
+ /* we needn't fill in remaining fields */
/*
* See if we can do it with a single LEFT JOIN query. A FALSE result
@@ -7514,6 +7514,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
}
fk_trigger->columns = NIL;
+ fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
@@ -7557,6 +7558,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
+ fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
@@ -7611,6 +7613,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
+ fk_trigger->transitionRels = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a13d75..1c264b77361 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -164,6 +164,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Oid constrrelid = InvalidOid;
ObjectAddress myself,
referenced;
+ char *oldtablename = NULL;
+ char *newtablename = NULL;
if (OidIsValid(relOid))
rel = heap_open(relOid, ShareRowExclusiveLock);
@@ -310,6 +312,87 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/*
+ * We don't yet support naming ROW transition variables, but the parser
+ * recognizes the syntax so we can give a nicer message here.
+ *
+ * Per standard, REFERENCING TABLE names are only allowed on AFTER
+ * triggers. Per standard, REFERENCING ROW names are not allowed with FOR
+ * EACH STATEMENT. Per standard, each OLD/NEW, ROW/TABLE permutation is
+ * only allowed once. Per standard, OLD may not be specified when
+ * creating a trigger only for INSERT, and NEW may not be specified when
+ * creating a trigger only for DELETE.
+ *
+ * Notice that the standard allows an AFTER ... FOR EACH ROW trigger to
+ * reference both ROW and TABLE transition data.
+ */
+ if (stmt->transitionRels != NIL)
+ {
+ List *varList = stmt->transitionRels;
+ ListCell *lc;
+
+ foreach(lc, varList)
+ {
+ TriggerTransition *tt = (TriggerTransition *) lfirst(lc);
+
+ Assert(IsA(tt, TriggerTransition));
+
+ if (!(tt->isTable))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ROW variable naming in the REFERENCING clause is not supported"),
+ errhint("Use OLD TABLE or NEW TABLE for naming transition tables.")));
+
+ /*
+ * Because of the above test, we omit further ROW-related testing
+ * below. If we later allow naming OLD and NEW ROW variables,
+ * adjustments will be needed below.
+ */
+
+ if (stmt->timing != TRIGGER_TYPE_AFTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transition table name can only be specified for an AFTER trigger")));
+
+ if (tt->isNew)
+ {
+ if (!(TRIGGER_FOR_INSERT(tgtype) ||
+ TRIGGER_FOR_UPDATE(tgtype)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("NEW TABLE can only be specified for an INSERT or UPDATE trigger")));
+
+ if (newtablename != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("NEW TABLE cannot be specified multiple times")));
+
+ newtablename = tt->name;
+ }
+ else
+ {
+ if (!(TRIGGER_FOR_DELETE(tgtype) ||
+ TRIGGER_FOR_UPDATE(tgtype)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("OLD TABLE can only be specified for a DELETE or UPDATE trigger")));
+
+ if (oldtablename != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("OLD TABLE cannot be specified multiple times")));
+
+ oldtablename = tt->name;
+ }
+ }
+
+ if (newtablename != NULL && oldtablename != NULL &&
+ strcmp(newtablename, oldtablename) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("OLD TABLE name and NEW TABLE name cannot be the same")));
+ }
+
+ /*
* Parse the WHEN clause, if any
*/
if (stmt->whenClause)
@@ -664,6 +747,17 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
else
nulls[Anum_pg_trigger_tgqual - 1] = true;
+ if (oldtablename)
+ values[Anum_pg_trigger_tgoldtable - 1] = DirectFunctionCall1(namein,
+ CStringGetDatum(oldtablename));
+ else
+ nulls[Anum_pg_trigger_tgoldtable - 1] = true;
+ if (newtablename)
+ values[Anum_pg_trigger_tgnewtable - 1] = DirectFunctionCall1(namein,
+ CStringGetDatum(newtablename));
+ else
+ nulls[Anum_pg_trigger_tgnewtable - 1] = true;
+
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
/* force tuple to have the desired OID */
@@ -682,6 +776,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
+ if (oldtablename)
+ pfree(DatumGetPointer(values[Anum_pg_trigger_tgoldtable - 1]));
+ if (newtablename)
+ pfree(DatumGetPointer(values[Anum_pg_trigger_tgnewtable - 1]));
/*
* Update relation's pg_class entry. Crucial side-effect: other backends
@@ -1584,6 +1682,23 @@ RelationBuildTriggers(Relation relation)
}
else
build->tgargs = NULL;
+
+ datum = fastgetattr(htup, Anum_pg_trigger_tgoldtable,
+ tgrel->rd_att, &isnull);
+ if (!isnull)
+ build->tgoldtable =
+ DatumGetCString(DirectFunctionCall1(nameout, datum));
+ else
+ build->tgoldtable = NULL;
+
+ datum = fastgetattr(htup, Anum_pg_trigger_tgnewtable,
+ tgrel->rd_att, &isnull);
+ if (!isnull)
+ build->tgnewtable =
+ DatumGetCString(DirectFunctionCall1(nameout, datum));
+ else
+ build->tgnewtable = NULL;
+
datum = fastgetattr(htup, Anum_pg_trigger_tgqual,
tgrel->rd_att, &isnull);
if (!isnull)
@@ -1680,6 +1795,19 @@ SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger)
trigdesc->trig_truncate_after_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_TRUNCATE);
+
+ trigdesc->trig_insert_new_table |=
+ (TRIGGER_FOR_INSERT(tgtype) &&
+ TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable));
+ trigdesc->trig_update_old_table |=
+ (TRIGGER_FOR_UPDATE(tgtype) &&
+ TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable));
+ trigdesc->trig_update_new_table |=
+ (TRIGGER_FOR_UPDATE(tgtype) &&
+ TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable));
+ trigdesc->trig_delete_old_table |=
+ (TRIGGER_FOR_DELETE(tgtype) &&
+ TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable));
}
/*
@@ -1729,6 +1857,10 @@ CopyTriggerDesc(TriggerDesc *trigdesc)
}
if (trigger->tgqual)
trigger->tgqual = pstrdup(trigger->tgqual);
+ if (trigger->tgoldtable)
+ trigger->tgoldtable = pstrdup(trigger->tgoldtable);
+ if (trigger->tgnewtable)
+ trigger->tgnewtable = pstrdup(trigger->tgnewtable);
trigger++;
}
@@ -1761,6 +1893,10 @@ FreeTriggerDesc(TriggerDesc *trigdesc)
}
if (trigger->tgqual)
pfree(trigger->tgqual);
+ if (trigger->tgoldtable)
+ pfree(trigger->tgoldtable);
+ if (trigger->tgnewtable)
+ pfree(trigger->tgnewtable);
trigger++;
}
pfree(trigdesc->triggers);
@@ -1839,6 +1975,18 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
else if (strcmp(trig1->tgqual, trig2->tgqual) != 0)
return false;
+ if (trig1->tgoldtable == NULL && trig2->tgoldtable == NULL)
+ /* ok */ ;
+ else if (trig1->tgoldtable == NULL || trig2->tgoldtable == NULL)
+ return false;
+ else if (strcmp(trig1->tgoldtable, trig2->tgoldtable) != 0)
+ return false;
+ if (trig1->tgnewtable == NULL && trig2->tgnewtable == NULL)
+ /* ok */ ;
+ else if (trig1->tgnewtable == NULL || trig2->tgnewtable == NULL)
+ return false;
+ else if (strcmp(trig1->tgnewtable, trig2->tgnewtable) != 0)
+ return false;
}
}
else if (trigdesc2 != NULL)
@@ -1870,6 +2018,18 @@ ExecCallTriggerFunc(TriggerData *trigdata,
Datum result;
MemoryContext oldContext;
+ /*
+ * Protect against code paths that may fail to initialize transition table
+ * info.
+ */
+ Assert(((TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
+ TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event) ||
+ TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) &&
+ TRIGGER_FIRED_AFTER(trigdata->tg_event) &&
+ !(trigdata->tg_event & AFTER_TRIGGER_DEFERRABLE) &&
+ !(trigdata->tg_event & AFTER_TRIGGER_INITDEFERRED)) ||
+ (trigdata->tg_oldtable == NULL && trigdata->tg_newtable == NULL));
+
finfo += tgindx;
/*
@@ -1960,6 +2120,8 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
@@ -2017,6 +2179,8 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
@@ -2070,7 +2234,8 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
- if (trigdesc && trigdesc->trig_insert_after_row)
+ if (trigdesc &&
+ (trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table))
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple, recheckIndexes, NULL);
}
@@ -2092,6 +2257,8 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
@@ -2159,6 +2326,8 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
@@ -2230,6 +2399,8 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
@@ -2273,7 +2444,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
- if (trigdesc && trigdesc->trig_delete_after_row)
+ if (trigdesc &&
+ (trigdesc->trig_delete_after_row || trigdesc->trig_delete_old_table))
{
HeapTuple trigtuple;
@@ -2310,6 +2482,8 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
@@ -2363,6 +2537,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
@@ -2464,6 +2640,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
updatedCols = GetUpdatedColumns(relinfo, estate);
for (i = 0; i < trigdesc->numtriggers; i++)
{
@@ -2528,7 +2706,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
- if (trigdesc && trigdesc->trig_update_after_row)
+ if (trigdesc && (trigdesc->trig_update_after_row ||
+ trigdesc->trig_update_old_table || trigdesc->trig_update_new_table))
{
HeapTuple trigtuple;
@@ -2567,6 +2746,8 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
@@ -2635,6 +2816,8 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
+ LocTriggerData.tg_oldtable = NULL;
+ LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
@@ -3163,8 +3346,11 @@ typedef struct AfterTriggerEventList
* fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples
* needed for the current query.
*
- * maxquerydepth is just the allocated length of query_stack and
- * fdw_tuplestores.
+ * old_tuplestores[query_depth] and new_tuplestores[query_depth] hold the
+ * transition relations for the current query.
+ *
+ * maxquerydepth is just the allocated length of query_stack and the
+ * tuplestores.
*
* state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS
* state data; each subtransaction level that modifies that state first
@@ -3193,7 +3379,9 @@ typedef struct AfterTriggersData
AfterTriggerEventList events; /* deferred-event list */
int query_depth; /* current query list index */
AfterTriggerEventList *query_stack; /* events pending from each query */
- Tuplestorestate **fdw_tuplestores; /* foreign tuples from each query */
+ Tuplestorestate **fdw_tuplestores; /* foreign tuples for one row from each query */
+ Tuplestorestate **old_tuplestores; /* all old tuples from each query */
+ Tuplestorestate **new_tuplestores; /* all new tuples from each query */
int maxquerydepth; /* allocated len of above array */
MemoryContext event_cxt; /* memory context for events, if any */
@@ -3222,14 +3410,16 @@ static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
/*
- * Gets the current query fdw tuplestore and initializes it if necessary
+ * Gets a current query transition tuplestore and initializes it if necessary.
+ * This can be holding a single transition row tuple (in the case of an FDW)
+ * or a transition table (for an AFTER trigger).
*/
static Tuplestorestate *
-GetCurrentFDWTuplestore(void)
+GetTriggerTransitionTuplestore(Tuplestorestate **tss)
{
Tuplestorestate *ret;
- ret = afterTriggers.fdw_tuplestores[afterTriggers.query_depth];
+ ret = tss[afterTriggers.query_depth];
if (ret == NULL)
{
MemoryContext oldcxt;
@@ -3256,7 +3446,7 @@ GetCurrentFDWTuplestore(void)
CurrentResourceOwner = saveResourceOwner;
MemoryContextSwitchTo(oldcxt);
- afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = ret;
+ tss[afterTriggers.query_depth] = ret;
}
return ret;
@@ -3554,7 +3744,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
{
case AFTER_TRIGGER_FDW_FETCH:
{
- Tuplestorestate *fdw_tuplestore = GetCurrentFDWTuplestore();
+ Tuplestorestate *fdw_tuplestore =
+ GetTriggerTransitionTuplestore
+ (afterTriggers.fdw_tuplestores);
if (!tuplestore_gettupleslot(fdw_tuplestore, true, false,
trig_tuple_slot1))
@@ -3624,6 +3816,20 @@ AfterTriggerExecute(AfterTriggerEvent event,
}
/*
+ * Set up the tuplestore information.
+ */
+ if (LocTriggerData.tg_trigger->tgoldtable)
+ LocTriggerData.tg_oldtable =
+ GetTriggerTransitionTuplestore(afterTriggers.old_tuplestores);
+ else
+ LocTriggerData.tg_oldtable = NULL;
+ if (LocTriggerData.tg_trigger->tgnewtable)
+ LocTriggerData.tg_newtable =
+ GetTriggerTransitionTuplestore(afterTriggers.new_tuplestores);
+ else
+ LocTriggerData.tg_newtable = NULL;
+
+ /*
* Setup the remaining trigger information
*/
LocTriggerData.type = T_TriggerData;
@@ -3912,6 +4118,8 @@ AfterTriggerBeginXact(void)
Assert(afterTriggers.state == NULL);
Assert(afterTriggers.query_stack == NULL);
Assert(afterTriggers.fdw_tuplestores == NULL);
+ Assert(afterTriggers.old_tuplestores == NULL);
+ Assert(afterTriggers.new_tuplestores == NULL);
Assert(afterTriggers.maxquerydepth == 0);
Assert(afterTriggers.event_cxt == NULL);
Assert(afterTriggers.events.head == NULL);
@@ -3956,6 +4164,8 @@ AfterTriggerEndQuery(EState *estate)
{
AfterTriggerEventList *events;
Tuplestorestate *fdw_tuplestore;
+ Tuplestorestate *old_tuplestore;
+ Tuplestorestate *new_tuplestore;
/* Must be inside a query, too */
Assert(afterTriggers.query_depth >= 0);
@@ -4014,6 +4224,18 @@ AfterTriggerEndQuery(EState *estate)
tuplestore_end(fdw_tuplestore);
afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL;
}
+ old_tuplestore = afterTriggers.old_tuplestores[afterTriggers.query_depth];
+ if (old_tuplestore)
+ {
+ tuplestore_end(old_tuplestore);
+ afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL;
+ }
+ new_tuplestore = afterTriggers.new_tuplestores[afterTriggers.query_depth];
+ if (new_tuplestore)
+ {
+ tuplestore_end(new_tuplestore);
+ afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL;
+ }
afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
afterTriggers.query_depth--;
@@ -4127,6 +4349,8 @@ AfterTriggerEndXact(bool isCommit)
*/
afterTriggers.query_stack = NULL;
afterTriggers.fdw_tuplestores = NULL;
+ afterTriggers.old_tuplestores = NULL;
+ afterTriggers.new_tuplestores = NULL;
afterTriggers.maxquerydepth = 0;
afterTriggers.state = NULL;
@@ -4259,6 +4483,18 @@ AfterTriggerEndSubXact(bool isCommit)
tuplestore_end(ts);
afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL;
}
+ ts = afterTriggers.old_tuplestores[afterTriggers.query_depth];
+ if (ts)
+ {
+ tuplestore_end(ts);
+ afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL;
+ }
+ ts = afterTriggers.new_tuplestores[afterTriggers.query_depth];
+ if (ts)
+ {
+ tuplestore_end(ts);
+ afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL;
+ }
afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
}
@@ -4338,6 +4574,12 @@ AfterTriggerEnlargeQueryState(void)
afterTriggers.fdw_tuplestores = (Tuplestorestate **)
MemoryContextAllocZero(TopTransactionContext,
new_alloc * sizeof(Tuplestorestate *));
+ afterTriggers.old_tuplestores = (Tuplestorestate **)
+ MemoryContextAllocZero(TopTransactionContext,
+ new_alloc * sizeof(Tuplestorestate *));
+ afterTriggers.new_tuplestores = (Tuplestorestate **)
+ MemoryContextAllocZero(TopTransactionContext,
+ new_alloc * sizeof(Tuplestorestate *));
afterTriggers.maxquerydepth = new_alloc;
}
else
@@ -4353,9 +4595,19 @@ AfterTriggerEnlargeQueryState(void)
afterTriggers.fdw_tuplestores = (Tuplestorestate **)
repalloc(afterTriggers.fdw_tuplestores,
new_alloc * sizeof(Tuplestorestate *));
+ afterTriggers.old_tuplestores = (Tuplestorestate **)
+ repalloc(afterTriggers.old_tuplestores,
+ new_alloc * sizeof(Tuplestorestate *));
+ afterTriggers.new_tuplestores = (Tuplestorestate **)
+ repalloc(afterTriggers.new_tuplestores,
+ new_alloc * sizeof(Tuplestorestate *));
/* Clear newly-allocated slots for subsequent lazy initialization. */
memset(afterTriggers.fdw_tuplestores + old_alloc,
0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
+ memset(afterTriggers.old_tuplestores + old_alloc,
+ 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
+ memset(afterTriggers.new_tuplestores + old_alloc,
+ 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
afterTriggers.maxquerydepth = new_alloc;
}
@@ -4800,7 +5052,14 @@ AfterTriggerPendingOnRel(Oid relid)
*
* NOTE: this is called whenever there are any triggers associated with
* the event (even if they are disabled). This function decides which
- * triggers actually need to be queued.
+ * triggers actually need to be queued. It is also called after each row,
+ * even if there are no triggers for that event, if there are any AFTER
+ * STATEMENT triggers for the statement which use transition tables, so that
+ * the transition tuplestores can be built.
+ *
+ * Transition tuplestores are built now, rather than when events are pulled
+ * off of the queue because AFTER ROW triggers are allowed to select from the
+ * transition tables for the statement.
* ----------
*/
static void
@@ -4832,6 +5091,46 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
AfterTriggerEnlargeQueryState();
/*
+ * If the relation has AFTER ... FOR EACH ROW triggers, capture rows into
+ * transition tuplestores for this depth.
+ */
+ if (row_trigger)
+ {
+ if ((event == TRIGGER_EVENT_DELETE &&
+ trigdesc->trig_delete_old_table) ||
+ (event == TRIGGER_EVENT_UPDATE &&
+ trigdesc->trig_update_old_table))
+ {
+ Tuplestorestate *old_tuplestore;
+
+ Assert(oldtup != NULL);
+ old_tuplestore =
+ GetTriggerTransitionTuplestore
+ (afterTriggers.old_tuplestores);
+ tuplestore_puttuple(old_tuplestore, oldtup);
+ }
+ if ((event == TRIGGER_EVENT_INSERT &&
+ trigdesc->trig_insert_new_table) ||
+ (event == TRIGGER_EVENT_UPDATE &&
+ trigdesc->trig_update_new_table))
+ {
+ Tuplestorestate *new_tuplestore;
+
+ Assert(newtup != NULL);
+ new_tuplestore =
+ GetTriggerTransitionTuplestore
+ (afterTriggers.new_tuplestores);
+ tuplestore_puttuple(new_tuplestore, newtup);
+ }
+
+ /* If transition tables are the only reason we're here, return. */
+ if ((event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
+ (event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) ||
+ (event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row))
+ return;
+ }
+
+ /*
* 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
@@ -4928,7 +5227,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
{
if (fdw_tuplestore == NULL)
{
- fdw_tuplestore = GetCurrentFDWTuplestore();
+ fdw_tuplestore =
+ GetTriggerTransitionTuplestore
+ (afterTriggers.fdw_tuplestores);
new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH;
}
else
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc1d67..04e49b77951 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2718,6 +2718,18 @@ _copyRoleSpec(const RoleSpec *from)
return newnode;
}
+static TriggerTransition *
+_copyTriggerTransition(const TriggerTransition *from)
+{
+ TriggerTransition *newnode = makeNode(TriggerTransition);
+
+ COPY_STRING_FIELD(name);
+ COPY_SCALAR_FIELD(isNew);
+ COPY_SCALAR_FIELD(isTable);
+
+ return newnode;
+}
+
static Query *
_copyQuery(const Query *from)
{
@@ -3893,6 +3905,7 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
COPY_NODE_FIELD(columns);
COPY_NODE_FIELD(whenClause);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_NODE_FIELD(transitionRels);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
COPY_NODE_FIELD(constrrel);
@@ -5088,6 +5101,9 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_TriggerTransition:
+ retval = _copyTriggerTransition(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090fc48b..2eaf41c37f8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1905,6 +1905,7 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
COMPARE_NODE_FIELD(columns);
COMPARE_NODE_FIELD(whenClause);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_NODE_FIELD(transitionRels);
COMPARE_SCALAR_FIELD(deferrable);
COMPARE_SCALAR_FIELD(initdeferred);
COMPARE_NODE_FIELD(constrrel);
@@ -2634,6 +2635,16 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_SCALAR_FIELD(isNew);
+ COMPARE_SCALAR_FIELD(isTable);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3387,6 +3398,9 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_TriggerTransition:
+ retval = _equalTriggerTransition(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae869547f35..748b6879292 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2562,6 +2562,16 @@ _outXmlSerialize(StringInfo str, const XmlSerialize *node)
}
static void
+_outTriggerTransition(StringInfo str, const TriggerTransition *node)
+{
+ WRITE_NODE_TYPE("TRIGGERTRANSITION");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_BOOL_FIELD(isNew);
+ WRITE_BOOL_FIELD(isTable);
+}
+
+static void
_outColumnDef(StringInfo str, const ColumnDef *node)
{
WRITE_NODE_TYPE("COLUMNDEF");
@@ -3852,6 +3862,9 @@ outNode(StringInfo str, const void *obj)
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
+ case T_TriggerTransition:
+ _outTriggerTransition(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc86586..0ec1cd345b2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <node> TriggerWhen
+%type <str> TransitionRelName
+%type <boolean> TransitionRowOrTable TransitionOldOrNew
+%type <node> TriggerTransition
%type <list> event_trigger_when_list event_trigger_value_list
%type <defelt> event_trigger_when_item
@@ -374,6 +377,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
+ TriggerTransitions TriggerReferencing
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -610,11 +614,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
- NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
+ NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
- OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
+ OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
@@ -623,8 +627,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
QUOTE
- RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
- RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+ RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
+ REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
@@ -4748,19 +4752,20 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
CreateTrigStmt:
CREATE TRIGGER name TriggerActionTime TriggerEvents ON
- qualified_name TriggerForSpec TriggerWhen
+ qualified_name TriggerReferencing TriggerForSpec TriggerWhen
EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
{
CreateTrigStmt *n = makeNode(CreateTrigStmt);
n->trigname = $3;
n->relation = $7;
- n->funcname = $12;
- n->args = $14;
- n->row = $8;
+ n->funcname = $13;
+ n->args = $15;
+ n->row = $9;
n->timing = $4;
n->events = intVal(linitial($5));
n->columns = (List *) lsecond($5);
- n->whenClause = $9;
+ n->whenClause = $10;
+ n->transitionRels = $8;
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
@@ -4782,6 +4787,7 @@ CreateTrigStmt:
n->events = intVal(linitial($6));
n->columns = (List *) lsecond($6);
n->whenClause = $14;
+ n->transitionRels = NIL;
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
&n->deferrable, &n->initdeferred, NULL,
@@ -4834,6 +4840,49 @@ TriggerOneEvent:
{ $$ = list_make2(makeInteger(TRIGGER_TYPE_TRUNCATE), NIL); }
;
+TriggerReferencing:
+ REFERENCING TriggerTransitions { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+TriggerTransitions:
+ TriggerTransition { $$ = list_make1($1); }
+ | TriggerTransitions TriggerTransition { $$ = lappend($1, $2); }
+ ;
+
+TriggerTransition:
+ TransitionOldOrNew TransitionRowOrTable opt_as TransitionRelName
+ {
+ TriggerTransition *n = makeNode(TriggerTransition);
+ n->name = $4;
+ n->isNew = $1;
+ n->isTable = $2;
+ $$ = (Node *)n;
+ }
+ ;
+
+TransitionOldOrNew:
+ NEW { $$ = TRUE; }
+ | OLD { $$ = FALSE; }
+ ;
+
+TransitionRowOrTable:
+ TABLE { $$ = TRUE; }
+ /*
+ * According to the standard, lack of a keyword here implies ROW.
+ * Support for that would require prohibiting ROW entirely here,
+ * reserving the keyword ROW, and/or requiring AS (instead of
+ * allowing it to be optional, as the standard specifies) as the
+ * next token. Requiring ROW seems cleanest and easiest to
+ * explain.
+ */
+ | ROW { $$ = FALSE; }
+ ;
+
+TransitionRelName:
+ ColId { $$ = $1; }
+ ;
+
TriggerForSpec:
FOR TriggerForOptEach TriggerForType
{
@@ -13810,6 +13859,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
+ | NEW
| NEXT
| NO
| NOTHING
@@ -13820,6 +13870,7 @@ unreserved_keyword:
| OF
| OFF
| OIDS
+ | OLD
| OPERATOR
| OPTION
| OPTIONS
@@ -13851,6 +13902,7 @@ unreserved_keyword:
| RECHECK
| RECURSIVE
| REF
+ | REFERENCING
| REFRESH
| REINDEX
| RELATIVE_P
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a078f..a3a4174abfb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -813,6 +813,8 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
SysScanDesc tgscan;
int findx = 0;
char *tgname;
+ char *tgoldtable;
+ char *tgnewtable;
Oid argtypes[1]; /* dummy */
Datum value;
bool isnull;
@@ -924,6 +926,27 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfoString(&buf, "IMMEDIATE ");
}
+ value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
+ tgrel->rd_att, &isnull);
+ if (!isnull)
+ tgoldtable = NameStr(*((NameData *) DatumGetPointer(value)));
+ else
+ tgoldtable = NULL;
+ value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
+ tgrel->rd_att, &isnull);
+ if (!isnull)
+ tgnewtable = NameStr(*((NameData *) DatumGetPointer(value)));
+ else
+ tgnewtable = NULL;
+ if (tgoldtable != NULL || tgnewtable != NULL)
+ {
+ appendStringInfoString(&buf, "REFERENCING ");
+ if (tgoldtable != NULL)
+ appendStringInfo(&buf, "OLD TABLE AS %s ", tgoldtable);
+ if (tgnewtable != NULL)
+ appendStringInfo(&buf, "NEW TABLE AS %s ", tgnewtable);
+ }
+
if (TRIGGER_FOR_ROW(trigrec->tgtype))
appendStringInfoString(&buf, "FOR EACH ROW ");
else
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index cd3048db868..880559650ad 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201610201
+#define CATALOG_VERSION_NO 201611041
#endif
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index eb39c50e637..da6a7f3a2e6 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -59,6 +59,8 @@ CATALOG(pg_trigger,2620)
#ifdef CATALOG_VARLEN
bytea tgargs BKI_FORCE_NOT_NULL; /* first\000second\000tgnargs\000 */
pg_node_tree tgqual; /* WHEN expression, or NULL if none */
+ NameData tgoldtable; /* old transition table, or NULL if none */
+ NameData tgnewtable; /* new transition table, or NULL if none */
#endif
} FormData_pg_trigger;
@@ -73,7 +75,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
-#define Natts_pg_trigger 15
+#define Natts_pg_trigger 17
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
@@ -89,6 +91,8 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgattr 13
#define Anum_pg_trigger_tgargs 14
#define Anum_pg_trigger_tgqual 15
+#define Anum_pg_trigger_tgoldtable 16
+#define Anum_pg_trigger_tgnewtable 17
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
@@ -142,4 +146,11 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define TRIGGER_TYPE_MATCHES(type, level, timing, event) \
(((type) & (TRIGGER_TYPE_LEVEL_MASK | TRIGGER_TYPE_TIMING_MASK | (event))) == ((level) | (timing) | (event)))
+/*
+ * Macro to determine whether tgnewtable or tgoldtable has been specified for
+ * a trigger.
+ */
+#define TRIGGER_USES_TRANSITION_TABLE(namepointer) \
+ ((namepointer) != (char *) NULL)
+
#endif /* PG_TRIGGER_H */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 0ed7c86eb27..c6e3e2c2346 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -37,6 +37,8 @@ typedef struct TriggerData
Trigger *tg_trigger;
Buffer tg_trigtuplebuf;
Buffer tg_newtuplebuf;
+ Tuplestorestate *tg_oldtable;
+ Tuplestorestate *tg_newtable;
} TriggerData;
/*
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bbe803..cb9307cd000 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,7 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_TriggerTransition,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab6b26..9b600a5f76d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1204,6 +1204,21 @@ typedef struct CommonTableExpr
((Query *) (cte)->ctequery)->targetList : \
((Query *) (cte)->ctequery)->returningList)
+/*
+ * TriggerTransition -
+ * representation of transition row or table naming clause
+ *
+ * Only transition tables are initially supported in the syntax, and only for
+ * AFTER triggers, but other permutations are accepted by the parser so we can
+ * give a meaningful message from C code.
+ */
+typedef struct TriggerTransition
+{
+ NodeTag type;
+ char *name;
+ bool isNew;
+ bool isTable;
+} TriggerTransition;
/*****************************************************************************
* Optimizable Statements
@@ -2105,6 +2120,8 @@ typedef struct CreateTrigStmt
List *columns; /* column names, or NIL for all columns */
Node *whenClause; /* qual expression, or NULL if none */
bool isconstraint; /* This is a constraint trigger */
+ /* explicitly named transition data */
+ List *transitionRels; /* TriggerTransition nodes, or NIL if none */
/* The remaining fields are only used for constraint triggers */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef53a70..77d873beca7 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -251,6 +251,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD)
PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD)
PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD)
PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("no", NO, UNRESERVED_KEYWORD)
PG_KEYWORD("none", NONE, COL_NAME_KEYWORD)
@@ -268,6 +269,7 @@ PG_KEYWORD("of", OF, UNRESERVED_KEYWORD)
PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD)
PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD)
PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD)
+PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD)
PG_KEYWORD("on", ON, RESERVED_KEYWORD)
PG_KEYWORD("only", ONLY, RESERVED_KEYWORD)
PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD)
@@ -313,6 +315,7 @@ PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
+PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD)
PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD)
PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/reltrigger.h b/src/include/utils/reltrigger.h
index e87f2283ec8..756b417128a 100644
--- a/src/include/utils/reltrigger.h
+++ b/src/include/utils/reltrigger.h
@@ -39,6 +39,8 @@ typedef struct Trigger
int16 *tgattr;
char **tgargs;
char *tgqual;
+ char *tgoldtable;
+ char *tgnewtable;
} Trigger;
typedef struct TriggerDesc
@@ -68,6 +70,11 @@ typedef struct TriggerDesc
/* there are no row-level truncate triggers */
bool trig_truncate_before_statement;
bool trig_truncate_after_statement;
+ /* Is there at least one trigger specifying each transition relation? */
+ bool trig_insert_new_table;
+ bool trig_update_old_table;
+ bool trig_update_new_table;
+ bool trig_delete_old_table;
} TriggerDesc;
#endif /* RELTRIGGER_H */