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);