]> granicus.if.org Git - postgresql/commitdiff
Follow-on cleanup for the transition table patch.
authorKevin Grittner <kgrittn@postgresql.org>
Tue, 4 Apr 2017 23:36:39 +0000 (18:36 -0500)
committerKevin Grittner <kgrittn@postgresql.org>
Tue, 4 Apr 2017 23:36:39 +0000 (18:36 -0500)
Commit 59702716 added transition table support to PL/pgsql so that
SQL queries in trigger functions could access those transient
tables.  In order to provide the same level of support for PL/perl,
PL/python and PL/tcl, refactor the relevant code into a new
function SPI_register_trigger_data.  Call the new function in the
trigger handler of all four PLs, and document it as a public SPI
function so that authors of out-of-tree PLs can do the same.

Also get rid of a second QueryEnvironment object that was
maintained by PL/pgsql.  That was previously used to deal with
cursors, but the same approach wasn't appropriate for PLs that are
less tangled up with core code.  Instead, have SPI_cursor_open
install the connection's current QueryEnvironment, as already
happens for SPI_execute_plan.

While in the docs, remove the note that transition tables were only
supported in C and PL/pgSQL triggers, and correct some ommissions.

Thomas Munro with some work by Kevin Grittner (mostly docs)

16 files changed:
doc/src/sgml/ref/create_trigger.sgml
doc/src/sgml/spi.sgml
doc/src/sgml/trigger.sgml
src/backend/executor/spi.c
src/include/executor/spi.h
src/pl/plperl/expected/plperl_trigger.out
src/pl/plperl/plperl.c
src/pl/plperl/sql/plperl_trigger.sql
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpython/expected/plpython_trigger.out
src/pl/plpython/plpy_exec.c
src/pl/plpython/sql/plpython_trigger.sql
src/pl/tcl/expected/pltcl_queries.out
src/pl/tcl/pltcl.c
src/pl/tcl/sql/pltcl_queries.sql

index e22e42e7dcfd71eea6bfb40b84512992e88c8031..24195b3849d79407c0f24de03c6262ae24453c49 100644 (file)
@@ -8,6 +8,11 @@ PostgreSQL documentation
   <primary>CREATE TRIGGER</primary>
  </indexterm>
 
+ <indexterm>
+  <primary>transition tables</primary>
+  <seealso>ephemeral named relation</seealso>
+ </indexterm>
+
  <refmeta>
   <refentrytitle>CREATE TRIGGER</refentrytitle>
   <manvolnum>7</manvolnum>
@@ -322,11 +327,6 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
      <para>
       The (unqualified) name to be used within the trigger for this relation.
      </para>
-     <note>
-      <para>
-       So far only triggers written in C or PL/pgSQL support this.
-      </para>
-     </note>
     </listitem>
    </varlistentry>
 
index af7eada2e1b1d4c80bf9e33968665b4e0c2bc84d..86be87c0fdb4597ce5405a31d54e63f17811e9d8 100644 (file)
@@ -2644,6 +2644,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
 <refentry id="spi-spi-register-relation">
  <indexterm><primary>SPI_register_relation</primary></indexterm>
 
+ <indexterm>
+  <primary>ephemeral named relation</primary>
+  <secondary>registering with SPI</secondary>
+ </indexterm>
+
  <refmeta>
   <refentrytitle>SPI_register_relation</refentrytitle>
   <manvolnum>3</manvolnum>
@@ -2746,6 +2751,11 @@ int SPI_register_relation(EphemeralNamedRelation <parameter>enr</parameter>)
 <refentry id="spi-spi-unregister-relation">
  <indexterm><primary>SPI_unregister_relation</primary></indexterm>
 
+ <indexterm>
+  <primary>ephemeral named relation</primary>
+  <secondary>unregistering from SPI</secondary>
+ </indexterm>
+
  <refmeta>
   <refentrytitle>SPI_unregister_relation</refentrytitle>
   <manvolnum>3</manvolnum>
@@ -2843,6 +2853,121 @@ int SPI_unregister_relation(const char * <parameter>name</parameter>)
 
 <!-- *********************************************** -->
 
