diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 836ce08..a4ca54d 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -2639,6 +2639,310 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
  </refsect1>
 </refentry>
 
+<!-- *********************************************** -->
+
+<refentry id="spi-spi-register-relation">
+ <indexterm><primary>SPI_register_relation</primary></indexterm>
+
+ <refmeta>
+  <refentrytitle>SPI_register_relation</refentrytitle>
+  <manvolnum>3</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_register_relation</refname>
+  <refpurpose>make a tuplestore available by name in SPI queries</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+  int SPI_register_relation(const char *<parameter>name</parameter>,
+  TupleDesc <parameter>tupdesc</parameter>, int <parameter>tuples</parameter>,
+  Tuplestorestate *<parameter>tuplestore</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_register_relation</function> makes a tuplestore and
+   associated metadata available to queries planned and executed through the
+   current SPI connection.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>const char *<parameter>name</parameter></literal></term>
+    <listitem>
+     <para>
+       the name that SQL queries may use to refer to the relation being registered
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry>
+    <term><literal>TupleDesc <parameter>tupdesc</parameter></literal></term>
+    <listitem>
+     <para>
+       a TupleDesc describing the columns of the relation
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry>
+    <term><literal>int <parameter>tuples</parameter></literal></term>
+    <listitem>
+     <para>
+       an estimate of the number of tuples, for use by the planner
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry>
+    <term><literal>Tuplestorestate <parameter>tuplestore</parameter></literal></term>
+    <listitem>
+     <para>
+       a tuplestore
+     </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_REGISTER</symbol></term>
+     <listitem>
+      <para>
+       if the tuplestore has been successfully registered by name
+      </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 any argument 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_DUPLICATE</symbol></term>
+     <listitem>
+      <para>
+       if the name specified in the <varname>name</varname> argument
+       is already registered for this connection
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
+<refentry id="spi-spi-unregister-relation">
+ <indexterm><primary>SPI_unregister_relation</primary></indexterm>
+
+ <refmeta>
+  <refentrytitle>SPI_unregister_relation</refentrytitle>
+  <manvolnum>3</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_unregister_relation</refname>
+  <refpurpose>remove a named tuplestore from the registry</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+int SPI_unregister_tuplestore(const char * <parameter>name</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_unregister_relation</function> removes a tuplestore from
+   the registry for the current connection.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>const char * <parameter>name</parameter></literal></term>
+    <listitem>
+     <para>
+      the tuplestore registry entry name
+     </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_UNREGISTER</symbol></term>
+     <listitem>
+      <para>
+       if the tuplestore has been successfully removed from the registry
+      </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>name</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_NOT_FOUND</symbol></term>
+     <listitem>
+      <para>
+       if <parameter>name</parameter> is not found in the registry for the
+       current connection
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
+<refentry id="spi-spi-get-caller-tuplestore">
+ <indexterm><primary>SPI_get_caller_tuplestore</primary></indexterm>
+
+ <refmeta>
+  <refentrytitle>SPI_get_caller_tuplestore</refentrytitle>
+  <manvolnum>3</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_get_caller_tuplestore</refname>
+  <refpurpose>return a tuplestore from the registry</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+int SPI_get_caller_tuplestore(const char * <parameter>name</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_get_caller_tuplestore</function> returns a tuplestore from
+   the registry for the current connection.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>const char * <parameter>name</parameter></literal></term>
+    <listitem>
+     <para>
+      the tuplestore registry entry name
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   Pointer to the tuplestore registry entry; or <symbol>NULL</symbol> if
+   unsuccessful.  On error, <varname>SPI_result</varname> is set thus:
+
+   <variablelist>
+    <varlistentry>
+     <term><symbol>SPI_ERROR_ARGUMENT</symbol></term>
+     <listitem>
+      <para>
+       if <parameter>name</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>
+   </variablelist>
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
 </sect1>
 
 <sect1 id="spi-interface-support">
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index c1d1505..e3341c8 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -940,7 +940,8 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 				querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
 																  prosrc,
 									   (ParserSetupHook) sql_fn_parser_setup,
-																  pinfo);
+																  pinfo,
+																  NULL);
 				querytree_list = list_concat(querytree_list,
 											 querytree_sublist);
 			}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..2a3b497 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1425,7 +1425,8 @@ BeginCopy(ParseState *pstate,
 		 * DECLARE CURSOR and PREPARE.)  XXX FIXME someday.
 		 */
 		rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
-										   pstate->p_sourcetext, NULL, 0);
+										   pstate->p_sourcetext, NULL, 0,
+											NULL);
 
 		/* check that we got back something we can work with */
 		if (rewritten == NIL)
@@ -1528,7 +1529,7 @@ BeginCopy(ParseState *pstate,
 		cstate->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
 											GetActiveSnapshot(),
 											InvalidSnapshot,
-											dest, NULL, 0);
+											dest, NULL, NULL, 0);
 
 		/*
 		 * Call ExecutorStart to prepare the plan for execution.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..8ed0ff2 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -222,7 +222,8 @@ create_ctas_nodata(List *tlist, IntoClause *into)
  */
 ObjectAddress
 ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
-				  ParamListInfo params, char *completionTag)
+				  ParamListInfo params, QueryEnvironment *queryEnv,
+				  char *completionTag)
 {
 	Query	   *query = (Query *) stmt->query;
 	IntoClause *into = stmt->into;
@@ -342,7 +343,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		/* Create a QueryDesc, redirecting output to our tuple receiver */
 		queryDesc = CreateQueryDesc(plan, queryString,
 									GetActiveSnapshot(), InvalidSnapshot,
-									dest, params, 0);
+									dest, params, queryEnv, 0);
 
 		/* call ExecutorStart to prepare the plan for execution */
 		ExecutorStart(queryDesc, GetIntoRelEFlags(into));
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0a669d9..8f0cd74 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -54,7 +54,8 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
 #define X_NOWHITESPACE 4
 
 static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
-				const char *queryString, ParamListInfo params);
+				const char *queryString, ParamListInfo params,
+				QueryEnvironment *queryEnv);
 static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
 				ExplainState *es);
 static double elapsed_time(instr_time *starttime);
@@ -141,7 +142,8 @@ static void escape_yaml(StringInfo buf, const char *str);
  */
 void
 ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
-			 ParamListInfo params, DestReceiver *dest)
+			 ParamListInfo params, QueryEnvironment *queryEnv,
+			 DestReceiver *dest)
 {
 	ExplainState *es = NewExplainState();
 	TupOutputState *tstate;
@@ -246,7 +248,7 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
 		foreach(l, rewritten)
 		{
 			ExplainOneQuery((Query *) lfirst(l), NULL, es,
-							queryString, params);
+							queryString, params, queryEnv);
 
 			/* Separate plans with an appropriate separator */
 			if (lnext(l) != NULL)
@@ -330,12 +332,14 @@ ExplainResultDesc(ExplainStmt *stmt)
  */
 static void
 ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
-				const char *queryString, ParamListInfo params)
+				const char *queryString, ParamListInfo params,
+				QueryEnvironment *queryEnv)
 {
 	/* planner will not cope with utility statements */
 	if (query->commandType == CMD_UTILITY)
 	{
-		ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
+		ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
+						  queryEnv);
 		return;
 	}
 
@@ -357,7 +361,8 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
 		INSTR_TIME_SUBTRACT(planduration, planstart);
 
 		/* run it (if needed) and produce output */
-		ExplainOnePlan(plan, into, es, queryString, params, &planduration);
+		ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
+					   &planduration);
 	}
 }
 
@@ -374,7 +379,8 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
  */
 void
 ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
-				  const char *queryString, ParamListInfo params)
+				  const char *queryString, ParamListInfo params,
+				  QueryEnvironment *queryEnv)
 {
 	if (utilityStmt == NULL)
 		return;
@@ -393,11 +399,11 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		rewritten = QueryRewrite((Query *) copyObject(ctas->query));
 		Assert(list_length(rewritten) == 1);
 		ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
-						queryString, params);
+						queryString, params, queryEnv);
 	}
 	else if (IsA(utilityStmt, ExecuteStmt))
 		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
-							queryString, params);
+							queryString, params, queryEnv);
 	else if (IsA(utilityStmt, NotifyStmt))
 	{
 		if (es->format == EXPLAIN_FORMAT_TEXT)
@@ -435,7 +441,7 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 void
 ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 			   const char *queryString, ParamListInfo params,
-			   const instr_time *planduration)
+			   QueryEnvironment *queryEnv, const instr_time *planduration)
 {
 	DestReceiver *dest;
 	QueryDesc  *queryDesc;
@@ -478,7 +484,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 	/* Create a QueryDesc for the query */
 	queryDesc = CreateQueryDesc(plannedstmt, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, params, instrument_option);
+								dest, params, queryEnv, instrument_option);
 
 	/* Select execution options */
 	if (es->analyze)
@@ -762,6 +768,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_FunctionScan:
 		case T_ValuesScan:
 		case T_CteScan:
+		case T_SpiRelationScan:
 		case T_WorkTableScan:
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
@@ -908,6 +915,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_CteScan:
 			pname = sname = "CTE Scan";
 			break;
+		case T_SpiRelationScan:
+			pname = sname = "SPI Relation Scan";
+			break;
 		case T_WorkTableScan:
 			pname = sname = "WorkTable Scan";
 			break;
@@ -1341,6 +1351,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_SeqScan:
 		case T_ValuesScan:
 		case T_CteScan:
+		case T_SpiRelationScan:
 		case T_WorkTableScan:
 		case T_SubqueryScan:
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
@@ -2581,6 +2592,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 			objectname = rte->ctename;
 			objecttag = "CTE Name";
 			break;
+		case T_SpiRelationScan:
+			Assert(rte->rtekind == RTE_SPIRELATION);
+			objectname = rte->srname;
+			objecttag = "SPI Relation Name";
+			break;
 		case T_WorkTableScan:
 			/* Assert it's on a self-reference CTE */
 			Assert(rte->rtekind == RTE_CTE);
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index f6c2c8a..ec4625e 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -719,7 +719,8 @@ execute_sql_string(const char *sql, const char *filename)
 		stmt_list = pg_analyze_and_rewrite(parsetree,
 										   sql,
 										   NULL,
-										   0);
+										   0,
+										   NULL);
 		stmt_list = pg_plan_queries(stmt_list, CURSOR_OPT_PARALLEL_OK, NULL);
 
 		foreach(lc2, stmt_list)
