Implement syntax for transition tables in AFTER triggers.
authorKevin Grittner <[email protected]>
Fri, 4 Nov 2016 15:49:50 +0000 (10:49 -0500)
committerKevin Grittner <[email protected]>
Fri, 4 Nov 2016 15:49:50 +0000 (10:49 -0500)
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.

16 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/create_trigger.sgml
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/catalog/pg_trigger.h
src/include/commands/trigger.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/include/utils/reltrigger.h

index 29738b07cb96194a3b4ae97b857bd14d701f9d0e..bac169a19e4f23e5508d534112db9cf88878f411 100644 (file)
        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>
index 4bde81501223e3419498828fa9151e5ff3822ac9..8590e226e317411f0fb5bb926c13f29d8cf399f0 100644 (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>
 
index 2137372c2348f4939916acdaa7deb6bb70a6f0f2..f97bee5b0e464265e2af103053800900f40058ff 100644 (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;
index 9de22a13d75c05128fe39e934ca7cefec65c4ae7..1c264b773613819ff3f0cc638d47d8a93deb0cd9 100644 (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
index 71714bc1d6709617787f06842c4690d6d0edf54d..04e49b77951d835da788db1aa52691831a7b40e4 100644 (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
index 29a090fc48b60349d87c55fd6354fa4031463fbd..2eaf41c37f896a1affda3f1dc579abf00d9c0571 100644 (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",
index ae869547f357da2944e3cf122ec6fd20026622ee..748b68792927b1c8acc63dd69208ca45bf441c19 100644 (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:
 
index 5547fc86586ae54a082734c9686e14f021bbc47f..0ec1cd345b232c8673ba006db26c642600cbfa11 100644 (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
index 8a81d7a078f98d84a89fea6279e1fd37d2217bb3..a3a4174abfb7924b6d8527c3d96559b5c069175c 100644 (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
index cd3048db868fd9ef82a8b6768fce8df580c4014d..880559650ad07d7c23659cd2620f47334950e49a 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201610201
+#define CATALOG_VERSION_NO 201611041
 
 #endif
index eb39c50e63708caf65306efa234c64a9c010d6ed..da6a7f3a2e68d110b3f3ac0637ca89e6f3c17484 100644 (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 */
index 0ed7c86eb27a2c71d4f5a0ff499bc334ec9fd96e..c6e3e2c234699446154d2aa7572ce9956f6473c6 100644 (file)
@@ -37,6 +37,8 @@ typedef struct TriggerData
    Trigger    *tg_trigger;
    Buffer      tg_trigtuplebuf;
    Buffer      tg_newtuplebuf;
+   Tuplestorestate *tg_oldtable;
+   Tuplestorestate *tg_newtable;
 } TriggerData;
 
 /*
index 88297bbe80377b7abf92353d7696741cce66219a..cb9307cd0007bb705e0518f4dfbb577f90a5b26a 100644 (file)
@@ -453,6 +453,7 @@ typedef enum NodeTag
    T_OnConflictClause,
    T_CommonTableExpr,
    T_RoleSpec,
+   T_TriggerTransition,
 
    /*
     * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
index 6de2cab6b260079de642b4b8cdc480c371733e62..9b600a5f76df6fca566218ad3a76f477187330cc 100644 (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} */
index 17ffef53a70cbf1358fd5fa6803452a45b44a053..77d873beca712e1e76d25d41e09048ca8456a330 100644 (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)
index e87f2283ec896af265e1cd42927cf59dacb0a379..756b417128af929c41b243d3e8adaef12c6e040e 100644 (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 */