+<refentry id="spi-spi-register-trigger-data">
+ <indexterm><primary>SPI_register_trigger_data</primary></indexterm>
+
+ <indexterm>
+  <primary>ephemeral named relation</primary>
+  <secondary>registering with SPI</secondary>
+ </indexterm>
+
+ <indexterm>
+  <primary>transition tables</primary>
+  <secondary>implementation in PLs</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>SPI_register_trigger_data</refentrytitle>
+  <manvolnum>3</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_register_trigger_data</refname>
+  <refpurpose>make ephemeral trigger data available in SPI queries</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+int SPI_register_trigger_data(TriggerData *<parameter>tdata</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_register_trigger_data</function> makes any ephemeral
+   relations captured by a trigger available to queries planned and executed
+   through the current SPI connection.  Currently, this means the transition
+   tables captured by an <literal>AFTER</literal> trigger defined with a
+   <literal>REFERENCING OLD/NEW TABLE AS</literal> ... clause.  This function
+   should be called by a PL trigger handler function after connecting.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>TriggerData *<parameter>tdata</parameter></literal></term>
+    <listitem>
+     <para>
+       the <structname>TriggerData</structname> object passed to a trigger
+       handler function as <literal>fcinfo->context</literal>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   If the execution of the command was successful then the following
+   (nonnegative) value will be returned:
+
+   <variablelist>
+    <varlistentry>
+     <term><symbol>SPI_OK_TD_REGISTER</symbol></term>
+     <listitem>
+      <para>
+       if the captured trigger data (if any) has been successfully registered
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+
+  <para>
+   On error, one of the following negative values is returned:
+
+   <variablelist>
+    <varlistentry>
+     <term><symbol>SPI_ERROR_ARGUMENT</symbol></term>
+     <listitem>
+      <para>
+       if <parameter>tdata</parameter> is <symbol>NULL</symbol>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><symbol>SPI_ERROR_UNCONNECTED</symbol></term>
+     <listitem>
+      <para>
+       if called from an unconnected procedure
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><symbol>SPI_ERROR_REL_DUPLICATE</symbol></term>
+     <listitem>
+      <para>
+       if the name of any trigger data transient relation is already
+       registered for this connection
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
 </sect1>
 
 <sect1 id="spi-interface-support">
index 8f724c86648e1ef2394012d241cd9ffd30c99057..2a718d7f47302bcfa4900d0db2f83da6ee2482b5 100644 (file)
     <secondary>in C</secondary>
    </indexterm>
 
+   <indexterm>
+    <primary>transition tables</primary>
+    <secondary>referencing from C trigger</secondary>
+   </indexterm>
+
    <para>
     This section describes the low-level details of the interface to a
     trigger function.  This information is only needed when writing
@@ -438,14 +443,16 @@ CALLED_AS_TRIGGER(fcinfo)
 <programlisting>
 typedef struct TriggerData
 {
-    NodeTag       type;
-    TriggerEvent  tg_event;
-    Relation      tg_relation;
-    HeapTuple     tg_trigtuple;
-    HeapTuple     tg_newtuple;
-    Trigger      *tg_trigger;
-    Buffer        tg_trigtuplebuf;
-    Buffer        tg_newtuplebuf;
+    NodeTag          type;
+    TriggerEvent     tg_event;
+    Relation         tg_relation;
+    HeapTuple        tg_trigtuple;
+    HeapTuple        tg_newtuple;
+    Trigger         *tg_trigger;
+    Buffer           tg_trigtuplebuf;
+    Buffer           tg_newtuplebuf;
+    Tuplestorestate *tg_oldtable;
+    Tuplestorestate *tg_newtable;
 } TriggerData;
 </programlisting>
 
@@ -629,6 +636,8 @@ typedef struct Trigger
     int16      *tgattr;
     char      **tgargs;
     char       *tgqual;
+    char       *tgoldtable;
+    char       *tgnewtable;
 } Trigger;
 </programlisting>
 