@@ -743,7 +744,7 @@ execute_sql_string(const char *sql, const char *filename)
 				qdesc = CreateQueryDesc((PlannedStmt *) stmt,
 										sql,
 										GetActiveSnapshot(), NULL,
-										dest, NULL, 0);
+										dest, NULL, NULL, 0);
 
 				ExecutorStart(qdesc, 0);
 				ExecutorRun(qdesc, ForwardScanDirection, 0);
@@ -758,6 +759,7 @@ execute_sql_string(const char *sql, const char *filename)
 							   sql,
 							   PROCESS_UTILITY_QUERY,
 							   NULL,
+							   NULL,
 							   dest,
 							   NULL);
 			}
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index eb531af..92e8596 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -1596,7 +1596,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 			/* Execute statement */
 			ProcessUtility((Node *) cstmt,
 						   cmd,
-						   PROCESS_UTILITY_SUBCOMMAND, NULL,
+						   PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
 						   None_Receiver, NULL);
 
 			/* Be sure to advance the command counter between subcommands */
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 6cddcbd..0c4ad16 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -401,7 +401,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
 	queryDesc = CreateQueryDesc(plan, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, NULL, 0);
+								dest, NULL, NULL, 0);
 
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, EXEC_FLAG_WITHOUT_OIDS);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index cec37ce..70c7f49 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -52,7 +52,8 @@ static Datum build_regtype_array(Oid *param_types, int num_params);
  * Implements the 'PREPARE' utility statement.
  */
 void
-PrepareQuery(PrepareStmt *stmt, const char *queryString)
+PrepareQuery(PrepareStmt *stmt, const char *queryString,
+			 QueryEnvironment *queryEnv)
 {
 	CachedPlanSource *plansource;
 	Oid		   *argtypes = NULL;
@@ -114,7 +115,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 	 */
 	query = parse_analyze_varparams((Node *) copyObject(stmt->query),
 									queryString,
-									&argtypes, &nargs);
+									&argtypes, &nargs,
+									queryEnv);
 
 	/*
 	 * Check that all parameter types were determined.
@@ -620,7 +622,8 @@ DropAllPreparedStatements(void)
  */
 void
 ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
-					const char *queryString, ParamListInfo params)
+					const char *queryString, ParamListInfo params,
+					QueryEnvironment *queryEnv)
 {
 	PreparedStatement *entry;
 	const char *query_string;
@@ -665,9 +668,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 		PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
 
 		if (IsA(pstmt, PlannedStmt))
-			ExplainOnePlan(pstmt, into, es, query_string, paramLI, NULL);
+			ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv,
+						   NULL);
 		else
-			ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
+			ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI,
+							  queryEnv);
 
 		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
 
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index a60ceb8..5238271 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -178,6 +178,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 					   queryString,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
+					   NULL,
 					   None_Receiver,
 					   NULL);
 		/* make sure later steps can see the object created here */
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..0fcb5f3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1162,7 +1162,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
 		/* ... and execute it */
 		ProcessUtility((Node *) atstmt,
 					   "(generated ALTER TABLE ADD FOREIGN KEY command)",
-					   PROCESS_UTILITY_SUBCOMMAND, NULL,
+					   PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
 					   None_Receiver, NULL);
 
 		/* Remove the matched item from the list */
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..5f62809 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -400,7 +400,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * this ensures we don't corrupt a prepared statement, for example.
 	 */
 	viewParse = parse_analyze((Node *) copyObject(stmt->query),
-							  queryString, NULL, 0);
+							  queryString, NULL, 0, NULL);
 
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 51edd4c..c760271 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -24,6 +24,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
        nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
+       nodeSpirelationscan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
        nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 2587ef7..d5b57ce 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -45,6 +45,7 @@
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
 #include "executor/nodeSort.h"
+#include "executor/nodeSpirelationscan.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
 #include "executor/nodeTidscan.h"
@@ -202,6 +203,10 @@ ExecReScan(PlanState *node)
 			ExecReScanCteScan((CteScanState *) node);
 			break;
 
+		case T_SpiRelationScanState:
+			ExecReScanSpiRelationScan((SpiRelationScanState *) node);
+			break;
+
 		case T_WorkTableScanState:
 			ExecReScanWorkTableScan((WorkTableScanState *) node);
 			break;
@@ -581,6 +586,7 @@ ExecMaterializesOutput(NodeTag plantype)
 		case T_Material:
 		case T_FunctionScan:
 		case T_CteScan:
+		case T_SpiRelationScan:
 		case T_WorkTableScan:
 		case T_Sort:
 			return true;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9d5cedc 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -187,6 +187,9 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		estate->es_param_exec_vals = (ParamExecData *)
 			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
 
+	if (queryDesc->queryEnv)
+		estate->es_spi_relations = queryDesc->queryEnv->spi_relations;
+
 	/*
 	 * If non-read-only query, set the command ID to mark output tuples with
 	 */
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 5aa6f02..30758d6 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -627,7 +627,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
 	return CreateQueryDesc(pstmt,
 						   "<parallel query>",
 						   GetActiveSnapshot(), InvalidSnapshot,
-						   receiver, paramLI, instrument_options);
+						   receiver, paramLI, NULL, instrument_options);
 }
 
 /*
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 554244f..dc894a8 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -107,6 +107,7 @@
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
 #include "executor/nodeSort.h"
+#include "executor/nodeSpirelationscan.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
 #include "executor/nodeTidscan.h"
@@ -243,6 +244,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 												   estate, eflags);
 			break;
 
+		case T_SpiRelationScan:
+			result = (PlanState *) ExecInitSpiRelationScan((SpiRelationScan *) node,
+														   estate, eflags);
+			break;
+
 		case T_WorkTableScan:
 			result = (PlanState *) ExecInitWorkTableScan((WorkTableScan *) node,
 														 estate, eflags);
@@ -457,6 +463,10 @@ ExecProcNode(PlanState *node)
 			result = ExecCteScan((CteScanState *) node);
 			break;
 
+		case T_SpiRelationScanState:
+			result = ExecSpiRelationScan((SpiRelationScanState *) node);
+			break;
+
 		case T_WorkTableScanState:
 			result = ExecWorkTableScan((WorkTableScanState *) node);
 			break;
@@ -709,6 +719,10 @@ ExecEndNode(PlanState *node)
 			ExecEndCteScan((CteScanState *) node);
 			break;
 
+		case T_SpiRelationScanState:
+			ExecEndSpiRelationScan((SpiRelationScanState *) node);
+			break;
+
 		case T_WorkTableScanState:
 			ExecEndWorkTableScan((WorkTableScanState *) node);
 			break;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index a3bcb10..d809809 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -115,6 +115,8 @@ CreateExecutorState(void)
 	estate->es_param_list_info = NULL;
 	estate->es_param_exec_vals = NULL;
 
+	estate->es_spi_relations = NULL;
+
 	estate->es_query_cxt = qcontext;
 
 	estate->es_tupleTable = NIL;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 470db5b..2a199cd 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -701,7 +701,8 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 		queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
 														  fcache->src,
 									   (ParserSetupHook) sql_fn_parser_setup,
-														  fcache->pinfo);
+														  fcache->pinfo,
+														  NULL);
 		queryTree_list = lappend(queryTree_list, queryTree_sublist);
 		flat_query_list = list_concat(flat_query_list,
 									  list_copy(queryTree_sublist));
@@ -798,13 +799,16 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 								 GetActiveSnapshot(),
 								 InvalidSnapshot,
 								 dest,
-								 fcache->paramLI, 0);
+								 fcache->paramLI,
+								 es->qd ? es->qd->queryEnv : NULL,
+								 0);
 	else
 		es->qd = CreateUtilityQueryDesc(es->stmt,
 										fcache->src,
 										GetActiveSnapshot(),
 										dest,
-										fcache->paramLI);
+										fcache->paramLI,
+										es->qd ? es->qd->queryEnv : NULL);
 
 	/* Utility commands don't need Executor. */
 	if (es->qd->utilitystmt == NULL)
@@ -844,6 +848,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
 					   fcache->src,
 					   PROCESS_UTILITY_QUERY,
 					   es->qd->params,
+					   es->qd->queryEnv,
 					   es->qd->dest,
 					   NULL);
 		result = true;			/* never stops early */
