diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index 7162fdb7aa343ccb4490715ea4f4065f8148b43b..ea6bfc2a3f53b082e47de3c55ea3309c3799b378 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -326,9 +326,7 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5); </para> <para> - You can pass multiple commands in one string, but later commands cannot - depend on the creation of objects earlier in the string, because the - whole string will be parsed and planned before execution begins. + You can pass multiple commands in one string; <function>SPI_execute</function> returns the result for the command executed last. The <parameter>count</parameter> limit applies to each command separately, but it is not applied to @@ -392,7 +390,8 @@ typedef struct TupleDesc tupdesc; /* row descriptor */ HeapTuple *vals; /* rows */ } SPITupleTable; -</programlisting><structfield>vals</> is an array of pointers to rows. (The number +</programlisting> + <structfield>vals</> is an array of pointers to rows. (The number of valid entries is given by <varname>SPI_processed</varname>.) <structfield>tupdesc</> is a row descriptor which you can pass to SPI functions dealing with rows. <structfield>tuptabcxt</>, @@ -432,7 +431,8 @@ typedef struct <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> @@ -671,7 +671,8 @@ int SPI_exec(const char * <parameter>command</parameter>, long <parameter>count< <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> @@ -809,7 +810,8 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>, <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> @@ -1452,7 +1454,8 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> @@ -1569,7 +1572,8 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>, <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> @@ -1669,7 +1673,8 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values< <term><literal>long <parameter>count</parameter></literal></term> <listitem> <para> - maximum number of rows to process or return + maximum number of rows to process or return, + or <literal>0</> for no limit </para> </listitem> </varlistentry> diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 5a11c6f7392296adeb41a276fc698cb1ca246bf4..da40f8207c19f98c80730c7d48b1c4daf6a164da 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -48,8 +48,9 @@ static int _SPI_curid = -1; static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamListInfo paramLI, bool read_only); -static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, - ParamListInfo boundParams); +static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan); + +static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan); static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, @@ -354,7 +355,7 @@ SPI_execute(const char *src, bool read_only, long tcount) plan.magic = _SPI_PLAN_MAGIC; plan.cursor_options = 0; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_oneshot_plan(src, &plan); res = _SPI_execute_plan(&plan, NULL, InvalidSnapshot, InvalidSnapshot, @@ -505,7 +506,7 @@ SPI_execute_with_args(const char *src, paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); - _SPI_prepare_plan(src, &plan, paramLI); + _SPI_prepare_oneshot_plan(src, &plan); res = _SPI_execute_plan(&plan, paramLI, InvalidSnapshot, InvalidSnapshot, @@ -546,7 +547,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, plan.parserSetup = NULL; plan.parserSetupArg = NULL; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); @@ -583,7 +584,7 @@ SPI_prepare_params(const char *src, plan.parserSetup = parserSetup; plan.parserSetupArg = parserSetupArg; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); @@ -598,7 +599,8 @@ SPI_keepplan(SPIPlanPtr plan) { ListCell *lc; - if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || + plan->saved || plan->oneshot) return SPI_ERROR_ARGUMENT; /* @@ -1082,7 +1084,7 @@ SPI_cursor_open_with_args(const char *name, paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); - _SPI_prepare_plan(src, &plan, paramLI); + _SPI_prepare_plan(src, &plan); /* We needn't copy the plan; SPI_cursor_open_internal will do so */ @@ -1644,10 +1646,6 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup * and plan->parserSetupArg) must be valid, as must plan->cursor_options. - * If boundParams isn't NULL then it represents parameter values that are made - * available to the planner (as either estimates or hard values depending on - * their PARAM_FLAG_CONST marking). The boundParams had better match the - * param type information embedded in the plan! * * Results are stored into *plan (specifically, plan->plancache_list). * Note that the result data is all in CurrentMemoryContext or child contexts @@ -1656,13 +1654,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * parsing is also left in CurrentMemoryContext. */ static void -_SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) +_SPI_prepare_plan(const char *src, SPIPlanPtr plan) { List *raw_parsetree_list; List *plancache_list; ListCell *list_item; ErrorContextCallback spierrcontext; - int cursor_options = plan->cursor_options; /* * Setup error traceback support for ereport() @@ -1725,13 +1722,80 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) plan->nargs, plan->parserSetup, plan->parserSetupArg, - cursor_options, + plan->cursor_options, false); /* not fixed result */ plancache_list = lappend(plancache_list, plansource); } plan->plancache_list = plancache_list; + plan->oneshot = false; + + /* + * Pop the error context stack + */ + error_context_stack = spierrcontext.previous; +} + +/* + * Parse, but don't analyze, a querystring. + * + * This is a stripped-down version of _SPI_prepare_plan that only does the + * initial raw parsing. It creates "one shot" CachedPlanSources + * that still require parse analysis before execution is possible. + * + * The advantage of using the "one shot" form of CachedPlanSource is that + * we eliminate data copying and invalidation overhead. Postponing parse + * analysis also prevents issues if some of the raw parsetrees are DDL + * commands that affect validity of later parsetrees. Both of these + * attributes are good things for SPI_execute() and similar cases. + * + * Results are stored into *plan (specifically, plan->plancache_list). + * Note that the result data is all in CurrentMemoryContext or child contexts + * thereof; in practice this means it is in the SPI executor context, and + * what we are creating is a "temporary" SPIPlan. Cruft generated during + * parsing is also left in CurrentMemoryContext. + */ +static void +_SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan) +{ + List *raw_parsetree_list; + List *plancache_list; + ListCell *list_item; + ErrorContextCallback spierrcontext; + + /* + * Setup error traceback support for ereport() + */ + spierrcontext.callback = _SPI_error_callback; + spierrcontext.arg = (void *) src; + spierrcontext.previous = error_context_stack; + error_context_stack = &spierrcontext; + + /* + * Parse the request string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(src); + + /* + * Construct plancache entries, but don't do parse analysis yet. + */ + plancache_list = NIL; + + foreach(list_item, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(list_item); + CachedPlanSource *plansource; + + plansource = CreateOneShotCachedPlan(parsetree, + src, + CreateCommandTag(parsetree)); + + plancache_list = lappend(plancache_list, plansource); + } + + plan->plancache_list = plancache_list; + plan->oneshot = true; /* * Pop the error context stack @@ -1769,7 +1833,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, * Setup error traceback support for ereport() */ spierrcontext.callback = _SPI_error_callback; - spierrcontext.arg = NULL; + spierrcontext.arg = NULL; /* we'll fill this below */ spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; @@ -1815,6 +1879,47 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, spierrcontext.arg = (void *) plansource->query_string; + /* + * If this is a one-shot plan, we still need to do parse analysis. + */ + if (plan->oneshot) + { + Node *parsetree = plansource->raw_parse_tree; + const char *src = plansource->query_string; + List *stmt_list; + + /* + * Parameter datatypes are driven by parserSetup hook if provided, + * otherwise we use the fixed parameter list. + */ + if (plan->parserSetup != NULL) + { + Assert(plan->nargs == 0); + stmt_list = pg_analyze_and_rewrite_params(parsetree, + src, + plan->parserSetup, + plan->parserSetupArg); + } + else + { + stmt_list = pg_analyze_and_rewrite(parsetree, + src, + plan->argtypes, + plan->nargs); + } + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + stmt_list, + NULL, + plan->argtypes, + plan->nargs, + plan->parserSetup, + plan->parserSetupArg, + plan->cursor_options, + false); /* not fixed result */ + } + /* * Replan if needed, and increment plan refcount. If it's a saved * plan, the refcount must be backed by the CurrentResourceOwner. @@ -2306,6 +2411,8 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan) /* Assert the input is a temporary SPIPlan */ Assert(plan->magic == _SPI_PLAN_MAGIC); Assert(plan->plancxt == NULL); + /* One-shot plans can't be saved */ + Assert(!plan->oneshot); /* * Create a memory context for the plan, underneath the procedure context. @@ -2323,6 +2430,7 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan) newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; + newplan->oneshot = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; @@ -2372,6 +2480,9 @@ _SPI_save_plan(SPIPlanPtr plan) MemoryContext oldcxt; ListCell *lc; + /* One-shot plans can't be saved */ + Assert(!plan->oneshot); + /* * Create a memory context for the plan. We don't expect the plan to be * very large, so use smaller-than-default alloc parameters. It's a @@ -2388,6 +2499,7 @@ _SPI_save_plan(SPIPlanPtr plan) newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; + newplan->oneshot = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index c42765c25a782a095cf3e81e45582c9cf353e558..c46264f1c61f39ac58d74d9d1034c14a41be41f3 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -180,6 +180,7 @@ CreateCachedPlan(Node *raw_parse_tree, plansource->invalItems = NIL; plansource->query_context = NULL; plansource->gplan = NULL; + plansource->is_oneshot = false; plansource->is_complete = false; plansource->is_saved = false; plansource->is_valid = false; @@ -194,6 +195,69 @@ CreateCachedPlan(Node *raw_parse_tree, return plansource; } +/* + * CreateOneShotCachedPlan: initially create a one-shot plan cache entry. + * + * This variant of CreateCachedPlan creates a plan cache entry that is meant + * to be used only once. No data copying occurs: all data structures remain + * in the caller's memory context (which typically should get cleared after + * completing execution). The CachedPlanSource struct itself is also created + * in that context. + * + * A one-shot plan cannot be saved or copied, since we make no effort to + * preserve the raw parse tree unmodified. There is also no support for + * invalidation, so plan use must be completed in the current transaction, + * and DDL that might invalidate the querytree_list must be avoided as well. + * + * raw_parse_tree: output of raw_parser() + * query_string: original query text + * commandTag: compile-time-constant tag for query, or NULL if empty query + */ +CachedPlanSource * +CreateOneShotCachedPlan(Node *raw_parse_tree, + const char *query_string, + const char *commandTag) +{ + CachedPlanSource *plansource; + + Assert(query_string != NULL); /* required as of 8.4 */ + + /* + * Create and fill the CachedPlanSource struct within the caller's memory + * context. Most fields are just left empty for the moment. + */ + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + plansource->magic = CACHEDPLANSOURCE_MAGIC; + plansource->raw_parse_tree = raw_parse_tree; + plansource->query_string = query_string; + plansource->commandTag = commandTag; + plansource->param_types = NULL; + plansource->num_params = 0; + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; + plansource->cursor_options = 0; + plansource->fixed_result = false; + plansource->resultDesc = NULL; + plansource->search_path = NULL; + plansource->context = CurrentMemoryContext; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->query_context = NULL; + plansource->gplan = NULL; + plansource->is_oneshot = true; + plansource->is_complete = false; + plansource->is_saved = false; + plansource->is_valid = false; + plansource->generation = 0; + plansource->next_saved = NULL; + plansource->generic_cost = -1; + plansource->total_custom_cost = 0; + plansource->num_custom_plans = 0; + + return plansource; +} + /* * CompleteCachedPlan: second step of creating a plan cache entry. * @@ -221,6 +285,10 @@ CreateCachedPlan(Node *raw_parse_tree, * option, it is caller's responsibility that the referenced data remains * valid for as long as the CachedPlanSource exists. * + * If the CachedPlanSource is a "oneshot" plan, then no querytree copying + * occurs at all, and querytree_context is ignored; it is caller's + * responsibility that the passed querytree_list is sufficiently long-lived. + * * plansource: structure returned by CreateCachedPlan * querytree_list: analyzed-and-rewritten form of query (list of Query nodes) * querytree_context: memory context containing querytree_list, @@ -253,9 +321,15 @@ CompleteCachedPlan(CachedPlanSource *plansource, /* * If caller supplied a querytree_context, reparent it underneath the * CachedPlanSource's context; otherwise, create a suitable context and - * copy the querytree_list into it. + * copy the querytree_list into it. But no data copying should be done + * for one-shot plans; for those, assume the passed querytree_list is + * sufficiently long-lived. */ - if (querytree_context != NULL) + if (plansource->is_oneshot) + { + querytree_context = CurrentMemoryContext; + } + else if (querytree_context != NULL) { MemoryContextSetParent(querytree_context, source_context); MemoryContextSwitchTo(querytree_context); @@ -278,11 +352,12 @@ CompleteCachedPlan(CachedPlanSource *plansource, /* * Use the planner machinery to extract dependencies. Data is saved in * query_context. (We assume that not a lot of extra cruft is created by - * this call.) + * this call.) We can skip this for one-shot plans. */ - extract_query_dependencies((Node *) querytree_list, - &plansource->relationOids, - &plansource->invalItems); + if (!plansource->is_oneshot) + extract_query_dependencies((Node *) querytree_list, + &plansource->relationOids, + &plansource->invalItems); /* * Save the final parameter types (or other parameter specification data) @@ -325,7 +400,8 @@ CompleteCachedPlan(CachedPlanSource *plansource, * it to the list of cached plans that are checked for invalidation when an * sinval event occurs. * - * This is guaranteed not to throw error; callers typically depend on that + * This is guaranteed not to throw error, except for the caller-error case + * of trying to save a one-shot plan. Callers typically depend on that * since this is called just before or just after adding a pointer to the * CachedPlanSource to some permanent data structure of their own. Up until * this is done, a CachedPlanSource is just transient data that will go away @@ -339,6 +415,10 @@ SaveCachedPlan(CachedPlanSource *plansource) Assert(plansource->is_complete); Assert(!plansource->is_saved); + /* This seems worth a real test, though */ + if (plansource->is_oneshot) + elog(ERROR, "cannot save one-shot cached plan"); + /* * In typical use, this function would be called before generating any * plans from the CachedPlanSource. If there is a generic plan, moving it @@ -401,11 +481,15 @@ DropCachedPlan(CachedPlanSource *plansource) /* Decrement generic CachePlan's refcount and drop if no longer needed */ ReleaseGenericPlan(plansource); + /* Mark it no longer valid */ + plansource->magic = 0; + /* * Remove the CachedPlanSource and all subsidiary data (including the - * query_context if any). + * query_context if any). But if it's a one-shot we can't free anything. */ - MemoryContextDelete(plansource->context); + if (!plansource->is_oneshot) + MemoryContextDelete(plansource->context); } /* @@ -450,6 +534,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource) MemoryContext querytree_context; MemoryContext oldcxt; + /* + * For one-shot plans, we do not support revalidation checking; it's + * assumed the query is parsed, planned, and executed in one transaction, + * so that no lock re-acquisition is necessary. + */ + if (plansource->is_oneshot) + { + Assert(plansource->is_valid); + return NIL; + } + /* * If the query is currently valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race @@ -648,6 +743,8 @@ CheckCachedPlan(CachedPlanSource *plansource) return false; Assert(plan->magic == CACHEDPLAN_MAGIC); + /* Generic plans are never one-shot */ + Assert(!plan->is_oneshot); /* * If it appears valid, acquire locks and recheck; this is much the same @@ -707,7 +804,8 @@ CheckCachedPlan(CachedPlanSource *plansource) * hint rather than a hard constant. * * Planning work is done in the caller's memory context. The finished plan - * is in a child memory context, which typically should get reparented. + * is in a child memory context, which typically should get reparented + * (unless this is a one-shot plan, in which case we don't copy the plan). */ static CachedPlan * BuildCachedPlan(CachedPlanSource *plansource, List *qlist, @@ -718,7 +816,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, bool snapshot_set; bool spi_pushed; MemoryContext plan_context; - MemoryContext oldcxt; + MemoryContext oldcxt = CurrentMemoryContext; /* * Normally the querytree should be valid already, but if it's not, @@ -738,10 +836,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, /* * If we don't already have a copy of the querytree list that can be - * scribbled on by the planner, make one. + * scribbled on by the planner, make one. For a one-shot plan, we assume + * it's okay to scribble on the original query_list. */ if (qlist == NIL) - qlist = (List *) copyObject(plansource->query_list); + { + if (!plansource->is_oneshot) + qlist = (List *) copyObject(plansource->query_list); + else + qlist = plansource->query_list; + } /* * Restore the search_path that was in use when the plan was made. See @@ -793,22 +897,29 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, PopOverrideSearchPath(); /* - * Make a dedicated memory context for the CachedPlan and its subsidiary - * data. It's probably not going to be large, but just in case, use the - * default maxsize parameter. It's transient for the moment. + * Normally we make a dedicated memory context for the CachedPlan and its + * subsidiary data. (It's probably not going to be large, but just in + * case, use the default maxsize parameter. It's transient for the + * moment.) But for a one-shot plan, we just leave it in the caller's + * memory context. */ - plan_context = AllocSetContextCreate(CurrentMemoryContext, - "CachedPlan", - ALLOCSET_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + if (!plansource->is_oneshot) + { + plan_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlan", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); - /* - * Copy plan into the new context. - */ - oldcxt = MemoryContextSwitchTo(plan_context); + /* + * Copy plan into the new context. + */ + MemoryContextSwitchTo(plan_context); - plist = (List *) copyObject(plist); + plist = (List *) copyObject(plist); + } + else + plan_context = CurrentMemoryContext; /* * Create and fill the CachedPlan struct within the new context. @@ -825,6 +936,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan->saved_xmin = InvalidTransactionId; plan->refcount = 0; plan->context = plan_context; + plan->is_oneshot = plansource->is_oneshot; plan->is_saved = false; plan->is_valid = true; @@ -846,7 +958,11 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams) { double avg_custom_cost; - /* Never any point in a custom plan if there's no parameters */ + /* One-shot plans will always be considered custom */ + if (plansource->is_oneshot) + return true; + + /* Otherwise, never any point in a custom plan if there's no parameters */ if (boundParams == NULL) return false; @@ -1048,7 +1164,14 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) Assert(plan->refcount > 0); plan->refcount--; if (plan->refcount == 0) - MemoryContextDelete(plan->context); + { + /* Mark it no longer valid */ + plan->magic = 0; + + /* One-shot plans do not own their context, so we can't free them */ + if (!plan->is_oneshot) + MemoryContextDelete(plan->context); + } } /* @@ -1065,9 +1188,11 @@ CachedPlanSetParentContext(CachedPlanSource *plansource, Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); - /* This seems worth a real test, though */ + /* These seem worth real tests, though */ if (plansource->is_saved) elog(ERROR, "cannot move a saved cached plan to another context"); + if (plansource->is_oneshot) + elog(ERROR, "cannot move a one-shot cached plan to another context"); /* OK, let the caller keep the plan where he wishes */ MemoryContextSetParent(plansource->context, newcontext); @@ -1104,6 +1229,13 @@ CopyCachedPlan(CachedPlanSource *plansource) Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); + /* + * One-shot plans can't be copied, because we haven't taken care that + * parsing/planning didn't scribble on the raw parse tree or querytrees. + */ + if (plansource->is_oneshot) + elog(ERROR, "cannot copy a one-shot cached plan"); + source_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanSource", ALLOCSET_SMALL_MINSIZE, @@ -1151,6 +1283,7 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->gplan = NULL; + newsource->is_oneshot = false; newsource->is_complete = true; newsource->is_saved = false; newsource->is_valid = plansource->is_valid; diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index 4fbb548af4b93d360b6cd48f8f03993bd383594f..1a828912fcddaa89d02a5faf5b837a6d037f679c 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -59,6 +59,12 @@ typedef struct * while additional data such as argtypes and list cells is loose in the SPI * executor context. Such plans can be identified by having plancxt == NULL. * + * We can also have "one-shot" SPI plans (which are typically temporary, + * as described above). These are meant to be executed once and discarded, + * and various optimizations are made on the assumption of single use. + * Note in particular that the CachedPlanSources within such an SPI plan + * are not "complete" until execution. + * * Note: if the original query string contained only whitespace and comments, * the plancache_list will be NIL and so there is no place to store the * query string. We don't care about that, but we do care about the @@ -68,6 +74,7 @@ typedef struct _SPI_plan { int magic; /* should equal _SPI_PLAN_MAGIC */ bool saved; /* saved or unsaved plan? */ + bool oneshot; /* one-shot plan? */ List *plancache_list; /* one CachedPlanSource per parsetree */ MemoryContext plancxt; /* Context containing _SPI_plan and data */ int cursor_options; /* Cursor options used for planning */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 413e8462a6c6e50241574d6ebcb2f38837b0af25..ccc7e3f053b6d1839215c8241c42b7ffdf7e8dbd 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -60,6 +60,14 @@ * context that holds the rewritten query tree and associated data. This * allows the query tree to be discarded easily when it is invalidated. * + * Some callers wish to use the CachedPlan API even with one-shot queries + * that have no reason to be saved at all. We therefore support a "oneshot" + * variant that does no data copying or invalidation checking. In this case + * there are no separate memory contexts: the CachedPlanSource struct and + * all subsidiary data live in the caller's CurrentMemoryContext, and there + * is no way to free memory short of clearing that entire context. A oneshot + * plan is always treated as unsaved. + * * Note: the string referenced by commandTag is not subsidiary storage; * it is assumed to be a compile-time-constant string. As with portals, * commandTag shall be NULL if and only if the original query string (before @@ -69,7 +77,7 @@ typedef struct CachedPlanSource { int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ Node *raw_parse_tree; /* output of raw_parser() */ - char *query_string; /* source text of query */ + const char *query_string; /* source text of query */ const char *commandTag; /* command tag (a constant!), or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ @@ -91,6 +99,7 @@ typedef struct CachedPlanSource bool is_complete; /* has CompleteCachedPlan been done? */ bool is_saved; /* has CachedPlanSource been "saved"? */ bool is_valid; /* is the query_list currently valid? */ + bool is_oneshot; /* is it a "oneshot" plan? */ int generation; /* increments each time we create a plan */ /* If CachedPlanSource has been saved, it is a member of a global list */ struct CachedPlanSource *next_saved; /* list link, if so */ @@ -106,7 +115,9 @@ typedef struct CachedPlanSource * (if any), and any active plan executions, so the plan can be discarded * exactly when refcount goes to zero. Both the struct itself and the * subsidiary data live in the context denoted by the context field. - * This makes it easy to free a no-longer-needed cached plan. + * This makes it easy to free a no-longer-needed cached plan. (However, + * if is_oneshot is true, the context does not belong solely to the CachedPlan + * so no freeing is possible.) */ typedef struct CachedPlan { @@ -115,6 +126,7 @@ typedef struct CachedPlan * bare utility statements) */ bool is_saved; /* is CachedPlan in a long-lived context? */ bool is_valid; /* is the stmt_list currently valid? */ + bool is_oneshot; /* is it a "oneshot" plan? */ TransactionId saved_xmin; /* if valid, replan when TransactionXmin * changes from this value */ int generation; /* parent's generation number for this plan */ @@ -129,6 +141,9 @@ extern void ResetPlanCache(void); extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, const char *query_string, const char *commandTag); +extern CachedPlanSource *CreateOneShotCachedPlan(Node *raw_parse_tree, + const char *query_string, + const char *commandTag); extern void CompleteCachedPlan(CachedPlanSource *plansource, List *querytree_list, MemoryContext querytree_context,