@@ -662,9 +671,38 @@ typedef struct Trigger
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><structfield>tg_oldtable</></term>
+      <listitem>
+       <para>
+        A pointer to a structure of type <structname>Tuplestorestate</structname>
+        containing zero or more rows in the format specified by
+        <structfield>tg_relation</structfield>, or a <symbol>NULL</> pointer
+        if there is no <literal>OLD TABLE</literal> transition relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><structfield>tg_newtable</></term>
+      <listitem>
+       <para>
+        A pointer to a structure of type <structname>Tuplestorestate</structname>
+        containing zero or more rows in the format specified by
+        <structfield>tg_relation</structfield>, or a <symbol>NULL</> pointer
+        if there is no <literal>NEW TABLE</literal> transition relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
+   <para>
+    To allow queries issued through SPI to reference transition tables, see
+    <xref linkend="spi-spi-register-trigger-data">.
+   </para>
+
    <para>
     A trigger function must return either a
     <structname>HeapTuple</> pointer or a <symbol>NULL</> pointer
index 2f07a444b4d47f194e56f0cdd25e403859ed6f75..3a89ccd913f8ae109445c6c4cc38d598439f8d03 100644 (file)
@@ -1257,6 +1257,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
                                         errdetail("Scrollable cursors must be READ ONLY.")));
        }
 
+       /* Make current query environment available to portal at execution time. */
+       portal->queryEnv = _SPI_current->queryEnv;
+
        /*
         * If told to be read-only, or in parallel mode, verify that this query is
         * in fact read-only.  This can't be done earlier because we need to look
@@ -2716,3 +2719,52 @@ SPI_unregister_relation(const char *name)
 
        return res;
 }
+
+/*
+ * Register the transient relations from 'tdata' using this SPI connection.
+ * This should be called by PL implementations' trigger handlers after
+ * connecting, in order to make transition tables visible to any queries run
+ * in this connection.
+ */
+int
+SPI_register_trigger_data(TriggerData *tdata)
+{
+       if (tdata == NULL)
+               return SPI_ERROR_ARGUMENT;
+
+       if (tdata->tg_newtable)
+       {
+               EphemeralNamedRelation enr =
+                       palloc(sizeof(EphemeralNamedRelationData));
+               int             rc;
+
+               enr->md.name = tdata->tg_trigger->tgnewtable;
+               enr->md.reliddesc = tdata->tg_relation->rd_id;
+               enr->md.tupdesc = NULL;
+               enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+               enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_newtable);
+               enr->reldata = tdata->tg_newtable;
+               rc = SPI_register_relation(enr);
+               if (rc != SPI_OK_REL_REGISTER)
+                       return rc;
+       }
+
+       if (tdata->tg_oldtable)
+       {
+               EphemeralNamedRelation enr =
+                       palloc(sizeof(EphemeralNamedRelationData));
+               int             rc;
+
+               enr->md.name = tdata->tg_trigger->tgoldtable;
+               enr->md.reliddesc = tdata->tg_relation->rd_id;
+               enr->md.tupdesc = NULL;
+               enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+               enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_oldtable);
+               enr->reldata = tdata->tg_oldtable;
+               rc = SPI_register_relation(enr);
+               if (rc != SPI_OK_REL_REGISTER)
+                       return rc;
+       }
+
+       return SPI_OK_TD_REGISTER;
+}
index e2e8bb955396d5654de4077705e0416ebc3a86e1..94a805d4778b11de6fb90967016ea2edc98fb34c 100644 (file)
@@ -13,6 +13,7 @@
 #ifndef SPI_H
 #define SPI_H
 
+#include "commands/trigger.h"
 #include "lib/ilist.h"
 #include "nodes/parsenodes.h"
 #include "utils/portal.h"
@@ -62,6 +63,7 @@ typedef struct _SPI_plan *SPIPlanPtr;
 #define SPI_OK_REWRITTEN               14
 #define SPI_OK_REL_REGISTER            15
 #define SPI_OK_REL_UNREGISTER  16
