@@ -181,6 +181,7 @@ CreateCachedPlan(Node *raw_parse_tree,
181
181
plansource -> invalItems = NIL ;
182
182
plansource -> query_context = NULL ;
183
183
plansource -> gplan = NULL ;
184
+ plansource -> is_oneshot = false;
184
185
plansource -> is_complete = false;
185
186
plansource -> is_saved = false;
186
187
plansource -> is_valid = false;
@@ -195,6 +196,69 @@ CreateCachedPlan(Node *raw_parse_tree,
195
196
return plansource ;
196
197
}
197
198
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
+
198
262
/*
199
263
* CompleteCachedPlan: second step of creating a plan cache entry.
200
264
*
@@ -222,6 +286,10 @@ CreateCachedPlan(Node *raw_parse_tree,
222
286
* option, it is caller's responsibility that the referenced data remains
223
287
* valid for as long as the CachedPlanSource exists.
224
288
*
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
+ *
225
293
* plansource: structure returned by CreateCachedPlan
226
294
* querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
227
295
* querytree_context: memory context containing querytree_list,
@@ -254,9 +322,15 @@ CompleteCachedPlan(CachedPlanSource *plansource,
254
322
/*
255
323
* If caller supplied a querytree_context, reparent it underneath the
256
324
* 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.
258
328
*/
259
- if (querytree_context != NULL )
329
+ if (plansource -> is_oneshot )
330
+ {
331
+ querytree_context = CurrentMemoryContext ;
332
+ }
333
+ else if (querytree_context != NULL )
260
334
{
261
335
MemoryContextSetParent (querytree_context , source_context );
262
336
MemoryContextSwitchTo (querytree_context );
@@ -279,11 +353,12 @@ CompleteCachedPlan(CachedPlanSource *plansource,
279
353
/*
280
354
* Use the planner machinery to extract dependencies. Data is saved in
281
355
* 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.
283
357
*/
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 );
287
362
288
363
/*
289
364
* Save the final parameter types (or other parameter specification data)
@@ -326,7 +401,8 @@ CompleteCachedPlan(CachedPlanSource *plansource,
326
401
* it to the list of cached plans that are checked for invalidation when an
327
402
* sinval event occurs.
328
403
*
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
330
406
* since this is called just before or just after adding a pointer to the
331
407
* CachedPlanSource to some permanent data structure of their own. Up until
332
408
* this is done, a CachedPlanSource is just transient data that will go away
@@ -340,6 +416,10 @@ SaveCachedPlan(CachedPlanSource *plansource)
340
416
Assert (plansource -> is_complete );
341
417
Assert (!plansource -> is_saved );
342
418
419
+ /* This seems worth a real test, though */
420
+ if (plansource -> is_oneshot )
421
+ elog (ERROR , "cannot save one-shot cached plan" );
422
+
343
423
/*
344
424
* In typical use, this function would be called before generating any
345
425
* plans from the CachedPlanSource. If there is a generic plan, moving it
@@ -402,11 +482,15 @@ DropCachedPlan(CachedPlanSource *plansource)
402
482
/* Decrement generic CachePlan's refcount and drop if no longer needed */
403
483
ReleaseGenericPlan (plansource );
404
484
485
+ /* Mark it no longer valid */
486
+ plansource -> magic = 0 ;
487
+
405
488
/*
406
489
* 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.
408
491
*/
409
- MemoryContextDelete (plansource -> context );
492
+ if (!plansource -> is_oneshot )
493
+ MemoryContextDelete (plansource -> context );
410
494
}
411
495
412
496
/*
@@ -451,6 +535,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
451
535
MemoryContext querytree_context ;
452
536
MemoryContext oldcxt ;
453
537
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
+
454
549
/*
455
550
* If the query is currently valid, acquire locks on the referenced
456
551
* objects; then check again. We need to do it this way to cover the race
@@ -649,6 +744,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
649
744
return false;
650
745
651
746
Assert (plan -> magic == CACHEDPLAN_MAGIC );
747
+ /* Generic plans are never one-shot */
748
+ Assert (!plan -> is_oneshot );
652
749
653
750
/*
654
751
* If it appears valid, acquire locks and recheck; this is much the same
@@ -708,7 +805,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
708
805
* hint rather than a hard constant.
709
806
*
710
807
* 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).
712
810
*/
713
811
static CachedPlan *
714
812
BuildCachedPlan (CachedPlanSource * plansource , List * qlist ,
@@ -719,7 +817,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
719
817
bool snapshot_set ;
720
818
bool spi_pushed ;
721
819
MemoryContext plan_context ;
722
- MemoryContext oldcxt ;
820
+ MemoryContext oldcxt = CurrentMemoryContext ;
723
821
724
822
/*
725
823
* Normally the querytree should be valid already, but if it's not,
@@ -739,10 +837,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
739
837
740
838
/*
741
839
* 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.
743
842
*/
744
843
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
+ }
746
850
747
851
/*
748
852
* Restore the search_path that was in use when the plan was made. See
@@ -794,22 +898,29 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
794
898
PopOverrideSearchPath ();
795
899
796
900
/*
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.
800
906
*/
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 );
806
914
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 );
811
919
812
- plist = (List * ) copyObject (plist );
920
+ plist = (List * ) copyObject (plist );
921
+ }
922
+ else
923
+ plan_context = CurrentMemoryContext ;
813
924
814
925
/*
815
926
* Create and fill the CachedPlan struct within the new context.
@@ -826,6 +937,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
826
937
plan -> saved_xmin = InvalidTransactionId ;
827
938
plan -> refcount = 0 ;
828
939
plan -> context = plan_context ;
940
+ plan -> is_oneshot = plansource -> is_oneshot ;
829
941
plan -> is_saved = false;
830
942
plan -> is_valid = true;
831
943
@@ -847,7 +959,11 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
847
959
{
848
960
double avg_custom_cost ;
849
961
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 */
851
967
if (boundParams == NULL )
852
968
return false;
853
969
@@ -1049,7 +1165,14 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
1049
1165
Assert (plan -> refcount > 0 );
1050
1166
plan -> refcount -- ;
1051
1167
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
+ }
1053
1176
}
1054
1177
1055
1178
/*
@@ -1066,9 +1189,11 @@ CachedPlanSetParentContext(CachedPlanSource *plansource,
1066
1189
Assert (plansource -> magic == CACHEDPLANSOURCE_MAGIC );
1067
1190
Assert (plansource -> is_complete );
1068
1191
1069
- /* This seems worth a real test , though */
1192
+ /* These seem worth real tests , though */
1070
1193
if (plansource -> is_saved )
1071
1194
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" );
1072
1197
1073
1198
/* OK, let the caller keep the plan where he wishes */
1074
1199
MemoryContextSetParent (plansource -> context , newcontext );
@@ -1105,6 +1230,13 @@ CopyCachedPlan(CachedPlanSource *plansource)
1105
1230
Assert (plansource -> magic == CACHEDPLANSOURCE_MAGIC );
1106
1231
Assert (plansource -> is_complete );
1107
1232
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
+
1108
1240
source_context = AllocSetContextCreate (CurrentMemoryContext ,
1109
1241
"CachedPlanSource" ,
1110
1242
ALLOCSET_SMALL_MINSIZE ,
@@ -1152,6 +1284,7 @@ CopyCachedPlan(CachedPlanSource *plansource)
1152
1284
1153
1285
newsource -> gplan = NULL ;
1154
1286
1287
+ newsource -> is_oneshot = false;
1155
1288
newsource -> is_complete = true;
1156
1289
newsource -> is_saved = false;
1157
1290
newsource -> is_valid = plansource -> is_valid ;
0 commit comments