diff --git a/src/backend/executor/nodeSpirelationscan.c b/src/backend/executor/nodeSpirelationscan.c
new file mode 100644
index 0000000..5e2c4bc
--- /dev/null
+++ b/src/backend/executor/nodeSpirelationscan.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSpirelationscan.c
+ *	  routines to handle SpiRelationScan nodes.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeSpirelationscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/execdebug.h"
+#include "executor/nodeSpirelationscan.h"
+#include "miscadmin.h"
+
+static TupleTableSlot *SpiRelationScanNext(SpiRelationScanState *node);
+
+/* ----------------------------------------------------------------
+ *		SpiRelationScanNext
+ *
+ *		This is a workhorse for ExecSpiRelationScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+SpiRelationScanNext(SpiRelationScanState *node)
+{
+	TupleTableSlot *slot;
+
+	/* We intentionally do not support backward scan. */
+	Assert(ScanDirectionIsForward(node->ss.ps.state->es_direction));
+
+	/*
+	 * Get the next tuple from tuplestore. Return NULL if no more tuples.
+	 */
+	slot = node->ss.ss_ScanTupleSlot;
+	tuplestore_select_read_pointer(node->table, node->readptr);
+	(void) tuplestore_gettupleslot(node->table, true, false, slot);
+	return slot;
+}
+
+/*
+ * SpiRelationScanRecheck -- access method routine to recheck a tuple in
+ * EvalPlanQual
+ */
+static bool
+SpiRelationScanRecheck(SpiRelationScanState *node, TupleTableSlot *slot)
+{
+	/* nothing to check */
+	return true;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecSpiRelationScan(node)
+ *
+ *		Scans the tuplestore sequentially and returns the next qualifying
+ *		tuple.  We call the ExecScan() routine and pass it the appropriate
+ *		access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecSpiRelationScan(SpiRelationScanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) SpiRelationScanNext,
+					(ExecScanRecheckMtd) SpiRelationScanRecheck);
+}
+
+
+/* ----------------------------------------------------------------
+ *		ExecInitSpiRelationScan
+ * ----------------------------------------------------------------
+ */
+SpiRelationScanState *
+ExecInitSpiRelationScan(SpiRelationScan *node, EState *estate, int eflags)
+{
+	SpiRelationScanState *scanstate;
+	ListCell *lc;
+
+	/* check for unsupported flags */
+	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+	/*
+	 * SpiRelationScan should not have any children.
+	 */
+	Assert(outerPlan(node) == NULL);
+	Assert(innerPlan(node) == NULL);
+
+	/*
+	 * create new SpiRelationScanState for node
+	 */
+	scanstate = makeNode(SpiRelationScanState);
+	scanstate->ss.ps.plan = (Plan *) node;
+	scanstate->ss.ps.state = estate;
+
+	/* Find the SpiRelation by name to get the tuplestore + TupleDesc. */
+	foreach (lc, estate->es_spi_relations)
+	{
+		SpiRelation *sr = (SpiRelation *) lfirst(lc);
+
+		if (strcmp(sr->name, node->srname) == 0)
+		{
+			scanstate->table = sr->tuplestore;
+			scanstate->tupdesc = sr->tupdesc;
+			break;
+		}
+	}
+
+	/*
+	 * We could fail to find it if you planned a query and then tried to run
+	 * it when the SPI relation was no longer registered.
+	 */
+	if (scanstate->table == NULL)
+		elog(ERROR, "cannot find SPI relation \"%s\"", node->srname);
+
+	scanstate->readptr = tuplestore_alloc_read_pointer(scanstate->table,
+													   EXEC_FLAG_REWIND);
+
+	tuplestore_select_read_pointer(scanstate->table, scanstate->readptr);
+
+	/*
+	 * The new read pointer copies its position from read pointer 0, which
+	 * could be anywhere, so explicitly rewind it.
+	 */
+	tuplestore_rescan(scanstate->table);
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	scanstate->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.targetlist,
+					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.qual,
+					 (PlanState *) scanstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+	ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+	/*
+	 * The scan tuple type is specified for the tuplestore.
+	 */
+	ExecAssignScanType(&scanstate->ss, scanstate->tupdesc);
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+	ExecAssignScanProjectionInfo(&scanstate->ss);
+
+	scanstate->ss.ps.ps_TupFromTlist = false;
+
+	return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndSpiRelationScan
+ *
+ *		frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndSpiRelationScan(SpiRelationScanState *node)
+{
+	/*
+	 * Free exprcontext
+	 */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/*
+	 * clean out the tuple table
+	 */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecReScanSpiRelationScan
+ *
+ *		Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanSpiRelationScan(SpiRelationScanState *node)
+{
+	Tuplestorestate *tuplestorestate = node->table;
+
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+
+	ExecScanReScan(&node->ss);
+
+	/*
+	 * Rewind my own pointer.
+	 */
+	tuplestore_select_read_pointer(tuplestorestate, node->readptr);
+	tuplestore_rescan(tuplestorestate);
+}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 80fc4c4..d465c9d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -122,6 +122,7 @@ SPI_connect(void)
 	_SPI_current->procCxt = NULL;		/* in case we fail to create 'em */
 	_SPI_current->execCxt = NULL;
 	_SPI_current->connectSubid = GetCurrentSubTransactionId();
+	_SPI_current->queryEnv = NULL;
 
 	/*
 	 * Create memory contexts for this procedure
@@ -1307,6 +1308,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 		MemoryContextSwitchTo(oldcontext);
 	}
 
+	/* Make the SPI connection's QueryEnvironment visible to this portal. */
+	portal->queryEnv = _SPI_current->queryEnv;
+
 	/*
 	 * Start portal execution.
 	 */
@@ -1532,6 +1536,10 @@ SPI_result_code_string(int code)
 			return "SPI_ERROR_NOOUTFUNC";
 		case SPI_ERROR_TYPUNKNOWN:
 			return "SPI_ERROR_TYPUNKNOWN";
+		case SPI_ERROR_DUPLICATE:
+			return "SPI_ERROR_DUPLICATE";
+		case SPI_ERROR_NOT_FOUND:
+			return "SPI_ERROR_NOT_FOUND";
 		case SPI_OK_CONNECT:
 			return "SPI_OK_CONNECT";
 		case SPI_OK_FINISH:
@@ -1560,6 +1568,10 @@ SPI_result_code_string(int code)
 			return "SPI_OK_UPDATE_RETURNING";
 		case SPI_OK_REWRITTEN:
 			return "SPI_OK_REWRITTEN";
+		case SPI_OK_REGISTER:
+			return "SPI_OK_REGISTER";
+		case SPI_OK_UNREGISTER:
+			return "SPI_OK_UNREGISTER";
 	}
 	/* Unrecognized code ... return something useful ... */
 	sprintf(buf, "Unrecognized SPI code %d", code);
@@ -1779,14 +1791,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
 			stmt_list = pg_analyze_and_rewrite_params(parsetree,
 													  src,
 													  plan->parserSetup,
-													  plan->parserSetupArg);
+													  plan->parserSetupArg,
+													  _SPI_current->queryEnv);
 		}
 		else
 		{
 			stmt_list = pg_analyze_and_rewrite(parsetree,
 											   src,
 											   plan->argtypes,
-											   plan->nargs);
+											   plan->nargs,
+											   _SPI_current->queryEnv);
 		}
 
 		/* Finish filling in the CachedPlanSource */
@@ -1975,14 +1989,16 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 				stmt_list = pg_analyze_and_rewrite_params(parsetree,
 														  src,
 														  plan->parserSetup,
-													   plan->parserSetupArg);
+													   plan->parserSetupArg,
+														  _SPI_current->queryEnv);
 			}
 			else
 			{
 				stmt_list = pg_analyze_and_rewrite(parsetree,
 												   src,
 												   plan->argtypes,
-												   plan->nargs);
+												   plan->nargs,
+												   _SPI_current->queryEnv);
 			}
 
 			/* Finish filling in the CachedPlanSource */
@@ -2089,7 +2105,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 										plansource->query_string,
 										snap, crosscheck_snapshot,
 										dest,
-										paramLI, 0);
+										paramLI,
+										_SPI_current->queryEnv,
+										0);
 				res = _SPI_pquery(qdesc, fire_triggers,
 								  canSetTag ? tcount : 0);
 				FreeQueryDesc(qdesc);
@@ -2102,6 +2120,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 							   plansource->query_string,
 							   PROCESS_UTILITY_QUERY,
 							   paramLI,
+							   _SPI_current->queryEnv,
 							   dest,
 							   completionTag);
 
@@ -2628,3 +2647,105 @@ _SPI_save_plan(SPIPlanPtr plan)
 
 	return newplan;
 }
