From a06f4ecc0ff2fe149d4a6183f32f868c527405b5 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Mon, 3 Apr 2017 15:26:29 +1200
Subject: [PATCH] Support transition tables in PL/tcl triggers.

If a trigger is created with transition tables using the new syntax
REFERENCING OLD/NEW TABLE AS ..., allow SQL executed by a PL/tcl
trigger to see the transient tables.
---
 src/pl/tcl/expected/pltcl_queries.out | 21 +++++++++++++++++++++
 src/pl/tcl/pltcl.c                    | 32 ++++++++++++++++++++++++++++++++
 src/pl/tcl/sql/pltcl_queries.sql      | 20 ++++++++++++++++++++
 3 files changed, 73 insertions(+)

diff --git a/src/pl/tcl/expected/pltcl_queries.out b/src/pl/tcl/expected/pltcl_queries.out
index 7300b315d64..5f50f468878 100644
--- a/src/pl/tcl/expected/pltcl_queries.out
+++ b/src/pl/tcl/expected/pltcl_queries.out
@@ -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();
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index b8fcf0673d3..a6365560de0 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1046,6 +1046,38 @@ 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 */
+	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 = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_newtable;
+		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 = trigdata->tg_relation->rd_id;
+		enr->md.tupdesc = NULL;
+		enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+		enr->md.enrtuples = tuplestore_tuple_count(trigdata->tg_oldtable);
+		enr->reldata = trigdata->tg_oldtable;
+		rc = SPI_register_relation(enr);
+		Assert(rc >= 0);
+	}
+
 	/* Find or compile the function */
 	prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
 									 RelationGetRelid(trigdata->tg_relation),
diff --git a/src/pl/tcl/sql/pltcl_queries.sql b/src/pl/tcl/sql/pltcl_queries.sql
index 29ed616cd0b..dabd8cd35f0 100644
--- a/src/pl/tcl/sql/pltcl_queries.sql
+++ b/src/pl/tcl/sql/pltcl_queries.sql
@@ -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();
-- 
2.12.2

