From 18ce3a4ab22d2984f8540ab480979c851dae5338 Mon Sep 17 00:00:00 2001 From: Kevin Grittner Date: Fri, 31 Mar 2017 23:17:18 -0500 Subject: [PATCH] Add infrastructure to support EphemeralNamedRelation references. A QueryEnvironment concept is added, which allows new types of objects to be passed into queries from parsing on through execution. At this point, the only thing implemented is a collection of EphemeralNamedRelation objects -- relations which can be referenced by name in queries, but do not exist in the catalogs. The only type of ENR implemented is NamedTuplestore, but provision is made to add more types fairly easily. An ENR can carry its own TupleDesc or reference a relation in the catalogs by relid. Although these features can be used without SPI, convenience functions are added to SPI so that ENRs can easily be used by code run through SPI. The initial use of all this is going to be transition tables in AFTER triggers, but that will be added to each PL as a separate commit. An incidental effect of this patch is to produce a more informative error message if an attempt is made to modify the contents of a CTE from a referencing DML statement. No tests previously covered that possibility, so one is added. Kevin Grittner and Thomas Munro Reviewed by Heikki Linnakangas, David Fetter, and Thomas Munro with valuable comments and suggestions from many others --- .../pg_stat_statements/pg_stat_statements.c | 15 +- doc/src/sgml/spi.sgml | 204 ++++++++++++++++++ src/backend/catalog/pg_proc.c | 3 +- src/backend/commands/copy.c | 5 +- src/backend/commands/createas.c | 5 +- src/backend/commands/explain.c | 40 ++-- src/backend/commands/extension.c | 6 +- src/backend/commands/foreigncmds.c | 2 +- src/backend/commands/matview.c | 2 +- src/backend/commands/prepare.c | 17 +- src/backend/commands/schemacmds.c | 1 + src/backend/commands/trigger.c | 9 +- src/backend/commands/view.c | 2 +- src/backend/executor/Makefile | 3 +- src/backend/executor/execAmi.c | 6 + src/backend/executor/execMain.c | 5 + src/backend/executor/execParallel.c | 2 +- src/backend/executor/execProcnode.c | 14 ++ src/backend/executor/execUtils.c | 2 + src/backend/executor/functions.c | 8 +- .../executor/nodeNamedtuplestorescan.c | 198 +++++++++++++++++ src/backend/executor/spi.c | 116 +++++++++- src/backend/nodes/copyfuncs.c | 25 +++ src/backend/nodes/nodeFuncs.c | 2 + src/backend/nodes/outfuncs.c | 20 ++ src/backend/nodes/print.c | 4 + src/backend/nodes/readfuncs.c | 7 + src/backend/optimizer/path/allpaths.c | 45 ++++ src/backend/optimizer/path/costsize.c | 70 ++++++ src/backend/optimizer/plan/createplan.c | 71 ++++++ src/backend/optimizer/plan/setrefs.c | 16 ++ src/backend/optimizer/plan/subselect.c | 4 + src/backend/optimizer/prep/prepjointree.c | 2 + src/backend/optimizer/util/clauses.c | 2 +- src/backend/optimizer/util/pathnode.c | 26 +++ src/backend/optimizer/util/plancat.c | 7 +- src/backend/optimizer/util/relnode.c | 1 + src/backend/parser/Makefile | 5 +- src/backend/parser/analyze.c | 14 +- src/backend/parser/parse_clause.c | 59 ++++- src/backend/parser/parse_enr.c | 29 +++ src/backend/parser/parse_node.c | 2 + src/backend/parser/parse_relation.c | 143 +++++++++++- src/backend/parser/parse_target.c | 2 + src/backend/tcop/postgres.c | 22 +- src/backend/tcop/pquery.c | 10 +- src/backend/tcop/utility.c | 34 +-- src/backend/utils/adt/ruleutils.c | 1 + src/backend/utils/cache/plancache.c | 34 +-- src/backend/utils/misc/Makefile | 4 +- src/backend/utils/misc/queryenvironment.c | 144 +++++++++++++ src/backend/utils/sort/tuplestore.c | 17 ++ src/include/catalog/catversion.h | 2 +- src/include/commands/createas.h | 3 +- src/include/commands/explain.h | 9 +- src/include/commands/prepare.h | 4 +- src/include/executor/execdesc.h | 2 + .../executor/nodeNamedtuplestorescan.h | 24 +++ src/include/executor/spi.h | 7 + src/include/executor/spi_priv.h | 2 + src/include/nodes/execnodes.h | 21 ++ src/include/nodes/nodes.h | 2 + src/include/nodes/parsenodes.h | 6 +- src/include/nodes/plannodes.h | 10 + src/include/optimizer/cost.h | 3 + src/include/optimizer/pathnode.h | 2 + src/include/parser/analyze.h | 2 +- src/include/parser/parse_enr.h | 22 ++ src/include/parser/parse_node.h | 3 + src/include/parser/parse_relation.h | 4 + src/include/tcop/tcopprot.h | 7 +- src/include/tcop/utility.h | 5 +- src/include/utils/plancache.h | 10 +- src/include/utils/portal.h | 1 + src/include/utils/queryenvironment.h | 74 +++++++ src/include/utils/tuplestore.h | 2 + src/test/regress/expected/with.out | 3 + src/test/regress/sql/with.sql | 3 + 78 files changed, 1598 insertions(+), 122 deletions(-) create mode 100644 src/backend/executor/nodeNamedtuplestorescan.c create mode 100644 src/backend/parser/parse_enr.c create mode 100644 src/backend/utils/misc/queryenvironment.c create mode 100644 src/include/executor/nodeNamedtuplestorescan.h create mode 100644 src/include/parser/parse_enr.h create mode 100644 src/include/utils/queryenvironment.h 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; - - cte = scanNameSpaceForCTE(pstate, rv->relname, &levelsup); - if (cte) - rte = transformCTEReference(pstate, rv, cte, levelsup); - } + rte = getRTEForSpecialRelationTypes(pstate, rv); - /* 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); -- 2.40.0