+
+/*
+ * Register a named tuplestore for use by the planner and executor on
+ * subsequent calls using this SPI connection.
+ */
+int
+SPI_register_relation(const char *name, TupleDesc tupdesc,
+					  int tuples, Tuplestorestate *tuplestore)
+{
+	MemoryContext oldcxt;
+	SpiRelation *sr = NULL;
+	ListCell *lc;
+	int res;
+
+	if (name == NULL || tupdesc == NULL || tuplestore == NULL)
+		return SPI_ERROR_ARGUMENT;
+
+	/* Make sure we are connected. */
+	res = _SPI_begin_call(false);
+	if (res < 0)
+		return res;
+
+	/* Use procedure-duration memory for QueryEnvironment and contents. */
+	oldcxt = _SPI_procmem();
+
+	/* Create a QueryEnvironment if we don't have one already. */
+	if (_SPI_current->queryEnv == NULL)
+		_SPI_current->queryEnv =
+			(QueryEnvironment *) palloc0(sizeof(QueryEnvironment));
+
+	/* Check if this name is already registered. */
+	foreach(lc, _SPI_current->queryEnv->spi_relations)
+	{
+		sr = (SpiRelation *) lfirst(lc);
+
+		if (strcmp(sr->name, name) == 0)
+			break;
+		sr = NULL;
+	}
+
+	/* Register if we didn't find it. */
+	if (sr == NULL)
+	{
+		sr = (SpiRelation *) palloc0(sizeof(SpiRelation));
+		sr->name = pstrdup(name);
+		sr->tupdesc = CreateTupleDescCopy(tupdesc);
+		sr->tuples = tuples;
+		sr->tuplestore = tuplestore;
+		_SPI_current->queryEnv->spi_relations =
+			lappend(_SPI_current->queryEnv->spi_relations, sr);
+		res = SPI_OK_REGISTER;
+	}
+	else
+		res = SPI_ERROR_DUPLICATE;
+
+	_SPI_end_call(false);
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return res;
+}
+
+/*
+ *  Unregister a named tuplestore by name.  This will probably be a rarely used
+ * function, since SPI_finish will clear it automatically.
+ */
+int
+SPI_unregister_relation(const char *name)
+{
+	ListCell *prev = NULL;
+	ListCell *lc;
+	int res = SPI_ERROR_NOT_FOUND;
+
+	/* Make sure we are connected. */
+	res = _SPI_begin_call(false);
+	if (res < 0)
+		return res;
+
+	/* Search for the relation by name. */
+	if (_SPI_current->queryEnv != NULL)
+	{
+		foreach (lc, _SPI_current->queryEnv->spi_relations)
+		{
+			SpiRelation *sr;
+
+			sr = (SpiRelation *) lfirst(lc);
+			if (strcmp(sr->name, name) == 0)
+			{
+				_SPI_current->queryEnv->spi_relations =
+					list_delete_cell(_SPI_current->queryEnv->spi_relations,
+									 lc, prev);
+				res = SPI_OK_UNREGISTER;
+				break;
+			}
+			prev = lc;
+		}
+	}
+
+	_SPI_end_call(false);
+
+	return res;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..04dfb81 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -613,6 +613,27 @@ _copyCteScan(const CteScan *from)
 }
 
 /*
+ * _copySpiRelationScan
+ */
+static SpiRelationScan *
+_copySpiRelationScan(const SpiRelationScan *from)
+{
+	SpiRelationScan    *newnode = makeNode(SpiRelationScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_STRING_FIELD(srname);
+
+	return newnode;
+}
+
+/*
  * _copyWorkTableScan
  */
 static WorkTableScan *
@@ -2156,6 +2177,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(ctecoltypes);
 	COPY_NODE_FIELD(ctecoltypmods);
 	COPY_NODE_FIELD(ctecolcollations);
+	COPY_STRING_FIELD(srname);
 	COPY_NODE_FIELD(alias);
 	COPY_NODE_FIELD(eref);
 	COPY_SCALAR_FIELD(lateral);
@@ -4389,6 +4411,9 @@ copyObject(const void *from)
 		case T_CteScan:
 			retval = _copyCteScan(from);
 			break;
+		case T_SpiRelationScan:
+			retval = _copySpiRelationScan(from);
+			break;
 		case T_WorkTableScan:
 			retval = _copyWorkTableScan(from);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..b4a25c9 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2302,6 +2302,7 @@ range_table_walker(List *rtable,
 					return true;
 				break;
 			case RTE_CTE:
+			case RTE_SPIRELATION:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -3098,6 +3099,7 @@ range_table_mutator(List *rtable,
 				/* we don't bother to copy eref, aliases, etc; OK? */
 				break;
 			case RTE_CTE:
+			case RTE_SPIRELATION:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..9e923ae 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -577,6 +577,16 @@ _outCteScan(StringInfo str, const CteScan *node)
 }
 
 static void
+_outSpiRelationScan(StringInfo str, const SpiRelationScan *node)
+{
+	WRITE_NODE_TYPE("SPIRELATIONSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+
+	WRITE_STRING_FIELD(srname);
+}
+
+static void
 _outWorkTableScan(StringInfo str, const WorkTableScan *node)
 {
 	WRITE_NODE_TYPE("WORKTABLESCAN");
@@ -2849,6 +2859,13 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(ctecoltypmods);
 			WRITE_NODE_FIELD(ctecolcollations);
 			break;
+		case RTE_SPIRELATION:
+			WRITE_STRING_FIELD(srname);
+			WRITE_OID_FIELD(relid);
+			WRITE_NODE_FIELD(ctecoltypes);
+			WRITE_NODE_FIELD(ctecoltypmods);
+			WRITE_NODE_FIELD(ctecolcollations);
+			break;
 		default:
 			elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
 			break;
@@ -3368,6 +3385,9 @@ outNode(StringInfo str, const void *obj)
 			case T_CteScan:
 				_outCteScan(str, obj);
 				break;
+			case T_SpiRelationScan:
+				_outSpiRelationScan(str, obj);
+				break;
 			case T_WorkTableScan:
 				_outWorkTableScan(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index a1f2941..53a6164 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -287,6 +287,10 @@ print_rt(const List *rtable)
 				printf("%d\t%s\t[cte]",
 					   i, rte->eref->aliasname);
 				break;
+			case RTE_SPIRELATION:
+				printf("%d\t%s\t[spirelation]",
+					   i, rte->eref->aliasname);
+				break;
 			default:
 				printf("%d\t%s\t[unknown rtekind]",
 					   i, rte->eref->aliasname);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..3eca76f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1324,6 +1324,13 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(ctecoltypmods);
 			READ_NODE_FIELD(ctecolcollations);
 			break;
+		case RTE_SPIRELATION:
+			READ_STRING_FIELD(srname);
+			READ_OID_FIELD(relid);
+			READ_NODE_FIELD(ctecoltypes);
+			READ_NODE_FIELD(ctecoltypmods);
+			READ_NODE_FIELD(ctecolcollations);
+			break;
 		default:
 			elog(ERROR, "unrecognized RTE kind: %d",
 				 (int) local_node->rtekind);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index e42ef98..6e9b112 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -107,6 +107,8 @@ static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					RangeTblEntry *rte);
 static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				 RangeTblEntry *rte);
+static void set_spirelation_pathlist(PlannerInfo *root, RelOptInfo *rel,
+				 RangeTblEntry *rte);
 static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					   RangeTblEntry *rte);
 static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
@@ -379,6 +381,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				else
 					set_cte_pathlist(root, rel, rte);
 				break;
+			case RTE_SPIRELATION:
+				set_spirelation_pathlist(root, rel, rte);
+				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
@@ -443,6 +448,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 			case RTE_CTE:
 				/* CTE reference --- fully handled during set_rel_size */
 				break;
+			case RTE_SPIRELATION:
+				/* SPI relation reference --- fully handled during set_rel_size */
+				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
@@ -614,6 +622,13 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * executed only once.
 			 */
 			return;
+
+		case RTE_SPIRELATION:
+			/*
+			 * SPI relation tuplestores cannot be shared, at least without
+			 * more infrastructure to support that.
+			 */
+			return;
 	}
 
 	/*
@@ -1948,6 +1963,35 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 }
 
 /*
+ * set_spirelation_pathlist
+ *		Build the (single) access path for an SPI relation RTE
+ *
+ * There's no need for a separate set_spirelation_size phase, since we don't
+ * support join-qual-parameterized paths for SPI relations.
+ */
+static void
+set_spirelation_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+	Relids		required_outer;
+
+	/* Mark rel with estimated output rows, width, etc */
+	set_spirelation_size_estimates(root, rel);
+
+	/*
+	 * We don't support pushing join clauses into the quals of an SPI relation
+	 * scan, but it could still have required parameterization due to LATERAL
+	 * refs in its tlist.
+	 */
+	required_outer = rel->lateral_relids;
+
+	/* Generate appropriate path */
+	add_path(rel, create_spirelationscan_path(root, rel, required_outer));
+
+	/* Select cheapest path (pretty easy in this case...) */
+	set_cheapest(rel);
+}
+
+/*
  * set_worktable_pathlist
  *		Build the (single) access path for a self-reference CTE RTE
  *
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e42895d..680e383 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1383,6 +1383,44 @@ cost_ctescan(Path *path, PlannerInfo *root,
 }
 
 /*
+ * cost_spirelationscan
+ *	  Determines and returns the cost of scanning a tuplestore supplied
+ *	  by SPI client code.
+ */
+void
+cost_spirelationscan(Path *path, PlannerInfo *root,
+					 RelOptInfo *baserel, ParamPathInfo *param_info)
+{
+	Cost		startup_cost = 0;
+	Cost		run_cost = 0;
+	QualCost	qpqual_cost;
+	Cost		cpu_per_tuple;
+
+	/* Should only be applied to base relations that are SPI relations */
+	Assert(baserel->relid > 0);
+	Assert(baserel->rtekind == RTE_SPIRELATION);
+
+	/* Mark the path with the correct row estimate */
+	if (param_info)
+		path->rows = param_info->ppi_rows;
+	else
+		path->rows = baserel->rows;
+
+	/* Charge one CPU tuple cost per row for tuplestore manipulation */
+	cpu_per_tuple = cpu_tuple_cost;
+
+	/* Add scanning CPU costs */
+	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+	startup_cost += qpqual_cost.startup;
+	cpu_per_tuple += cpu_tuple_cost + qpqual_cost.per_tuple;
+	run_cost += cpu_per_tuple * baserel->tuples;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
  * cost_recursive_union
  *	  Determines and returns the cost of performing a recursive union,
  *	  and also the estimated output size.
@@ -4463,6 +4501,38 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows)
 }
 
 /*
+ * set_spirelation_size_estimates
+ *		Set the size estimates for a base relation that is a SPI relation
+ *		reference.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already.
+ *
+ * We set the same fields as set_baserel_size_estimates.
+ */
+void
+set_spirelation_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+
+	/* Should only be applied to base relations that are SPI relation refs */
+	Assert(rel->relid > 0);
+	rte = planner_rt_fetch(rel->relid, root);
+	Assert(rte->rtekind == RTE_SPIRELATION);
+
+	/*
+	 * Use the esimate provided by the SPI client that provided this SPI
+	 * relation.  We could instead ask the tuplestore, but since the resulting
+	 * plan may be reused for later queries, it seems better to let the user
+	 * control the estimate.
+	 */
+	rel->tuples = rte->srtuples;
+
+	/* Now estimate number of output rows, etc */
+	set_baserel_size_estimates(root, rel);
+}
+
+/*
  * set_foreign_size_estimates
  *		Set the size estimates for a base relation that is a foreign table.
  *
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..7c94e21 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -135,6 +135,8 @@ static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
 					   List *tlist, List *scan_clauses);
 static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
 					List *tlist, List *scan_clauses);
+static SpiRelationScan *create_spirelationscan_plan(PlannerInfo *root, Path *best_path,
+					List *tlist, List *scan_clauses);
 static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
 						  List *tlist, List *scan_clauses);
 static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
@@ -191,6 +193,8 @@ static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
 				Index scanrelid, List *values_lists);
 static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
+static SpiRelationScan *make_spirelationscan(List *qptlist, List *qpqual,
+			 Index scanrelid, char *srname);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 				   Index scanrelid, int wtParam);
 static Append *make_append(List *appendplans, List *tlist);
@@ -356,6 +360,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
+		case T_SpiRelationScan:
 		case T_ForeignScan:
 		case T_CustomScan:
 			plan = create_scan_plan(root, best_path, flags);
@@ -644,6 +649,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 												scan_clauses);
 			break;
 
+		case T_SpiRelationScan:
+			plan = (Plan *) create_spirelationscan_plan(root,
+														best_path,
+														tlist,
+														scan_clauses);
+			break;
+
 		case T_WorkTableScan:
 			plan = (Plan *) create_worktablescan_plan(root,
 													  best_path,
@@ -3124,6 +3136,45 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
 }
 
 /*
+ * create_spirelationscan_plan
+ *	 Returns an SpiRelationScan plan for the base relation scanned by
+ *	'best_path' with restriction clauses 'scan_clauses' and targetlist
+ *	'tlist'.
+ */
+static SpiRelationScan *
+create_spirelationscan_plan(PlannerInfo *root, Path *best_path,
+							List *tlist, List *scan_clauses)
+{
+	SpiRelationScan *scan_plan;
+	Index		scan_relid = best_path->parent->relid;
+	RangeTblEntry *rte;
+
+	Assert(scan_relid > 0);
+	rte = planner_rt_fetch(scan_relid, root);
+	Assert(rte->rtekind == RTE_SPIRELATION);
+
+	/* Sort clauses into best execution order */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/* Replace any outer-relation variables with nestloop params */
+	if (best_path->param_info)
+	{
+		scan_clauses = (List *)
+			replace_nestloop_params(root, (Node *) scan_clauses);
+	}
+
+	scan_plan = make_spirelationscan(tlist, scan_clauses, scan_relid,
+									 rte->srname);
+
+	copy_generic_path_info(&scan_plan->scan.plan, best_path);
+
+	return scan_plan;
+}
+
+/*
  * create_worktablescan_plan
  *	 Returns a worktablescan plan for the base relation scanned by 'best_path'
  *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
@@ -4887,6 +4938,26 @@ make_ctescan(List *qptlist,
 	return node;
 }
 
+static SpiRelationScan *
+make_spirelationscan(List *qptlist,
+					 List *qpqual,
+					 Index scanrelid,
+					 char *srname)
+{
+	SpiRelationScan *node = makeNode(SpiRelationScan);
+	Plan	   *plan = &node->scan.plan;
+
+	/* cost should be inserted by caller */
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->srname = srname;
+
+	return node;
+}
+
 static WorkTableScan *
 make_worktablescan(List *qptlist,
 				   List *qpqual,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d91bc3b..935af31 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -580,6 +580,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->scan.plan.qual, rtoffset);
 			}
 			break;
