Commit 5ebeb579 authored by Kevin Grittner's avatar Kevin Grittner

Follow-on cleanup for the transition table patch.

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)
parent 9a321502
...@@ -8,6 +8,11 @@ PostgreSQL documentation ...@@ -8,6 +8,11 @@ PostgreSQL documentation
<primary>CREATE TRIGGER</primary> <primary>CREATE TRIGGER</primary>
</indexterm> </indexterm>
<indexterm>
<primary>transition tables</primary>
<seealso>ephemeral named relation</seealso>
</indexterm>
<refmeta> <refmeta>
<refentrytitle>CREATE TRIGGER</refentrytitle> <refentrytitle>CREATE TRIGGER</refentrytitle>
<manvolnum>7</manvolnum> <manvolnum>7</manvolnum>
...@@ -322,11 +327,6 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</ ...@@ -322,11 +327,6 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
<para> <para>
The (unqualified) name to be used within the trigger for this relation. The (unqualified) name to be used within the trigger for this relation.
</para> </para>
<note>
<para>
So far only triggers written in C or PL/pgSQL support this.
</para>
</note>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -2644,6 +2644,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -2644,6 +2644,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<refentry id="spi-spi-register-relation"> <refentry id="spi-spi-register-relation">
<indexterm><primary>SPI_register_relation</primary></indexterm> <indexterm><primary>SPI_register_relation</primary></indexterm>
<indexterm>
<primary>ephemeral named relation</primary>
<secondary>registering with SPI</secondary>
</indexterm>
<refmeta> <refmeta>
<refentrytitle>SPI_register_relation</refentrytitle> <refentrytitle>SPI_register_relation</refentrytitle>
<manvolnum>3</manvolnum> <manvolnum>3</manvolnum>
...@@ -2746,6 +2751,11 @@ int SPI_register_relation(EphemeralNamedRelation <parameter>enr</parameter>) ...@@ -2746,6 +2751,11 @@ int SPI_register_relation(EphemeralNamedRelation <parameter>enr</parameter>)
<refentry id="spi-spi-unregister-relation"> <refentry id="spi-spi-unregister-relation">
<indexterm><primary>SPI_unregister_relation</primary></indexterm> <indexterm><primary>SPI_unregister_relation</primary></indexterm>
<indexterm>
<primary>ephemeral named relation</primary>
<secondary>unregistering from SPI</secondary>
</indexterm>
<refmeta> <refmeta>
<refentrytitle>SPI_unregister_relation</refentrytitle> <refentrytitle>SPI_unregister_relation</refentrytitle>
<manvolnum>3</manvolnum> <manvolnum>3</manvolnum>
...@@ -2843,6 +2853,121 @@ int SPI_unregister_relation(const char * <parameter>name</parameter>) ...@@ -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>
<sect1 id="spi-interface-support"> <sect1 id="spi-interface-support">
......
...@@ -395,6 +395,11 @@ ...@@ -395,6 +395,11 @@
<secondary>in C</secondary> <secondary>in C</secondary>
</indexterm> </indexterm>
<indexterm>
<primary>transition tables</primary>
<secondary>referencing from C trigger</secondary>
</indexterm>
<para> <para>
This section describes the low-level details of the interface to a This section describes the low-level details of the interface to a
trigger function. This information is only needed when writing trigger function. This information is only needed when writing
...@@ -446,6 +451,8 @@ typedef struct TriggerData ...@@ -446,6 +451,8 @@ typedef struct TriggerData
Trigger *tg_trigger; Trigger *tg_trigger;
Buffer tg_trigtuplebuf; Buffer tg_trigtuplebuf;
Buffer tg_newtuplebuf; Buffer tg_newtuplebuf;
Tuplestorestate *tg_oldtable;
Tuplestorestate *tg_newtable;
} TriggerData; } TriggerData;
</programlisting> </programlisting>
...@@ -629,6 +636,8 @@ typedef struct Trigger ...@@ -629,6 +636,8 @@ typedef struct Trigger
int16 *tgattr; int16 *tgattr;
char **tgargs; char **tgargs;
char *tgqual; char *tgqual;
char *tgoldtable;
char *tgnewtable;
} Trigger; } Trigger;
</programlisting> </programlisting>
...@@ -662,9 +671,38 @@ typedef struct Trigger ...@@ -662,9 +671,38 @@ typedef struct Trigger
</listitem> </listitem>
</varlistentry> </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> </variablelist>
</para> </para>
<para>
To allow queries issued through SPI to reference transition tables, see
<xref linkend="spi-spi-register-trigger-data">.
</para>
<para> <para>
A trigger function must return either a A trigger function must return either a
<structname>HeapTuple</> pointer or a <symbol>NULL</> pointer <structname>HeapTuple</> pointer or a <symbol>NULL</> pointer
......
...@@ -1257,6 +1257,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1257,6 +1257,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
errdetail("Scrollable cursors must be READ ONLY."))); 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 * 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 * 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) ...@@ -2716,3 +2719,52 @@ SPI_unregister_relation(const char *name)
return res; 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;
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#ifndef SPI_H #ifndef SPI_H
#define SPI_H #define SPI_H
#include "commands/trigger.h"
#include "lib/ilist.h" #include "lib/ilist.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "utils/portal.h" #include "utils/portal.h"
...@@ -62,6 +63,7 @@ typedef struct _SPI_plan *SPIPlanPtr; ...@@ -62,6 +63,7 @@ typedef struct _SPI_plan *SPIPlanPtr;
#define SPI_OK_REWRITTEN 14 #define SPI_OK_REWRITTEN 14
#define SPI_OK_REL_REGISTER 15 #define SPI_OK_REL_REGISTER 15
#define SPI_OK_REL_UNREGISTER 16 #define SPI_OK_REL_UNREGISTER 16
#define SPI_OK_TD_REGISTER 17
/* These used to be functions, now just no-ops for backwards compatibility */ /* These used to be functions, now just no-ops for backwards compatibility */
#define SPI_push() ((void) 0) #define SPI_push() ((void) 0)
...@@ -152,6 +154,7 @@ extern void SPI_cursor_close(Portal portal); ...@@ -152,6 +154,7 @@ extern void SPI_cursor_close(Portal portal);
extern int SPI_register_relation(EphemeralNamedRelation enr); extern int SPI_register_relation(EphemeralNamedRelation enr);
extern int SPI_unregister_relation(const char *name); extern int SPI_unregister_relation(const char *name);
extern int SPI_register_trigger_data(TriggerData *tdata);
extern void AtEOXact_SPI(bool isCommit); extern void AtEOXact_SPI(bool isCommit);
extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid); extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid);
......
...@@ -241,6 +241,35 @@ $$ LANGUAGE plperl; ...@@ -241,6 +241,35 @@ $$ LANGUAGE plperl;
SELECT direct_trigger(); SELECT direct_trigger();
ERROR: trigger functions can only be called as triggers ERROR: trigger functions can only be called as triggers
CONTEXT: compilation of PL/Perl function "direct_trigger" 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 -- test plperl command triggers
create or replace function perlsnitch() returns event_trigger language plperl as $$ create or replace function perlsnitch() returns event_trigger language plperl as $$
elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " "); elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
......
...@@ -2442,11 +2442,18 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -2442,11 +2442,18 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
SV *svTD; SV *svTD;
HV *hvTD; HV *hvTD;
ErrorContextCallback pl_error_context; ErrorContextCallback pl_error_context;
TriggerData *tdata;
int rc PG_USED_FOR_ASSERTS_ONLY;
/* Connect to SPI manager */ /* Connect to SPI manager */
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager"); 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 */ /* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false); prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false);
current_call_data->prodesc = prodesc; current_call_data->prodesc = prodesc;
......
...@@ -170,6 +170,38 @@ $$ LANGUAGE plperl; ...@@ -170,6 +170,38 @@ $$ LANGUAGE plperl;
SELECT direct_trigger(); 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 -- test plperl command triggers
create or replace function perlsnitch() returns event_trigger language plperl as $$ create or replace function perlsnitch() returns event_trigger language plperl as $$
elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " "); elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
......
...@@ -689,46 +689,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func, ...@@ -689,46 +689,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
else else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE"); elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
/* /* Make transition tables visible to this SPI connection */
* Capture the NEW and OLD transition TABLE tuplestores (if specified for rc = SPI_register_trigger_data(trigdata);
* 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); 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);
}
}
/* /*
* Assign the special tg_ variables * Assign the special tg_ variables
...@@ -3483,9 +3446,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, ...@@ -3483,9 +3446,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->paramLI->paramMask = NULL; estate->paramLI->paramMask = NULL;
estate->params_dirty = false; estate->params_dirty = false;
/* default tuplestore cache to "none" */
estate->queryEnv = NULL;
/* set up for use of appropriate simple-expression EState and cast hash */ /* set up for use of appropriate simple-expression EState and cast hash */
if (simple_eval_estate) if (simple_eval_estate)
{ {
...@@ -7373,9 +7333,6 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, ...@@ -7373,9 +7333,6 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
/* Release transient data */ /* Release transient data */
MemoryContextReset(stmt_mcontext); MemoryContextReset(stmt_mcontext);
/* Make sure the portal knows about any named tuplestores. */
portal->queryEnv = estate->queryEnv;
return portal; return portal;
} }
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include "commands/event_trigger.h" #include "commands/event_trigger.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/spi.h" #include "executor/spi.h"
#include "utils/queryenvironment.h"
/********************************************************************** /**********************************************************************
* Definitions * Definitions
...@@ -911,9 +910,6 @@ typedef struct PLpgSQL_execstate ...@@ -911,9 +910,6 @@ typedef struct PLpgSQL_execstate
ParamListInfo paramLI; ParamListInfo paramLI;
bool params_dirty; /* T if any resettable datum has been passed */ 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 to use for "simple" expression evaluation */
EState *simple_eval_estate; EState *simple_eval_estate;
......
...@@ -503,3 +503,24 @@ SELECT * FROM b; ...@@ -503,3 +503,24 @@ SELECT * FROM b;
1234 1234
(1 row) (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();
...@@ -345,6 +345,11 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -345,6 +345,11 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
PG_TRY(); 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); plargs = PLy_trigger_build_args(fcinfo, proc, &rv);
plrv = PLy_procedure_call(proc, "TD", plargs); plrv = PLy_procedure_call(proc, "TD", plargs);
......
...@@ -406,3 +406,27 @@ SELECT * FROM a; ...@@ -406,3 +406,27 @@ SELECT * FROM a;
DROP TABLE a; DROP TABLE a;
INSERT INTO b DEFAULT VALUES; INSERT INTO b DEFAULT VALUES;
SELECT * FROM b; 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();
...@@ -660,3 +660,24 @@ select tcl_eval($$ ...@@ -660,3 +660,24 @@ select tcl_eval($$
(1 row) (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();
...@@ -1039,6 +1039,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, ...@@ -1039,6 +1039,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
const char *result; const char *result;
int result_Objc; int result_Objc;
Tcl_Obj **result_Objv; Tcl_Obj **result_Objv;
int rc PG_USED_FOR_ASSERTS_ONLY;
call_state->trigdata = trigdata; call_state->trigdata = trigdata;
...@@ -1046,6 +1047,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, ...@@ -1046,6 +1047,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager"); 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 */ /* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation), RelationGetRelid(trigdata->tg_relation),
......
...@@ -216,3 +216,23 @@ select tcl_eval($$ ...@@ -216,3 +216,23 @@ select tcl_eval($$
after 100 {set ::tcl_vwait 1} after 100 {set ::tcl_vwait 1}
vwait ::tcl_vwait vwait ::tcl_vwait
unset -nocomplain ::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();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment