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.
This commit is contained in:
Kevin Grittner 2016-11-04 10:49:50 -05:00
parent 69d590fffb
commit 8c48375e5f
16 changed files with 580 additions and 43 deletions

View File

@ -6231,6 +6231,22 @@
representation) for the trigger's <literal>WHEN</> condition, or null
if none</entry>
</row>
<row>
<entry><structfield>tgoldtable</structfield></entry>
<entry><type>name</type></entry>
<entry></entry>
<entry><literal>REFERENCING</> clause name for <literal>OLD TABLE</>,
or null if none</entry>
</row>
<row>
<entry><structfield>tgnewtable</structfield></entry>
<entry><type>name</type></entry>
<entry></entry>
<entry><literal>REFERENCING</> clause name for <literal>NEW TABLE</>,
or null if none</entry>
</row>
</tbody>
</tgroup>
</table>

View File

@ -25,6 +25,7 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
ON <replaceable class="PARAMETER">table_name</replaceable>
[ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
[ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
[ REFERENCING { { OLD | NEW } TABLE [ AS ] <replaceable class="PARAMETER">transition_relation_name</replaceable> } [ ... ] ]
[ FOR [ EACH ] { ROW | STATEMENT } ]
[ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
@ -177,6 +178,15 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
when the constraints they implement are violated.
</para>
<para>
The <literal>REFERENCING</> option is only allowed for an <literal>AFTER</>
trigger which is not a constraint trigger. <literal>OLD TABLE</> may only
be specified once, and only on a trigger which can fire on
<literal>UPDATE</> or <literal>DELETE</>. <literal>NEW TABLE</> may only
be specified once, and only on a trigger which can fire on
<literal>UPDATE</> or <literal>INSERT</>.
</para>
<para>
<command>SELECT</command> does not modify any rows so you cannot
create <command>SELECT</command> triggers. Rules and views are more
@ -281,6 +291,40 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
</listitem>
</varlistentry>
<varlistentry>
<term><literal>REFERENCING</literal></term>
<listitem>
<para>
This immediately preceeds the declaration of one or two relations which
can be used to read the before and/or after images of all rows directly
affected by the triggering statement. An <literal>AFTER EACH ROW</>
trigger is allowed to use both these transition relation names and the
row names (<literal>OLD</> and <literal>NEW</>) which reference each
individual row for which the trigger fires.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OLD TABLE</literal></term>
<term><literal>NEW TABLE</literal></term>
<listitem>
<para>
This specifies whether the named relation contains the before or after
images for rows affected by the statement which fired the trigger.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">transition_relation_name</replaceable></term>
<listitem>
<para>
The (unqualified) name to be used within the trigger for this relation.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>FOR EACH ROW</literal></term>
<term><literal>FOR EACH STATEMENT</literal></term>
@ -474,6 +518,30 @@ CREATE TRIGGER view_insert
FOR EACH ROW
EXECUTE PROCEDURE view_insert_row();
</programlisting>
Execute the function <function>check_transfer_balances_to_zero</> for each
statement to confirm that the <literal>transfer</> rows offset to a net of
zero:
<programlisting>
CREATE TRIGGER transfer_insert
AFTER INSERT ON transfer
FOR EACH STATEMENT
REFERENCING NEW TABLE AS inserted
EXECUTE PROCEDURE check_transfer_balances_to_zero();
</programlisting>
Execute the function <function>check_matching_pairs</> for each row to
confirm that changes are made to matching pairs at the same time (by the
same statement):
<programlisting>
CREATE TRIGGER paired_items_update
AFTER UPDATE ON paired_items
FOR EACH ROW
REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab
EXECUTE PROCEDURE check_matching_pairs();
</programlisting>
</para>
<para>
@ -502,24 +570,14 @@ CREATE TRIGGER view_insert
<itemizedlist>
<listitem>
<para>
SQL allows you to define aliases for the <quote>old</quote>
and <quote>new</quote> rows or tables for use in the definition
of the triggered action (e.g., <literal>CREATE TRIGGER ... ON
tablename REFERENCING OLD ROW AS somename NEW ROW AS othername
...</literal>). Since <productname>PostgreSQL</productname>
allows trigger procedures to be written in any number of
user-defined languages, access to the data is handled in a
language-specific way.
</para>
</listitem>
<listitem>
<para>
<productname>PostgreSQL</productname> does not allow the old and new
tables to be referenced in statement-level triggers, i.e., the tables
that contain all the old and/or new rows, which are referred to by the
<literal>OLD TABLE</literal> and <literal>NEW TABLE</literal> clauses in
the <acronym>SQL</> standard.
While transition tables for <literal>AFTER</> triggers are specified
using the <literal>REFERENCING</> clause in the standard way, the row
variables used in <literal>FOR EACH ROW</> triggers may not be
specified in <literal>REFERENCING</> clause. They are available in a
manner which is dependent on the language in which the trigger function
is written. Some languages effectively behave as though there is a
<literal>REFERENCING</> clause containing <literal>OLD ROW AS OLD NEW
ROW AS NEW</>.
</para>
</listitem>

View File

@ -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;

View File

@ -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);
@ -309,6 +311,87 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("INSTEAD OF triggers cannot have column lists")));
}
/*
* 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
*/
@ -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))
@ -3623,6 +3815,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
*/
@ -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
@ -4831,6 +5090,46 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
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.
*
@ -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

View File

@ -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

View File

@ -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",

View File

@ -2561,6 +2561,16 @@ _outXmlSerialize(StringInfo str, const XmlSerialize *node)
WRITE_LOCATION_FIELD(location);
}
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)
{
@ -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:

View File

@ -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

View File

@ -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

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201610201
#define CATALOG_VERSION_NO 201611041
#endif

View File

@ -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 */

View File

@ -37,6 +37,8 @@ typedef struct TriggerData
Trigger *tg_trigger;
Buffer tg_trigtuplebuf;
Buffer tg_newtuplebuf;
Tuplestorestate *tg_oldtable;
Tuplestorestate *tg_newtable;
} TriggerData;
/*

View File

@ -453,6 +453,7 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)

View File

@ -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} */

View File

@ -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)

View File

@ -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 */