+		case T_SpiRelationScan:
+			{
+				SpiRelationScan *splan = (SpiRelationScan *) plan;
+
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+				splan->scan.plan.qual =
+					fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+			}
+			break;
 		case T_WorkTableScan:
 			{
 				WorkTableScan *splan = (WorkTableScan *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 263ba45..bd51c7b 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2459,6 +2459,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_SpiRelationScan:
+			context.paramids = bms_add_members(context.paramids, scan_params);
+			break;
+
 		case T_ForeignScan:
 			{
 				ForeignScan *fscan = (ForeignScan *) plan;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index b681230..114c319 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1121,6 +1121,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 					break;
 				case RTE_JOIN:
 				case RTE_CTE:
+				case RTE_SPIRELATION:
 					/* these can't contain any lateral references */
 					break;
 			}
@@ -1973,6 +1974,7 @@ replace_vars_in_jointree(Node *jtnode,
 						break;
 					case RTE_JOIN:
 					case RTE_CTE:
+					case RTE_SPIRELATION:
 						/* these shouldn't be marked LATERAL */
 						Assert(false);
 						break;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 1688310..8c336a2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4974,7 +4974,8 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	querytree_list = pg_analyze_and_rewrite_params(linitial(raw_parsetree_list),
 												   src,
 									   (ParserSetupHook) sql_fn_parser_setup,
-												   pinfo);
+												   pinfo,
+												   NULL);
 	if (list_length(querytree_list) != 1)
 		goto fail;
 	querytree = linitial(querytree_list);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6d3ccfd..190c432 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1805,6 +1805,31 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
 }
 
 /*
+ * create_spirelationscan_path
+ *	  Creates a path corresponding to a scan of tuples supplied by SPI code,
+ *    returning the pathnode.
+ */
+Path *
+create_spirelationscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
+{
+	Path	   *pathnode = makeNode(Path);
+
+	pathnode->pathtype = T_SpiRelationScan;
+	pathnode->parent = rel;
+	pathnode->pathtarget = rel->reltarget;
+	pathnode->param_info = get_baserel_parampathinfo(root, rel,
+													 required_outer);
+	pathnode->parallel_aware = false;
+	pathnode->parallel_safe = rel->consider_parallel;
+	pathnode->parallel_workers = 0;
+	pathnode->pathkeys = NIL;	/* result is always unordered */
+
+	cost_spirelationscan(pathnode, root, rel, pathnode->param_info);
+
+	return pathnode;
+}
+
+/*
  * create_worktablescan_path
  *	  Creates a path corresponding to a scan of a self-reference CTE,
  *	  returning the pathnode.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..f5935c1 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1360,7 +1360,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
  *
  * We also support building a "physical" tlist for subqueries, functions,
  * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * SubqueryScan, FunctionScan, ValuesScan, CteScan, SpiRelationScan, and
+ * WorkTableScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -1434,6 +1435,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 		case RTE_FUNCTION:
 		case RTE_VALUES:
 		case RTE_CTE:
+		case RTE_SPIRELATION:
 			/* Not all of these can have dropped cols, but share code anyway */
 			expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
 					  NULL, &colvars);
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index deef560..16aadc7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -149,6 +149,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 		case RTE_FUNCTION:
 		case RTE_VALUES:
 		case RTE_CTE:
+		case RTE_SPIRELATION:
 
 			/*
 			 * Subquery, function, or values list --- set up attr range and
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..79c4644 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -93,7 +93,8 @@ static bool test_raw_expression_coverage(Node *node, void *context);
  */
 Query *
 parse_analyze(Node *parseTree, const char *sourceText,
-			  Oid *paramTypes, int numParams)
+			  Oid *paramTypes, int numParams,
+			  QueryEnvironment *queryEnv)
 {
 	ParseState *pstate = make_parsestate(NULL);
 	Query	   *query;
@@ -105,6 +106,9 @@ parse_analyze(Node *parseTree, const char *sourceText,
 	if (numParams > 0)
 		parse_fixed_parameters(pstate, paramTypes, numParams);
 
+	if (queryEnv)
+		pstate->p_spirelationnamespace = queryEnv->spi_relations;
+
 	query = transformTopLevelStmt(pstate, parseTree);
 
 	if (post_parse_analyze_hook)
@@ -124,7 +128,8 @@ parse_analyze(Node *parseTree, const char *sourceText,
  */
 Query *
 parse_analyze_varparams(Node *parseTree, const char *sourceText,
-						Oid **paramTypes, int *numParams)
+						Oid **paramTypes, int *numParams,
+						QueryEnvironment *queryEnv)
 {
 	ParseState *pstate = make_parsestate(NULL);
 	Query	   *query;
@@ -135,6 +140,9 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
 
 	parse_variable_parameters(pstate, paramTypes, numParams);
 
+	if (queryEnv)
+		pstate->p_spirelationnamespace = queryEnv->spi_relations;
+
 	query = transformTopLevelStmt(pstate, parseTree);
 
 	/* make sure all is well with parameter types */
@@ -2716,6 +2724,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 									  LCS_asString(lc->strength)),
 							 parser_errposition(pstate, thisrel->location)));
 							break;
+						case RTE_SPIRELATION:
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							/*------
+							  translator: %s is a SQL row locking clause such as FOR UPDATE */
+							   errmsg("%s cannot be applied to an SPI relation",
+									  LCS_asString(lc->strength)),
+							 parser_errposition(pstate, thisrel->location)));
+							break;
 						default:
 							elog(ERROR, "unrecognized RTE type: %d",
 								 (int) rte->rtekind);
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..67bedb4 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -458,6 +458,21 @@ transformCTEReference(ParseState *pstate, RangeVar *r,
 }
 
 /*
+ * transformSpiRelationReference --- transform a RangeVar that references an
+ * SPI relation
+ */
+static RangeTblEntry *
+transformSpiRelationReference(ParseState *pstate, RangeVar *r,
+							  SpiRelation *sr)
+{
+	RangeTblEntry *rte;
+
+	rte = addRangeTableEntryForSpiRelation(pstate, sr, r, true);
+
+	return rte;
+}
+
+/*
  * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
  */
 static RangeTblEntry *
@@ -850,7 +865,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 {
 	if (IsA(n, RangeVar))
 	{
-		/* Plain relation reference, or perhaps a CTE reference */
+		/* Plain relation reference, or perhaps a CTE or SPI reference */
 		RangeVar   *rv = (RangeVar *) n;
 		RangeTblRef *rtr;
 		RangeTblEntry *rte = NULL;
@@ -867,7 +882,18 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				rte = transformCTEReference(pstate, rv, cte, levelsup);
 		}
 
-		/* if not found as a CTE, must be a table reference */
+		/* or it might be an SPI relation reference */
+		if (!rte && !rv->schemaname)
+		{
+			SpiRelation *sr;
+
+			sr = scanNameSpaceForSpiRelation(pstate, rv->relname);
+
+			if (sr)
+				rte = transformSpiRelationReference(pstate, rv, sr);
+		}
+
+		/* if not found above, must be a table reference */
 		if (!rte)
 			rte = transformTableEntry(pstate, rv);
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 1e3ecbc..3fa2d56 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -281,6 +281,27 @@ isFutureCTE(ParseState *pstate, const char *refname)
 }
 
 /*
+ * Search for an SPI relation.
+ */
+SpiRelation *
+scanNameSpaceForSpiRelation(ParseState *pstate, const char *refname)
+{
+	for (; pstate != NULL; pstate = pstate->parentParseState)
+	{
+		ListCell   *lc;
+
+		foreach(lc, pstate->p_spirelationnamespace)
+		{
+			SpiRelation *sr = (SpiRelation *) lfirst(lc);
+
+			if (strcmp(sr->name, refname) == 0)
+				return sr;
+		}
+	}
+	return NULL;
+}
+
+/*
  * searchRangeTableForRel
  *	  See if any RangeTblEntry could possibly match the RangeVar.
  *	  If so, return a pointer to the RangeTblEntry; else return NULL.
@@ -301,6 +322,7 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
 	const char *refname = relation->relname;
 	Oid			relId = InvalidOid;
 	CommonTableExpr *cte = NULL;
+	bool		is_spi_relation = false;
 	Index		ctelevelsup = 0;
 	Index		levelsup;
 
@@ -317,8 +339,14 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
 	 * unlocked.
 	 */
 	if (!relation->schemaname)
+	{
 		cte = scanNameSpaceForCTE(pstate, refname, &ctelevelsup);
-	if (!cte)
+		if (!cte)
+			is_spi_relation =
+				scanNameSpaceForSpiRelation(pstate, refname) != NULL;
+	}
+
+	if (!cte && !is_spi_relation)
 		relId = RangeVarGetRelid(relation, NoLock, true);
 
 	/* Now look for RTEs matching either the relation/CTE or the alias */
@@ -341,6 +369,9 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
 				rte->ctelevelsup + levelsup == ctelevelsup &&
 				strcmp(rte->ctename, refname) == 0)
 				return rte;
+			if (rte->rtekind == RTE_SPIRELATION &&
+				strcmp(rte->srname, refname) == 0)
+				return rte;
 			if (strcmp(rte->eref->aliasname, refname) == 0)
 				return rte;
 		}
@@ -1873,6 +1904,81 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	return rte;
 }
 