+#define SPI_OK_TD_REGISTER             17
 
 /* These used to be functions, now just no-ops for backwards compatibility */
 #define SPI_push()     ((void) 0)
@@ -152,6 +154,7 @@ extern void SPI_cursor_close(Portal portal);
 
 extern int SPI_register_relation(EphemeralNamedRelation enr);
 extern int SPI_unregister_relation(const char *name);
+extern int SPI_register_trigger_data(TriggerData *tdata);
 
 extern void AtEOXact_SPI(bool isCommit);
 extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid);
index 5e3860ef97b3418adccff51e3093881129e16f07..28011cd9f643cd4270ccac560176063514d57d45 100644 (file)
@@ -241,6 +241,35 @@ $$ LANGUAGE plperl;
 SELECT direct_trigger();
 ERROR:  trigger functions can only be called as triggers
 CONTEXT:  compilation of PL/Perl function "direct_trigger"
+-- check that SQL run in trigger code can see transition tables
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plperl AS
+$$
+    my $cursor = spi_query("SELECT * FROM old_table");
+    my $row = spi_fetchrow($cursor);
+    defined($row) || die "expected a row";
+    elog(INFO, "old: " . $row->{id} . " -> " . $row->{name});
+    my $row = spi_fetchrow($cursor);
+    !defined($row) || die "expected no more rows";
+
+    my $cursor = spi_query("SELECT * FROM new_table");
+    my $row = spi_fetchrow($cursor);
+    defined($row) || die "expected a row";
+    elog(INFO, "new: " . $row->{id} . " -> " . $row->{name});
+    my $row = spi_fetchrow($cursor);
+    !defined($row) || die "expected no more rows";
+
+    return undef;
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+INFO:  old: 1 -> a
+INFO:  new: 1 -> b
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
 -- test plperl command triggers
 create or replace function perlsnitch() returns event_trigger language plperl as $$
   elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
index 00979cba743bbc4f857f53c4243c3f53f45a5fc8..1de770b2505c02f69d1bf76a1cd2ed358da35660 100644 (file)
@@ -2442,11 +2442,18 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
        SV                 *svTD;
        HV                 *hvTD;
        ErrorContextCallback pl_error_context;
+       TriggerData *tdata;
+       int             rc PG_USED_FOR_ASSERTS_ONLY;
 
        /* Connect to SPI manager */
        if (SPI_connect() != SPI_OK_CONNECT)
                elog(ERROR, "could not connect to SPI manager");
 
+       /* Make transition tables visible to this SPI connection */
+       tdata = (TriggerData *) fcinfo->context;
+       rc = SPI_register_trigger_data(tdata);
+       Assert(rc >= 0);
+
        /* Find or compile the function */
        prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false);
        current_call_data->prodesc = prodesc;
index a375b401ea2705344678b5e8b495bade7f3c2897..624193b9d08e69262cb89bb4ff2576bffdde8478 100644 (file)
@@ -170,6 +170,38 @@ $$ LANGUAGE plperl;
 
 SELECT direct_trigger();
 
+-- check that SQL run in trigger code can see transition tables
+
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plperl AS
+$$
+    my $cursor = spi_query("SELECT * FROM old_table");
+    my $row = spi_fetchrow($cursor);
+    defined($row) || die "expected a row";
+    elog(INFO, "old: " . $row->{id} . " -> " . $row->{name});
+    my $row = spi_fetchrow($cursor);
+    !defined($row) || die "expected no more rows";
+
+    my $cursor = spi_query("SELECT * FROM new_table");
+    my $row = spi_fetchrow($cursor);
+    defined($row) || die "expected a row";
+    elog(INFO, "new: " . $row->{id} . " -> " . $row->{name});
+    my $row = spi_fetchrow($cursor);
+    !defined($row) || die "expected no more rows";
+
+    return undef;
+$$;
+
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
+
 -- test plperl command triggers
 create or replace function perlsnitch() returns event_trigger language plperl as $$
   elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
index 43da986fc0ad1489df4097608ab9d1bf28acf581..8d7c7caa1c67ae8bf42c6525824fd5d635d98eb0 100644 (file)
@@ -689,46 +689,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        else
                elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
 
