aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/spi.c144
-rw-r--r--src/backend/utils/cache/plancache.c191
-rw-r--r--src/include/executor/spi_priv.h7
-rw-r--r--src/include/utils/plancache.h19
4 files changed, 314 insertions, 47 deletions
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 9b167af6608..3da2d263023 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -49,8 +49,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,
@@ -355,7 +356,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,
@@ -506,7 +507,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,
@@ -547,7 +548,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);
@@ -584,7 +585,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);
@@ -599,7 +600,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;
/*
@@ -1083,7 +1085,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 */
@@ -1645,10 +1647,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
@@ -1657,13 +1655,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()
@@ -1726,13 +1723,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
@@ -1770,7 +1834,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;
@@ -1817,6 +1881,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.
*/
@@ -2313,6 +2418,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.
@@ -2330,6 +2437,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;
@@ -2379,6 +2487,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
@@ -2395,6 +2506,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 f509f96ca7f..cbc7c498d0d 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -181,6 +181,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;
@@ -196,6 +197,69 @@ CreateCachedPlan(Node *raw_parse_tree,
}
/*
+ * 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.
*
* Pass in the analyzed-and-rewritten form of the query, as well as the
@@ -222,6 +286,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,
@@ -254,9 +322,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);
@@ -279,11 +353,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)
@@ -326,7 +401,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
@@ -340,6 +416,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
@@ -402,11 +482,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);
}
/*
@@ -452,6 +536,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
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
* condition that an invalidation message arrives before we get the locks.
@@ -649,6 +744,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
@@ -708,7 +805,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,
@@ -719,7 +817,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,
@@ -739,10 +837,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
@@ -794,22 +898,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.
@@ -826,6 +937,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;
@@ -847,7 +959,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;
@@ -1049,7 +1165,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);
+ }
}
/*
@@ -1066,9 +1189,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);
@@ -1105,6 +1230,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,
@@ -1152,6 +1284,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 faa81dbb7f8..ef7903abd09 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 a447eaccc58..8185427fc4b 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 */
@@ -88,6 +96,7 @@ typedef struct CachedPlanSource
/* If we have a generic plan, this is a reference-counted link to it: */
struct CachedPlan *gplan; /* generic plan, or NULL if not valid */
/* Some state flags: */
+ bool is_oneshot; /* is it a "oneshot" plan? */
bool is_complete; /* has CompleteCachedPlan been done? */
bool is_saved; /* has CachedPlanSource been "saved"? */
bool is_valid; /* is the query_list currently valid? */
@@ -106,13 +115,16 @@ 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
{
int magic; /* should equal CACHEDPLAN_MAGIC */
List *stmt_list; /* list of statement nodes (PlannedStmts and
* bare utility statements) */
+ bool is_oneshot; /* is it a "oneshot" plan? */
bool is_saved; /* is CachedPlan in a long-lived context? */
bool is_valid; /* is the stmt_list currently valid? */
TransactionId saved_xmin; /* if valid, replan when TransactionXmin
@@ -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,