+/*
+ * Add an entry for an SPI relation reference to the pstate's range
+ * table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a RTE
+ * representing data to be supplied by a client of the SPI interface,
+ * including PLs such as plpgsql.
+ */
+RangeTblEntry *
+addRangeTableEntryForSpiRelation(ParseState *pstate,
+								 SpiRelation *sr,
+								 RangeVar *rv,
+								 bool inFromCl)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	Alias	   *alias = rv->alias;
+	char	   *refname = alias ? alias->aliasname : rv->relname;
+	int			attno;
+	TupleDesc	tupdesc = sr->tupdesc;
+
+	Assert(tupdesc != NULL);
+
+	rte->rtekind = RTE_SPIRELATION;
+
+	/*
+	 * Build the list of effective column names using user-supplied aliases
+	 * and/or actual column names.  Also build the cannibalized fields.
+	 */
+	rte->eref = makeAlias(refname, NIL);
+	buildRelationAliases(tupdesc, alias, rte->eref);
+	rte->srname = rv->relname;
+	rte->srtuples = sr->tuples;
+
+	rte->ctecoltypes = NIL;
+	rte->ctecoltypmods = NIL;
+	rte->ctecolcollations = NIL;
+	for (attno = 1; attno <= tupdesc->natts; ++attno)
+	{
+		if (tupdesc->attrs[attno - 1]->atttypid == InvalidOid &&
+			!(tupdesc->attrs[attno - 1]->attisdropped))
+			elog(ERROR, "atttypid was invalid for column which has not been dropped from \"%s\"",
+				 rv->relname);
+		rte->ctecoltypes =
+			lappend_oid(rte->ctecoltypes,
+						tupdesc->attrs[attno - 1]->atttypid);
+		rte->ctecoltypmods =
+			lappend_int(rte->ctecoltypmods,
+						tupdesc->attrs[attno - 1]->atttypmod);
+		rte->ctecolcollations =
+			lappend_oid(rte->ctecolcollations,
+						tupdesc->attrs[attno - 1]->attcollation);
+	}
+
+	/*
+	 * Set flags and access permissions.
+	 *
+	 * Tuplestores are never checked for access rights.
+	 */
+	rte->lateral = false;
+	rte->inh = false;			/* never true for tuplestores */
+	rte->inFromCl = inFromCl;
+
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+
+	/*
+	 * Add completed RTE to pstate's range table list, but not to join list
+	 * nor namespace --- caller must do that if appropriate.
+	 */
+	if (pstate != NULL)
+		pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+	return rte;
+}
 
 /*
  * Has the specified refname been selected FOR UPDATE/FOR SHARE?
@@ -2263,6 +2369,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 			}
 			break;
 		case RTE_CTE:
+		case RTE_SPIRELATION:
 			{
 				ListCell   *aliasp_item = list_head(rte->eref->colnames);
 				ListCell   *lct;
@@ -2686,8 +2793,10 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 			}
 			break;
 		case RTE_CTE:
+		case RTE_SPIRELATION:
 			{
 				/* CTE RTE --- get type info from lists in the RTE */
+				/* Also used by SPI relation RTE */
 				Assert(attnum > 0 && attnum <= list_length(rte->ctecoltypes));
 				*vartype = list_nth_oid(rte->ctecoltypes, attnum - 1);
 				*vartypmod = list_nth_int(rte->ctecoltypmods, attnum - 1);
@@ -2735,6 +2844,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 			/* Subselect, Values, CTE RTEs never have dropped columns */
 			result = false;
 			break;
+		case RTE_SPIRELATION:
+			/*
+			 * We checked when we loaded ctecoltypes for the tuplestore
+			 * that InvalidOid was only used for dropped columns, so it is
+			 * safe to count on that here.
+			 */
+			Assert(rte->srname != NULL);
+			result = (list_nth(rte->ctecoltypes, attnum - 1) != InvalidOid);
+			break;
 		case RTE_JOIN:
 			{
 				/*
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a76c33f..b836097 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -347,6 +347,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			break;
 		case RTE_FUNCTION:
 		case RTE_VALUES:
+		case RTE_SPIRELATION:
 			/* not a simple relation, leave it unmarked */
 			break;
 		case RTE_CTE:
@@ -1455,6 +1456,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 	{
 		case RTE_RELATION:
 		case RTE_VALUES:
+		case RTE_SPIRELATION:
 
 			/*
 			 * This case should not occur: a column of a table or values list
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cc84754..d096cdd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -642,7 +642,8 @@ pg_parse_query(const char *query_string)
  */
 List *
 pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
-					   Oid *paramTypes, int numParams)
+					   Oid *paramTypes, int numParams,
+					   QueryEnvironment *queryEnv)
 {
 	Query	   *query;
 	List	   *querytree_list;
@@ -655,7 +656,8 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
 	if (log_parser_stats)
 		ResetUsage();
 
-	query = parse_analyze(parsetree, query_string, paramTypes, numParams);
+	query = parse_analyze(parsetree, query_string, paramTypes, numParams,
+						  queryEnv);
 
 	if (log_parser_stats)
 		ShowUsage("PARSE ANALYSIS STATISTICS");
@@ -679,7 +681,8 @@ List *
 pg_analyze_and_rewrite_params(Node *parsetree,
 							  const char *query_string,
 							  ParserSetupHook parserSetup,
-							  void *parserSetupArg)
+							  void *parserSetupArg,
+							  QueryEnvironment *queryEnv)
 {
 	ParseState *pstate;
 	Query	   *query;
@@ -699,6 +702,9 @@ pg_analyze_and_rewrite_params(Node *parsetree,
 	pstate->p_sourcetext = query_string;
 	(*parserSetup) (pstate, parserSetupArg);
 
+	if (queryEnv)
+		pstate->p_spirelationnamespace = queryEnv->spi_relations;
+
 	query = transformTopLevelStmt(pstate, parsetree);
 
 	if (post_parse_analyze_hook)
@@ -1017,7 +1023,7 @@ exec_simple_query(const char *query_string)
 		oldcontext = MemoryContextSwitchTo(MessageContext);
 
 		querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
-												NULL, 0);
+												NULL, 0, NULL);
 
 		plantree_list = pg_plan_queries(querytree_list,
 										CURSOR_OPT_PARALLEL_OK, NULL);
@@ -1328,7 +1334,8 @@ exec_parse_message(const char *query_string,	/* string to execute */
 		query = parse_analyze_varparams(raw_parse_tree,
 										query_string,
 										&paramTypes,
-										&numParams);
+										&numParams,
+										NULL);
 
 		/*
 		 * Check all parameter types got determined.
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 5443d0b..3a31bf3 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -36,6 +36,7 @@ Portal		ActivePortal = NULL;
 static void ProcessQuery(PlannedStmt *plan,
 			 const char *sourceText,
 			 ParamListInfo params,
+			 QueryEnvironment *queryEnv,
 			 DestReceiver *dest,
 			 char *completionTag);
 static void FillPortalStore(Portal portal, bool isTopLevel);
@@ -67,6 +68,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 				Snapshot crosscheck_snapshot,
 				DestReceiver *dest,
 				ParamListInfo params,
+				QueryEnvironment *queryEnv,
 				int instrument_options)
 {
 	QueryDesc  *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
@@ -80,6 +82,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 	qd->crosscheck_snapshot = RegisterSnapshot(crosscheck_snapshot);
 	qd->dest = dest;			/* output dest */
 	qd->params = params;		/* parameter values passed into query */
+	qd->queryEnv = queryEnv;
 	qd->instrument_options = instrument_options;		/* instrumentation
 														 * wanted? */
 
@@ -100,7 +103,8 @@ CreateUtilityQueryDesc(Node *utilitystmt,
 					   const char *sourceText,
 					   Snapshot snapshot,
 					   DestReceiver *dest,
-					   ParamListInfo params)
+					   ParamListInfo params,
+					   QueryEnvironment *queryEnv)
 {
 	QueryDesc  *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
 
@@ -112,6 +116,7 @@ CreateUtilityQueryDesc(Node *utilitystmt,
 	qd->crosscheck_snapshot = InvalidSnapshot;	/* RI check snapshot */
 	qd->dest = dest;			/* output dest */
 	qd->params = params;		/* parameter values passed into query */
+	qd->queryEnv = queryEnv;
 	qd->instrument_options = false;		/* uninteresting for utilities */
 
 	/* null these fields until set by ExecutorStart */
@@ -162,6 +167,7 @@ static void
 ProcessQuery(PlannedStmt *plan,
 			 const char *sourceText,
 			 ParamListInfo params,
+			 QueryEnvironment *queryEnv,
 			 DestReceiver *dest,
 			 char *completionTag)
 {
@@ -172,7 +178,7 @@ ProcessQuery(PlannedStmt *plan,
 	 */
 	queryDesc = CreateQueryDesc(plan, sourceText,
 								GetActiveSnapshot(), InvalidSnapshot,
-								dest, params, 0);
+								dest, params, queryEnv, 0);
 
 	/*
 	 * Call ExecutorStart to prepare the plan for execution
@@ -519,6 +525,7 @@ PortalStart(Portal portal, ParamListInfo params,
 											InvalidSnapshot,
 											None_Receiver,
 											params,
+											portal->queryEnv,
 											0);
 
 				/*
@@ -1190,6 +1197,7 @@ PortalRunUtility(Portal portal, Node *utilityStmt,
 				   portal->sourceText,
 			   isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
 				   portal->portalParams,
+				   portal->queryEnv,
 				   dest,
 				   completionTag);
 
@@ -1299,6 +1307,7 @@ PortalRunMulti(Portal portal,
 				ProcessQuery(pstmt,
 							 portal->sourceText,
 							 portal->portalParams,
+							 portal->queryEnv,
 							 dest, completionTag);
 			}
 			else
@@ -1307,6 +1316,7 @@ PortalRunMulti(Portal portal,
 				ProcessQuery(pstmt,
 							 portal->sourceText,
 							 portal->portalParams,
+							 portal->queryEnv,
 							 altdest, NULL);
 			}
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..3618f7c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -76,6 +76,7 @@ static void ProcessUtilitySlow(ParseState *pstate,
 				   const char *queryString,
 				   ProcessUtilityContext context,
 				   ParamListInfo params,
+				   QueryEnvironment *queryEnv,
 				   DestReceiver *dest,
 				   char *completionTag);
 static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
@@ -319,6 +320,7 @@ ProcessUtility(Node *parsetree,
 			   const char *queryString,
 			   ProcessUtilityContext context,
 			   ParamListInfo params,
+			   QueryEnvironment *queryEnv,
 			   DestReceiver *dest,
 			   char *completionTag)
 {
@@ -331,11 +333,11 @@ ProcessUtility(Node *parsetree,
 	 */
 	if (ProcessUtility_hook)
 		(*ProcessUtility_hook) (parsetree, queryString,
-								context, params,
+								context, params, queryEnv,
 								dest, completionTag);
 	else
 		standard_ProcessUtility(parsetree, queryString,
-								context, params,
+								context, params, queryEnv,
 								dest, completionTag);
 }
 
@@ -355,6 +357,7 @@ standard_ProcessUtility(Node *parsetree,
 						const char *queryString,
 						ProcessUtilityContext context,
 						ParamListInfo params,
+						QueryEnvironment *queryEnv,
 						DestReceiver *dest,
 						char *completionTag)
 {
@@ -554,7 +557,7 @@ standard_ProcessUtility(Node *parsetree,
 
 		case T_PrepareStmt:
 			CheckRestrictedOperation("PREPARE");
-			PrepareQuery((PrepareStmt *) parsetree, queryString);
+			PrepareQuery((PrepareStmt *) parsetree, queryString, queryEnv);
 			break;
 
 		case T_ExecuteStmt:
@@ -662,7 +665,8 @@ standard_ProcessUtility(Node *parsetree,
 			break;
 
 		case T_ExplainStmt:
-			ExplainQuery(pstate, (ExplainStmt *) parsetree, queryString, params, dest);
+			ExplainQuery(pstate, (ExplainStmt *) parsetree, queryString, params,
+						 queryEnv, dest);
 			break;
 
 		case T_AlterSystemStmt:
@@ -809,7 +813,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsGrantObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecuteGrantStmt((GrantStmt *) parsetree);
@@ -822,7 +826,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->removeType))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecDropStmt(stmt, isTopLevel);
@@ -835,7 +839,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->renameType))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecRenameStmt(stmt);
@@ -848,7 +852,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecAlterObjectDependsStmt(stmt, NULL);
@@ -861,7 +865,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecAlterObjectSchemaStmt(stmt, NULL);
@@ -874,7 +878,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->objectType))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecAlterOwnerStmt(stmt);
@@ -887,7 +891,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					CommentObject((CommentStmt *) parsetree);
@@ -900,7 +904,7 @@ standard_ProcessUtility(Node *parsetree,
 
 				if (EventTriggerSupportsObjectType(stmt->objtype))
 					ProcessUtilitySlow(pstate, parsetree, queryString,
-									   context, params,
+									   context, params, queryEnv,
 									   dest, completionTag);
 				else
 					ExecSecLabelStmt(stmt);
@@ -910,7 +914,7 @@ standard_ProcessUtility(Node *parsetree,
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, parsetree, queryString,
-							   context, params,
+							   context, params, queryEnv,
 							   dest, completionTag);
 			break;
 	}
