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)
This commit is contained in:
parent
9a3215026b
commit
5ebeb579b9
@ -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</
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
|
|
||||||
<!-- *********************************************** -->
|
<!-- *********************************************** -->
|
||||||
|
|
||||||
|
<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 @@
|
|||||||
<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
|
|||||||
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
|
|||||||
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
|
|||||||
</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,
|
|||||||
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)
|
|||||||
|
|
||||||
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 @@
|
|||||||
#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;
|
|||||||
#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);
|
|||||||
|
|
||||||
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;
|
|||||||
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)
|
|||||||
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;
|
|||||||
|
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
/* 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 @@
|
|||||||
#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
|
|||||||
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;
|
|||||||
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)
|
|||||||
|
|
||||||
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;
|
|||||||
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($$
|
|||||||
|
|
||||||
(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,
|
|||||||
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,
|
|||||||
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($$
|
|||||||
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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user