diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index c300261852..6b7503df42 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -299,6 +299,7 @@ static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
ProcessUtilityContext context, ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag);
static uint32 pgss_hash_fn(const void *key, Size keysize);
static int pgss_match_fn(const void *key1, const void *key2, Size keysize);
@@ -956,7 +957,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
*/
static void
pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
- ProcessUtilityContext context, ParamListInfo params,
+ ProcessUtilityContext context,
+ ParamListInfo params, QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag)
{
Node *parsetree = pstmt->utilityStmt;
@@ -994,11 +996,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
{
if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
standard_ProcessUtility(pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
nested_level--;
}
@@ -1058,11 +1060,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
{
if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
standard_ProcessUtility(pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
}
}
@@ -2424,6 +2426,9 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
APP_JUMB_STRING(rte->ctename);
APP_JUMB(rte->ctelevelsup);
break;
+ case RTE_NAMEDTUPLESTORE:
+ APP_JUMB_STRING(rte->enrname);
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break;
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 836ce0822f..af7eada2e1 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -2639,6 +2639,210 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr plan)
+
+
+
+ SPI_register_relation
+
+
+ SPI_register_relation
+ 3
+
+
+
+ SPI_register_relation
+ make a ephemeral named relation available by name in SPI queries
+
+
+
+
+int SPI_register_relation(EphemeralNamedRelation enr)
+
+
+
+
+ Description
+
+
+ SPI_register_relation makes an ephemeral named
+ relation, with associated information, available to queries planned and
+ executed through the current SPI connection.
+
+
+
+
+ Arguments
+
+
+
+ EphemeralNamedRelation enr
+
+
+ the ephemeral named relation registry entry
+
+
+
+
+
+
+
+ Return Value
+
+
+ If the execution of the command was successful then the following
+ (nonnegative) value will be returned:
+
+
+
+ SPI_OK_REL_REGISTER
+
+
+ if the relation has been successfully registered by name
+
+
+
+
+
+
+
+ On error, one of the following negative values is returned:
+
+
+
+ SPI_ERROR_ARGUMENT
+
+
+ if enr is NULL or its
+ name field is NULL
+
+
+
+
+
+ SPI_ERROR_UNCONNECTED
+
+
+ if called from an unconnected procedure
+
+
+
+
+
+ SPI_ERROR_REL_DUPLICATE
+
+
+ if the name specified in the name field of
+ enr is already registered for this connection
+
+
+
+
+
+
+
+
+
+
+
+ SPI_unregister_relation
+
+
+ SPI_unregister_relation
+ 3
+
+
+
+ SPI_unregister_relation
+ remove an ephemeral named relation from the registry
+
+
+
+
+int SPI_unregister_relation(const char * name)
+
+
+
+
+ Description
+
+
+ SPI_unregister_relation removes an ephemeral named
+ relation from the registry for the current connection.
+
+
+
+
+ Arguments
+
+
+
+ const char * name
+
+
+ the relation registry entry name
+
+
+
+
+
+
+
+ Return Value
+
+
+ If the execution of the command was successful then the following
+ (nonnegative) value will be returned:
+
+
+
+ SPI_OK_REL_UNREGISTER
+
+
+ if the tuplestore has been successfully removed from the registry
+
+
+
+
+
+
+
+ On error, one of the following negative values is returned:
+
+
+
+ SPI_ERROR_ARGUMENT
+
+
+ if name is NULL
+
+
+
+
+
+ SPI_ERROR_UNCONNECTED
+
+
+ if called from an unconnected procedure
+
+
+
+
+
+ SPI_ERROR_REL_NOT_FOUND
+
+
+ if name is not found in the registry for the
+ current connection
+
+
+
+
+
+
+
+
+
+
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index ae27848116..f058d1274f 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -934,7 +934,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 0158eda591..8c58808686 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1471,7 +1471,8 @@ BeginCopy(ParseState *pstate,
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
rewritten = pg_analyze_and_rewrite(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)
@@ -1574,7 +1575,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 20cb64661a..f49b391505 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 = castNode(Query, stmt->query);
IntoClause *into = stmt->into;
@@ -341,7 +342,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 ea19ba60c5..a18ab43616 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -55,7 +55,8 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
static void ExplainOneQuery(Query *query, int cursorOptions,
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);
@@ -142,7 +143,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;
@@ -253,7 +255,7 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
{
ExplainOneQuery(castNode(Query, lfirst(l)),
CURSOR_OPT_PARALLEL_OK, NULL, es,
- queryString, params);
+ queryString, params, queryEnv);
/* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
@@ -338,12 +340,14 @@ ExplainResultDesc(ExplainStmt *stmt)
static void
ExplainOneQuery(Query *query, int cursorOptions,
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;
}
@@ -366,7 +370,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
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);
}
}
@@ -383,7 +388,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
*/
void
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
- const char *queryString, ParamListInfo params)
+ const char *queryString, ParamListInfo params,
+ QueryEnvironment *queryEnv)
{
if (utilityStmt == NULL)
return;
@@ -404,7 +410,7 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
Assert(list_length(rewritten) == 1);
ExplainOneQuery(castNode(Query, linitial(rewritten)),
0, ctas->into, es,
- queryString, params);
+ queryString, params, queryEnv);
}
else if (IsA(utilityStmt, DeclareCursorStmt))
{
@@ -423,11 +429,11 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
Assert(list_length(rewritten) == 1);
ExplainOneQuery(castNode(Query, linitial(rewritten)),
dcs->options, NULL, 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)
@@ -460,7 +466,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;
@@ -505,7 +511,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)
@@ -796,6 +802,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
case T_TableFuncScan:
case T_ValuesScan:
case T_CteScan:
+ case T_NamedTuplestoreScan:
case T_WorkTableScan:
*rels_used = bms_add_member(*rels_used,
((Scan *) plan)->scanrelid);
@@ -951,6 +958,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_CteScan:
pname = sname = "CTE Scan";
break;
+ case T_NamedTuplestoreScan:
+ pname = sname = "Named Tuplestore Scan";
+ break;
case T_WorkTableScan:
pname = sname = "WorkTable Scan";
break;
@@ -1389,6 +1399,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_SeqScan:
case T_ValuesScan:
case T_CteScan:
+ case T_NamedTuplestoreScan:
case T_WorkTableScan:
case T_SubqueryScan:
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
@@ -2679,6 +2690,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
objectname = rte->ctename;
objecttag = "CTE Name";
break;
+ case T_NamedTuplestoreScan:
+ Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
+ objectname = rte->enrname;
+ objecttag = "Tuplestore 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 5a84bedf46..6be9bc457c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -721,7 +721,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)
@@ -739,7 +740,7 @@ execute_sql_string(const char *sql, const char *filename)
qdesc = CreateQueryDesc(stmt,
sql,
GetActiveSnapshot(), NULL,
- dest, NULL, 0);
+ dest, NULL, NULL, 0);
ExecutorStart(qdesc, 0);
ExecutorRun(qdesc, ForwardScanDirection, 0, true);
@@ -759,6 +760,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 68100df083..4ffe1bca75 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -1623,7 +1623,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
/* Execute statement */
ProcessUtility(pstmt,
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 9d41ad8fad..2f93328318 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -418,7 +418,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 dc6d43ec6d..5b3f777f2c 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -91,7 +91,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
* to see the unmodified raw parse tree.
*/
plansource = CreateCachedPlan(rawstmt, queryString,
- CreateCommandTag(stmt->query));
+ CreateCommandTag(stmt->query), NULL);
/* Transform list of TypeNames to array of type OIDs */
nargs = list_length(stmt->argtypes);
@@ -243,7 +243,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
entry->plansource->query_string);
/* Replan if needed, and increment plan refcount for portal */
- cplan = GetCachedPlan(entry->plansource, paramLI, false);
+ cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
plan_list = cplan->stmt_list;
/*
@@ -551,7 +551,7 @@ FetchPreparedStatementTargetList(PreparedStatement *stmt)
List *tlist;
/* Get the plan's primary targetlist */
- tlist = CachedPlanGetTargetList(stmt->plansource);
+ tlist = CachedPlanGetTargetList(stmt->plansource, NULL);
/* Copy into caller's context in case plan gets invalidated */
return copyObject(tlist);
@@ -629,7 +629,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;
@@ -668,7 +669,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
}
/* Replan if needed, and acquire a transient refcount */
- cplan = GetCachedPlan(entry->plansource, paramLI, true);
+ cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
INSTR_TIME_SET_CURRENT(planduration);
INSTR_TIME_SUBTRACT(planduration, planstart);
@@ -681,9 +682,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
PlannedStmt *pstmt = castNode(PlannedStmt, lfirst(p));
if (pstmt->commandType != CMD_UTILITY)
- ExplainOnePlan(pstmt, into, es, query_string, paramLI, &planduration);
+ ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv,
+ &planduration);
else
- ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI);
+ ExplainOneUtility(pstmt->utilityStmt, 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 722b965d65..93425babbe 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -194,6 +194,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
queryString,
PROCESS_UTILITY_SUBCOMMAND,
NULL,
+ NULL,
None_Receiver,
NULL);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index f3b1a52682..ebf23a0d94 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -354,6 +354,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* adjustments will be needed below.
*/
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Triggers on partitioned tables cannot have transition tables.")));
+
if (stmt->timing != TRIGGER_TYPE_AFTER)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -1173,7 +1180,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
/* ... and execute it */
ProcessUtility(wrapper,
"(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 35e25db7dc..6909a67e77 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -436,7 +436,7 @@ DefineView(ViewStmt *stmt, const char *queryString,
rawstmt->stmt_location = stmt_location;
rawstmt->stmt_len = stmt_len;
- viewParse = parse_analyze(rawstmt, queryString, NULL, 0);
+ viewParse = parse_analyze(rawstmt, 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 d1c1324399..083b20f3fe 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -25,7 +25,8 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \
nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
- nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
+ nodeValuesscan.o \
+ nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
nodeTableFuncscan.o
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 5d59f95a91..7e85c66da3 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -38,6 +38,7 @@
#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
+#include "executor/nodeNamedtuplestorescan.h"
#include "executor/nodeNestloop.h"
#include "executor/nodeProjectSet.h"
#include "executor/nodeRecursiveunion.h"
@@ -211,6 +212,10 @@ ExecReScan(PlanState *node)
ExecReScanCteScan((CteScanState *) node);
break;
+ case T_NamedTuplestoreScanState:
+ ExecReScanNamedTuplestoreScan((NamedTuplestoreScanState *) node);
+ break;
+
case T_WorkTableScanState:
ExecReScanWorkTableScan((WorkTableScanState *) node);
break;
@@ -571,6 +576,7 @@ ExecMaterializesOutput(NodeTag plantype)
case T_FunctionScan:
case T_TableFuncScan:
case T_CteScan:
+ case T_NamedTuplestoreScan:
case T_WorkTableScan:
case T_Sort:
return true;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f2995f2e7b..920b12072f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -198,6 +198,11 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
estate->es_sourceText = queryDesc->sourceText;
+ /*
+ * Fill in the query environment, if any, from queryDesc.
+ */
+ estate->es_queryEnv = queryDesc->queryEnv;
+
/*
* 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 b91b663c46..469a32c7b0 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -710,7 +710,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
return CreateQueryDesc(pstmt,
queryString,
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 80c77addb8..486ddf1762 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -101,6 +101,7 @@
#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
+#include "executor/nodeNamedtuplestorescan.h"
#include "executor/nodeNestloop.h"
#include "executor/nodeProjectSet.h"
#include "executor/nodeRecursiveunion.h"
@@ -256,6 +257,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_NamedTuplestoreScan:
+ result = (PlanState *) ExecInitNamedTuplestoreScan((NamedTuplestoreScan *) node,
+ estate, eflags);
+ break;
+
case T_WorkTableScan:
result = (PlanState *) ExecInitWorkTableScan((WorkTableScan *) node,
estate, eflags);
@@ -483,6 +489,10 @@ ExecProcNode(PlanState *node)
result = ExecCteScan((CteScanState *) node);
break;
+ case T_NamedTuplestoreScanState:
+ result = ExecNamedTuplestoreScan((NamedTuplestoreScanState *) node);
+ break;
+
case T_WorkTableScanState:
result = ExecWorkTableScan((WorkTableScanState *) node);
break;
@@ -751,6 +761,10 @@ ExecEndNode(PlanState *node)
ExecEndCteScan((CteScanState *) node);
break;
+ case T_NamedTuplestoreScanState:
+ ExecEndNamedTuplestoreScan((NamedTuplestoreScanState *) node);
+ break;
+
case T_WorkTableScanState:
ExecEndWorkTableScan((WorkTableScanState *) node);
break;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 2613ffbb71..ce7b064217 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -120,6 +120,8 @@ CreateExecutorState(void)
estate->es_param_list_info = NULL;
estate->es_param_exec_vals = NULL;
+ estate->es_queryEnv = 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 3e4b0191c7..3cadf95304 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -713,7 +713,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));
@@ -809,7 +810,9 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
GetActiveSnapshot(),
InvalidSnapshot,
dest,
- fcache->paramLI, 0);
+ fcache->paramLI,
+ es->qd ? es->qd->queryEnv : NULL,
+ 0);
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
@@ -846,6 +849,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/nodeNamedtuplestorescan.c b/src/backend/executor/nodeNamedtuplestorescan.c
new file mode 100644
index 0000000000..917b05197a
--- /dev/null
+++ b/src/backend/executor/nodeNamedtuplestorescan.c
@@ -0,0 +1,198 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeNamedtuplestorescan.c
+ * routines to handle NamedTuplestoreScan nodes.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/nodeNamedtuplestorescan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/execdebug.h"
+#include "executor/nodeNamedtuplestorescan.h"
+#include "miscadmin.h"
+#include "utils/queryenvironment.h"
+
+static TupleTableSlot *NamedTuplestoreScanNext(NamedTuplestoreScanState *node);
+
+/* ----------------------------------------------------------------
+ * NamedTuplestoreScanNext
+ *
+ * This is a workhorse for ExecNamedTuplestoreScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+NamedTuplestoreScanNext(NamedTuplestoreScanState *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;
+ (void) tuplestore_gettupleslot(node->relation, true, false, slot);
+ return slot;
+}
+
+/*
+ * NamedTuplestoreScanRecheck -- access method routine to recheck a tuple in
+ * EvalPlanQual
+ */
+static bool
+NamedTuplestoreScanRecheck(NamedTuplestoreScanState *node, TupleTableSlot *slot)
+{
+ /* nothing to check */
+ return true;
+}
+
+/* ----------------------------------------------------------------
+ * ExecNamedTuplestoreScan(node)
+ *
+ * Scans the CTE sequentially and returns the next qualifying tuple.
+ * We call the ExecScan() routine and pass it the appropriate
+ * access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecNamedTuplestoreScan(NamedTuplestoreScanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) NamedTuplestoreScanNext,
+ (ExecScanRecheckMtd) NamedTuplestoreScanRecheck);
+}
+
+
+/* ----------------------------------------------------------------
+ * ExecInitNamedTuplestoreScan
+ * ----------------------------------------------------------------
+ */
+NamedTuplestoreScanState *
+ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflags)
+{
+ NamedTuplestoreScanState *scanstate;
+ EphemeralNamedRelation enr;
+
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+ /*
+ * NamedTuplestoreScan should not have any children.
+ */
+ Assert(outerPlan(node) == NULL);
+ Assert(innerPlan(node) == NULL);
+
+ /*
+ * create new NamedTuplestoreScanState for node
+ */
+ scanstate = makeNode(NamedTuplestoreScanState);
+ scanstate->ss.ps.plan = (Plan *) node;
+ scanstate->ss.ps.state = estate;
+
+ enr = get_ENR(estate->es_queryEnv, node->enrname);
+ if (!enr)
+ elog(ERROR, "executor could not find named tuplestore \"%s\"",
+ node->enrname);
+
+ Assert(enr->reldata);
+ scanstate->relation = (Tuplestorestate *) enr->reldata;
+ scanstate->tupdesc = ENRMetadataGetTupDesc(&(enr->md));
+ scanstate->readptr =
+ tuplestore_alloc_read_pointer(scanstate->relation, 0);
+
+ /*
+ * The new read pointer copies its position from read pointer 0, which
+ * could be anywhere, so explicitly rewind it.
+ */
+ tuplestore_rescan(scanstate->relation);
+
+ /*
+ * XXX: Should we add a function to free that read pointer when done?
+ * This was attempted, but it did not improve performance or memory usage
+ * in any tested cases.
+ */
+
+ /*
+ * Miscellaneous initialization
+ *
+ * create expression context for node
+ */
+ ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+ /*
+ * initialize child expressions
+ */
+ scanstate->ss.ps.qual =
+ ExecInitQual(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);
+
+ return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEndNamedTuplestoreScan
+ *
+ * frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndNamedTuplestoreScan(NamedTuplestoreScanState *node)
+{
+ /*
+ * Free exprcontext
+ */
+ ExecFreeExprContext(&node->ss.ps);
+
+ /*
+ * clean out the tuple table
+ */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+}
+
+/* ----------------------------------------------------------------
+ * ExecReScanNamedTuplestoreScan
+ *
+ * Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanNamedTuplestoreScan(NamedTuplestoreScanState *node)
+{
+ Tuplestorestate *tuplestorestate = node->relation;
+
+ 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 eeaa4805e4..54c022d013 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
@@ -1193,7 +1194,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
*/
/* Replan if needed, and increment plan refcount for portal */
- cplan = GetCachedPlan(plansource, paramLI, false);
+ cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
stmt_list = cplan->stmt_list;
/* Pop the error context stack */
@@ -1532,6 +1533,10 @@ SPI_result_code_string(int code)
return "SPI_ERROR_NOOUTFUNC";
case SPI_ERROR_TYPUNKNOWN:
return "SPI_ERROR_TYPUNKNOWN";
+ case SPI_ERROR_REL_DUPLICATE:
+ return "SPI_ERROR_REL_DUPLICATE";
+ case SPI_ERROR_REL_NOT_FOUND:
+ return "SPI_ERROR_REL_NOT_FOUND";
case SPI_OK_CONNECT:
return "SPI_OK_CONNECT";
case SPI_OK_FINISH:
@@ -1560,6 +1565,10 @@ SPI_result_code_string(int code)
return "SPI_OK_UPDATE_RETURNING";
case SPI_OK_REWRITTEN:
return "SPI_OK_REWRITTEN";
+ case SPI_OK_REL_REGISTER:
+ return "SPI_OK_REL_REGISTER";
+ case SPI_OK_REL_UNREGISTER:
+ return "SPI_OK_REL_UNREGISTER";
}
/* Unrecognized code ... return something useful ... */
sprintf(buf, "Unrecognized SPI code %d", code);
@@ -1615,7 +1624,8 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
error_context_stack = &spierrcontext;
/* Get the generic plan for the query */
- cplan = GetCachedPlan(plansource, NULL, plan->saved);
+ cplan = GetCachedPlan(plansource, NULL, plan->saved,
+ _SPI_current->queryEnv);
Assert(cplan == plansource->gplan);
/* Pop the error context stack */
@@ -1767,7 +1777,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
*/
plansource = CreateCachedPlan(parsetree,
src,
- CreateCommandTag(parsetree->stmt));
+ CreateCommandTag(parsetree->stmt),
+ _SPI_current->queryEnv);
/*
* Parameter datatypes are driven by parserSetup hook if provided,
@@ -1779,14 +1790,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 +1988,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 */
@@ -2001,7 +2016,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
* Replan if needed, and increment plan refcount. If it's a saved
* plan, the refcount must be backed by the CurrentResourceOwner.
*/
- cplan = GetCachedPlan(plansource, paramLI, plan->saved);
+ cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
stmt_list = cplan->stmt_list;
/*
@@ -2081,7 +2096,8 @@ _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);
@@ -2094,6 +2110,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
plansource->query_string,
PROCESS_UTILITY_QUERY,
paramLI,
+ _SPI_current->queryEnv,
dest,
completionTag);
@@ -2619,3 +2636,84 @@ _SPI_save_plan(SPIPlanPtr plan)
return newplan;
}
+
+/*
+ * Internal lookup of ephemeral named relation by name.
+ */
+static EphemeralNamedRelation
+_SPI_find_ENR_by_name(const char *name)
+{
+ /* internal static function; any error is bug in SPI itself */
+ Assert(name != NULL);
+
+ /* fast exit if no tuplestores have been added */
+ if (_SPI_current->queryEnv == NULL)
+ return NULL;
+
+ return get_ENR(_SPI_current->queryEnv, name);
+}
+
+/*
+ * Register an ephemeral named relation for use by the planner and executor on
+ * subsequent calls using this SPI connection.
+ */
+int
+SPI_register_relation(EphemeralNamedRelation enr)
+{
+ EphemeralNamedRelation match;
+ int res;
+
+ if (enr == NULL || enr->md.name == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(false); /* keep current memory context */
+ if (res < 0)
+ return res;
+
+ match = _SPI_find_ENR_by_name(enr->md.name);
+ if (match)
+ res = SPI_ERROR_REL_DUPLICATE;
+ else
+ {
+ if (_SPI_current->queryEnv == NULL)
+ _SPI_current->queryEnv = create_queryEnv();
+
+ register_ENR(_SPI_current->queryEnv, enr);
+ res = SPI_OK_REL_REGISTER;
+ }
+
+ _SPI_end_call(false);
+
+ return res;
+}
+
+/*
+ * Unregister an ephemeral named relation by name. This will probably be a
+ * rarely used function, since SPI_finish will clear it automatically.
+ */
+int
+SPI_unregister_relation(const char *name)
+{
+ EphemeralNamedRelation match;
+ int res;
+
+ if (name == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(false); /* keep current memory context */
+ if (res < 0)
+ return res;
+
+ match = _SPI_find_ENR_by_name(name);
+ if (match)
+ {
+ unregister_ENR(_SPI_current->queryEnv, match->md.name);
+ res = SPI_OK_REL_UNREGISTER;
+ }
+ else
+ res = SPI_ERROR_REL_NOT_FOUND;
+
+ _SPI_end_call(false);
+
+ return res;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c88d601bd..61bc5025e2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -682,6 +682,27 @@ _copyCteScan(const CteScan *from)
return newnode;
}
+/*
+ * _copyNamedTuplestoreScan
+ */
+static NamedTuplestoreScan *
+_copyNamedTuplestoreScan(const NamedTuplestoreScan *from)
+{
+ NamedTuplestoreScan *newnode = makeNode(NamedTuplestoreScan);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_STRING_FIELD(enrname);
+
+ return newnode;
+}
+
/*
* _copyWorkTableScan
*/
@@ -2265,6 +2286,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
COPY_STRING_FIELD(ctename);
COPY_SCALAR_FIELD(ctelevelsup);
COPY_SCALAR_FIELD(self_reference);
+ COPY_STRING_FIELD(enrname);
COPY_NODE_FIELD(coltypes);
COPY_NODE_FIELD(coltypmods);
COPY_NODE_FIELD(colcollations);
@@ -4706,6 +4728,9 @@ copyObjectImpl(const void *from)
case T_CteScan:
retval = _copyCteScan(from);
break;
+ case T_NamedTuplestoreScan:
+ retval = _copyNamedTuplestoreScan(from);
+ break;
case T_WorkTableScan:
retval = _copyWorkTableScan(from);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..d5293a1a78 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2321,6 +2321,7 @@ range_table_walker(List *rtable,
return true;
break;
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY:
@@ -3135,6 +3136,7 @@ range_table_mutator(List *rtable,
/* we don't bother to copy eref, aliases, etc; OK? */
break;
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0b45c25a49..766ca49216 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -631,6 +631,16 @@ _outCteScan(StringInfo str, const CteScan *node)
WRITE_INT_FIELD(cteParam);
}
+static void
+_outNamedTuplestoreScan(StringInfo str, const NamedTuplestoreScan *node)
+{
+ WRITE_NODE_TYPE("NAMEDTUPLESTORESCAN");
+
+ _outScanInfo(str, (const Scan *) node);
+
+ WRITE_STRING_FIELD(enrname);
+}
+
static void
_outWorkTableScan(StringInfo str, const WorkTableScan *node)
{
@@ -3024,6 +3034,13 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_NODE_FIELD(coltypmods);
WRITE_NODE_FIELD(colcollations);
break;
+ case RTE_NAMEDTUPLESTORE:
+ WRITE_STRING_FIELD(enrname);
+ WRITE_OID_FIELD(relid);
+ WRITE_NODE_FIELD(coltypes);
+ WRITE_NODE_FIELD(coltypmods);
+ WRITE_NODE_FIELD(colcollations);
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
break;
@@ -3621,6 +3638,9 @@ outNode(StringInfo str, const void *obj)
case T_CteScan:
_outCteScan(str, obj);
break;
+ case T_NamedTuplestoreScan:
+ _outNamedTuplestoreScan(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 dfb8bfa803..380e8b71f2 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -291,6 +291,10 @@ print_rt(const List *rtable)
printf("%d\t%s\t[cte]",
i, rte->eref->aliasname);
break;
+ case RTE_NAMEDTUPLESTORE:
+ printf("%d\t%s\t[tuplestore]",
+ 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 474f221a75..766f2d8db1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1355,6 +1355,13 @@ _readRangeTblEntry(void)
READ_NODE_FIELD(coltypmods);
READ_NODE_FIELD(colcollations);
break;
+ case RTE_NAMEDTUPLESTORE:
+ READ_STRING_FIELD(enrname);
+ READ_OID_FIELD(relid);
+ READ_NODE_FIELD(coltypes);
+ READ_NODE_FIELD(coltypmods);
+ READ_NODE_FIELD(colcollations);
+ 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 a1e1a87c29..343b35aa32 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -111,6 +111,8 @@ static void set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
+static void set_namedtuplestore_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);
@@ -396,6 +398,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
else
set_cte_pathlist(root, rel, rte);
break;
+ case RTE_NAMEDTUPLESTORE:
+ set_namedtuplestore_pathlist(root, rel, rte);
+ break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break;
@@ -464,6 +469,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
case RTE_CTE:
/* CTE reference --- fully handled during set_rel_size */
break;
+ case RTE_NAMEDTUPLESTORE:
+ /* tuplestore reference --- fully handled during set_rel_size */
+ break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break;
@@ -639,6 +647,13 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* executed only once.
*/
return;
+
+ case RTE_NAMEDTUPLESTORE:
+ /*
+ * tuplestore cannot be shared, at least without more
+ * infrastructure to support that.
+ */
+ return;
}
/*
@@ -2089,6 +2104,36 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
add_path(rel, create_ctescan_path(root, rel, required_outer));
}
+/*
+ * set_namedtuplestore_pathlist
+ * Build the (single) access path for a named tuplestore RTE
+ *
+ * There's no need for a separate set_namedtuplestore_size phase, since we
+ * don't support join-qual-parameterized paths for tuplestores.
+ */
+static void
+set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ RangeTblEntry *rte)
+{
+ Relids required_outer;
+
+ /* Mark rel with estimated output rows, width, etc */
+ set_namedtuplestore_size_estimates(root, rel);
+
+ /*
+ * We don't support pushing join clauses into the quals of a tuplestore
+ * 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_namedtuplestorescan_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 92de2b7d48..ed07e2f655 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1516,6 +1516,43 @@ cost_ctescan(Path *path, PlannerInfo *root,
path->total_cost = startup_cost + run_cost;
}
+/*
+ * cost_namedtuplestorescan
+ * Determines and returns the cost of scanning a named tuplestore.
+ */
+void
+cost_namedtuplestorescan(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 Tuplestores */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_NAMEDTUPLESTORE);
+
+ /* 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,
@@ -4684,6 +4721,39 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows)
set_baserel_size_estimates(root, rel);
}
+/*
+ * set_namedtuplestore_size_estimates
+ * Set the size estimates for a base relation that is a tuplestore 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_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+{
+ RangeTblEntry *rte;
+
+ /* Should only be applied to base relations that are tuplestore references */
+ Assert(rel->relid > 0);
+ rte = planner_rt_fetch(rel->relid, root);
+ Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
+
+ /*
+ * Use the estimate provided by the code which is generating the named
+ * tuplestore. In some cases, the actual number might be available; in
+ * others the same plan will be re-used, so a "typical" value might be
+ * estimated and used.
+ */
+ rel->tuples = rte->enrtuples;
+ if (rel->tuples < 0)
+ rel->tuples = 1000;
+
+ /* 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 ed06a8de78..2a78595e1f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -139,6 +139,8 @@ static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_pa
List *tlist, List *scan_clauses);
static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
+static NamedTuplestoreScan *create_namedtuplestorescan_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,
@@ -197,6 +199,8 @@ static TableFuncScan *make_tablefuncscan(List *qptlist, List *qpqual,
Index scanrelid, TableFunc *tablefunc);
static CteScan *make_ctescan(List *qptlist, List *qpqual,
Index scanrelid, int ctePlanId, int cteParam);
+static NamedTuplestoreScan *make_namedtuplestorescan(List *qptlist, List *qpqual,
+ Index scanrelid, char *enrname);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
static Append *make_append(List *appendplans, List *tlist, List *partitioned_rels);
@@ -366,6 +370,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_NamedTuplestoreScan:
case T_ForeignScan:
case T_CustomScan:
plan = create_scan_plan(root, best_path, flags);
@@ -668,6 +673,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
scan_clauses);
break;
+ case T_NamedTuplestoreScan:
+ plan = (Plan *) create_namedtuplestorescan_plan(root,
+ best_path,
+ tlist,
+ scan_clauses);
+ break;
+
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
@@ -3285,6 +3297,45 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
return scan_plan;
}
+/*
+ * create_namedtuplestorescan_plan
+ * Returns a tuplestorescan plan for the base relation scanned by
+ * 'best_path' with restriction clauses 'scan_clauses' and targetlist
+ * 'tlist'.
+ */
+static NamedTuplestoreScan *
+create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses)
+{
+ NamedTuplestoreScan *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_NAMEDTUPLESTORE);
+
+ /* 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_namedtuplestorescan(tlist, scan_clauses, scan_relid,
+ rte->enrname);
+
+ 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'
@@ -5120,6 +5171,26 @@ make_ctescan(List *qptlist,
return node;
}
+static NamedTuplestoreScan *
+make_namedtuplestorescan(List *qptlist,
+ List *qpqual,
+ Index scanrelid,
+ char *enrname)
+{
+ NamedTuplestoreScan *node = makeNode(NamedTuplestoreScan);
+ 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->enrname = enrname;
+
+ 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 4e3f6ee960..cdb8e95deb 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -591,6 +591,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
}
break;
+ case T_NamedTuplestoreScan:
+ {
+ NamedTuplestoreScan *splan = (NamedTuplestoreScan *) 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;
@@ -2571,6 +2582,11 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
if (rte->rtekind == RTE_RELATION)
context->glob->relationOids =
lappend_oid(context->glob->relationOids, rte->relid);
+ else if (rte->rtekind == RTE_NAMEDTUPLESTORE &&
+ OidIsValid(rte->relid))
+ context->glob->relationOids =
+ lappend_oid(context->glob->relationOids,
+ rte->relid);
}
/* And recurse into the query's subexpressions */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index db0e5b31e2..87cc44d678 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2476,6 +2476,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
context.paramids = bms_add_members(context.paramids, scan_params);
break;
+ case T_NamedTuplestoreScan:
+ 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 348c6b791f..749ea805f8 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1123,6 +1123,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
break;
case RTE_JOIN:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/* these can't contain any lateral references */
break;
}
@@ -1977,6 +1978,7 @@ replace_vars_in_jointree(Node *jtnode,
break;
case RTE_JOIN:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/* 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 a578867cce..59d71c1b32 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4910,7 +4910,7 @@ 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 c6298072c9..8536212177 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1892,6 +1892,32 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
return pathnode;
}
+/*
+ * create_namedtuplestorescan_path
+ * Creates a path corresponding to a scan of a named tuplestore, returning
+ * the pathnode.
+ */
+Path *
+create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
+ Relids required_outer)
+{
+ Path *pathnode = makeNode(Path);
+
+ pathnode->pathtype = T_NamedTuplestoreScan;
+ 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_namedtuplestorescan(pathnode, root, rel, pathnode->param_info);
+
+ return pathnode;
+}
+
/*
* create_worktablescan_path
* Creates a path corresponding to a scan of a self-reference CTE,
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index cc88dcc28e..1cd21c0fdc 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1446,9 +1446,9 @@ relation_excluded_by_constraints(PlannerInfo *root,
* dropped cols.
*
* We also support building a "physical" tlist for subqueries, functions,
- * values lists, table expressions and CTEs, since the same optimization can
- * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc
- * and WorkTableScan nodes.
+ * values lists, table expressions, and CTEs, since the same optimization can
+ * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc,
+ * NamedTuplestoreScan, and WorkTableScan nodes.
*/
List *
build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -1523,6 +1523,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/* 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 6ab78545c3..7912df0baa 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -156,6 +156,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
/*
* Subquery, function, tablefunc, or values list --- set up attr
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index df9a9fbb35..4b97f83803 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -14,8 +14,9 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o scan.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
- parse_expr.o parse_func.o parse_node.o parse_oper.o parse_param.o \
- parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
+ parse_enr.o parse_expr.o parse_func.o parse_node.o parse_oper.o \
+ parse_param.o parse_relation.o parse_target.o parse_type.o \
+ parse_utilcmd.o scansup.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 8f11c46621..811fccaec9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -94,7 +94,8 @@ static bool test_raw_expression_coverage(Node *node, void *context);
*/
Query *
parse_analyze(RawStmt *parseTree, const char *sourceText,
- Oid *paramTypes, int numParams)
+ Oid *paramTypes, int numParams,
+ QueryEnvironment *queryEnv)
{
ParseState *pstate = make_parsestate(NULL);
Query *query;
@@ -106,6 +107,8 @@ parse_analyze(RawStmt *parseTree, const char *sourceText,
if (numParams > 0)
parse_fixed_parameters(pstate, paramTypes, numParams);
+ pstate->p_queryEnv = queryEnv;
+
query = transformTopLevelStmt(pstate, parseTree);
if (post_parse_analyze_hook)
@@ -2799,6 +2802,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
+ case RTE_NAMEDTUPLESTORE:
+ 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 a named tuplestore",
+ 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 4f391d2d41..e268a127d1 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -59,9 +59,12 @@ static Node *transformJoinUsingClause(ParseState *pstate,
List *leftVars, List *rightVars);
static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
List *namespace);
+static RangeTblEntry *getRTEForSpecialRelationTypes(ParseState *pstate,
+ RangeVar *rv);
static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
CommonTableExpr *cte, Index levelsup);
+static RangeTblEntry *transformENRReference(ParseState *pstate, RangeVar *r);
static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
RangeSubselect *r);
static RangeTblEntry *transformRangeFunction(ParseState *pstate,
@@ -181,6 +184,14 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
RangeTblEntry *rte;
int rtindex;
+ /* So far special relations are immutable; so they cannot be targets. */
+ rte = getRTEForSpecialRelationTypes(pstate, relation);
+ if (rte != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("relation \"%s\" cannot be the target of a modifying statement",
+ relation->relname)));
+
/* Close old target; this could only happen for multi-action rules */
if (pstate->p_target_relation != NULL)
heap_close(pstate->p_target_relation, NoLock);
@@ -434,6 +445,20 @@ transformCTEReference(ParseState *pstate, RangeVar *r,
return rte;
}
+/*
+ * transformENRReference --- transform a RangeVar that references an ephemeral
+ * named relation
+ */
+static RangeTblEntry *
+transformENRReference(ParseState *pstate, RangeVar *r)
+{
+ RangeTblEntry *rte;
+
+ rte = addRangeTableEntryForENR(pstate, r, true);
+
+ return rte;
+}
+
/*
* transformRangeSubselect --- transform a sub-SELECT appearing in FROM
*/
@@ -1021,6 +1046,24 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
return tablesample;
}
+
+static RangeTblEntry *
+getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
+{
+
+ CommonTableExpr *cte;
+ Index levelsup;
+ RangeTblEntry *rte = NULL;
+
+ cte = scanNameSpaceForCTE(pstate, rv->relname, &levelsup);
+ if (cte)
+ rte = transformCTEReference(pstate, rv, cte, levelsup);
+ if (!rte && scanNameSpaceForENR(pstate, rv->relname))
+ rte = transformENRReference(pstate, rv);
+
+ return rte;
+}
+
/*
* transformFromClauseItem -
* Transform a FROM-clause item, adding any required entries to the
@@ -1055,18 +1098,14 @@ transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry *rte = NULL;
int rtindex;
- /* if it is an unqualified name, it might be a CTE reference */
+ /*
+ * if it is an unqualified name, it might be a CTE or tuplestore
+ * reference
+ */
if (!rv->schemaname)
- {
- CommonTableExpr *cte;
- Index levelsup;
+ rte = getRTEForSpecialRelationTypes(pstate, rv);
- cte = scanNameSpaceForCTE(pstate, rv->relname, &levelsup);
- if (cte)
- rte = transformCTEReference(pstate, rv, cte, levelsup);
- }
-
- /* if not found as a CTE, must be a table reference */
+ /* if not found above, must be a table reference */
if (!rte)
rte = transformTableEntry(pstate, rv);
diff --git a/src/backend/parser/parse_enr.c b/src/backend/parser/parse_enr.c
new file mode 100644
index 0000000000..1cfcf65a51
--- /dev/null
+++ b/src/backend/parser/parse_enr.c
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_enr.c
+ * parser support routines dealing with ephemeral named relations
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/parser/parse_enr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "parser/parse_enr.h"
+
+bool
+name_matches_visible_ENR(ParseState *pstate, const char *refname)
+{
+ return (get_visible_ENR_metadata(pstate->p_queryEnv, refname) != NULL);
+}
+
+EphemeralNamedRelationMetadata
+get_visible_ENR(ParseState *pstate, const char *refname)
+{
+ return get_visible_ENR_metadata(pstate->p_queryEnv, refname);
+}
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 30cc7dadca..34006c70cd 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -62,6 +62,8 @@ make_parsestate(ParseState *parentParseState)
pstate->p_paramref_hook = parentParseState->p_paramref_hook;
pstate->p_coerce_param_hook = parentParseState->p_coerce_param_hook;
pstate->p_ref_hook_state = parentParseState->p_ref_hook_state;
+ /* query environment stays in context for the whole parse analysis */
+ pstate->p_queryEnv = parentParseState->p_queryEnv;
}
return pstate;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2c19e0cbf5..7db13f37f7 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -25,6 +25,7 @@
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
+#include "parser/parse_enr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
@@ -281,6 +282,16 @@ isFutureCTE(ParseState *pstate, const char *refname)
return false;
}
+/*
+ * Search the query's ephemeral named relation namespace for a relation
+ * matching the given unqualified refname.
+ */
+bool
+scanNameSpaceForENR(ParseState *pstate, const char *refname)
+{
+ return name_matches_visible_ENR(pstate, refname);
+}
+
/*
* searchRangeTableForRel
* See if any RangeTblEntry could possibly match the RangeVar.
@@ -302,6 +313,7 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
const char *refname = relation->relname;
Oid relId = InvalidOid;
CommonTableExpr *cte = NULL;
+ bool isenr = false;
Index ctelevelsup = 0;
Index levelsup;
@@ -318,11 +330,16 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
* unlocked.
*/
if (!relation->schemaname)
+ {
cte = scanNameSpaceForCTE(pstate, refname, &ctelevelsup);
- if (!cte)
+ if (!cte)
+ isenr = scanNameSpaceForENR(pstate, refname);
+ }
+
+ if (!cte && !isenr)
relId = RangeVarGetRelid(relation, NoLock, true);
- /* Now look for RTEs matching either the relation/CTE or the alias */
+ /* Now look for RTEs matching either the relation/CTE/ENR or the alias */
for (levelsup = 0;
pstate != NULL;
pstate = pstate->parentParseState, levelsup++)
@@ -342,6 +359,10 @@ searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
rte->ctelevelsup + levelsup == ctelevelsup &&
strcmp(rte->ctename, refname) == 0)
return rte;
+ if (rte->rtekind == RTE_NAMEDTUPLESTORE &&
+ isenr &&
+ strcmp(rte->enrname, refname) == 0)
+ return rte;
if (strcmp(rte->eref->aliasname, refname) == 0)
return rte;
}
@@ -1138,13 +1159,18 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
relation->schemaname, relation->relname)));
else
{
+ /*
+ * An unqualified name might be a named ephemeral relation.
+ */
+ if (get_visible_ENR_metadata(pstate->p_queryEnv, relation->relname))
+ rel = NULL;
/*
* An unqualified name might have been meant as a reference to
* some not-yet-in-scope CTE. The bare "does not exist" message
* has proven remarkably unhelpful for figuring out such problems,
* so we take pains to offer a specific hint.
*/
- if (isFutureCTE(pstate, relation->relname))
+ else if (isFutureCTE(pstate, relation->relname))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
@@ -1940,6 +1966,102 @@ addRangeTableEntryForCTE(ParseState *pstate,
return rte;
}
+/*
+ * Add an entry for an ephemeral named relation reference to the pstate's
+ * range table (p_rtable).
+ *
+ * It is expected that the RangeVar, which up until now is only known to be an
+ * ephemeral named relation, will (in conjunction with the QueryEnvironment in
+ * the ParseState), create a RangeTblEntry for a specific *kind* of ephemeral
+ * named relation, based on enrtype.
+ *
+ * This is much like addRangeTableEntry() except that it makes an RTE for an
+ * ephemeral named relation.
+ */
+RangeTblEntry *
+addRangeTableEntryForENR(ParseState *pstate,
+ RangeVar *rv,
+ bool inFromCl)
+{
+ RangeTblEntry *rte = makeNode(RangeTblEntry);
+ Alias *alias = rv->alias;
+ char *refname = alias ? alias->aliasname : rv->relname;
+ EphemeralNamedRelationMetadata enrmd =
+ get_visible_ENR(pstate, rv->relname);
+ TupleDesc tupdesc;
+ int attno;
+
+ Assert(enrmd != NULL);
+
+ switch (enrmd->enrtype)
+ {
+ case ENR_NAMED_TUPLESTORE:
+ rte->rtekind = RTE_NAMEDTUPLESTORE;
+ break;
+
+ default:
+ elog(ERROR, "unexpected enrtype of %i", enrmd->enrtype);
+ return NULL; /* for fussy compilers */
+ }
+
+ /*
+ * Record dependency on a relation. This allows plans to be invalidated
+ * if they access transition tables linked to a table that is altered.
+ */
+ rte->relid = enrmd->reliddesc;
+
+ /*
+ * Build the list of effective column names using user-supplied aliases
+ * and/or actual column names. Also build the cannibalized fields.
+ */
+ tupdesc = ENRMetadataGetTupDesc(enrmd);
+ rte->eref = makeAlias(refname, NIL);
+ buildRelationAliases(tupdesc, alias, rte->eref);
+ rte->enrname = enrmd->name;
+ rte->enrtuples = enrmd->enrtuples;
+ rte->coltypes = NIL;
+ rte->coltypmods = NIL;
+ rte->colcollations = 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->coltypes =
+ lappend_oid(rte->coltypes,
+ tupdesc->attrs[attno - 1]->atttypid);
+ rte->coltypmods =
+ lappend_int(rte->coltypmods,
+ tupdesc->attrs[attno - 1]->atttypmod);
+ rte->colcollations =
+ lappend_oid(rte->colcollations,
+ tupdesc->attrs[attno - 1]->attcollation);
+ }
+
+ /*
+ * Set flags and access permissions.
+ *
+ * ENRs are never checked for access rights.
+ */
+ rte->lateral = false;
+ rte->inh = false; /* never true for ENRs */
+ 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?
@@ -2292,6 +2414,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
{
/* Tablefunc, Values or CTE RTE */
ListCell *aliasp_item = list_head(rte->eref->colnames);
@@ -2705,6 +2828,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
{
/*
* tablefunc, VALUES or CTE RTE --- get type info from lists
@@ -2762,6 +2886,19 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
*/
result = false;
break;
+ case RTE_NAMEDTUPLESTORE:
+ {
+ Assert(rte->enrname);
+
+ /*
+ * 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.
+ */
+ result =
+ (list_nth(rte->coltypes, attnum - 1) != InvalidOid);
+ }
+ break;
case RTE_JOIN:
{
/*
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3b84140a9b..c46c3b38a4 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -397,6 +397,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
case RTE_FUNCTION:
case RTE_VALUES:
case RTE_TABLEFUNC:
+ case RTE_NAMEDTUPLESTORE:
/* not a simple relation, leave it unmarked */
break;
case RTE_CTE:
@@ -1505,6 +1506,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
{
case RTE_RELATION:
case RTE_VALUES:
+ case RTE_NAMEDTUPLESTORE:
/*
* 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 3055b483b1..139c4c0f68 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(RawStmt *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(RawStmt *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(RawStmt *parsetree,
const char *query_string,
ParserSetupHook parserSetup,
- void *parserSetupArg)
+ void *parserSetupArg,
+ QueryEnvironment *queryEnv)
{
ParseState *pstate;
Query *query;
@@ -697,6 +700,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
pstate = make_parsestate(NULL);
pstate->p_sourcetext = query_string;
+ pstate->p_queryEnv = queryEnv;
(*parserSetup) (pstate, parserSetupArg);
query = transformTopLevelStmt(pstate, parsetree);
@@ -1024,7 +1028,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);
@@ -1314,7 +1318,8 @@ exec_parse_message(const char *query_string, /* string to execute */
* Create the CachedPlanSource before we do parse analysis, since it
* needs to see the unmodified raw parse tree.
*/
- psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
+ psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag,
+ NULL);
/*
* Set up a snapshot if parse analysis will need one.
@@ -1366,7 +1371,8 @@ exec_parse_message(const char *query_string, /* string to execute */
/* Empty input string. This is legal. */
raw_parse_tree = NULL;
commandTag = NULL;
- psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
+ psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag,
+ NULL);
querytree_list = NIL;
}
@@ -1769,7 +1775,7 @@ exec_bind_message(StringInfo input_message)
* will be generated in MessageContext. The plan refcount will be
* assigned to the Portal, so it will be released at portal destruction.
*/
- cplan = GetCachedPlan(psrc, params, false);
+ cplan = GetCachedPlan(psrc, params, false, NULL);
/*
* Now we can define the portal.
@@ -2367,7 +2373,7 @@ exec_describe_statement_message(const char *stmt_name)
List *tlist;
/* Get the plan's primary targetlist */
- tlist = CachedPlanGetTargetList(psrc);
+ tlist = CachedPlanGetTargetList(psrc, NULL);
SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index f538b7787c..988c9ff43c 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -38,6 +38,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);
@@ -69,6 +70,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
int instrument_options)
{
QueryDesc *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
@@ -81,6 +83,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? */
@@ -135,6 +138,7 @@ static void
ProcessQuery(PlannedStmt *plan,
const char *sourceText,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest,
char *completionTag)
{
@@ -145,7 +149,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
@@ -498,6 +502,7 @@ PortalStart(Portal portal, ParamListInfo params,
InvalidSnapshot,
None_Receiver,
params,
+ portal->queryEnv,
0);
/*
@@ -1175,6 +1180,7 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
portal->sourceText,
isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
portal->portalParams,
+ portal->queryEnv,
dest,
completionTag);
@@ -1281,6 +1287,7 @@ PortalRunMulti(Portal portal,
ProcessQuery(pstmt,
portal->sourceText,
portal->portalParams,
+ portal->queryEnv,
dest, completionTag);
}
else
@@ -1289,6 +1296,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 584f4f13cc..b610c8e7ce 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -78,6 +78,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);
@@ -333,6 +334,7 @@ ProcessUtility(PlannedStmt *pstmt,
const char *queryString,
ProcessUtilityContext context,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest,
char *completionTag)
{
@@ -347,11 +349,11 @@ ProcessUtility(PlannedStmt *pstmt,
*/
if (ProcessUtility_hook)
(*ProcessUtility_hook) (pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
standard_ProcessUtility(pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
}
@@ -371,6 +373,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
const char *queryString,
ProcessUtilityContext context,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest,
char *completionTag)
{
@@ -672,7 +675,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_ExplainStmt:
- ExplainQuery(pstate, (ExplainStmt *) parsetree, queryString, params, dest);
+ ExplainQuery(pstate, (ExplainStmt *) parsetree, queryString, params,
+ queryEnv, dest);
break;
case T_AlterSystemStmt:
@@ -819,7 +823,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsGrantObjectType(stmt->objtype))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecuteGrantStmt(stmt);
@@ -832,7 +836,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->removeType))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecDropStmt(stmt, isTopLevel);
@@ -845,7 +849,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->renameType))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecRenameStmt(stmt);
@@ -858,7 +862,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->objectType))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecAlterObjectDependsStmt(stmt, NULL);
@@ -871,7 +875,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->objectType))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecAlterObjectSchemaStmt(stmt, NULL);
@@ -884,7 +888,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->objectType))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecAlterOwnerStmt(stmt);
@@ -897,7 +901,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->objtype))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
CommentObject(stmt);
@@ -910,7 +914,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (EventTriggerSupportsObjectType(stmt->objtype))
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
else
ExecSecLabelStmt(stmt);
@@ -920,7 +924,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
default:
/* All other statement types have event trigger support */
ProcessUtilitySlow(pstate, pstmt, queryString,
- context, params,
+ context, params, queryEnv,
dest, completionTag);
break;
}
@@ -939,6 +943,7 @@ ProcessUtilitySlow(ParseState *pstate,
const char *queryString,
ProcessUtilityContext context,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest,
char *completionTag)
{
@@ -1062,6 +1067,7 @@ ProcessUtilitySlow(ParseState *pstate,
queryString,
PROCESS_UTILITY_SUBCOMMAND,
params,
+ NULL,
None_Receiver,
NULL);
}
@@ -1140,6 +1146,7 @@ ProcessUtilitySlow(ParseState *pstate,
queryString,
PROCESS_UTILITY_SUBCOMMAND,
params,
+ NULL,
None_Receiver,
NULL);
EventTriggerAlterTableStart(parsetree);
@@ -1438,7 +1445,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 c2681ced2a..0c1a201ecb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6710,6 +6710,7 @@ get_name_for_var_field(Var *var, int fieldno,
{
case RTE_RELATION:
case RTE_VALUES:
+ case RTE_NAMEDTUPLESTORE:
/*
* 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 043085d3a7..a116d5ed63 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -88,10 +88,11 @@
static CachedPlanSource *first_saved_plan = NULL;
static void ReleaseGenericPlan(CachedPlanSource *plansource);
-static List *RevalidateCachedQuery(CachedPlanSource *plansource);
+static List *RevalidateCachedQuery(CachedPlanSource *plansource,
+ QueryEnvironment *queryEnv);
static bool CheckCachedPlan(CachedPlanSource *plansource);
static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
- ParamListInfo boundParams);
+ ParamListInfo boundParams, QueryEnvironment *queryEnv);
static bool choose_custom_plan(CachedPlanSource *plansource,
ParamListInfo boundParams);
static double cached_plan_cost(CachedPlan *plan, bool include_planner);
@@ -150,7 +151,8 @@ InitPlanCache(void)
CachedPlanSource *
CreateCachedPlan(RawStmt *raw_parse_tree,
const char *query_string,
- const char *commandTag)
+ const char *commandTag,
+ QueryEnvironment *queryEnv)
{
CachedPlanSource *plansource;
MemoryContext source_context;
@@ -553,7 +555,8 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
* a tree copying step in a subsequent BuildCachedPlan call.)
*/
static List *
-RevalidateCachedQuery(CachedPlanSource *plansource)
+RevalidateCachedQuery(CachedPlanSource *plansource,
+ QueryEnvironment *queryEnv)
{
bool snapshot_set;
RawStmt *rawtree;
@@ -685,12 +688,14 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
tlist = pg_analyze_and_rewrite_params(rawtree,
plansource->query_string,
plansource->parserSetup,
- plansource->parserSetupArg);
+ plansource->parserSetupArg,
+ queryEnv);
else
tlist = pg_analyze_and_rewrite(rawtree,
plansource->query_string,
plansource->param_types,
- plansource->num_params);
+ plansource->num_params,
+ queryEnv);
/* Release snapshot if we got one */
if (snapshot_set)
@@ -875,7 +880,7 @@ CheckCachedPlan(CachedPlanSource *plansource)
*/
static CachedPlan *
BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
- ParamListInfo boundParams)
+ ParamListInfo boundParams, QueryEnvironment *queryEnv)
{
CachedPlan *plan;
List *plist;
@@ -899,7 +904,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
* safety, let's treat it as real and redo the RevalidateCachedQuery call.
*/
if (!plansource->is_valid)
- qlist = RevalidateCachedQuery(plansource);
+ qlist = RevalidateCachedQuery(plansource, queryEnv);
/*
* If we don't already have a copy of the querytree list that can be
@@ -1129,7 +1134,7 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
*/
CachedPlan *
GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
- bool useResOwner)
+ bool useResOwner, QueryEnvironment *queryEnv)
{
CachedPlan *plan = NULL;
List *qlist;
@@ -1143,7 +1148,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
/* Make sure the querytree list is valid and we have parse-time locks */
- qlist = RevalidateCachedQuery(plansource);
+ qlist = RevalidateCachedQuery(plansource, queryEnv);
/* Decide whether to use a custom plan */
customplan = choose_custom_plan(plansource, boundParams);
@@ -1159,7 +1164,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
else
{
/* Build a new generic plan */
- plan = BuildCachedPlan(plansource, qlist, NULL);
+ plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
/* Just make real sure plansource->gplan is clear */
ReleaseGenericPlan(plansource);
/* Link the new generic plan into the plansource */
@@ -1204,7 +1209,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
if (customplan)
{
/* Build a custom plan */
- plan = BuildCachedPlan(plansource, qlist, boundParams);
+ plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
/* Accumulate total costs of custom plans, but 'ware overflow */
if (plansource->num_custom_plans < INT_MAX)
{
@@ -1418,7 +1423,8 @@ CachedPlanIsValid(CachedPlanSource *plansource)
* within the cached plan, and may disappear next time the plan is updated.
*/
List *
-CachedPlanGetTargetList(CachedPlanSource *plansource)
+CachedPlanGetTargetList(CachedPlanSource *plansource,
+ QueryEnvironment *queryEnv)
{
Query *pstmt;
@@ -1434,7 +1440,7 @@ CachedPlanGetTargetList(CachedPlanSource *plansource)
return NIL;
/* Make sure the querytree list is valid and we have parse-time locks */
- RevalidateCachedQuery(plansource);
+ RevalidateCachedQuery(plansource, queryEnv);
/* Get the primary statement and find out what it returns */
pstmt = QueryListGetPrimaryStmt(plansource->query_list);
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index 45cdf76ec2..a53fcdf188 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -15,8 +15,8 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = backend_random.o guc.o help_config.o pg_config.o pg_controldata.o \
- pg_rusage.o ps_status.o rls.o sampling.o superuser.o timeout.o \
- tzparser.o
+ pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \
+ superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't substitute it into pg_config.h.
diff --git a/src/backend/utils/misc/queryenvironment.c b/src/backend/utils/misc/queryenvironment.c
new file mode 100644
index 0000000000..a0b10d402b
--- /dev/null
+++ b/src/backend/utils/misc/queryenvironment.c
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * queryenvironment.c
+ * Query environment, to store context-specific values like ephemeral named
+ * relations. Initial use is for named tuplestores for delta information
+ * from "normal" relations.
+ *
+ * The initial implementation uses a list because the number of such relations
+ * in any one context is expected to be very small. If that becomes a
+ * performance problem, the implementation can be changed with no other impact
+ * on callers, since this is an opaque structure. This is the reason to
+ * require a create function.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/backend/utils/misc/queryenvironment.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "utils/queryenvironment.h"
+#include "utils/rel.h"
+
+/*
+ * Private state of a query environment.
+ */
+struct QueryEnvironment
+{
+ List *namedRelList;
+};
+
+
+QueryEnvironment *
+create_queryEnv()
+{
+ return (QueryEnvironment *) palloc0(sizeof(QueryEnvironment));
+}
+
+EphemeralNamedRelationMetadata
+get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname)
+{
+ EphemeralNamedRelation enr;
+
+ Assert(refname != NULL);
+
+ if (queryEnv == NULL)
+ return NULL;
+
+ enr = get_ENR(queryEnv, refname);
+
+ if (enr)
+ return &(enr->md);
+
+ return NULL;
+}
+
+/*
+ * Register a named relation for use in the given environment.
+ *
+ * If this is intended exclusively for planning purposes, the tstate field can
+ * be left NULL;
+ */
+void
+register_ENR(QueryEnvironment *queryEnv, EphemeralNamedRelation enr)
+{
+ Assert(enr != NULL);
+ Assert(get_ENR(queryEnv, enr->md.name) == NULL);
+
+ queryEnv->namedRelList = lappend(queryEnv->namedRelList, enr);
+}
+
+/*
+ * Unregister an ephemeral relation by name. This will probably be a rarely
+ * used function, but seems like it should be provided "just in case".
+ */
+void
+unregister_ENR(QueryEnvironment *queryEnv, const char *name)
+{
+ EphemeralNamedRelation match;
+
+ match = get_ENR(queryEnv, name);
+ if (match)
+ queryEnv->namedRelList = list_delete(queryEnv->namedRelList, match);
+}
+
+/*
+ * This returns an ENR if there is a name match in the given collection. It
+ * must quietly return NULL if no match is found.
+ */
+EphemeralNamedRelation
+get_ENR(QueryEnvironment *queryEnv, const char *name)
+{
+ ListCell *lc;
+
+ Assert(name != NULL);
+
+ if (queryEnv == NULL)
+ return NULL;
+
+ foreach(lc, queryEnv->namedRelList)
+ {
+ EphemeralNamedRelation enr = (EphemeralNamedRelation) lfirst(lc);
+
+ if (strcmp(enr->md.name, name) == 0)
+ return enr;
+ }
+
+ return NULL;
+}
+
+/*
+ * Gets the TupleDesc for a Ephemeral Named Relation, based on which field was
+ * filled.
+ *
+ * When the TupleDesc is based on a relation from the catalogs, we count on
+ * that relation being used at the same time, so that appropriate locks will
+ * already be held. Locking here would be too late anyway.
+ */
+TupleDesc
+ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd)
+{
+ TupleDesc tupdesc;
+
+ /* One, and only one, of these fields must be filled. */
+ Assert((enrmd->reliddesc == InvalidOid) != (enrmd->tupdesc == NULL));
+
+ if (enrmd->tupdesc != NULL)
+ tupdesc = enrmd->tupdesc;
+ else
+ {
+ Relation relation;
+
+ relation = heap_open(enrmd->reliddesc, NoLock);
+ tupdesc = relation->rd_att;
+ heap_close(relation, NoLock);
+ }
+
+ return tupdesc;
+}
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index 84abf5f67e..b3f6be7457 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -109,6 +109,7 @@ struct Tuplestorestate
bool truncated; /* tuplestore_trim has removed tuples? */
int64 availMem; /* remaining memory available, in bytes */
int64 allowedMem; /* total memory allowed, in bytes */
+ int64 tuples; /* number of tuples added */
BufFile *myfile; /* underlying file, or NULL if none */
MemoryContext context; /* memory context for holding tuples */
ResourceOwner resowner; /* resowner for holding temp files */
@@ -267,6 +268,7 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
state->memtupdeleted = 0;
state->memtupcount = 0;
+ state->tuples = 0;
/*
* Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
@@ -433,6 +435,7 @@ tuplestore_clear(Tuplestorestate *state)
state->truncated = false;
state->memtupdeleted = 0;
state->memtupcount = 0;
+ state->tuples = 0;
readptr = state->readptrs;
for (i = 0; i < state->readptrcount; readptr++, i++)
{
@@ -533,6 +536,18 @@ tuplestore_select_read_pointer(Tuplestorestate *state, int ptr)
state->activeptr = ptr;
}
+/*
+ * tuplestore_tuple_count
+ *
+ * Returns the number of tuples added since creation or the last
+ * tuplestore_clear().
+ */
+int64
+tuplestore_tuple_count(Tuplestorestate *state)
+{
+ return state->tuples;
+}
+
/*
* tuplestore_ateof
*
@@ -753,6 +768,8 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
int i;
ResourceOwner oldowner;
+ state->tuples++;
+
switch (state->status)
{
case TSS_INMEM:
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index d067b757b0..48c5a570a0 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201703311
+#define CATALOG_VERSION_NO 201703312
#endif
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 7b78cc4a13..0fb9990e04 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -18,10 +18,11 @@
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
+#include "utils/queryenvironment.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 9191e186c1..b77f81db97 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -62,19 +62,20 @@ 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);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
- ExplainState *es,
- const char *queryString, ParamListInfo params);
+ ExplainState *es, 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 d8d22edbbc..c60e6f30b8 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -42,8 +42,8 @@ extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
DestReceiver *dest, char *completionTag);
extern void DeallocateQuery(DeallocateStmt *stmt);
extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into,
- ExplainState *es,
- const char *queryString, ParamListInfo params);
+ ExplainState *es, 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/executor/execdesc.h b/src/include/executor/execdesc.h
index 87e7ca8508..37de6f2011 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -40,6 +40,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; /* query environment passed in */
int instrument_options; /* OR of InstrumentOption flags */
/* These fields are set by ExecutorStart */
@@ -61,6 +62,7 @@ extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt,
Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
+ QueryEnvironment *queryEnv,
int instrument_options);
extern void FreeQueryDesc(QueryDesc *qdesc);
diff --git a/src/include/executor/nodeNamedtuplestorescan.h b/src/include/executor/nodeNamedtuplestorescan.h
new file mode 100644
index 0000000000..9ef477e7ff
--- /dev/null
+++ b/src/include/executor/nodeNamedtuplestorescan.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeNamedtuplestorescan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeNamedtuplestorescan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODENAMEDTUPLESTORESCAN_H
+#define NODENAMEDTUPLESTORESCAN_H
+
+#include "nodes/execnodes.h"
+
+extern NamedTuplestoreScanState *ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecNamedTuplestoreScan(NamedTuplestoreScanState *node);
+extern void ExecEndNamedTuplestoreScan(NamedTuplestoreScanState *node);
+extern void ExecReScanNamedTuplestoreScan(NamedTuplestoreScanState *node);
+
+#endif /* NODENAMEDTUPLESTORESCAN_H */
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index a18ae63245..e2e8bb9553 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_REL_DUPLICATE (-12)
+#define SPI_ERROR_REL_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_REL_REGISTER 15
+#define SPI_OK_REL_UNREGISTER 16
/* These used to be functions, now just no-ops for backwards compatibility */
#define SPI_push() ((void) 0)
@@ -146,6 +150,9 @@ 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(EphemeralNamedRelation enr);
+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 db8b59c387..49aa7c94e7 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -14,6 +14,7 @@
#define SPI_PRIV_H
#include "executor/spi.h"
+#include "utils/queryenvironment.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; /* query environment setup for SPI level */
} _SPI_connection;
/*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 11a68500ee..fa992449f4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -22,6 +22,7 @@
#include "nodes/params.h"
#include "nodes/plannodes.h"
#include "utils/hsearch.h"
+#include "utils/queryenvironment.h"
#include "utils/reltrigger.h"
#include "utils/sortsupport.h"
#include "utils/tuplestore.h"
@@ -431,6 +432,8 @@ typedef struct EState
ParamListInfo es_param_list_info; /* values of external params */
ParamExecData *es_param_exec_vals; /* values of internal params */
+ QueryEnvironment *es_queryEnv; /* query environment */
+
/* Other working state: */
MemoryContext es_query_cxt; /* per-query context in which EState lives */
@@ -1445,6 +1448,24 @@ typedef struct CteScanState
bool eof_cte; /* reached end of CTE query? */
} CteScanState;
+/* ----------------
+ * NamedTuplestoreScanState information
+ *
+ * NamedTuplestoreScan nodes are used to scan a Tuplestore created and
+ * named prior to execution of the query. An example is a transition
+ * table for an AFTER trigger.
+ *
+ * Multiple NamedTuplestoreScan nodes can read out from the same Tuplestore.
+ * ----------------
+ */
+typedef struct NamedTuplestoreScanState
+{
+ 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 *relation; /* the rows */
+} NamedTuplestoreScanState;
+
/* ----------------
* WorkTableScanState information
*
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 963ce45ae3..177853b3bf 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -63,6 +63,7 @@ typedef enum NodeTag
T_ValuesScan,
T_TableFuncScan,
T_CteScan,
+ T_NamedTuplestoreScan,
T_WorkTableScan,
T_ForeignScan,
T_CustomScan,
@@ -114,6 +115,7 @@ typedef enum NodeTag
T_TableFuncScanState,
T_ValuesScanState,
T_CteScanState,
+ T_NamedTuplestoreScanState,
T_WorkTableScanState,
T_ForeignScanState,
T_CustomScanState,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a71dd5b37..b2afd50818 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -906,7 +906,8 @@ typedef enum RTEKind
RTE_FUNCTION, /* function in FROM */
RTE_TABLEFUNC, /* TableFunc(.., column list) */
RTE_VALUES, /* VALUES (), (), ... */
- RTE_CTE /* common table expr (WITH list element) */
+ RTE_CTE, /* common table expr (WITH list element) */
+ RTE_NAMEDTUPLESTORE /* tuplestore, e.g. for AFTER triggers */
} RTEKind;
typedef struct RangeTblEntry
@@ -993,6 +994,9 @@ typedef struct RangeTblEntry
List *coltypmods; /* integer list of column typmods */
List *colcollations; /* OID list of column collation OIDs */
+ char *enrname; /* name of ephemeral named relation */
+ double enrtuples; /* estimated or actual from caller */
+
/*
* Fields valid in all RTEs:
*/
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6e531b6238..a2dd26f8a9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -527,6 +527,16 @@ typedef struct CteScan
int cteParam; /* ID of Param representing CTE output */
} CteScan;
+/* ----------------
+ * NamedTuplestoreScan node
+ * ----------------
+ */
+typedef struct NamedTuplestoreScan
+{
+ Scan scan;
+ char *enrname; /* Name given to Ephemeral Named Relation */
+} NamedTuplestoreScan;
+
/* ----------------
* WorkTableScan node
* ----------------
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index d9a9b12a06..6909359bcf 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -98,6 +98,8 @@ extern void cost_tablefuncscan(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_namedtuplestorescan(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,
@@ -187,6 +189,7 @@ 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_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
+extern void set_namedtuplestore_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);
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index c72c7e02cb..82d4e8701c 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -100,6 +100,8 @@ extern Path *create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
+extern Path *create_namedtuplestorescan_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 17259409a7..9b33ba5dfd 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -23,7 +23,7 @@ extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook;
extern Query *parse_analyze(RawStmt *parseTree, const char *sourceText,
- Oid *paramTypes, int numParams);
+ Oid *paramTypes, int numParams, QueryEnvironment *queryEnv);
extern Query *parse_analyze_varparams(RawStmt *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams);
diff --git a/src/include/parser/parse_enr.h b/src/include/parser/parse_enr.h
new file mode 100644
index 0000000000..48a7576f2c
--- /dev/null
+++ b/src/include/parser/parse_enr.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_enr.h
+ * Internal definitions for parser
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/parser/parse_enr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_ENR_H
+#define PARSE_ENR_H
+
+#include "parser/parse_node.h"
+
+extern bool name_matches_visible_ENR(ParseState *pstate, const char *refname);
+extern EphemeralNamedRelationMetadata get_visible_ENR(ParseState *pstate, const char *refname);
+
+#endif /* PARSE_ENR_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 3a25d9598d..1035bad322 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 "utils/queryenvironment.h"
#include "utils/relcache.h"
@@ -188,6 +189,8 @@ struct ParseState
bool p_resolve_unknowns; /* resolve unknown-type SELECT outputs
* as type text */
+ QueryEnvironment *p_queryEnv; /* curr env, incl refs to enclosing env */
+
/* Flags telling about things found in the query: */
bool p_hasAggs;
bool p_hasWindowFuncs;
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 515c06cfef..2f42cc8ef0 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -42,6 +42,7 @@ extern RangeTblEntry *refnameRangeTblEntry(ParseState *pstate,
extern CommonTableExpr *scanNameSpaceForCTE(ParseState *pstate,
const char *refname,
Index *ctelevelsup);
+extern bool scanNameSpaceForENR(ParseState *pstate, const char *refname);
extern void checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
List *namespace2);
extern int RTERangeTablePosn(ParseState *pstate,
@@ -107,6 +108,9 @@ extern RangeTblEntry *addRangeTableEntryForCTE(ParseState *pstate,
Index levelsup,
RangeVar *rv,
bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForENR(ParseState *pstate,
+ RangeVar *rv,
+ bool inFromCl);
extern bool isLockedRefname(ParseState *pstate, const char *refname);
extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
bool addToJoinList,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 1958be85b7..f1a34a1c72 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -24,6 +24,7 @@
#include "nodes/plannodes.h"
#include "storage/procsignal.h"
#include "utils/guc.h"
+#include "utils/queryenvironment.h"
/* Required daylight between max_stack_depth and the kernel limit, in bytes */
@@ -49,11 +50,13 @@ extern int log_statement;
extern List *pg_parse_query(const char *query_string);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
- Oid *paramTypes, int numParams);
+ Oid *paramTypes, int numParams,
+ QueryEnvironment *queryEnv);
extern List *pg_analyze_and_rewrite_params(RawStmt *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 4f8d353900..90f1215aec 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -26,15 +26,18 @@ typedef enum
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context,
- ParamListInfo params,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag);
extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString,
ProcessUtilityContext context, ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag);
extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
ProcessUtilityContext context, ParamListInfo params,
+ QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag);
extern bool UtilityReturnsTuples(Node *parsetree);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 84952d56e7..48d4ac94b2 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -17,6 +17,7 @@
#include "access/tupdesc.h"
#include "nodes/params.h"
+#include "utils/queryenvironment.h"
/* Forward declaration, to avoid including parsenodes.h here */
struct RawStmt;
@@ -148,7 +149,8 @@ extern void ResetPlanCache(void);
extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree,
const char *query_string,
- const char *commandTag);
+ const char *commandTag,
+ QueryEnvironment *queryEnv);
extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree,
const char *query_string,
const char *commandTag);
@@ -172,11 +174,13 @@ extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
-extern List *CachedPlanGetTargetList(CachedPlanSource *plansource);
+extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
+ QueryEnvironment *queryEnv);
extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
ParamListInfo boundParams,
- bool useResOwner);
+ bool useResOwner,
+ QueryEnvironment *queryEnv);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
#endif /* PLANCACHE_H */
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index e7c5a8bd09..ef3898c98c 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; /* environment for query */
/* Features/options */
PortalStrategy strategy; /* see above */
diff --git a/src/include/utils/queryenvironment.h b/src/include/utils/queryenvironment.h
new file mode 100644
index 0000000000..b4f65a1976
--- /dev/null
+++ b/src/include/utils/queryenvironment.h
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * queryenvironment.h
+ * Access to functions to mutate the query environment and retrieve the
+ * actual data related to entries (if any).
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/queryenvironment.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef QUERYENVIRONMENT_H
+#define QUERYENVIRONMENT_H
+
+#include "access/tupdesc.h"
+
+
+typedef enum EphemeralNameRelationType
+{
+ ENR_NAMED_TUPLESTORE /* named tuplestore relation; e.g., deltas */
+} EphemeralNameRelationType;
+
+/*
+ * Some ephemeral named relations must match some relation (e.g., trigger
+ * transition tables), so to properly handle cached plans and DDL, we should
+ * carry the OID of that relation. In other cases an ENR might be independent
+ * of any relation which is stored in the system catalogs, so we need to be
+ * able to directly store the TupleDesc. We never need both.
+ */
+typedef struct EphemeralNamedRelationMetadataData
+{
+ char *name; /* name used to identify the relation */
+
+ /* only one of the next two fields should be used */
+ Oid reliddesc; /* oid of relation to get tupdesc */
+ TupleDesc tupdesc; /* description of result rows */
+
+ EphemeralNameRelationType enrtype; /* to identify type of relation */
+ double enrtuples; /* estimated number of tuples */
+} EphemeralNamedRelationMetadataData;
+
+typedef EphemeralNamedRelationMetadataData *EphemeralNamedRelationMetadata;
+
+/*
+ * Ephemeral Named Relation data; used for parsing named relations not in the
+ * catalog, like transition tables in AFTER triggers.
+ */
+typedef struct EphemeralNamedRelationData
+{
+ EphemeralNamedRelationMetadataData md;
+ void *reldata; /* structure for execution-time access to data */
+} EphemeralNamedRelationData;
+
+typedef EphemeralNamedRelationData *EphemeralNamedRelation;
+
+/*
+ * This is an opaque structure outside of queryenvironment.c itself. The
+ * intention is to be able to change the implementation or add new context
+ * features without needing to change existing code for use of existing
+ * features.
+ */
+typedef struct QueryEnvironment QueryEnvironment;
+
+
+extern QueryEnvironment *create_queryEnv(void);
+extern EphemeralNamedRelationMetadata get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname);
+extern void register_ENR(QueryEnvironment *queryEnv, EphemeralNamedRelation enr);
+extern void unregister_ENR(QueryEnvironment *queryEnv, const char *name);
+extern EphemeralNamedRelation get_ENR(QueryEnvironment *queryEnv, const char *name);
+extern TupleDesc ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd);
+
+#endif /* QUERYENVIRONMENT_H */
diff --git a/src/include/utils/tuplestore.h b/src/include/utils/tuplestore.h
index a52a547037..b31ede882b 100644
--- a/src/include/utils/tuplestore.h
+++ b/src/include/utils/tuplestore.h
@@ -78,6 +78,8 @@ extern bool tuplestore_advance(Tuplestorestate *state, bool forward);
extern bool tuplestore_skiptuples(Tuplestorestate *state,
int64 ntuples, bool forward);
+extern int64 tuplestore_tuple_count(Tuplestorestate *state);
+
extern bool tuplestore_ateof(Tuplestorestate *state);
extern void tuplestore_rescan(Tuplestorestate *state);
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 3b7f689a98..fdcc4970a1 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2272,3 +2272,6 @@ with ordinality as (select 1 as x) select * from ordinality;
1
(1 row)
+-- check sane response to attempt to modify CTE relation
+WITH d AS (SELECT 42) INSERT INTO d VALUES (1);
+ERROR: relation "d" cannot be the target of a modifying statement
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 08ddc8bae0..8ae5184d0f 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -1028,3 +1028,6 @@ DROP RULE y_rule ON y;
create table foo (with baz); -- fail, WITH is a reserved word
create table foo (with ordinality); -- fail, WITH is a reserved word
with ordinality as (select 1 as x) select * from ordinality;
+
+-- check sane response to attempt to modify CTE relation
+WITH d AS (SELECT 42) INSERT INTO d VALUES (1);