@@ -929,6 +933,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				   const char *queryString,
 				   ProcessUtilityContext context,
 				   ParamListInfo params,
+				   QueryEnvironment *queryEnv,
 				   DestReceiver *dest,
 				   char *completionTag)
 {
@@ -1038,6 +1043,7 @@ ProcessUtilitySlow(ParseState *pstate,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
 										   params,
+										   queryEnv,
 										   None_Receiver,
 										   NULL);
 						}
@@ -1108,6 +1114,7 @@ ProcessUtilitySlow(ParseState *pstate,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
+											   queryEnv,
 											   None_Receiver,
 											   NULL);
 								EventTriggerAlterTableStart(parsetree);
@@ -1404,7 +1411,8 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateTableAsStmt:
 				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
-										 queryString, params, completionTag);
+											queryString, params, queryEnv,
+											completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..1b5a30c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6454,6 +6454,7 @@ get_name_for_var_field(Var *var, int fieldno,
 	{
 		case RTE_RELATION:
 		case RTE_VALUES:
+		case RTE_SPIRELATION:
 
 			/*
 			 * This case should not occur: a column of a table or values list
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 884cdab..4739c54 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -682,12 +682,14 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
 		tlist = pg_analyze_and_rewrite_params(rawtree,
 											  plansource->query_string,
 											  plansource->parserSetup,
-											  plansource->parserSetupArg);
+											  plansource->parserSetupArg,
+											  NULL);
 	else
 		tlist = pg_analyze_and_rewrite(rawtree,
 									   plansource->query_string,
 									   plansource->param_types,
-									   plansource->num_params);
+									   plansource->num_params,
+									   NULL);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 3c42864..d0b52ef 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -17,11 +17,13 @@
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
+#include "parser/query_environment.h"
 #include "tcop/dest.h"
 
 
 extern ObjectAddress ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
-				  ParamListInfo params, char *completionTag);
+				  ParamListInfo params, QueryEnvironment *queryEnv,
+				  char *completionTag);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 8b3acab..bc20af6 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -61,7 +61,7 @@ extern PGDLLIMPORT explain_get_index_name_hook_type explain_get_index_name_hook;
 
 
 extern void ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
-			 ParamListInfo params, DestReceiver *dest);
+			 ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest);
 
 extern ExplainState *NewExplainState(void);
 
@@ -69,11 +69,13 @@ extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
 
 extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
 				  ExplainState *es,
-				  const char *queryString, ParamListInfo params);
+				  const char *queryString, ParamListInfo params,
+				  QueryEnvironment *queryEnv);
 
 extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
 			   ExplainState *es, const char *queryString,
-			   ParamListInfo params, const instr_time *planduration);
+			   ParamListInfo params, QueryEnvironment *queryEnv,
+			   const instr_time *planduration);
 
 extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
 extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc);
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index ba1247a..cffc934 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -35,14 +35,16 @@ typedef struct
 
 
 /* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
-extern void PrepareQuery(PrepareStmt *stmt, const char *queryString);
+extern void PrepareQuery(PrepareStmt *stmt, const char *queryString,
+						 QueryEnvironment *queryEnv);
 extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 			 const char *queryString, ParamListInfo params,
 			 DestReceiver *dest, char *completionTag);
 extern void DeallocateQuery(DeallocateStmt *stmt);
 extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into,
 					ExplainState *es,
-					const char *queryString, ParamListInfo params);
+					const char *queryString, ParamListInfo params,
+					QueryEnvironment *queryEnv);
 
 /* Low-level access to stored prepared statements */
 extern void StorePreparedStatement(const char *stmt_name,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index c6e3e2c..828d9c5 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -16,6 +16,7 @@
 #include "catalog/objectaddress.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
+#include "utils/tuplestore.h"
 
 /*
  * TriggerData is the node type that is passed as fmgr "context" info
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index d5b3fc8..dddcfcd 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -41,6 +41,7 @@ typedef struct QueryDesc
 	Snapshot	crosscheck_snapshot;	/* crosscheck for RI update/delete */
 	DestReceiver *dest;			/* the destination for tuple output */
 	ParamListInfo params;		/* param values being passed in */
+	QueryEnvironment *queryEnv;	/* transient namespaces */
 	int			instrument_options;		/* OR of InstrumentOption flags */
 
 	/* These fields are set by ExecutorStart */
@@ -59,13 +60,15 @@ extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt,
 				Snapshot crosscheck_snapshot,
 				DestReceiver *dest,
 				ParamListInfo params,
+				QueryEnvironment *queryEnv,
 				int instrument_options);
 
 extern QueryDesc *CreateUtilityQueryDesc(Node *utilitystmt,
 					   const char *sourceText,
 					   Snapshot snapshot,
 					   DestReceiver *dest,
-					   ParamListInfo params);
+					   ParamListInfo params,
+					   QueryEnvironment *queryEnv);
 
 extern void FreeQueryDesc(QueryDesc *qdesc);
 
diff --git a/src/include/executor/nodeSpirelationscan.h b/src/include/executor/nodeSpirelationscan.h
new file mode 100644
index 0000000..9893dee
--- /dev/null
+++ b/src/include/executor/nodeSpirelationscan.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSpirelationscan.h
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeSpirelationscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODESPIRELATIONSCAN_H
+#define NODESPIRELATIONSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern SpiRelationScanState *ExecInitSpiRelationScan(SpiRelationScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecSpiRelationScan(SpiRelationScanState *node);
+extern void ExecEndSpiRelationScan(SpiRelationScanState *node);
+extern void ExecReScanSpiRelationScan(SpiRelationScanState *node);
+
+#endif   /* NODESPIRELATIONSCAN_H */
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 76ba394..149cdb8 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -43,6 +43,8 @@ typedef struct _SPI_plan *SPIPlanPtr;
 #define SPI_ERROR_NOATTRIBUTE	(-9)
 #define SPI_ERROR_NOOUTFUNC		(-10)
 #define SPI_ERROR_TYPUNKNOWN	(-11)
+#define SPI_ERROR_DUPLICATE		(-12)
+#define SPI_ERROR_NOT_FOUND		(-13)
 
 #define SPI_OK_CONNECT			1
 #define SPI_OK_FINISH			2
@@ -58,6 +60,8 @@ typedef struct _SPI_plan *SPIPlanPtr;
 #define SPI_OK_DELETE_RETURNING 12
 #define SPI_OK_UPDATE_RETURNING 13
 #define SPI_OK_REWRITTEN		14
+#define SPI_OK_REGISTER			15
+#define SPI_OK_UNREGISTER		16
 
 /* These used to be functions, now just no-ops for backwards compatibility */
 #define SPI_push()	((void) 0)
@@ -145,6 +149,9 @@ extern void SPI_cursor_move(Portal portal, bool forward, long count);
 extern void SPI_scroll_cursor_fetch(Portal, FetchDirection direction, long count);
 extern void SPI_scroll_cursor_move(Portal, FetchDirection direction, long count);
 extern void SPI_cursor_close(Portal portal);
+extern int SPI_register_relation(const char *name, TupleDesc tupdesc,
+								 int tuples, Tuplestorestate *tuplestore);
+extern int SPI_unregister_relation(const char *name);
 
 extern void AtEOXact_SPI(bool isCommit);
 extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index e8084df..e3fd7c1 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -1,5 +1,5 @@
 /*-------------------------------------------------------------------------
- *
+c *
  * spi_priv.h
  *				Server Programming Interface private declarations
  *
@@ -14,6 +14,7 @@
 #define SPI_PRIV_H
 
 #include "executor/spi.h"
+#include "parser/query_environment.h"
 
 
 #define _SPI_PLAN_MAGIC		569278163
@@ -31,6 +32,7 @@ typedef struct
 	MemoryContext execCxt;		/* executor context */
 	MemoryContext savedcxt;		/* context of SPI_connect's caller */
 	SubTransactionId connectSubid;		/* ID of connecting subtransaction */
+	QueryEnvironment *queryEnv;	/* objects exposed to SQL */
 } _SPI_connection;
 
 /*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..08cee3b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -20,6 +20,7 @@
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "parser/query_environment.h"
 #include "utils/hsearch.h"
 #include "utils/reltrigger.h"
 #include "utils/sortsupport.h"
@@ -383,6 +384,9 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Relations made available via SPI. */
+	List	   *es_spi_relations;
+
 	/* Other working state: */
 	MemoryContext es_query_cxt; /* per-query context in which EState lives */
 
