<primary>CREATE TRIGGER</primary>
</indexterm>
+ <indexterm>
+ <primary>transition tables</primary>
+ <seealso>ephemeral named relation</seealso>
+ </indexterm>
+
<refmeta>
<refentrytitle>CREATE TRIGGER</refentrytitle>
<manvolnum>7</manvolnum>
<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>
<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>
<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>
<!-- *********************************************** -->
+<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">
<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
<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>
int16 *tgattr;
char **tgargs;
char *tgqual;
+ char *tgoldtable;
+ char *tgnewtable;
} Trigger;
</programlisting>
</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
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
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;
+}
#ifndef SPI_H
#define SPI_H
+#include "commands/trigger.h"
#include "lib/ilist.h"
#include "nodes/parsenodes.h"
#include "utils/portal.h"
#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)
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);
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} . " ");
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;
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} . " ");
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
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)
{
/* Release transient data */
MemoryContextReset(stmt_mcontext);
- /* Make sure the portal knows about any named tuplestores. */
- portal->queryEnv = estate->queryEnv;
-
return portal;
}
#include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
-#include "utils/queryenvironment.h"
/**********************************************************************
* Definitions
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;
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();
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);
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();
(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();
const char *result;
int result_Objc;
Tcl_Obj **result_Objv;
+ int rc PG_USED_FOR_ASSERTS_ONLY;
call_state->trigdata = trigdata;
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),
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();