Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 94afbd5

Browse files
committedJan 4, 2013
Invent a "one-shot" variant of CachedPlans for better performance.
SPI_execute() and related functions create a CachedPlan, execute it once, and immediately discard it, so that the functionality offered by plancache.c is of no value in this code path. And performance measurements show that the extra data copying and invalidation checking done by plancache.c slows down simple queries by 10% or more compared to 9.1. However, enough of the SPI code is shared with functions that do need plan caching that it seems impractical to bypass plancache.c altogether. Instead, let's invent a variant version of cached plans that preserves 99% of the API but doesn't offer any of the actual functionality, nor the overhead. This puts SPI_execute() performance back on par, or maybe even slightly better, than it was before. This change should resolve recent complaints of performance degradation from Dong Ye, Pavel Stehule, and others. By avoiding data copying, this change also reduces the amount of memory needed to execute many-statement SPI_execute() strings, as for instance in a recent complaint from Tomas Vondra. An additional benefit of this change is that multi-statement SPI_execute() query strings are now processed fully serially, that is we complete execution of earlier statements before running parse analysis and planning on following ones. This eliminates a long-standing POLA violation, in that DDL that affects the behavior of a later statement will now behave as expected. Back-patch to 9.2, since this was a performance regression compared to 9.1. (In 9.2, place the added struct fields so as to avoid changing the offsets of existing fields.) Heikki Linnakangas and Tom Lane
1 parent 78a5e73 commit 94afbd5

File tree

5 files changed

+329
-57
lines changed

5 files changed

+329
-57
lines changed
 

‎doc/src/sgml/spi.sgml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,7 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5);
326326
</para>
327327

328328
<para>
329-
You can pass multiple commands in one string, but later commands cannot
330-
depend on the creation of objects earlier in the string, because the
331-
whole string will be parsed and planned before execution begins.
329+
You can pass multiple commands in one string;
332330
<function>SPI_execute</function> returns the
333331
result for the command executed last. The <parameter>count</parameter>
334332
limit applies to each command separately, but it is not applied to
@@ -395,7 +393,8 @@ typedef struct
395393
TupleDesc tupdesc; /* row descriptor */
396394
HeapTuple *vals; /* rows */
397395
} SPITupleTable;
398-
</programlisting><structfield>vals</> is an array of pointers to rows. (The number
396+
</programlisting>
397+
<structfield>vals</> is an array of pointers to rows. (The number
399398
of valid entries is given by <varname>SPI_processed</varname>.)
400399
<structfield>tupdesc</> is a row descriptor which you can pass to
401400
SPI functions dealing with rows. <structfield>tuptabcxt</>,
@@ -435,7 +434,8 @@ typedef struct
435434
<term><literal>long <parameter>count</parameter></literal></term>
436435
<listitem>
437436
<para>
438-
maximum number of rows to process or return
437+
maximum number of rows to process or return,
438+
or <literal>0</> for no limit
439439
</para>
440440
</listitem>
441441
</varlistentry>
@@ -674,7 +674,8 @@ int SPI_exec(const char * <parameter>command</parameter>, long <parameter>count<
674674
<term><literal>long <parameter>count</parameter></literal></term>
675675
<listitem>
676676
<para>
677-
maximum number of rows to process or return
677+
maximum number of rows to process or return,
678+
or <literal>0</> for no limit
678679
</para>
679680
</listitem>
680681
</varlistentry>
@@ -812,7 +813,8 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
812813
<term><literal>long <parameter>count</parameter></literal></term>
813814
<listitem>
814815
<para>
815-
maximum number of rows to process or return
816+
maximum number of rows to process or return,
817+
or <literal>0</> for no limit
816818
</para>
817819
</listitem>
818820
</varlistentry>
@@ -1455,7 +1457,8 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
14551457
<term><literal>long <parameter>count</parameter></literal></term>
14561458
<listitem>
14571459
<para>
1458-
maximum number of rows to process or return
1460+
maximum number of rows to process or return,
1461+
or <literal>0</> for no limit
14591462
</para>
14601463
</listitem>
14611464
</varlistentry>
@@ -1572,7 +1575,8 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
15721575
<term><literal>long <parameter>count</parameter></literal></term>
15731576
<listitem>
15741577
<para>
1575-
maximum number of rows to process or return
1578+
maximum number of rows to process or return,
1579+
or <literal>0</> for no limit
15761580
</para>
15771581
</listitem>
15781582
</varlistentry>
@@ -1672,7 +1676,8 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
16721676
<term><literal>long <parameter>count</parameter></literal></term>
16731677
<listitem>
16741678
<para>
1675-
maximum number of rows to process or return
1679+
maximum number of rows to process or return,
1680+
or <literal>0</> for no limit
16761681
</para>
16771682
</listitem>
16781683
</varlistentry>

‎src/backend/executor/spi.c

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ static int _SPI_curid = -1;
4949
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
5050
ParamListInfo paramLI, bool read_only);
5151

52-
static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
53-
ParamListInfo boundParams);
52+
static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
53+
54+
static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan);
5455