@@ -1571,6 +1575,24 @@ typedef struct CteScanState
 } CteScanState;
 
 /* ----------------
+ *	 SpiRelationScanState information
+ *
+ *		SpiRelationScan nodes are used to scan a tuplestore created and named
+ *		by SPI client code prior to execution of the query.  An example is a
+ *		transition table for an AFTER trigger.
+ *
+ * Multiple SpiRelationScan nodes can read out from the same Tuplestore.
+ * ----------------
+ */
+typedef struct SpiRelationScanState
+{
+	ScanState	ss;				/* its first field is NodeTag */
+	int			readptr;		/* index of my tuplestore read pointer */
+	TupleDesc	tupdesc;		/* format of the tuples in the tuplestore */
+	Tuplestorestate *table;		/* the rows */
+} SpiRelationScanState;
+
+/* ----------------
  *	 WorkTableScanState information
  *
  *		WorkTableScan nodes are used to scan the work table created by
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..e2a3716 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -61,6 +61,7 @@ typedef enum NodeTag
 	T_FunctionScan,
 	T_ValuesScan,
 	T_CteScan,
+	T_SpiRelationScan,
 	T_WorkTableScan,
 	T_ForeignScan,
 	T_CustomScan,
@@ -109,6 +110,7 @@ typedef enum NodeTag
 	T_FunctionScanState,
 	T_ValuesScanState,
 	T_CteScanState,
+	T_SpiRelationScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
 	T_CustomScanState,
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index a8aa530..2d66292 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -17,6 +17,7 @@
 /* Forward declarations, to avoid including other headers */
 struct Bitmapset;
 struct ParseState;
+struct EState;
 
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..51d2d14 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -791,7 +791,8 @@ typedef enum RTEKind
 	RTE_JOIN,					/* join */
 	RTE_FUNCTION,				/* function in FROM */
 	RTE_VALUES,					/* VALUES (<exprlist>), (<exprlist>), ... */
-	RTE_CTE						/* common table expr (WITH list element) */
+	RTE_CTE,					/* common table expr (WITH list element) */
+	RTE_SPIRELATION				/* relation supplied via SPI */
 } RTEKind;
 
 typedef struct RangeTblEntry
@@ -867,6 +868,15 @@ typedef struct RangeTblEntry
 	List	   *ctecolcollations;		/* OID list of column collation OIDs */
 
 	/*
+	 * Needed for SPI relation RTE...
+	 *
+	 * ... include the cte* List fields from CTE and relid.  We could
+	 * duplicate them here with slightly different names, but why?
+	 */
+	char	   *srname;			/* name by which relation will be referenced */
+	int			srtuples;		/* estimated number of rows */
+
+	/*
 	 * Fields valid in all RTEs:
 	 */
 	Alias	   *alias;			/* user-written alias clause, if any */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..1bcbcab 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -487,6 +487,16 @@ typedef struct CteScan
 } CteScan;
 
 /* ----------------
+ *		SpiRelationScan node
+ * ----------------
+ */
+typedef struct SpiRelationScan
+{
+	Scan		scan;
+	char	   *srname;
+} SpiRelationScan;
+
+/* ----------------
  *		WorkTableScan node
  * ----------------
  */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 2a4df2f..78722cb 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -93,6 +93,8 @@ extern void cost_valuesscan(Path *path, PlannerInfo *root,
 				RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_ctescan(Path *path, PlannerInfo *root,
 			 RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_spirelationscan(Path *path, PlannerInfo *root,
+			 RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
 extern void cost_sort(Path *path, PlannerInfo *root,
 		  List *pathkeys, Cost input_cost, double tuples, int width,
@@ -181,6 +183,7 @@ extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 					   double cte_rows);
+extern void set_spirelation_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 71d9154..14fbfe2 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -85,6 +85,8 @@ extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
 					   Relids required_outer);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
 					Relids required_outer);
+extern Path *create_spirelationscan_path(PlannerInfo *root, RelOptInfo *rel,
+					Relids required_outer);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
 						  Relids required_outer);
 extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 5ba322a..e76b4a3 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -23,9 +23,11 @@ extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook;
 
 
 extern Query *parse_analyze(Node *parseTree, const char *sourceText,
-			  Oid *paramTypes, int numParams);
+			   Oid *paramTypes, int numParams,
+			   QueryEnvironment *queryEnv);
 extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
-						Oid **paramTypes, int *numParams);
+						Oid **paramTypes, int *numParams,
+						QueryEnvironment *queryEnv);
 
 extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
 				  CommonTableExpr *parentCTE,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..5cd0330 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -15,6 +15,7 @@
 #define PARSE_NODE_H
 
 #include "nodes/parsenodes.h"
+#include "parser/query_environment.h"
 #include "utils/relcache.h"
 
 
@@ -68,6 +69,7 @@ typedef enum ParseExprKind
 } ParseExprKind;
 
 
+
 /*
  * Function signatures for parser hooks
  */
@@ -80,7 +82,6 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
 									   Oid targetTypeId, int32 targetTypeMod,
 											  int location);
 
-
 /*
  * State information used during parse analysis
  *
@@ -142,6 +143,7 @@ struct ParseState
 	List	   *p_ctenamespace; /* current namespace for common table exprs */
 	List	   *p_future_ctes;	/* common table exprs not yet in namespace */
 	CommonTableExpr *p_parent_cte;		/* this query's containing CTE */
+	List	   *p_spirelationnamespace;	/* SPI relations */
 	List	   *p_windowdefs;	/* raw representations of window clauses */
 	ParseExprKind p_expr_kind;	/* what kind of expression we're parsing */
 	int			p_next_resno;	/* next targetlist resno to assign */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 3ef3d7b..df56efd 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -15,6 +15,7 @@
 #define PARSE_RELATION_H
 
 #include "parser/parse_node.h"
+#include "parser/query_environment.h"
 
 
 /*
@@ -42,6 +43,8 @@ extern RangeTblEntry *refnameRangeTblEntry(ParseState *pstate,
 extern CommonTableExpr *scanNameSpaceForCTE(ParseState *pstate,
 					const char *refname,
 					Index *ctelevelsup);
+extern SpiRelation *scanNameSpaceForSpiRelation(ParseState *pstate,
+												const char *refname);
 extern void checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
 						List *namespace2);
 extern int RTERangeTablePosn(ParseState *pstate,
@@ -71,6 +74,10 @@ extern RangeTblEntry *addRangeTableEntryForRelation(ParseState *pstate,
 							  Alias *alias,
 							  bool inh,
 							  bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForSpiRelation(ParseState *pstate,
+							SpiRelation *sr,
+							RangeVar *rv,
+							bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 							  Query *subquery,
 							  Alias *alias,
diff --git a/src/include/parser/query_environment.h b/src/include/parser/query_environment.h
new file mode 100644
index 0000000..70fa49f
--- /dev/null
+++ b/src/include/parser/query_environment.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * query_environment.h
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * src/include/parser/query_environment.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef QUERY_ENVIRONMENT_H
+#define QUERY_ENVIRONMENT_H
+
+#include "access/tupdesc.h"
+#include "nodes/pg_list.h"
+
+struct Tuplestorestate;
+
+/*
+ * A relation that SPI client code has exposed to the parser, planner and
+ * executor.
+ */
+typedef struct SpiRelation
+{
+	char	   *name;			/* Name of the relation */
+	TupleDesc	tupdesc;		/* Descriptor for tuples */
+	int			tuples;			/* Estimated number of tuples. */
+	struct Tuplestorestate *tuplestore;	/* The tuples. */
+} SpiRelation;
+
+/*
+ * Users of the SPI interface such as PLs can make transient database objects
+ * visible to the SQL parser, planner and executor.  Currently the only example
+ * is SPI relations.
+ */
+typedef struct QueryEnvironment
+{
+	List *spi_relations;		/* A list of pointers to SpiRelation */
+} QueryEnvironment;
+
+#endif   /* QUERY_ENVIRONMENT_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 7254355..07935b9 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -22,6 +22,7 @@
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
+#include "parser/query_environment.h"
 #include "storage/procsignal.h"
 #include "utils/guc.h"
 
@@ -48,11 +49,13 @@ extern int	log_statement;
 
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
-					   Oid *paramTypes, int numParams);
+					   Oid *paramTypes, int numParams,
+					   QueryEnvironment *queryEnv);
 extern List *pg_analyze_and_rewrite_params(Node *parsetree,
 							  const char *query_string,
 							  ParserSetupHook parserSetup,
-							  void *parserSetupArg);
+							  void *parserSetupArg,
+							  QueryEnvironment *queryEnv);
 extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
 			  ParamListInfo boundParams);
 extern List *pg_plan_queries(List *querytrees, int cursorOptions,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index dad8246..1b8589c 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -27,14 +27,17 @@ typedef enum
 typedef void (*ProcessUtility_hook_type) (Node *parsetree,
 					  const char *queryString, ProcessUtilityContext context,
 													  ParamListInfo params,
+									QueryEnvironment *queryEnv,
 									DestReceiver *dest, char *completionTag);
 extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
 
 extern void ProcessUtility(Node *parsetree, const char *queryString,
 			   ProcessUtilityContext context, ParamListInfo params,
+			   QueryEnvironment *queryEnv,
 			   DestReceiver *dest, char *completionTag);
 extern void standard_ProcessUtility(Node *parsetree, const char *queryString,
 						ProcessUtilityContext context, ParamListInfo params,
+						QueryEnvironment *queryEnv,
 						DestReceiver *dest, char *completionTag);
 
 extern bool UtilityReturnsTuples(Node *parsetree);
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index c1d93a9..7b38af2 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -137,6 +137,7 @@ typedef struct PortalData
 	CachedPlan *cplan;			/* CachedPlan, if stmts are from one */
 
 	ParamListInfo portalParams; /* params to pass to query */
+	QueryEnvironment *queryEnv; /* transient objects introduced by SPI */
 
 	/* Features/options */
 	PortalStrategy strategy;	/* see above */