-       /*
-        * Capture the NEW and OLD transition TABLE tuplestores (if specified for
-        * this trigger).
-        */
-       if (trigdata->tg_newtable || trigdata->tg_oldtable)
-       {
-               estate.queryEnv = create_queryEnv();
-               if (trigdata->tg_newtable)
-               {
-                       EphemeralNamedRelation enr =
-                         palloc(sizeof(EphemeralNamedRelationData));
-                       int rc PG_USED_FOR_ASSERTS_ONLY;
-
-                       enr->md.name = trigdata->tg_trigger->tgnewtable;
-                       enr->md.reliddesc = RelationGetRelid(trigdata->tg_relation);
-                       enr->md.tupdesc = NULL;
-                       enr->md.enrtype = ENR_NAMED_TUPLESTORE;
-                       enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_newtable);
-                       enr->reldata = trigdata->tg_newtable;
-                       register_ENR(estate.queryEnv, enr);
-                       rc = SPI_register_relation(enr);
-                       Assert(rc >= 0);
-               }
-               if (trigdata->tg_oldtable)
-               {
-                       EphemeralNamedRelation enr =
-                         palloc(sizeof(EphemeralNamedRelationData));
-                       int rc PG_USED_FOR_ASSERTS_ONLY;
-
-                       enr->md.name = trigdata->tg_trigger->tgoldtable;
-                       enr->md.reliddesc = RelationGetRelid(trigdata->tg_relation);
-                       enr->md.tupdesc = NULL;
-                       enr->md.enrtype = ENR_NAMED_TUPLESTORE;
-                       enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
-                       enr->reldata = trigdata->tg_oldtable;
-                       register_ENR(estate.queryEnv, enr);
-                       rc = SPI_register_relation(enr);
-                       Assert(rc >= 0);
-               }
-       }
+       /* Make transition tables visible to this SPI connection */
+       rc = SPI_register_trigger_data(trigdata);
+       Assert(rc >= 0);
 
        /*
         * Assign the special tg_ variables
@@ -3483,9 +3446,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        estate->paramLI->paramMask = NULL;
        estate->params_dirty = false;
 
-       /* default tuplestore cache to "none" */
-       estate->queryEnv = NULL;
-
        /* set up for use of appropriate simple-expression EState and cast hash */
        if (simple_eval_estate)
        {
@@ -7373,9 +7333,6 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
        /* Release transient data */
        MemoryContextReset(stmt_mcontext);
 
-       /* Make sure the portal knows about any named tuplestores. */
-       portal->queryEnv = estate->queryEnv;
-
        return portal;
 }
 
index 43a62ef34e4236191b83e980ac999f533f72d0e4..abb19035b66094f1a8b6dc9428ecd563a32dcda5 100644 (file)
@@ -20,7 +20,6 @@
 #include "commands/event_trigger.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
-#include "utils/queryenvironment.h"
 
 /**********************************************************************
  * Definitions
@@ -911,9 +910,6 @@ typedef struct PLpgSQL_execstate
        ParamListInfo paramLI;
        bool            params_dirty;   /* T if any resettable datum has been passed */
 
-       /* custom environment for parsing/execution of query for this context */
-       QueryEnvironment *queryEnv;
-
        /* EState to use for "simple" expression evaluation */
        EState     *simple_eval_estate;
 
index 4148963f3a28bcb10ac7e3d28b784546847ea724..d7ab8ac6b8e20b3e155336d746eb9c7adb99fc65 100644 (file)
@@ -503,3 +503,24 @@ SELECT * FROM b;
  1234
 (1 row)
 