5556
static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
5657
Snapshot snapshot, Snapshot crosscheck_snapshot,
@@ -355,7 +356,7 @@ SPI_execute(const char *src, bool read_only, long tcount)
355356
plan.magic = _SPI_PLAN_MAGIC;
356357
plan.cursor_options = 0;
357358

358-
_SPI_prepare_plan(src, &plan, NULL);
359+
_SPI_prepare_oneshot_plan(src, &plan);
359360

360361
res = _SPI_execute_plan(&plan, NULL,
361362
InvalidSnapshot, InvalidSnapshot,
@@ -506,7 +507,7 @@ SPI_execute_with_args(const char *src,
506507
paramLI = _SPI_convert_params(nargs, argtypes,
507508
Values, Nulls);
508509

509-
_SPI_prepare_plan(src, &plan, paramLI);
510+
_SPI_prepare_oneshot_plan(src, &plan);
510511

511512
res = _SPI_execute_plan(&plan, paramLI,
512513
InvalidSnapshot, InvalidSnapshot,
@@ -547,7 +548,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
547548
plan.parserSetup = NULL;
548549
plan.parserSetupArg = NULL;
549550

550-
_SPI_prepare_plan(src, &plan, NULL);
551+
_SPI_prepare_plan(src, &plan);
551552

552553
/* copy plan to procedure context */
553554
result = _SPI_make_plan_non_temp(&plan);
@@ -584,7 +585,7 @@ SPI_prepare_params(const char *src,
584585
plan.parserSetup = parserSetup;
585586
plan.parserSetupArg = parserSetupArg;
586587

587-
_SPI_prepare_plan(src, &plan, NULL);
588+
_SPI_prepare_plan(src, &plan);
588589

589590
/* copy plan to procedure context */
590591
result = _SPI_make_plan_non_temp(&plan);
@@ -599,7 +600,8 @@ SPI_keepplan(SPIPlanPtr plan)
599600
{
600601
ListCell *lc;
601602

602-
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
603+
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
604+
plan->saved || plan->oneshot)
603605
return SPI_ERROR_ARGUMENT;
604606

605607
/*
@@ -1083,7 +1085,7 @@ SPI_cursor_open_with_args(const char *name,
10831085
paramLI = _SPI_convert_params(nargs, argtypes,
10841086
Values, Nulls);
10851087

1086-
_SPI_prepare_plan(src, &plan, paramLI);
1088+
_SPI_prepare_plan(src, &plan);
10871089

10881090
/* We needn't copy the plan; SPI_cursor_open_internal will do so */
10891091

@@ -1645,10 +1647,6 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
16451647
*
16461648
* At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
16471649
* and plan->parserSetupArg) must be valid, as must plan->cursor_options.
1648-
* If boundParams isn't NULL then it represents parameter values that are made
1649-
* available to the planner (as either estimates or hard values depending on
1650-
* their PARAM_FLAG_CONST marking). The boundParams had better match the
1651-
* param type information embedded in the plan!
16521650
*
16531651
* Results are stored into *plan (specifically, plan->plancache_list).
16541652
* Note that the result data is all in CurrentMemoryContext or child contexts
@@ -1657,13 +1655,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
16571655
* parsing is also left in CurrentMemoryContext.
16581656
*/
16591657
static void
1660-
_SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
1658+
_SPI_prepare_plan(const char *src, SPIPlanPtr plan)
16611659
{
16621660
List *raw_parsetree_list;
16631661
List *plancache_list;
16641662
ListCell *list_item;
16651663
ErrorContextCallback spierrcontext;
1666-
int cursor_options = plan->cursor_options;
16671664

16681665
/*
16691666
* Setup error traceback support for ereport()
@@ -1726,13 +1723,80 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
17261723
plan->nargs,
17271724
plan->parserSetup,
17281725
plan->parserSetupArg,
1729-
cursor_options,
1726+
plan->cursor_options,
17301727
false); /* not fixed result */
17311728

17321729
plancache_list = lappend(plancache_list, plansource);
17331730
}
17341731

17351732
plan->plancache_list = plancache_list;
1733+
plan->oneshot = false;
1734+
1735+
/*
1736+
* Pop the error context stack
1737+
*/
1738+
error_context_stack = spierrcontext.previous;
1739+
}
1740+
1741+
/*
1742+
* Parse, but don't analyze, a querystring.
1743+
*
1744+
* This is a stripped-down version of _SPI_prepare_plan that only does the
1745+
* initial raw parsing. It creates "one shot" CachedPlanSources
1746+
* that still require parse analysis before execution is possible.
1747+
*
1748+
* The advantage of using the "one shot" form of CachedPlanSource is that
1749+
* we eliminate data copying and invalidation overhead. Postponing parse
1750+
* analysis also prevents issues if some of the raw parsetrees are DDL
1751+
* commands that affect validity of later parsetrees. Both of these
1752+
* attributes are good things for SPI_execute() and similar cases.
1753+
*
1754+
* Results are stored into *plan (specifically, plan->plancache_list).
1755+
* Note that the result data is all in CurrentMemoryContext or child contexts
1756+
* thereof; in practice this means it is in the SPI executor context, and
1757+
* what we are creating is a "temporary" SPIPlan. Cruft generated during
1758+
* parsing is also left in CurrentMemoryContext.
1759+
*/
1760+
static void
1761+
_SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
1762+
{
1763+
List *raw_parsetree_list;
1764+
List *plancache_list;
1765+
ListCell *list_item;
1766+
ErrorContextCallback spierrcontext;
1767+
1768+
/*
1769+
* Setup error traceback support for ereport()
1770+
*/
1771+
spierrcontext.callback = _SPI_error_callback;
1772+
spierrcontext.arg = (void *) src;
1773+
spierrcontext.previous = error_context_stack;
1774+
error_context_stack = &spierrcontext;
1775+
1776+
/*
1777+
* Parse the request string into a list of raw parse trees.
1778+
*/
1779+
raw_parsetree_list = pg_parse_query(src);
1780+
1781+
/*
1782+
* Construct plancache entries, but don't do parse analysis yet.
1783+
*/
1784+
plancache_list = NIL;
1785+
1786+
foreach(list_item, raw_parsetree_list)
1787+
{
1788+
Node *parsetree = (Node *) lfirst(list_item);
1789+
CachedPlanSource *plansource;
1790+
1791+
plansource = CreateOneShotCachedPlan(parsetree,
1792+
src,
1793+
CreateCommandTag(parsetree));
1794+
1795+
plancache_list = lappend(plancache_list, plansource);
1796+
}
1797+
1798+
plan->plancache_list = plancache_list;
1799+
plan->oneshot = true;
17361800

17371801
/*
17381802
* Pop the error context stack
@@ -1770,7 +1834,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
17701834
* Setup error traceback support for ereport()
17711835
*/
17721836
spierrcontext.callback = _SPI_error_callback;
1773-
spierrcontext.arg = NULL;
1837+
spierrcontext.arg = NULL; /* we'll fill this below */
17741838
spierrcontext.previous = error_context_stack;
17751839
error_context_stack = &spierrcontext;
17761840

@@ -1816,6 +1880,47 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
18161880

18171881
spierrcontext.arg = (void *) plansource->query_string;
18181882

1883+
/*
1884+
* If this is a one-shot plan, we still need to do parse analysis.
1885+
*/
1886+
if (plan->oneshot)
1887+
{
1888+
Node *parsetree = plansource->raw_parse_tree;
1889+
const char *src = plansource->query_string;
1890+
List *stmt_list;
1891+
1892+
/*
1893+
* Parameter datatypes are driven by parserSetup hook if provided,
1894+
* otherwise we use the fixed parameter list.
1895+
*/
1896+
if (plan->parserSetup != NULL)
1897+
{
1898+
Assert(plan->nargs == 0);
1899+
stmt_list = pg_analyze_and_rewrite_params(parsetree,
1900+
src,
1901+
plan->parserSetup,
1902+
plan->parserSetupArg);
1903+
}
1904+
else
1905+
{
1906+
stmt_list = pg_analyze_and_rewrite(parsetree,
1907+
src,
1908+
plan->argtypes,
1909+
plan->nargs);
1910+
}
1911+
1912+
/* Finish filling in the CachedPlanSource */
1913+
CompleteCachedPlan(plansource,
1914+
stmt_list,
1915+
NULL,
1916+
plan->argtypes,
1917+
plan->nargs,
1918+
plan->parserSetup,
1919+
plan->parserSetupArg,
1920+
plan->cursor_options,
1921+
false); /* not fixed result */
1922+
}
1923+
18191924
/*
18201925
* Replan if needed, and increment plan refcount. If it's a saved
18211926
* plan, the refcount must be backed by the CurrentResourceOwner.
@@ -2313,6 +2418,8 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan)
23132418
/* Assert the input is a temporary SPIPlan */
23142419
Assert(plan->magic == _SPI_PLAN_MAGIC);
23152420
Assert(plan->plancxt == NULL);
2421+
/* One-shot plans can't be saved */
2422+
Assert(!plan->oneshot);
23162423

23172424
/*
23182425
* Create a memory context for the plan, underneath the procedure context.
@@ -2330,6 +2437,7 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan)
23302437
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
23312438
newplan->magic = _SPI_PLAN_MAGIC;
23322439
newplan->saved = false;
2440+
newplan->oneshot = false;
23332441
newplan->plancache_list = NIL;
23342442
newplan->plancxt = plancxt;
23352443
newplan->cursor_options = plan->cursor_options;
@@ -2379,6 +2487,9 @@ _SPI_save_plan(SPIPlanPtr plan)
23792487
MemoryContext oldcxt;
23802488
ListCell *lc;
23812489

2490+
/* One-shot plans can't be saved */
2491+
Assert(!plan->oneshot);
2492+
23822493
/*
23832494
* Create a memory context for the plan. We don't expect the plan to be
23842495
* very large, so use smaller-than-default alloc parameters. It's a
@@ -2395,6 +2506,7 @@ _SPI_save_plan(SPIPlanPtr plan)
23952506
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
23962507
newplan->magic = _SPI_PLAN_MAGIC;
23972508
newplan->saved = false;
2509+
newplan->oneshot = false;
23982510
newplan->plancache_list = NIL;
23992511
newplan->plancxt = plancxt;
24002512
newplan->cursor_options = plan->cursor_options;

‎src/backend/utils/cache/plancache.c

Lines changed: 162 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ CreateCachedPlan(Node *raw_parse_tree,
181181
plansource->invalItems = NIL;
182182
plansource->query_context = NULL;
183183
plansource->gplan = NULL;
184+
plansource->is_oneshot = false;
184185
plansource->is_complete = false;
185186
plansource->is_saved = false;
186187
plansource->is_valid = false;
@@ -195,6 +196,69 @@ CreateCachedPlan(Node *raw_parse_tree,
195196
return plansource;
196197
}
197198

199+
/*
200+
* CreateOneShotCachedPlan: initially create a one-shot plan cache entry.
201+
*
202+
* This variant of CreateCachedPlan creates a plan cache entry that is meant
203+
* to be used only once. No data copying occurs: all data structures remain
204+
* in the caller's memory context (which typically should get cleared after
205+
* completing execution). The CachedPlanSource struct itself is also created
206+
* in that context.
207+
*
208+
* A one-shot plan cannot be saved or copied, since we make no effort to
209+
* preserve the raw parse tree unmodified. There is also no support for
210+
* invalidation, so plan use must be completed in the current transaction,
211+
* and DDL that might invalidate the querytree_list must be avoided as well.
212+
*
213+
* raw_parse_tree: output of raw_parser()
214+
* query_string: original query text
215+
* commandTag: compile-time-constant tag for query, or NULL if empty query
216+
*/
217+
CachedPlanSource *
218+
CreateOneShotCachedPlan(Node *raw_parse_tree,
219+
const char *query_string,
220+
const char *commandTag)
221+
{
222+
CachedPlanSource *plansource;
223+
224+
Assert(query_string != NULL); /* required as of 8.4 */
225+
226+
/*
227+
* Create and fill the CachedPlanSource struct within the caller's memory
228+
* context. Most fields are just left empty for the moment.
229+
*/
230+
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
231+
plansource->magic = CACHEDPLANSOURCE_MAGIC;
232+
plansource->raw_parse_tree = raw_parse_tree;
233+
plansource->query_string = query_string;
234+
plansource->commandTag = commandTag;
235+
plansource->param_types = NULL;
236+
plansource->num_params = 0;
237+
plansource->parserSetup = NULL;
238+
plansource->parserSetupArg = NULL;
239+
plansource->cursor_options = 0;
240+
plansource->fixed_result = false;
241+
plansource->resultDesc = NULL;
242+
plansource->search_path = NULL;
243+
plansource->context = CurrentMemoryContext;
244+
plansource->query_list = NIL;
245+
plansource->relationOids = NIL;
246+
plansource->invalItems = NIL;
247+
plansource->query_context = NULL;
248+
plansource->gplan = NULL;
249+
plansource->is_oneshot = true;
250+
plansource->is_complete = false;
251+
plansource->is_saved = false;
252+
plansource->is_valid = false;
253+
plansource->generation = 0;
254+
plansource->next_saved = NULL;
255+
plansource->generic_cost = -1;
256+
plansource->total_custom_cost = 0;
257+
plansource->num_custom_plans = 0;
258+
259+
return plansource;
260+
}
261+
198262
/*
199263
* CompleteCachedPlan: second step of creating a plan cache entry.
200264
*
@@ -222,6 +286,10 @@ CreateCachedPlan(Node *raw_parse_tree,
222286
* option, it is caller's responsibility that the referenced data remains
223287
* valid for as long as the CachedPlanSource exists.
224288
*
289+
* If the CachedPlanSource is a "oneshot" plan, then no querytree copying
290+
* occurs at all, and querytree_context is ignored; it is caller's
291+
* responsibility that the passed querytree_list is sufficiently long-lived.
292+
*
225293
* plansource: structure returned by CreateCachedPlan
226294
* querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
227295
* querytree_context: memory context containing querytree_list,
@@ -254,9 +322,15 @@ CompleteCachedPlan(CachedPlanSource *plansource,
254322
/*
255323
* If caller supplied a querytree_context, reparent it underneath the
256324
* CachedPlanSource's context; otherwise, create a suitable context and
257-
* copy the querytree_list into it.
325+
* copy the querytree_list into it. But no data copying should be done
326+
* for one-shot plans; for those, assume the passed querytree_list is
327+
* sufficiently long-lived.
258328
*/
259-
if (querytree_context != NULL)
329+
if (plansource->is_oneshot)
330+
{
331+
querytree_context = CurrentMemoryContext;
332+
}
333+
else if (querytree_context != NULL)
260334
{
261335
MemoryContextSetParent(querytree_context, source_context);
262336
MemoryContextSwitchTo(querytree_context);
@@ -279,11 +353,12 @@ CompleteCachedPlan(CachedPlanSource *plansource,
279353
/*
280354
* Use the planner machinery to extract dependencies. Data is saved in
281355
* query_context. (We assume that not a lot of extra cruft is created by
282-
* this call.)
356+
* this call.) We can skip this for one-shot plans.
283357
*/
284-
extract_query_dependencies((Node *) querytree_list,
285-
&plansource->relationOids,
286-
&plansource->invalItems);
358+
if (!plansource->is_oneshot)
359+
extract_query_dependencies((Node *) querytree_list,
360+
&plansource->relationOids,
361+
&plansource->invalItems);
287362

288363
/*
289364
* Save the final parameter types (or other parameter specification data)
@@ -326,7 +401,8 @@ CompleteCachedPlan(CachedPlanSource *plansource,
326401
* it to the list of cached plans that are checked for invalidation when an
327402
* sinval event occurs.
328403
*
329-
* This is guaranteed not to throw error; callers typically depend on that
404+
* This is guaranteed not to throw error, except for the caller-error case
405+
* of trying to save a one-shot plan. Callers typically depend on that
330406
* since this is called just before or just after adding a pointer to the
331407
* CachedPlanSource to some permanent data structure of their own. Up until
332408
* this is done, a CachedPlanSource is just transient data that will go away
@@ -340,6 +416,10 @@ SaveCachedPlan(CachedPlanSource *plansource)
340416
Assert(plansource->is_complete);
341417
Assert(!plansource->is_saved);
342418

419+
/* This seems worth a real test, though */
420+
if (plansource->is_oneshot)
421+
elog(ERROR, "cannot save one-shot cached plan");
422+
343423
/*
344424
* In typical use, this function would be called before generating any
345425
* plans from the CachedPlanSource. If there is a generic plan, moving it
@@ -402,11 +482,15 @@ DropCachedPlan(CachedPlanSource *plansource)
402482
/* Decrement generic CachePlan's refcount and drop if no longer needed */
403483
ReleaseGenericPlan(plansource);
404484

485+
/* Mark it no longer valid */
486+
plansource->magic = 0;
487+
405488
/*
406489
* Remove the CachedPlanSource and all subsidiary data (including the
407-
* query_context if any).
490+
* query_context if any). But if it's a one-shot we can't free anything.
408491
*/
409-
MemoryContextDelete(plansource->context);
492+
if (!plansource->is_oneshot)
493+
MemoryContextDelete(plansource->context);
410494
}
411495

412496
/*
@@ -451,6 +535,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
451535
MemoryContext querytree_context;
452536
MemoryContext oldcxt;
453537

538+
/*
539+
* For one-shot plans, we do not support revalidation checking; it's
540+
* assumed the query is parsed, planned, and executed in one transaction,
541+
* so that no lock re-acquisition is necessary.
542+
*/
543+
if (plansource->is_oneshot)
544+
{
545+
Assert(plansource->is_valid);
546+
return NIL;
547+
}
548+
454549
/*
455550
* If the query is currently valid, acquire locks on the referenced
456551
* objects; then check again. We need to do it this way to cover the race
@@ -649,6 +744,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
649744
return false;
650745

651746
Assert(plan->magic == CACHEDPLAN_MAGIC);
747+
/* Generic plans are never one-shot */
748+
Assert(!plan->is_oneshot);
652749

653750
/*
654751
* If it appears valid, acquire locks and recheck; this is much the same
@@ -708,7 +805,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
708805
* hint rather than a hard constant.
709806
*
710807
* Planning work is done in the caller's memory context. The finished plan
711-
* is in a child memory context, which typically should get reparented.
808+
* is in a child memory context, which typically should get reparented
809+
* (unless this is a one-shot plan, in which case we don't copy the plan).
712810
*/
713811
static CachedPlan *
714812
BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
@@ -719,7 +817,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
719817
bool snapshot_set;
720818
bool spi_pushed;
721819
MemoryContext plan_context;
722-
MemoryContext oldcxt;
820+
MemoryContext oldcxt = CurrentMemoryContext;
723821

724822
/*
725823
* Normally the querytree should be valid already, but if it's not,
@@ -739,10 +837,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
739837

740838
/*
741839
* If we don't already have a copy of the querytree list that can be
742-
* scribbled on by the planner, make one.
840+
* scribbled on by the planner, make one. For a one-shot plan, we assume
841+
* it's okay to scribble on the original query_list.
743842
*/
744843
if (qlist == NIL)
745-
qlist = (List *) copyObject(plansource->query_list);
844+
{
845+
if (!plansource->is_oneshot)
846+
qlist = (List *) copyObject(plansource->query_list);
847+
else
848+
qlist = plansource->query_list;
849+
}
746850

747851
/*
748852
* Restore the search_path that was in use when the plan was made. See
@@ -794,22 +898,29 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
794898
PopOverrideSearchPath();
795899

796900
/*
797-
* Make a dedicated memory context for the CachedPlan and its subsidiary
798-
* data. It's probably not going to be large, but just in case, use the
799-
* default maxsize parameter. It's transient for the moment.
901+
* Normally we make a dedicated memory context for the CachedPlan and its
902+
* subsidiary data. (It's probably not going to be large, but just in
903+
* case, use the default maxsize parameter. It's transient for the
904+
* moment.) But for a one-shot plan, we just leave it in the caller's
905+
* memory context.
800906
*/
801-
plan_context = AllocSetContextCreate(CurrentMemoryContext,
802-
"CachedPlan",
803-
ALLOCSET_SMALL_MINSIZE,
804-
ALLOCSET_SMALL_INITSIZE,
805-
ALLOCSET_DEFAULT_MAXSIZE);
907+
if (!plansource->is_oneshot)
908+
{
909+
plan_context = AllocSetContextCreate(CurrentMemoryContext,
910+
"CachedPlan",
911+
ALLOCSET_SMALL_MINSIZE,
912+
ALLOCSET_SMALL_INITSIZE,
913+
ALLOCSET_DEFAULT_MAXSIZE);
806914

807-
/*
808-
* Copy plan into the new context.
809-
*/
810-
oldcxt = MemoryContextSwitchTo(plan_context);
915+
/*
916+
* Copy plan into the new context.
917+
*/
918+
MemoryContextSwitchTo(plan_context);
811919

812-
plist = (List *) copyObject(plist);
920+
plist = (List *) copyObject(plist);
921+
}
922+
else
923+
plan_context = CurrentMemoryContext;
813924

814925
/*
815926
* Create and fill the CachedPlan struct within the new context.
@@ -826,6 +937,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
826937
plan->saved_xmin = InvalidTransactionId;
827938
plan->refcount = 0;
828939
plan->context = plan_context;
940+
plan->is_oneshot = plansource->is_oneshot;
829941
plan->is_saved = false;
830942
plan->is_valid = true;
831943

@@ -847,7 +959,11 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
847959
{
848960
double avg_custom_cost;
849961

850-
/* Never any point in a custom plan if there's no parameters */
962+
/* One-shot plans will always be considered custom */
963+
if (plansource->is_oneshot)
964+
return true;
965+
966+
/* Otherwise, never any point in a custom plan if there's no parameters */
851967
if (boundParams == NULL)
852968
return false;
853969

@@ -1049,7 +1165,14 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
10491165
Assert(plan->refcount > 0);
10501166
plan->refcount--;
10511167
if (plan->refcount == 0)
1052-
MemoryContextDelete(plan->context);
1168+
{
1169+
/* Mark it no longer valid */
1170+
plan->magic = 0;
1171+
1172+
/* One-shot plans do not own their context, so we can't free them */
1173+
if (!plan->is_oneshot)
1174+
MemoryContextDelete(plan->context);
1175+
}
10531176
}
10541177

10551178
/*
@@ -1066,9 +1189,11 @@ CachedPlanSetParentContext(CachedPlanSource *plansource,
10661189
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
10671190
Assert(plansource->is_complete);
10681191

1069-
/* This seems worth a real test, though */
1192+
/* These seem worth real tests, though */
10701193
if (plansource->is_saved)
10711194
elog(ERROR, "cannot move a saved cached plan to another context");
1195+
if (plansource->is_oneshot)
1196+
elog(ERROR, "cannot move a one-shot cached plan to another context");
10721197

10731198
/* OK, let the caller keep the plan where he wishes */
10741199
MemoryContextSetParent(plansource->context, newcontext);
@@ -1105,6 +1230,13 @@ CopyCachedPlan(CachedPlanSource *plansource)
11051230
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
11061231
Assert(plansource->is_complete);
11071232

1233+
/*
1234+
* One-shot plans can't be copied, because we haven't taken care that
1235+
* parsing/planning didn't scribble on the raw parse tree or querytrees.
1236+
*/
1237+
if (plansource->is_oneshot)
1238+
elog(ERROR, "cannot copy a one-shot cached plan");
1239+
11081240
source_context = AllocSetContextCreate(CurrentMemoryContext,
11091241
"CachedPlanSource",
11101242
ALLOCSET_SMALL_MINSIZE,
@@ -1152,6 +1284,7 @@ CopyCachedPlan(CachedPlanSource *plansource)
11521284

11531285
newsource->gplan = NULL;
11541286

1287+
newsource->is_oneshot = false;
11551288
newsource->is_complete = true;
11561289
newsource->is_saved = false;
11571290
newsource->is_valid = plansource->is_valid;

‎src/include/executor/spi_priv.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ typedef struct
5959
* while additional data such as argtypes and list cells is loose in the SPI
6060
* executor context. Such plans can be identified by having plancxt == NULL.
6161
*
62+
* We can also have "one-shot" SPI plans (which are typically temporary,
63+
* as described above). These are meant to be executed once and discarded,
64+
* and various optimizations are made on the assumption of single use.
65+
* Note in particular that the CachedPlanSources within such an SPI plan
66+
* are not "complete" until execution.
67+
*
6268
* Note: if the original query string contained only whitespace and comments,
6369
* the plancache_list will be NIL and so there is no place to store the
6470
* query string. We don't care about that, but we do care about the
@@ -68,6 +74,7 @@ typedef struct _SPI_plan
6874
{
6975
int magic; /* should equal _SPI_PLAN_MAGIC */
7076
bool saved; /* saved or unsaved plan? */
77+
bool oneshot; /* one-shot plan? */
7178
List *plancache_list; /* one CachedPlanSource per parsetree */
7279
MemoryContext plancxt; /* Context containing _SPI_plan and data */
7380
int cursor_options; /* Cursor options used for planning */

‎src/include/utils/plancache.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
* context that holds the rewritten query tree and associated data. This
6161
* allows the query tree to be discarded easily when it is invalidated.
6262
*
63+
* Some callers wish to use the CachedPlan API even with one-shot queries
64+
* that have no reason to be saved at all. We therefore support a "oneshot"
65+
* variant that does no data copying or invalidation checking. In this case
66+
* there are no separate memory contexts: the CachedPlanSource struct and
67+
* all subsidiary data live in the caller's CurrentMemoryContext, and there
68+
* is no way to free memory short of clearing that entire context. A oneshot
69+
* plan is always treated as unsaved.
70+
*
6371
* Note: the string referenced by commandTag is not subsidiary storage;
6472
* it is assumed to be a compile-time-constant string. As with portals,
6573
* commandTag shall be NULL if and only if the original query string (before
@@ -69,7 +77,7 @@ typedef struct CachedPlanSource
6977
{
7078
int magic; /* should equal CACHEDPLANSOURCE_MAGIC */
7179
Node *raw_parse_tree; /* output of raw_parser() */
72-
char *query_string; /* source text of query */
80+
const char *query_string; /* source text of query */
7381
const char *commandTag; /* command tag (a constant!), or NULL */
7482
Oid *param_types; /* array of parameter type OIDs, or NULL */
7583
int num_params; /* length of param_types array */
@@ -88,6 +96,7 @@ typedef struct CachedPlanSource
8896
/* If we have a generic plan, this is a reference-counted link to it: */
8997
struct CachedPlan *gplan; /* generic plan, or NULL if not valid */
9098
/* Some state flags: */
99+
bool is_oneshot; /* is it a "oneshot" plan? */
91100
bool is_complete; /* has CompleteCachedPlan been done? */
92101
bool is_saved; /* has CachedPlanSource been "saved"? */
93102
bool is_valid; /* is the query_list currently valid? */
@@ -106,13 +115,16 @@ typedef struct CachedPlanSource
106115
* (if any), and any active plan executions, so the plan can be discarded
107116
* exactly when refcount goes to zero. Both the struct itself and the
108117
* subsidiary data live in the context denoted by the context field.
109-
* This makes it easy to free a no-longer-needed cached plan.
118+
* This makes it easy to free a no-longer-needed cached plan. (However,
119+
* if is_oneshot is true, the context does not belong solely to the CachedPlan
120+
* so no freeing is possible.)
110121
*/
111122
typedef struct CachedPlan
112123
{
113124
int magic; /* should equal CACHEDPLAN_MAGIC */
114125
List *stmt_list; /* list of statement nodes (PlannedStmts and
115126
* bare utility statements) */
127+
bool is_oneshot; /* is it a "oneshot" plan? */
116128
bool is_saved; /* is CachedPlan in a long-lived context? */
117129
bool is_valid; /* is the stmt_list currently valid? */
118130
TransactionId saved_xmin; /* if valid, replan when TransactionXmin
@@ -129,6 +141,9 @@ extern void ResetPlanCache(void);
129141
extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
130142
const char *query_string,
131143
const char *commandTag);
144+
extern CachedPlanSource *CreateOneShotCachedPlan(Node *raw_parse_tree,
145+
const char *query_string,
146+
const char *commandTag);
132147
extern void CompleteCachedPlan(CachedPlanSource *plansource,
133148
List *querytree_list,
134149
MemoryContext querytree_context,

0 commit comments

Comments
 (0)
Please sign in to comment.