diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 416a2c4f3bbe2132ed6d66f6eeaee9067e915d9b..cb5b84509ce8a89dfa8bf309c9c64ffdfdffe43d 100644
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
*************** static int	_SPI_curid = -1;
*** 49,56 ****
  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 int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
  				  Snapshot snapshot, Snapshot crosscheck_snapshot,
--- 49,57 ----
  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);
! 
! 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,
*************** SPI_execute(const char *src, bool read_o
*** 355,361 ****
  	plan.magic = _SPI_PLAN_MAGIC;
  	plan.cursor_options = 0;
  
! 	_SPI_prepare_plan(src, &plan, NULL);
  
  	res = _SPI_execute_plan(&plan, NULL,
  							InvalidSnapshot, InvalidSnapshot,
--- 356,362 ----
  	plan.magic = _SPI_PLAN_MAGIC;
  	plan.cursor_options = 0;
  
! 	_SPI_prepare_oneshot_plan(src, &plan);
  
  	res = _SPI_execute_plan(&plan, NULL,
  							InvalidSnapshot, InvalidSnapshot,
*************** SPI_execute_with_args(const char *src,
*** 506,512 ****
  	paramLI = _SPI_convert_params(nargs, argtypes,
  								  Values, Nulls);
  
! 	_SPI_prepare_plan(src, &plan, paramLI);
  
  	res = _SPI_execute_plan(&plan, paramLI,
  							InvalidSnapshot, InvalidSnapshot,
--- 507,513 ----
  	paramLI = _SPI_convert_params(nargs, argtypes,
  								  Values, Nulls);
  
! 	_SPI_prepare_oneshot_plan(src, &plan);
  
  	res = _SPI_execute_plan(&plan, paramLI,
  							InvalidSnapshot, InvalidSnapshot,
*************** SPI_prepare_cursor(const char *src, int 
*** 547,553 ****
  	plan.parserSetup = NULL;
  	plan.parserSetupArg = NULL;
  
! 	_SPI_prepare_plan(src, &plan, NULL);
  
  	/* copy plan to procedure context */
  	result = _SPI_make_plan_non_temp(&plan);
--- 548,554 ----
  	plan.parserSetup = NULL;
  	plan.parserSetupArg = NULL;
  
! 	_SPI_prepare_plan(src, &plan);
  
  	/* copy plan to procedure context */
  	result = _SPI_make_plan_non_temp(&plan);
*************** SPI_prepare_params(const char *src,
*** 584,590 ****
  	plan.parserSetup = parserSetup;
  	plan.parserSetupArg = parserSetupArg;
  
! 	_SPI_prepare_plan(src, &plan, NULL);
  
  	/* copy plan to procedure context */
  	result = _SPI_make_plan_non_temp(&plan);
--- 585,591 ----
  	plan.parserSetup = parserSetup;
  	plan.parserSetupArg = parserSetupArg;
  
! 	_SPI_prepare_plan(src, &plan);
  
  	/* copy plan to procedure context */
  	result = _SPI_make_plan_non_temp(&plan);
*************** SPI_keepplan(SPIPlanPtr plan)
*** 599,605 ****
  {
  	ListCell   *lc;
  
! 	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
  		return SPI_ERROR_ARGUMENT;
  
  	/*
--- 600,607 ----
  {
  	ListCell   *lc;
  
! 	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
! 		plan->saved || plan->oneshot)
  		return SPI_ERROR_ARGUMENT;
  
  	/*
*************** SPI_cursor_open_with_args(const char *na
*** 1083,1089 ****
  	paramLI = _SPI_convert_params(nargs, argtypes,
  								  Values, Nulls);
  
! 	_SPI_prepare_plan(src, &plan, paramLI);
  
  	/* We needn't copy the plan; SPI_cursor_open_internal will do so */
  
--- 1085,1091 ----
  	paramLI = _SPI_convert_params(nargs, argtypes,
  								  Values, Nulls);
  
! 	_SPI_prepare_plan(src, &plan);
  
  	/* We needn't copy the plan; SPI_cursor_open_internal will do so */
  
*************** spi_printtup(TupleTableSlot *slot, DestR
*** 1645,1654 ****
   *
   * 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
--- 1647,1652 ----
*************** spi_printtup(TupleTableSlot *slot, DestR
*** 1657,1669 ****
   * parsing is also left in CurrentMemoryContext.
   */
  static void
! _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
  {
  	List	   *raw_parsetree_list;
  	List	   *plancache_list;
  	ListCell   *list_item;
  	ErrorContextCallback spierrcontext;
- 	int			cursor_options = plan->cursor_options;
  
  	/*
  	 * Setup error traceback support for ereport()
--- 1655,1666 ----
   * parsing is also left in CurrentMemoryContext.
   */
  static void
! _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
  {
  	List	   *raw_parsetree_list;
  	List	   *plancache_list;
  	ListCell   *list_item;
  	ErrorContextCallback spierrcontext;
  
  	/*
  	 * Setup error traceback support for ereport()
*************** _SPI_prepare_plan(const char *src, SPIPl
*** 1726,1738 ****
  						   plan->nargs,
  						   plan->parserSetup,
  						   plan->parserSetupArg,
! 						   cursor_options,
  						   false);		/* not fixed result */
  
  		plancache_list = lappend(plancache_list, plansource);
  	}
  
  	plan->plancache_list = plancache_list;
  
  	/*
  	 * Pop the error context stack
--- 1723,1802 ----
  						   plan->nargs,
  						   plan->parserSetup,
  						   plan->parserSetupArg,
! 						   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
*************** _SPI_execute_plan(SPIPlanPtr plan, Param
*** 1770,1776 ****
  	 * Setup error traceback support for ereport()
  	 */
  	spierrcontext.callback = _SPI_error_callback;
! 	spierrcontext.arg = NULL;
  	spierrcontext.previous = error_context_stack;
  	error_context_stack = &spierrcontext;
  
--- 1834,1840 ----
  	 * Setup error traceback support for ereport()
  	 */
  	spierrcontext.callback = _SPI_error_callback;
! 	spierrcontext.arg = NULL;	/* we'll fill this below */
  	spierrcontext.previous = error_context_stack;
  	error_context_stack = &spierrcontext;
  
*************** _SPI_execute_plan(SPIPlanPtr plan, Param
*** 1817,1822 ****
--- 1881,1927 ----
  		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.
  		 */
*************** _SPI_make_plan_non_temp(SPIPlanPtr plan)
*** 2313,2318 ****
--- 2418,2425 ----
  	/* 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.
*************** _SPI_make_plan_non_temp(SPIPlanPtr plan)
*** 2330,2335 ****
--- 2437,2443 ----
  	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;
*************** _SPI_save_plan(SPIPlanPtr plan)
*** 2379,2384 ****
--- 2487,2495 ----
  	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
*************** _SPI_save_plan(SPIPlanPtr plan)
*** 2395,2400 ****
--- 2506,2512 ----
  	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 8c0391ffcbdfa9058c345930c6d9568dbc9c526d..a211869866674ffbf06393d29a742cf5c69a7639 100644
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 181,186 ****
--- 181,187 ----
  	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;
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 196,201 ****
--- 197,265 ----
  }
  
  /*
+  * 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
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 222,227 ****
--- 286,295 ----
   * 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,
*************** CompleteCachedPlan(CachedPlanSource *pla
*** 254,262 ****
  	/*
  	 * 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.
  	 */
! 	if (querytree_context != NULL)
  	{
  		MemoryContextSetParent(querytree_context, source_context);
  		MemoryContextSwitchTo(querytree_context);
--- 322,336 ----
  	/*
  	 * 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.  But no data copying should be done
! 	 * for one-shot plans; for those, assume the passed querytree_list is
! 	 * sufficiently long-lived.
  	 */
! 	if (plansource->is_oneshot)
! 	{
! 		querytree_context = CurrentMemoryContext;
! 	}
! 	else if (querytree_context != NULL)
  	{
  		MemoryContextSetParent(querytree_context, source_context);
  		MemoryContextSwitchTo(querytree_context);
*************** CompleteCachedPlan(CachedPlanSource *pla
*** 279,289 ****
  	/*
  	 * 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.)
  	 */
! 	extract_query_dependencies((Node *) querytree_list,
! 							   &plansource->relationOids,
! 							   &plansource->invalItems);
  
  	/*
  	 * Save the final parameter types (or other parameter specification data)
--- 353,364 ----
  	/*
  	 * 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.)  We can skip this for one-shot plans.
  	 */
! 	if (!plansource->is_oneshot)
! 		extract_query_dependencies((Node *) querytree_list,
! 								   &plansource->relationOids,
! 								   &plansource->invalItems);
  
  	/*
  	 * Save the final parameter types (or other parameter specification data)
*************** CompleteCachedPlan(CachedPlanSource *pla
*** 326,332 ****
   * 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
   * 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
--- 401,408 ----
   * it to the list of cached plans that are checked for invalidation when an
   * sinval event occurs.
   *
!  * 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
*************** SaveCachedPlan(CachedPlanSource *plansou
*** 340,345 ****
--- 416,425 ----
  	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
*************** DropCachedPlan(CachedPlanSource *plansou
*** 402,412 ****
  	/* Decrement generic CachePlan's refcount and drop if no longer needed */
  	ReleaseGenericPlan(plansource);
  
  	/*
  	 * Remove the CachedPlanSource and all subsidiary data (including the
! 	 * query_context if any).
  	 */
! 	MemoryContextDelete(plansource->context);
  }
  
  /*
--- 482,496 ----
  	/* 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).  But if it's a one-shot we can't free anything.
  	 */
! 	if (!plansource->is_oneshot)
! 		MemoryContextDelete(plansource->context);
  }
  
  /*
*************** RevalidateCachedQuery(CachedPlanSource *
*** 452,457 ****
--- 536,552 ----
  	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.
*************** CheckCachedPlan(CachedPlanSource *planso
*** 649,654 ****
--- 744,751 ----
  		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
*************** CheckCachedPlan(CachedPlanSource *planso
*** 708,714 ****
   * 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.
   */
  static CachedPlan *
  BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
--- 805,812 ----
   * 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
!  * (unless this is a one-shot plan, in which case we don't copy the plan).
   */
  static CachedPlan *
  BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
*************** BuildCachedPlan(CachedPlanSource *planso
*** 719,725 ****
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
! 	MemoryContext oldcxt;
  
  	/*
  	 * Normally the querytree should be valid already, but if it's not,
--- 817,823 ----
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
! 	MemoryContext oldcxt = CurrentMemoryContext;
  
  	/*
  	 * Normally the querytree should be valid already, but if it's not,
*************** BuildCachedPlan(CachedPlanSource *planso
*** 739,748 ****
  
  	/*
  	 * If we don't already have a copy of the querytree list that can be
! 	 * scribbled on by the planner, make one.
  	 */
  	if (qlist == NIL)
! 		qlist = (List *) copyObject(plansource->query_list);
  
  	/*
  	 * Restore the search_path that was in use when the plan was made. See
--- 837,852 ----
  
  	/*
  	 * If we don't already have a copy of the querytree list that can be
! 	 * 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)
! 	{
! 		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
*************** BuildCachedPlan(CachedPlanSource *planso
*** 794,815 ****
  	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.
  	 */
! 	plan_context = AllocSetContextCreate(CurrentMemoryContext,
! 										 "CachedPlan",
! 										 ALLOCSET_SMALL_MINSIZE,
! 										 ALLOCSET_SMALL_INITSIZE,
! 										 ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/*
! 	 * Copy plan into the new context.
! 	 */
! 	oldcxt = MemoryContextSwitchTo(plan_context);
  
! 	plist = (List *) copyObject(plist);
  
  	/*
  	 * Create and fill the CachedPlan struct within the new context.
--- 898,926 ----
  	PopOverrideSearchPath();
  
  	/*
! 	 * 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.
  	 */
! 	if (!plansource->is_oneshot)
! 	{
! 		plan_context = AllocSetContextCreate(CurrentMemoryContext,
! 											 "CachedPlan",
! 											 ALLOCSET_SMALL_MINSIZE,
! 											 ALLOCSET_SMALL_INITSIZE,
! 											 ALLOCSET_DEFAULT_MAXSIZE);
  
! 		/*
! 		 * Copy plan into the new context.
! 		 */
! 		MemoryContextSwitchTo(plan_context);
  
! 		plist = (List *) copyObject(plist);
! 	}
! 	else
! 		plan_context = CurrentMemoryContext;
  
  	/*
  	 * Create and fill the CachedPlan struct within the new context.
*************** BuildCachedPlan(CachedPlanSource *planso
*** 826,831 ****
--- 937,943 ----
  		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;
  
*************** choose_custom_plan(CachedPlanSource *pla
*** 847,853 ****
  {
  	double		avg_custom_cost;
  
! 	/* Never any point in a custom plan if there's no parameters */
  	if (boundParams == NULL)
  		return false;
  
--- 959,969 ----
  {
  	double		avg_custom_cost;
  
! 	/* 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;
  
*************** ReleaseCachedPlan(CachedPlan *plan, bool
*** 1049,1055 ****
  	Assert(plan->refcount > 0);
  	plan->refcount--;
  	if (plan->refcount == 0)
! 		MemoryContextDelete(plan->context);
  }
  
  /*
--- 1165,1178 ----
  	Assert(plan->refcount > 0);
  	plan->refcount--;
  	if (plan->refcount == 0)
! 	{
! 		/* 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);
! 	}
  }
  
  /*
*************** CachedPlanSetParentContext(CachedPlanSou
*** 1066,1074 ****
  	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
  	Assert(plansource->is_complete);
  
! 	/* This seems worth a real test, though */
  	if (plansource->is_saved)
  		elog(ERROR, "cannot move a saved cached plan to another context");
  
  	/* OK, let the caller keep the plan where he wishes */
  	MemoryContextSetParent(plansource->context, newcontext);
--- 1189,1199 ----
  	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
  	Assert(plansource->is_complete);
  
! 	/* 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);
*************** CopyCachedPlan(CachedPlanSource *plansou
*** 1105,1110 ****
--- 1230,1242 ----
  	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,
*************** CopyCachedPlan(CachedPlanSource *plansou
*** 1152,1157 ****
--- 1284,1290 ----
  
  	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
*************** typedef struct
*** 59,64 ****
--- 59,70 ----
   * 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
*************** typedef struct _SPI_plan
*** 68,73 ****
--- 74,80 ----
  {
  	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..7d0fd6e4d2a6c6695b79e3a9efcbc7ee9d749122 100644
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
***************
*** 60,65 ****
--- 60,73 ----
   * 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
*************** typedef struct CachedPlanSource
*** 69,75 ****
  {
  	int			magic;			/* should equal CACHEDPLANSOURCE_MAGIC */
  	Node	   *raw_parse_tree; /* output of raw_parser() */
! 	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 */
--- 77,83 ----
  {
  	int			magic;			/* should equal CACHEDPLANSOURCE_MAGIC */
  	Node	   *raw_parse_tree; /* output of raw_parser() */
! 	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 */
*************** typedef struct CachedPlanSource
*** 88,93 ****
--- 96,102 ----
  	/* 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? */
*************** typedef struct CachedPlanSource
*** 106,118 ****
   * (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.
   */
  typedef struct CachedPlan
  {
  	int			magic;			/* should equal CACHEDPLAN_MAGIC */
  	List	   *stmt_list;		/* list of statement nodes (PlannedStmts and
  								 * bare utility statements) */
  	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
--- 115,130 ----
   * (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.  (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
*************** extern void ResetPlanCache(void);
*** 129,134 ****
--- 141,149 ----
  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,