+-- check that SQL run in trigger code can see transition tables
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpythonu AS
+$$
+    rv = plpy.execute("SELECT * FROM old_table")
+    assert(rv.nrows() == 1)
+    plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+    rv = plpy.execute("SELECT * FROM new_table")
+    assert(rv.nrows() == 1)
+    plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+    return None
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+INFO:  old: 1 -> a
+INFO:  new: 1 -> b
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
index 697a0e1cc03396d279de20dc664379cdae565350..7ccd2c82352e0a7d03af8e66263bc9500dc6b905 100644 (file)
@@ -345,6 +345,11 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
 
        PG_TRY();
        {
+               int             rc PG_USED_FOR_ASSERTS_ONLY;
+
+               rc = SPI_register_trigger_data(tdata);
+               Assert(rc >= 0);
+
                plargs = PLy_trigger_build_args(fcinfo, proc, &rv);
                plrv = PLy_procedure_call(proc, "TD", plargs);
 
index a054fe729bcf644605d8ad925e1ff280a73a267b..79c24b714b500078beb0d456c485dc61d8fbaa53 100644 (file)
@@ -406,3 +406,27 @@ SELECT * FROM a;
 DROP TABLE a;
 INSERT INTO b DEFAULT VALUES;
 SELECT * FROM b;
+
+-- check that SQL run in trigger code can see transition tables
+
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpythonu AS
+$$
+    rv = plpy.execute("SELECT * FROM old_table")
+    assert(rv.nrows() == 1)
+    plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+    rv = plpy.execute("SELECT * FROM new_table")
+    assert(rv.nrows() == 1)
+    plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+    return None
+$$;
+
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
index 7300b315d64dc3bdf035bbc06f8754b1e1ec7eef..5f50f4688787a5ff055f0025e4e43acfb8dcc3ed 100644 (file)
@@ -660,3 +660,24 @@ select tcl_eval($$
  
 (1 row)
 
+-- test transition table visibility
+create table transition_table_test (id int, name text);
+insert into transition_table_test values (1, 'a');
+create function transition_table_test_f() returns trigger language pltcl as
+$$
+  spi_exec -array C "SELECT id, name FROM old_table" {
+    elog INFO "old: $C(id) -> $C(name)"
+  }
+  spi_exec -array C "SELECT id, name FROM new_table" {
+    elog INFO "new: $C(id) -> $C(name)"
+  }
+  return OK
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+update transition_table_test set name = 'b';
+INFO:  old: 1 -> a
+INFO:  new: 1 -> b
+drop table transition_table_test;
+drop function transition_table_test_f();
index b8fcf0673d38c7a9fb7766f09307846d65c5b0c3..5a48e5d175ae5dc2714e4087f54611f67c5ed054 100644 (file)
@@ -1039,6 +1039,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
        const char *result;
        int                     result_Objc;
        Tcl_Obj   **result_Objv;
+       int                     rc PG_USED_FOR_ASSERTS_ONLY;
 
        call_state->trigdata = trigdata;
 
@@ -1046,6 +1047,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
        if (SPI_connect() != SPI_OK_CONNECT)
                elog(ERROR, "could not connect to SPI manager");
 
+       /* Make transition tables visible to this SPI connection */
+       rc = SPI_register_trigger_data(trigdata);
+       Assert(rc >= 0);
+
        /* Find or compile the function */
        prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
                                                                         RelationGetRelid(trigdata->tg_relation),
index 29ed616cd0b0c2abb37a3b4e6054c19f55fe8d91..dabd8cd35f08b7224be738a80d8a0a015eb7592c 100644 (file)
@@ -216,3 +216,23 @@ select tcl_eval($$
   after 100 {set ::tcl_vwait 1}
   vwait ::tcl_vwait
   unset -nocomplain ::tcl_vwait$$);
+
+-- test transition table visibility
+create table transition_table_test (id int, name text);
+insert into transition_table_test values (1, 'a');
+create function transition_table_test_f() returns trigger language pltcl as
+$$
+  spi_exec -array C "SELECT id, name FROM old_table" {
+    elog INFO "old: $C(id) -> $C(name)"
+  }
+  spi_exec -array C "SELECT id, name FROM new_table" {
+    elog INFO "new: $C(id) -> $C(name)"
+  }
+  return OK
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+update transition_table_test set name = 'b';
+drop table transition_table_test;
+drop function transition_table_test_f();