From 39fd91953e94947083b8382c10bb1c4b37b63149 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 15:00:47 +0300
Subject: [PATCH v9] Precalculate stable/immutable expressions: prepared
 statements

Use the new flag PARAM_FLAG_PRECALCULATED for generic plans with precalculated
external parameters.
---
 src/backend/commands/prepare.c                     |   4 +-
 src/backend/executor/spi.c                         |   8 +-
 src/backend/optimizer/util/clauses.c               |   7 +
 src/backend/tcop/postgres.c                        |   2 +-
 src/backend/utils/cache/plancache.c                | 299 +++++++++++++++++++--
 src/include/nodes/params.h                         |  15 +-
 src/include/utils/plancache.h                      |  13 +-
 .../expected/precalculate_stable_functions.out     |  72 +++++
 .../expected/precalculate_stable_functions_1.out   |  72 +++++
 .../regress/sql/precalculate_stable_functions.sql  |  14 +
 10 files changed, 479 insertions(+), 27 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b15..ef9f134 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -243,7 +243,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL, true);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -670,7 +670,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv, true);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 22dd55c..b4fd18d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1292,7 +1292,8 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	 */
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv,
+						  false);
 	stmt_list = cplan->stmt_list;
 
 	if (!plan->saved)
@@ -1726,7 +1727,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
 
 	/* Get the generic plan for the query */
 	cplan = GetCachedPlan(plansource, NULL, plan->saved,
-						  _SPI_current->queryEnv);
+						  _SPI_current->queryEnv, false);
 	Assert(cplan == plansource->gplan);
 
 	/* Pop the error context stack */
@@ -2119,7 +2120,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 * Replan if needed, and increment plan refcount.  If it's a saved
 		 * plan, the refcount must be backed by the CurrentResourceOwner.
 		 */
-		cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+		cplan = GetCachedPlan(plansource, paramLI, plan->saved,
+							  _SPI_current->queryEnv, false);
 		stmt_list = cplan->stmt_list;
 
 		/*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 883a31a..b3e8b7d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2828,6 +2828,13 @@ eval_const_expressions_mutator(Node *node,
 													  prm->isnull,
 													  typByVal);
 						}
+						/* Otherwise OK to cache parameter value? */
+						else if (!context->estimate &&
+								 (prm->pflags & PARAM_FLAG_PRECALCULATED))
+						{
+							return (Node *) makeCachedExpr(
+										(CacheableExpr *) copyObject(param));
+						}
 					}
 				}
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index f413395..a98b8e3 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1796,7 +1796,7 @@ exec_bind_message(StringInfo input_message)
 	 * will be generated in MessageContext.  The plan refcount will be
 	 * assigned to the Portal, so it will be released at portal destruction.
 	 */
-	cplan = GetCachedPlan(psrc, params, false, NULL);
+	cplan = GetCachedPlan(psrc, params, false, NULL, false);
 
 	/*
 	 * Now we can define the portal.
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 0ad3e3c..c921ea0 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -80,6 +80,15 @@
 	 IsA((plansource)->raw_parse_tree->stmt, TransactionStmt))
 
 /*
+ * This flag is used to reuse the already created generic plan with
+ * precalculated bound parameters: we need it to check if the parameter was
+ * precalculated and will be precalculated because the flag PARAM_FLAG_CONST is
+ * replaced by the flag PARAM_FLAG_PRECALCULATED in the generic plan.
+ */
+#define PARAM_FLAG_ALWAYS_PRECALCULATED ( PARAM_FLAG_CONST | \
+										  PARAM_FLAG_PRECALCULATED )
+
+/*
  * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
  * those that are in long-lived storage and are examined for sinval events).
  * We thread the structs manually instead of using List cells so that we can
@@ -90,9 +99,11 @@ static CachedPlanSource *first_saved_plan = NULL;
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource,
+				ParamListInfo boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv);
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool genericPlanPrecalculateConstBoundParams);
 static bool choose_custom_plan(CachedPlanSource *plansource,
 				   ParamListInfo boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
@@ -105,6 +116,12 @@ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
 static void PlanCacheRelCallback(Datum arg, Oid relid);
 static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
+static ParamExternData *GetParamExternData(ParamListInfo boundParams,
+				   int paramid,
+				   ParamExternData *workspace);
+static bool IsParamValid(const ParamExternData *prm);
+static bool CheckBoundParams(ParamListInfo firstBoundParams,
+				 ParamListInfo secondBoundParams);
 
 
 /*
@@ -795,7 +812,7 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  * (We must do this for the "true" result to be race-condition-free.)
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -846,8 +863,10 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		 */
 		if (plan->is_valid)
 		{
-			/* Successfully revalidated and locked the query. */
-			return true;
+			/*
+			 * Successfully revalidated and locked the query. Check boundParams.
+			 */
+			return CheckBoundParams(plan->boundParams, boundParams);
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
@@ -868,11 +887,15 @@ CheckCachedPlan(CachedPlanSource *plansource)
  * qlist should be the result value from a previous RevalidateCachedQuery,
  * or it can be set to NIL if we need to re-copy the plansource's query_list.
  *
- * To build a generic, parameter-value-independent plan, pass NULL for
- * boundParams.  To build a custom plan, pass the actual parameter values via
- * boundParams.  For best effect, the PARAM_FLAG_CONST flag should be set on
- * each parameter value; otherwise the planner will treat the value as a
- * hint rather than a hard constant.
+ * To build a generic, absolutely parameter-value-independent plan, pass NULL
+ * for boundParams.  To build a generic, parameter-value-independent plan with
+ * CachedExpr nodes for constant parameters, pass the actual parameter values
+ * via boundParams and set genericPlanPrecalculateConstBoundParams to true.  To
+ * build a custom plan, pass the actual parameter values via boundParams and set
+ * genericPlanPrecalculateConstBoundParams to false.
+ * For best effect, the PARAM_FLAG_CONST flag should be set on each parameter
+ * value; otherwise the planner will treat the value as a 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
@@ -880,7 +903,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
  */
 static CachedPlan *
 BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv)
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan;
 	List	   *plist;
@@ -889,6 +913,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	MemoryContext plan_context;
 	MemoryContext oldcxt = CurrentMemoryContext;
 	ListCell   *lc;
+	ParamListInfo planBoundParams;
 
 	/*
 	 * Normally the querytree should be valid already, but if it's not,
@@ -932,10 +957,72 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		snapshot_set = true;
 	}
 
+	/* Specify boundParams for the planner */
+	if (boundParams == NULL)
+	{
+		/* Absolutely parameter-value-independent generic plan */
+		planBoundParams = NULL;
+	}
+	else if (!genericPlanPrecalculateConstBoundParams)
+	{
+		/* Custom plan */
+		planBoundParams = boundParams;
+	}
+	else
+	{
+		/*
+		 * Parameter-value-independent generic plan with precalculated
+		 * parameters.
+		 */
+
+		Size		size = offsetof(ParamListInfoData, params) +
+			boundParams->numParams * sizeof(ParamExternData);
+
+		planBoundParams = (ParamListInfo) palloc(size);
+		memcpy(planBoundParams, boundParams, size);
+
+		/*
+		 * The generic plan should know as little as possible about the
+		 * parameters, and ideally we should pass boundParams as NULL. But
+		 * on ther other hand we need to know whether they are constant or
+		 * not, so that we can insert the CachedExpr nodes into the plan
+		 * where possible.
+		 *
+		 * Therefore let's put PARAM_FLAG_PRECALCULATED instead of
+		 * PARAM_FLAG_CONST for all parameters (for example, to prevent the
+		 * creation of Const nodes instead of them).
+		 */
+		if (planBoundParams->paramFetch)
+		{
+			/*
+			 * Use the same fetch function, but put PARAM_FLAG_PRECALCULATED
+			 * instead of PARAM_FLAG_CONST after its call.
+			 */
+			planBoundParams->paramFetchArg =
+				(void *) planBoundParams->paramFetch;
+			planBoundParams->paramFetch = ParamFetchPrecalculated;
+		}
+		else
+		{
+			int			index;
+
+			for (index = 0; index < planBoundParams->numParams; ++index)
+			{
+				ParamExternData *prm = &planBoundParams->params[index];
+
+				if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+				{
+					prm->pflags &= ~PARAM_FLAG_CONST;
+					prm->pflags |= PARAM_FLAG_PRECALCULATED;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Generate the plan.
 	 */
-	plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+	plist = pg_plan_queries(qlist, plansource->cursor_options, planBoundParams);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1004,6 +1091,30 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Set the precalculation parameters of the generic plan. Copy them into the
+	 * new context if the plan is not one-shot.
+	 */
+	if (planBoundParams != NULL && genericPlanPrecalculateConstBoundParams)
+	{
+		if (!plansource->is_oneshot)
+		{
+			Size		size = offsetof(ParamListInfoData, params) +
+				planBoundParams->numParams * sizeof(ParamExternData);
+
+			plan->boundParams = (ParamListInfo) palloc(size);
+			memcpy(plan->boundParams, planBoundParams, size);
+		}
+		else
+		{
+			plan->boundParams = planBoundParams;
+		}
+	}
+	else
+	{
+		plan->boundParams = NULL;
+	}
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1132,14 +1243,22 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
+ *
+ * Note: set genericPlanPrecalculateConstBoundParams to true only if you are
+ * sure that the bound parameters will remain (non)constant quite often for the
+ * next calls to this function.  Otherwise the generic plan will be rebuilt each
+ * time when there's a bound parameter that was constant/precalculated for the
+ * previous generic plan and is not constant/precalculated now or vice versa.
  */
 CachedPlan *
 GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-			  bool useResOwner, QueryEnvironment *queryEnv)
+			  bool useResOwner, QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan = NULL;
 	List	   *qlist;
 	bool		customplan;
+	ParamListInfo genericPlanBoundParams;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1156,7 +1275,13 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		/* set the parameters for the generic plan */
+		if (genericPlanPrecalculateConstBoundParams)
+			genericPlanBoundParams = boundParams;
+		else
+			genericPlanBoundParams = NULL;
+
+		if (CheckCachedPlan(plansource, genericPlanBoundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1165,7 +1290,8 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		else
 		{
 			/* Build a new generic plan */
-			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
+			plan = BuildCachedPlan(plansource, qlist, genericPlanBoundParams,
+								   queryEnv, true);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
 			/* Link the new generic plan into the plansource */
@@ -1210,7 +1336,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	if (customplan)
 	{
 		/* Build a custom plan */
-		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
+		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv, false);
 		/* Accumulate total costs of custom plans, but 'ware overflow */
 		if (plansource->num_custom_plans < INT_MAX)
 		{
@@ -1906,3 +2032,144 @@ ResetPlanCache(void)
 		}
 	}
 }
+
+/*
+ * ParamFetchPrecalculated
+ *		Fetch of parameters in generic plans. Use the same fetch function as in
+ *		the custom plan, but after its call put PARAM_FLAG_PRECALCULATED instead
+ *		of PARAM_FLAG_CONST to keep the plan as generic and prevent, for
+ *		example, the creation of a Const node for this parameter.
+ */
+ParamExternData *
+ParamFetchPrecalculated(ParamListInfo params, int paramid, bool speculative,
+						ParamExternData *workspace)
+{
+	/* Fetch back the hook data */
+	ParamFetchHook paramFetch = (ParamFetchHook) params->paramFetchArg;
+	ParamExternData *prm;
+
+	Assert(paramFetch != NULL);
+
+	prm = (*paramFetch) (params, paramid, speculative, workspace);
+	Assert(prm);
+
+	if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+	{
+		prm->pflags &= ~PARAM_FLAG_CONST;
+		prm->pflags |= PARAM_FLAG_PRECALCULATED;
+	}
+
+	return prm;
+}
+
+/*
+ * GetParamExternData: get ParamExternData with this paramid from ParamListInfo.
+ *
+ * If the parameter is dynamic, use speculative fetching, so it should avoid
+ * erroring out if parameter is unavailable.
+ */
+static ParamExternData *
+GetParamExternData(ParamListInfo boundParams, int paramid,
+				   ParamExternData *workspace)
+{
+	if (boundParams == NULL)
+		return NULL;
+
+	/*
+	 * Give hook a chance in case parameter is dynamic.  Tell it that this fetch
+	 * is speculative, so it should avoid erroring out if parameter is
+	 * unavailable.
+	 */
+	if (boundParams->paramFetch != NULL)
+		return boundParams->paramFetch(boundParams, paramid, true, workspace);
+
+	return &boundParams->params[paramid - 1];
+}
+
+/*
+ * IsParamValid: return true if prm is not NULL and its ptype is valid.
+ */
+static bool
+IsParamValid(const ParamExternData *prm)
+{
+	return prm && OidIsValid(prm->ptype);
+}
+
+/*
+ * CheckBoundParams
+ *		Check if bound params are compatible in the generic plan:
+ *		1) Check that the parameters with the same paramid are equal in terms of
+ *		   the CachedExpr node: both are constants/precalculated so they have
+ *		   previously been precalculated and will be precalculated, or both are
+ *		   not.
+ *		2) Check that the other parameters are not constants or precalculated,
+ *		   so they have not previously been precalculated and will not be
+ *		   precalculated.
+ */
+static bool
+CheckBoundParams(ParamListInfo firstBoundParams,
+				 ParamListInfo secondBoundParams)
+{
+	int			maxNumParams = 0,
+				paramid;
+	ParamExternData *first_prm,
+					*second_prm;
+	ParamExternData first_prmdata,
+					second_prmdata;
+
+	/* Get the maximum number of parameters to check */
+	if (firstBoundParams && firstBoundParams->numParams > maxNumParams)
+		maxNumParams = firstBoundParams->numParams;
+	if (secondBoundParams && secondBoundParams->numParams > maxNumParams)
+		maxNumParams = secondBoundParams->numParams;
+
+	/*
+	 * If there're parameters with the same paramid, check that they are equal
+	 * in terms of the CachedExpr node: both are constants/precalculated so they
+	 * have previously been precalculated and will be precalculated, or both are
+	 * not.
+	 *
+	 * Check that the other parameters are not constants or precalculated, so
+	 * they have not previously been precalculated and will not be
+	 * precalculated.
+	 */
+	for (paramid = 1; paramid <= maxNumParams; ++paramid)
+	{
+		first_prm = GetParamExternData(firstBoundParams, paramid,
+									   &first_prmdata);
+		second_prm = GetParamExternData(secondBoundParams, paramid,
+										&second_prmdata);
+
+		if (IsParamValid(first_prm) && IsParamValid(second_prm))
+		{
+			/*
+			 * Check that both are constants/precalculated or both are not.
+			 */
+			if ((first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED) !=
+				(second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED))
+				return false;
+		}
+		else if (IsParamValid(first_prm))
+		{
+			/*
+			 * The second parameter with this paramid is not
+			 * constant/precalculated, so check that the first one is also not
+			 * constant/precalculated.
+			 */
+			if (first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+		else if (IsParamValid(second_prm))
+		{
+			/*
+			 * The first parameter with this paramid is not
+			 * constant/precalculated, so check that the second one is also not
+			 * constant/precalculated.
+			 */
+			if (second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+	}
+
+	return true;
+}
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 0622a2b..bf3770c 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -37,10 +37,15 @@ struct ParseState;
  *	  Although parameter numbers are normally consecutive, we allow
  *	  ptype == InvalidOid to signal an unused array entry.
  *
- *	  pflags is a flags field.  Currently the only used bit is:
+ *	  pflags is a flags field.  Currently used bits are:
+ *
  *	  PARAM_FLAG_CONST signals the planner that it may treat this parameter
- *	  as a constant (i.e., generate a plan that works only for this value
- *	  of the parameter).
+ *			as a constant (i.e., generate a plan that works only for this value
+ *			of the parameter).
+ *
+ *	  PARAM_FLAG_PRECALCULATED signals the planner that it cannot be treated as
+ *			a pure constant, such as PARAM_FLAG_CONST. But it can be used as an
+ *			argument to the CachedExpr node.
  *
  *	  In the dynamic approach, all access to parameter values is done through
  *	  hook functions found in the ParamListInfo struct.  In this case,
@@ -85,7 +90,9 @@ struct ParseState;
  *	  and paramCompileArg is rather arbitrary.
  */
 
-#define PARAM_FLAG_CONST	0x0001	/* parameter is constant */
+#define PARAM_FLAG_CONST			0x0001	/* parameter is constant */
+#define PARAM_FLAG_PRECALCULATED	0x0002	/* parameter is precalculated: it is
+											 * cached in the generic plan */
 
 typedef struct ParamExternData
 {
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa0..859461d 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -138,6 +138,13 @@ typedef struct CachedPlan
 	bool		dependsOnRole;	/* is plan specific to that role? */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
+
+	/*
+	 * Used to check whether the generic plan is valid for the new boundParams;
+	 * NULL for the custom plans.
+	 */
+	ParamListInfo boundParams;
+
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
@@ -179,7 +186,11 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
 			  ParamListInfo boundParams,
 			  bool useResOwner,
-			  QueryEnvironment *queryEnv);
+			  QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern ParamExternData *ParamFetchPrecalculated(ParamListInfo params,
+						int paramid, bool speculative,
+						ParamExternData *workspace);
 
 #endif							/* PLANCACHE_H */
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
index 6a81993..2beee2e 100644
--- a/src/test/regress/expected/precalculate_stable_functions.out
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -6113,6 +6113,78 @@ SELECT simple();
 (1 row)
 
 ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
 -- Drop tables for testing
 DROP TABLE x;
 DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
index 10f66d4..9ff2530 100644
--- a/src/test/regress/expected/precalculate_stable_functions_1.out
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -5759,6 +5759,78 @@ SELECT simple();
 (1 row)
 
 ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
 -- Drop tables for testing
 DROP TABLE x;
 DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
index 23b1f04..44fcacb 100644
--- a/src/test/regress/sql/precalculate_stable_functions.sql
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -1920,6 +1920,20 @@ INSERT INTO x VALUES (5);
 SELECT simple();
 ROLLBACK;
 
+-- Prepared statements testing
+
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+
 -- Drop tables for testing
 
 DROP TABLE x;
-- 
2.7.4

