aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/namespace.c18
-rw-r--r--src/backend/commands/prepare.c144
-rw-r--r--src/backend/executor/spi.c363
-rw-r--r--src/backend/tcop/postgres.c226
-rw-r--r--src/backend/utils/adt/ri_triggers.c4
-rw-r--r--src/backend/utils/adt/ruleutils.c6
-rw-r--r--src/backend/utils/cache/plancache.c1521
-rw-r--r--src/backend/utils/mmgr/mcxt.c12
-rw-r--r--src/backend/utils/mmgr/portalmem.c6
-rw-r--r--src/include/catalog/namespace.h1
-rw-r--r--src/include/commands/prepare.h8
-rw-r--r--src/include/executor/spi.h1
-rw-r--r--src/include/executor/spi_priv.h41
-rw-r--r--src/include/nodes/parsenodes.h3
-rw-r--r--src/include/utils/memutils.h1
-rw-r--r--src/include/utils/plancache.h156
-rw-r--r--src/pl/plperl/plperl.c18
-rw-r--r--src/pl/plpgsql/src/pl_exec.c215
-rw-r--r--src/pl/plpython/plpython.c11
-rw-r--r--src/pl/tcl/pltcl.c17
-rw-r--r--src/test/regress/regress.c5
21 files changed, 1644 insertions, 1133 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index fcb41a8adb8..040bef6addb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -2941,6 +2941,24 @@ GetOverrideSearchPath(MemoryContext context)
}
/*
+ * CopyOverrideSearchPath - copy the specified OverrideSearchPath.
+ *
+ * The result structure is allocated in CurrentMemoryContext.
+ */
+OverrideSearchPath *
+CopyOverrideSearchPath(OverrideSearchPath *path)
+{
+ OverrideSearchPath *result;
+
+ result = (OverrideSearchPath *) palloc(sizeof(OverrideSearchPath));
+ result->schemas = list_copy(path->schemas);
+ result->addCatalog = path->addCatalog;
+ result->addTemp = path->addTemp;
+
+ return result;
+}
+
+/*
* PushOverrideSearchPath - temporarily override the search path
*
* We allow nested overrides, hence the push/pop terminology. The GUC
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index d929e14e0ed..a94921574fa 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -53,11 +53,11 @@ static Datum build_regtype_array(Oid *param_types, int num_params);
void
PrepareQuery(PrepareStmt *stmt, const char *queryString)
{
+ CachedPlanSource *plansource;
Oid *argtypes = NULL;
int nargs;
Query *query;
- List *query_list,
- *plan_list;
+ List *query_list;
int i;
/*
@@ -69,6 +69,13 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
errmsg("invalid statement name: must not be empty")));
+ /*
+ * Create the CachedPlanSource before we do parse analysis, since it needs
+ * to see the unmodified raw parse tree.
+ */
+ plansource = CreateCachedPlan(stmt->query, queryString,
+ CreateCommandTag(stmt->query));
+
/* Transform list of TypeNames to array of type OIDs */
nargs = list_length(stmt->argtypes);
@@ -102,7 +109,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* information about unknown parameters to be deduced from context.
*
* Because parse analysis scribbles on the raw querytree, we must make a
- * copy to ensure we have a pristine raw tree to cache. FIXME someday.
+ * copy to ensure we don't modify the passed-in tree. FIXME someday.
*/
query = parse_analyze_varparams((Node *) copyObject(stmt->query),
queryString,
@@ -143,20 +150,22 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
/* Rewrite the query. The result could be 0, 1, or many queries. */
query_list = QueryRewrite(query);
- /* Generate plans for queries. */
- plan_list = pg_plan_queries(query_list, 0, NULL);
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ query_list,
+ NULL,
+ argtypes,
+ nargs,
+ NULL,
+ NULL,
+ 0, /* default cursor options */
+ true); /* fixed result */
/*
* Save the results.
*/
StorePreparedStatement(stmt->name,
- stmt->query,
- queryString,
- CreateCommandTag((Node *) query),
- argtypes,
- nargs,
- 0, /* default cursor options */
- plan_list,
+ plansource,
true);
}
@@ -185,10 +194,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
/* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true);
- /* Shouldn't have a non-fully-planned plancache entry */
- if (!entry->plansource->fully_planned)
- elog(ERROR, "EXECUTE does not support unplanned prepared statements");
- /* Shouldn't get any non-fixed-result cached plan, either */
+ /* Shouldn't find a non-fixed-result cached plan */
if (!entry->plansource->fixed_result)
elog(ERROR, "EXECUTE does not support variable-result cached plans");
@@ -197,7 +203,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
{
/*
* Need an EState to evaluate parameters; must not delete it till end
- * of query, in case parameters are pass-by-reference.
+ * of query, in case parameters are pass-by-reference. Note that the
+ * passed-in "params" could possibly be referenced in the parameter
+ * expressions.
*/
estate = CreateExecutorState();
estate->es_param_list_info = params;
@@ -226,7 +234,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
PlannedStmt *pstmt;
/* Replan if needed, and increment plan refcount transiently */
- cplan = RevalidateCachedPlan(entry->plansource, true);
+ cplan = GetCachedPlan(entry->plansource, paramLI, true);
/* Copy plan into portal's context, and modify */
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
@@ -256,7 +264,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
else
{
/* Replan if needed, and increment plan refcount for portal */
- cplan = RevalidateCachedPlan(entry->plansource, false);
+ cplan = GetCachedPlan(entry->plansource, paramLI, false);
plan_list = cplan->stmt_list;
}
@@ -396,7 +404,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
ParamExternData *prm = &paramLI->params[i];
prm->ptype = param_types[i];
- prm->pflags = 0;
+ prm->pflags = PARAM_FLAG_CONST;
prm->value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate),
&prm->isnull,
@@ -430,54 +438,24 @@ InitQueryHashTable(void)
/*
* Store all the data pertaining to a query in the hash table using
- * the specified key. All the given data is copied into either the hashtable
- * entry or the underlying plancache entry, so the caller can dispose of its
- * copy.
- *
- * Exception: commandTag is presumed to be a pointer to a constant string,
- * or possibly NULL, so it need not be copied. Note that commandTag should
- * be NULL only if the original query (before rewriting) was empty.
+ * the specified key. The passed CachedPlanSource should be "unsaved"
+ * in case we get an error here; we'll save it once we've created the hash
+ * table entry.
*/
void
StorePreparedStatement(const char *stmt_name,
- Node *raw_parse_tree,
- const char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
+ CachedPlanSource *plansource,
bool from_sql)
{
PreparedStatement *entry;
- CachedPlanSource *plansource;
+ TimestampTz cur_ts = GetCurrentStatementStartTimestamp();
bool found;
/* Initialize the hash table, if necessary */
if (!prepared_queries)
InitQueryHashTable();
- /* Check for pre-existing entry of same name */
- hash_search(prepared_queries, stmt_name, HASH_FIND, &found);
-
- if (found)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_PSTATEMENT),
- errmsg("prepared statement \"%s\" already exists",
- stmt_name)));
-
- /* Create a plancache entry */
- plansource = CreateCachedPlan(raw_parse_tree,
- query_string,
- commandTag,
- param_types,
- num_params,
- cursor_options,
- stmt_list,
- true,
- true);
-
- /* Now we can add entry to hash table */
+ /* Add entry to hash table */
entry = (PreparedStatement *) hash_search(prepared_queries,
stmt_name,
HASH_ENTER,
@@ -485,13 +463,18 @@ StorePreparedStatement(const char *stmt_name,
/* Shouldn't get a duplicate entry */
if (found)
- elog(ERROR, "duplicate prepared statement \"%s\"",
- stmt_name);
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_PSTATEMENT),
+ errmsg("prepared statement \"%s\" already exists",
+ stmt_name)));
/* Fill in the hash table entry */
entry->plansource = plansource;
entry->from_sql = from_sql;
- entry->prepare_time = GetCurrentStatementStartTimestamp();
+ entry->prepare_time = cur_ts;
+
+ /* Now it's safe to move the CachedPlanSource to permanent memory */
+ SaveCachedPlan(plansource);
}
/*
@@ -538,7 +521,7 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt)
{
/*
* Since we don't allow prepared statements' result tupdescs to change,
- * there's no need for a revalidate call here.
+ * there's no need to worry about revalidating the cached plan here.
*/
Assert(stmt->plansource->fixed_result);
if (stmt->plansource->resultDesc)
@@ -560,24 +543,12 @@ List *
FetchPreparedStatementTargetList(PreparedStatement *stmt)
{
List *tlist;
- CachedPlan *cplan;
-
- /* No point in looking if it doesn't return tuples */
- if (stmt->plansource->resultDesc == NULL)
- return NIL;
- /* Make sure the plan is up to date */
- cplan = RevalidateCachedPlan(stmt->plansource, true);
-
- /* Get the primary statement and find out what it returns */
- tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
-
- /* Copy into caller's context so we can release the plancache entry */
- tlist = (List *) copyObject(tlist);
-
- ReleaseCachedPlan(cplan, true);
+ /* Get the plan's primary targetlist */
+ tlist = CachedPlanGetTargetList(stmt->plansource);
- return tlist;
+ /* Copy into caller's context in case plan gets invalidated */
+ return (List *) copyObject(tlist);
}
/*
@@ -662,26 +633,20 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
/* Look it up in the hash table */
entry = FetchPreparedStatement(execstmt->name, true);
- /* Shouldn't have a non-fully-planned plancache entry */
- if (!entry->plansource->fully_planned)
- elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements");
- /* Shouldn't get any non-fixed-result cached plan, either */
+ /* Shouldn't find a non-fixed-result cached plan */
if (!entry->plansource->fixed_result)
elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");
query_string = entry->plansource->query_string;
- /* Replan if needed, and acquire a transient refcount */
- cplan = RevalidateCachedPlan(entry->plansource, true);
-
- plan_list = cplan->stmt_list;
-
/* Evaluate parameters, if any */
if (entry->plansource->num_params)
{
/*
* Need an EState to evaluate parameters; must not delete it till end
- * of query, in case parameters are pass-by-reference.
+ * of query, in case parameters are pass-by-reference. Note that the
+ * passed-in "params" could possibly be referenced in the parameter
+ * expressions.
*/
estate = CreateExecutorState();
estate->es_param_list_info = params;
@@ -689,6 +654,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
queryString, estate);
}
+ /* Replan if needed, and acquire a transient refcount */
+ cplan = GetCachedPlan(entry->plansource, paramLI, true);
+
+ plan_list = cplan->stmt_list;
+
/* Explain each query */
foreach(p, plan_list)
{
@@ -714,7 +684,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
}
else
{
- ExplainOneUtility((Node *) pstmt, es, query_string, params);
+ ExplainOneUtility((Node *) pstmt, es, query_string, paramLI);
}
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index d71ea60b317..688279c716e 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -56,8 +56,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
bool read_only, bool fire_triggers, long tcount);
static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes,
- Datum *Values, const char *Nulls,
- int pflags);
+ Datum *Values, const char *Nulls);
static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount);
@@ -67,7 +66,7 @@ static void _SPI_cursor_operation(Portal portal,
FetchDirection direction, long count,
DestReceiver *dest);
-static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt);
+static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan);
static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
static int _SPI_begin_call(bool execmem);
@@ -391,8 +390,7 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
res = _SPI_execute_plan(plan,
_SPI_convert_params(plan->nargs, plan->argtypes,
- Values, Nulls,
- 0),
+ Values, Nulls),
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount);
@@ -462,8 +460,7 @@ SPI_execute_snapshot(SPIPlanPtr plan,
res = _SPI_execute_plan(plan,
_SPI_convert_params(plan->nargs, plan->argtypes,
- Values, Nulls,
- 0),
+ Values, Nulls),
snapshot, crosscheck_snapshot,
read_only, fire_triggers, tcount);
@@ -474,11 +471,8 @@ SPI_execute_snapshot(SPIPlanPtr plan,
/*
* SPI_execute_with_args -- plan and execute a query with supplied arguments
*
- * This is functionally comparable to SPI_prepare followed by
- * SPI_execute_plan, except that since we know the plan will be used only
- * once, we can tell the planner to rely on the parameter values as constants.
- * This eliminates potential performance disadvantages compared to
- * inserting the parameter values directly into the query text.
+ * This is functionally equivalent to SPI_prepare followed by
+ * SPI_execute_plan.
*/
int
SPI_execute_with_args(const char *src,
@@ -509,13 +503,10 @@ SPI_execute_with_args(const char *src,
plan.parserSetupArg = NULL;
paramLI = _SPI_convert_params(nargs, argtypes,
- Values, Nulls,
- PARAM_FLAG_CONST);
+ Values, Nulls);
_SPI_prepare_plan(src, &plan, paramLI);
- /* We don't need to copy the plan since it will be thrown away anyway */
-
res = _SPI_execute_plan(&plan, paramLI,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount);
@@ -558,7 +549,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
_SPI_prepare_plan(src, &plan, NULL);
/* copy plan to procedure context */
- result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
+ result = _SPI_make_plan_non_temp(&plan);
_SPI_end_call(true);
@@ -595,20 +586,45 @@ SPI_prepare_params(const char *src,
_SPI_prepare_plan(src, &plan, NULL);
/* copy plan to procedure context */
- result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
+ result = _SPI_make_plan_non_temp(&plan);
_SPI_end_call(true);
return result;
}
+int
+SPI_keepplan(SPIPlanPtr plan)
+{
+ ListCell *lc;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
+ return SPI_ERROR_ARGUMENT;
+
+ /*
+ * Mark it saved, reparent it under CacheMemoryContext, and mark all the
+ * component CachedPlanSources as saved. This sequence cannot fail
+ * partway through, so there's no risk of long-term memory leakage.
+ */
+ plan->saved = true;
+ MemoryContextSetParent(plan->plancxt, CacheMemoryContext);
+
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ SaveCachedPlan(plansource);
+ }
+
+ return 0;
+}
+
SPIPlanPtr
SPI_saveplan(SPIPlanPtr plan)
{
SPIPlanPtr newplan;
- /* We don't currently support copying an already-saved plan */
- if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
{
SPI_result = SPI_ERROR_ARGUMENT;
return NULL;
@@ -620,8 +636,7 @@ SPI_saveplan(SPIPlanPtr plan)
newplan = _SPI_save_plan(plan);
- _SPI_curid--;
- SPI_result = 0;
+ SPI_result = _SPI_end_call(false);
return newplan;
}
@@ -629,20 +644,17 @@ SPI_saveplan(SPIPlanPtr plan)
int
SPI_freeplan(SPIPlanPtr plan)
{
+ ListCell *lc;
+
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
return SPI_ERROR_ARGUMENT;
- /* If plancache.c owns the plancache entries, we must release them */
- if (plan->saved)
+ /* Release the plancache entries */
+ foreach(lc, plan->plancache_list)
{
- ListCell *lc;
-
- foreach(lc, plan->plancache_list)
- {
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
- DropCachedPlan(plansource);
- }
+ DropCachedPlan(plansource);
}
/* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
@@ -1020,8 +1032,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
/* build transient ParamListInfo in caller's context */
paramLI = _SPI_convert_params(plan->nargs, plan->argtypes,
- Values, Nulls,
- 0);
+ Values, Nulls);
portal = SPI_cursor_open_internal(name, plan, paramLI, read_only);
@@ -1036,9 +1047,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
/*
* SPI_cursor_open_with_args()
*
- * Parse and plan a query and open it as a portal. Like SPI_execute_with_args,
- * we can tell the planner to rely on the parameter values as constants,
- * because the plan will only be used once.
+ * Parse and plan a query and open it as a portal.
*/
Portal
SPI_cursor_open_with_args(const char *name,
@@ -1071,8 +1080,7 @@ SPI_cursor_open_with_args(const char *name,
/* build transient ParamListInfo in executor context */
paramLI = _SPI_convert_params(nargs, argtypes,
- Values, Nulls,
- PARAM_FLAG_CONST);
+ Values, Nulls);
_SPI_prepare_plan(src, &plan, paramLI);
@@ -1081,9 +1089,6 @@ SPI_cursor_open_with_args(const char *name,
/* Adjust stack so that SPI_cursor_open_internal doesn't complain */
_SPI_curid--;
- /* SPI_cursor_open_internal must be called in procedure memory context */
- _SPI_procmem();
-
result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);
/* And clean up */
@@ -1148,7 +1153,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Push the SPI stack */
- if (_SPI_begin_call(false) < 0)
+ if (_SPI_begin_call(true) < 0)
elog(ERROR, "SPI_cursor_open called while not connected");
/* Reset SPI result (note we deliberately don't touch lastoid) */
@@ -1174,22 +1179,27 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
plansource->query_string);
/*
- * Note: we mustn't have any failure occur between RevalidateCachedPlan
- * and PortalDefineQuery; that would result in leaking our plancache
- * refcount.
+ * Note: for a saved plan, we mustn't have any failure occur between
+ * GetCachedPlan and PortalDefineQuery; that would result in leaking our
+ * plancache refcount.
*/
- if (plan->saved)
- {
- /* Replan if needed, and increment plan refcount for portal */
- cplan = RevalidateCachedPlan(plansource, false);
- stmt_list = cplan->stmt_list;
- }
- else
+
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = GetCachedPlan(plansource, paramLI, false);
+ stmt_list = cplan->stmt_list;
+
+ if (!plan->saved)
{
- /* No replan, but copy the plan into the portal's context */
+ /*
+ * We don't want the portal to depend on an unsaved CachedPlanSource,
+ * so must copy the plan into the portal's context. An error here
+ * will result in leaking our refcount on the plan, but it doesn't
+ * matter because the plan is unsaved and hence transient anyway.
+ */
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
- stmt_list = copyObject(plansource->plan->stmt_list);
+ stmt_list = copyObject(stmt_list);
MemoryContextSwitchTo(oldcontext);
+ ReleaseCachedPlan(cplan, false);
cplan = NULL; /* portal shouldn't depend on cplan */
}
@@ -1238,9 +1248,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
/*
* If told to be read-only, we'd better check for read-only queries. This
* can't be done earlier because we need to look at the finished, planned
- * queries. (In particular, we don't want to do it between
- * RevalidateCachedPlan and PortalDefineQuery, because throwing an error
- * between those steps would result in leaking our plancache refcount.)
+ * queries. (In particular, we don't want to do it between GetCachedPlan
+ * and PortalDefineQuery, because throwing an error between those steps
+ * would result in leaking our plancache refcount.)
*/
if (read_only)
{
@@ -1288,7 +1298,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Assert(portal->strategy != PORTAL_MULTI_QUERY);
/* Pop the SPI stack */
- _SPI_end_call(false);
+ _SPI_end_call(true);
/* Return the created portal */
return portal;
@@ -1420,7 +1430,6 @@ bool
SPI_is_cursor_plan(SPIPlanPtr plan)
{
CachedPlanSource *plansource;
- CachedPlan *cplan;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
{
@@ -1435,19 +1444,11 @@ SPI_is_cursor_plan(SPIPlanPtr plan)
}
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
- /* Need _SPI_begin_call in case replanning invokes SPI-using functions */
- SPI_result = _SPI_begin_call(false);
- if (SPI_result < 0)
- return false;
-
- if (plan->saved)
- {
- /* Make sure the plan is up to date */
- cplan = RevalidateCachedPlan(plansource, true);
- ReleaseCachedPlan(cplan, true);
- }
-
- _SPI_end_call(false);
+ /*
+ * We used to force revalidation of the cached plan here, but that seems
+ * unnecessary: invalidation could mean a change in the rowtype of the
+ * tuples returned by a plan, but not whether it returns tuples at all.
+ */
SPI_result = 0;
/* Does it return tuples? */
@@ -1466,25 +1467,18 @@ SPI_is_cursor_plan(SPIPlanPtr plan)
bool
SPI_plan_is_valid(SPIPlanPtr plan)
{
- Assert(plan->magic == _SPI_PLAN_MAGIC);
- if (plan->saved)
- {
- ListCell *lc;
+ ListCell *lc;
- foreach(lc, plan->plancache_list)
- {
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
- if (!CachedPlanIsValid(plansource))
- return false;
- }
- return true;
- }
- else
+ foreach(lc, plan->plancache_list)
{
- /* An unsaved plan is assumed valid for its (short) lifetime */
- return true;
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ if (!CachedPlanIsValid(plansource))
+ return false;
}
+ return true;
}
/*
@@ -1646,7 +1640,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
*/
/*
- * Parse and plan a querystring.
+ * Parse and analyze a querystring.
*
* At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
* and plan->parserSetupArg) must be valid, as must plan->cursor_options.
@@ -1656,8 +1650,10 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
* param type information embedded in the plan!
*
* Results are stored into *plan (specifically, plan->plancache_list).
- * Note however that the result trees are all in CurrentMemoryContext
- * and need to be copied somewhere to survive.
+ * 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_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
@@ -1682,8 +1678,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
raw_parsetree_list = pg_parse_query(src);
/*
- * Do parse analysis, rule rewrite, and planning for each raw parsetree,
- * then cons up a phony plancache entry for each one.
+ * Do parse analysis and rule rewrite for each raw parsetree, storing
+ * the results into unsaved plancache entries.
*/
plancache_list = NIL;
@@ -1692,7 +1688,14 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
Node *parsetree = (Node *) lfirst(list_item);
List *stmt_list;
CachedPlanSource *plansource;
- CachedPlan *cplan;
+
+ /*
+ * Create the CachedPlanSource before we do parse analysis, since
+ * it needs to see the unmodified raw parse tree.
+ */
+ plansource = CreateCachedPlan(parsetree,
+ src,
+ CreateCommandTag(parsetree));
/*
* Parameter datatypes are driven by parserSetup hook if provided,
@@ -1701,41 +1704,29 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
if (plan->parserSetup != NULL)
{
Assert(plan->nargs == 0);
- /* Need a copyObject here to keep parser from modifying raw tree */
- stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree),
+ stmt_list = pg_analyze_and_rewrite_params(parsetree,
src,
plan->parserSetup,
plan->parserSetupArg);
}
else
{
- /* Need a copyObject here to keep parser from modifying raw tree */
- stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
+ stmt_list = pg_analyze_and_rewrite(parsetree,
src,
plan->argtypes,
plan->nargs);
}
- stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);
-
- plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
- cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
-
- plansource->raw_parse_tree = parsetree;
- /* cast-away-const here is a bit ugly, but there's no reason to copy */
- plansource->query_string = (char *) src;
- plansource->commandTag = CreateCommandTag(parsetree);
- plansource->param_types = plan->argtypes;
- plansource->num_params = plan->nargs;
- plansource->parserSetup = plan->parserSetup;
- plansource->parserSetupArg = plan->parserSetupArg;
- plansource->fully_planned = true;
- plansource->fixed_result = false;
- /* no need to set search_path, generation or saved_xmin */
- plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
- plansource->plan = cplan;
-
- cplan->stmt_list = stmt_list;
- cplan->fully_planned = true;
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ stmt_list,
+ NULL,
+ plan->argtypes,
+ plan->nargs,
+ plan->parserSetup,
+ plan->parserSetupArg,
+ cursor_options,
+ false); /* not fixed result */
plancache_list = lappend(plancache_list, plansource);
}
@@ -1824,18 +1815,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
spierrcontext.arg = (void *) plansource->query_string;
- if (plan->saved)
- {
- /* Replan if needed, and increment plan refcount locally */
- cplan = RevalidateCachedPlan(plansource, true);
- stmt_list = cplan->stmt_list;
- }
- else
- {
- /* No replan here */
- cplan = NULL;
- stmt_list = plansource->plan->stmt_list;
- }
+ /*
+ * 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);
+ stmt_list = cplan->stmt_list;
/*
* In the default non-read-only case, get a new snapshot, replacing
@@ -1966,8 +1951,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
}
/* Done with this plan, so release refcount */
- if (cplan)
- ReleaseCachedPlan(cplan, true);
+ ReleaseCachedPlan(cplan, plan->saved);
cplan = NULL;
/*
@@ -1987,7 +1971,7 @@ fail:
/* We no longer need the cached plan refcount, if any */
if (cplan)
- ReleaseCachedPlan(cplan, true);
+ ReleaseCachedPlan(cplan, plan->saved);
/*
* Pop the error context stack
@@ -2018,8 +2002,7 @@ fail:
*/
static ParamListInfo
_SPI_convert_params(int nargs, Oid *argtypes,
- Datum *Values, const char *Nulls,
- int pflags)
+ Datum *Values, const char *Nulls)
{
ParamListInfo paramLI;
@@ -2043,7 +2026,7 @@ _SPI_convert_params(int nargs, Oid *argtypes,
prm->value = Values[i];
prm->isnull = (Nulls && Nulls[i] == 'n');
- prm->pflags = pflags;
+ prm->pflags = PARAM_FLAG_CONST;
prm->ptype = argtypes[i];
}
}
@@ -2283,21 +2266,31 @@ _SPI_checktuples(void)
}
/*
- * Make an "unsaved" copy of the given plan, in a child context of parentcxt.
+ * Convert a "temporary" SPIPlan into an "unsaved" plan.
+ *
+ * The passed _SPI_plan struct is on the stack, and all its subsidiary data
+ * is in or under the current SPI executor context. Copy the plan into the
+ * SPI procedure context so it will survive _SPI_end_call(). To minimize
+ * data copying, this destructively modifies the input plan, by taking the
+ * plancache entries away from it and reparenting them to the new SPIPlan.
*/
static SPIPlanPtr
-_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
+_SPI_make_plan_non_temp(SPIPlanPtr plan)
{
SPIPlanPtr newplan;
+ MemoryContext parentcxt = _SPI_current->procCxt;
MemoryContext plancxt;
MemoryContext oldcxt;
ListCell *lc;
- Assert(!plan->saved); /* not currently supported */
+ /* Assert the input is a temporary SPIPlan */
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
+ Assert(plan->plancxt == NULL);
/*
- * Create a memory context for the plan. We don't expect the plan to be
- * very large, so use smaller-than-default alloc parameters.
+ * Create a memory context for the plan, underneath the procedure context.
+ * We don't expect the plan to be very large, so use smaller-than-default
+ * alloc parameters.
*/
plancxt = AllocSetContextCreate(parentcxt,
"SPI Plan",
@@ -2306,7 +2299,7 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(plancxt);
- /* Copy the SPI plan into its own context */
+ /* Copy the SPI_plan struct and subsidiary data into the new context */
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC;
newplan->saved = false;
@@ -2324,46 +2317,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg;
+ /*
+ * Reparent all the CachedPlanSources into the procedure context. In
+ * theory this could fail partway through due to the pallocs, but we
+ * don't care too much since both the procedure context and the executor
+ * context would go away on error.
+ */
foreach(lc, plan->plancache_list)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
- CachedPlanSource *newsource;
- CachedPlan *cplan;
- CachedPlan *newcplan;
-
- /* Note: we assume we don't need to revalidate the plan */
- cplan = plansource->plan;
-
- newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
- newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
-
- newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
- newsource->query_string = pstrdup(plansource->query_string);
- newsource->commandTag = plansource->commandTag;
- newsource->param_types = newplan->argtypes;
- newsource->num_params = newplan->nargs;
- newsource->parserSetup = newplan->parserSetup;
- newsource->parserSetupArg = newplan->parserSetupArg;
- newsource->fully_planned = plansource->fully_planned;
- newsource->fixed_result = plansource->fixed_result;
- /* no need to worry about seach_path, generation or saved_xmin */
- if (plansource->resultDesc)
- newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
- newsource->plan = newcplan;
-
- newcplan->stmt_list = copyObject(cplan->stmt_list);
- newcplan->fully_planned = cplan->fully_planned;
- newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+ CachedPlanSetParentContext(plansource, parentcxt);
+
+ /* Build new list, with list cells in plancxt */
+ newplan->plancache_list = lappend(newplan->plancache_list, plansource);
}
MemoryContextSwitchTo(oldcxt);
+ /* For safety, unlink the CachedPlanSources from the temporary plan */
+ plan->plancache_list = NIL;
+
return newplan;
}
/*
- * Make a "saved" copy of the given plan, entrusting everything to plancache.c
+ * Make a "saved" copy of the given plan.
*/
static SPIPlanPtr
_SPI_save_plan(SPIPlanPtr plan)
@@ -2373,13 +2352,12 @@ _SPI_save_plan(SPIPlanPtr plan)
MemoryContext oldcxt;
ListCell *lc;
- Assert(!plan->saved); /* not currently supported */
-
/*
* Create a memory context for the plan. We don't expect the plan to be
- * very large, so use smaller-than-default alloc parameters.
+ * very large, so use smaller-than-default alloc parameters. It's a
+ * transient context until we finish copying everything.
*/
- plancxt = AllocSetContextCreate(CacheMemoryContext,
+ plancxt = AllocSetContextCreate(CurrentMemoryContext,
"SPI Plan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
@@ -2389,7 +2367,7 @@ _SPI_save_plan(SPIPlanPtr plan)
/* Copy the SPI plan into its own context */
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC;
- newplan->saved = true;
+ newplan->saved = false;
newplan->plancache_list = NIL;
newplan->plancxt = plancxt;
newplan->cursor_options = plan->cursor_options;
@@ -2404,33 +2382,32 @@ _SPI_save_plan(SPIPlanPtr plan)
newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg;
+ /* Copy all the plancache entries */
foreach(lc, plan->plancache_list)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
CachedPlanSource *newsource;
- CachedPlan *cplan;
-
- /* Note: we assume we don't need to revalidate the plan */
- cplan = plansource->plan;
-
- newsource = CreateCachedPlan(plansource->raw_parse_tree,
- plansource->query_string,
- plansource->commandTag,
- newplan->argtypes,
- newplan->nargs,
- newplan->cursor_options,
- cplan->stmt_list,
- true,
- false);
- if (newplan->parserSetup != NULL)
- CachedPlanSetParserHook(newsource,
- newplan->parserSetup,
- newplan->parserSetupArg);
+ newsource = CopyCachedPlan(plansource);
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
}
MemoryContextSwitchTo(oldcxt);
+ /*
+ * Mark it saved, reparent it under CacheMemoryContext, and mark all the
+ * component CachedPlanSources as saved. This sequence cannot fail
+ * partway through, so there's no risk of long-term memory leakage.
+ */
+ newplan->saved = true;
+ MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
+
+ foreach(lc, newplan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ SaveCachedPlan(plansource);
+ }
+
return newplan;
}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 072d50c3951..c7eac71e91e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -161,10 +161,6 @@ static bool ignore_till_sync = false;
*/
static CachedPlanSource *unnamed_stmt_psrc = NULL;
-/* workspace for building a new unnamed statement in */
-static MemoryContext unnamed_stmt_context = NULL;
-
-
/* assorted command-line switches */
static const char *userDoption = NULL; /* -D switch */
@@ -1116,14 +1112,14 @@ exec_parse_message(const char *query_string, /* string to execute */
Oid *paramTypes, /* parameter types */
int numParams) /* number of parameters */
{
+ MemoryContext unnamed_stmt_context = NULL;
MemoryContext oldcontext;
List *parsetree_list;
Node *raw_parse_tree;
const char *commandTag;
- List *querytree_list,
- *stmt_list;
+ List *querytree_list;
+ CachedPlanSource *psrc;
bool is_named;
- bool fully_planned;
bool save_log_statement_stats = log_statement_stats;
char msec_str[32];
@@ -1158,11 +1154,11 @@ exec_parse_message(const char *query_string, /* string to execute */
* named or not. For a named prepared statement, we do parsing in
* MessageContext and copy the finished trees into the prepared
* statement's plancache entry; then the reset of MessageContext releases
- * temporary space used by parsing and planning. For an unnamed prepared
+ * temporary space used by parsing and rewriting. For an unnamed prepared
* statement, we assume the statement isn't going to hang around long, so
* getting rid of temp space quickly is probably not worth the costs of
- * copying parse/plan trees. So in this case, we create the plancache
- * entry's context here, and do all the parsing work therein.
+ * copying parse trees. So in this case, we create the plancache entry's
+ * query_context here, and do all the parsing work therein.
*/
is_named = (stmt_name[0] != '\0');
if (is_named)
@@ -1174,9 +1170,9 @@ exec_parse_message(const char *query_string, /* string to execute */
{
/* Unnamed prepared statement --- release any prior unnamed stmt */
drop_unnamed_stmt();
- /* Create context for parsing/planning */
+ /* Create context for parsing */
unnamed_stmt_context =
- AllocSetContextCreate(CacheMemoryContext,
+ AllocSetContextCreate(MessageContext,
"unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
@@ -1230,7 +1226,13 @@ exec_parse_message(const char *query_string, /* string to execute */
errdetail_abort()));
/*
- * Set up a snapshot if parse analysis/planning will need one.
+ * Create the CachedPlanSource before we do parse analysis, since
+ * it needs to see the unmodified raw parse tree.
+ */
+ psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
+
+ /*
+ * Set up a snapshot if parse analysis will need one.
*/
if (analyze_requires_snapshot(raw_parse_tree))
{
@@ -1239,18 +1241,14 @@ exec_parse_message(const char *query_string, /* string to execute */
}
/*
- * OK to analyze, rewrite, and plan this query. Note that the
- * originally specified parameter set is not required to be complete,
- * so we have to use parse_analyze_varparams().
- *
- * XXX must use copyObject here since parse analysis scribbles on its
- * input, and we need the unmodified raw parse tree for possible
- * replanning later.
+ * Analyze and rewrite the query. Note that the originally specified
+ * parameter set is not required to be complete, so we have to use
+ * parse_analyze_varparams().
*/
if (log_parser_stats)
ResetUsage();
- query = parse_analyze_varparams(copyObject(raw_parse_tree),
+ query = parse_analyze_varparams(raw_parse_tree,
query_string,
&paramTypes,
&numParams);
@@ -1274,22 +1272,7 @@ exec_parse_message(const char *query_string, /* string to execute */
querytree_list = pg_rewrite_query(query);
- /*
- * If this is the unnamed statement and it has parameters, defer query
- * planning until Bind. Otherwise do it now.
- */
- if (!is_named && numParams > 0)
- {
- stmt_list = querytree_list;
- fully_planned = false;
- }
- else
- {
- stmt_list = pg_plan_queries(querytree_list, 0, NULL);
- fully_planned = true;
- }
-
- /* Done with the snapshot used for parsing/planning */
+ /* Done with the snapshot used for parsing */
if (snapshot_set)
PopActiveSnapshot();
}
@@ -1298,56 +1281,47 @@ exec_parse_message(const char *query_string, /* string to execute */
/* Empty input string. This is legal. */
raw_parse_tree = NULL;
commandTag = NULL;
- stmt_list = NIL;
- fully_planned = true;
+ psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
+ querytree_list = NIL;
}
- /* If we got a cancel signal in analysis or planning, quit */
- CHECK_FOR_INTERRUPTS();
-
/*
- * Store the query as a prepared statement. See above comments.
+ * CachedPlanSource must be a direct child of MessageContext before we
+ * reparent unnamed_stmt_context under it, else we have a disconnected
+ * circular subgraph. Klugy, but less so than flipping contexts even
+ * more above.
*/
+ if (unnamed_stmt_context)
+ MemoryContextSetParent(psrc->context, MessageContext);
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(psrc,
+ querytree_list,
+ unnamed_stmt_context,
+ paramTypes,
+ numParams,
+ NULL,
+ NULL,
+ 0, /* default cursor options */
+ true); /* fixed result */
+
+ /* If we got a cancel signal during analysis, quit */
+ CHECK_FOR_INTERRUPTS();
+
if (is_named)
{
- StorePreparedStatement(stmt_name,
- raw_parse_tree,
- query_string,
- commandTag,
- paramTypes,
- numParams,
- 0, /* default cursor options */
- stmt_list,
- false);
+ /*
+ * Store the query as a prepared statement.
+ */
+ StorePreparedStatement(stmt_name, psrc, false);
}
else
{
/*
- * paramTypes and query_string need to be copied into
- * unnamed_stmt_context. The rest is there already
+ * We just save the CachedPlanSource into unnamed_stmt_psrc.
*/
- Oid *newParamTypes;
-
- if (numParams > 0)
- {
- newParamTypes = (Oid *) palloc(numParams * sizeof(Oid));
- memcpy(newParamTypes, paramTypes, numParams * sizeof(Oid));
- }
- else
- newParamTypes = NULL;
-
- unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree,
- pstrdup(query_string),
- commandTag,
- newParamTypes,
- numParams,
- 0, /* cursor options */
- stmt_list,
- fully_planned,
- true,
- unnamed_stmt_context);
- /* context now belongs to the plancache entry */
- unnamed_stmt_context = NULL;
+ SaveCachedPlan(psrc);
+ unnamed_stmt_psrc = psrc;
}
MemoryContextSwitchTo(oldcontext);
@@ -1412,7 +1386,6 @@ exec_bind_message(StringInfo input_message)
char *query_string;
char *saved_stmt_name;
ParamListInfo params;
- List *plan_list;
MemoryContext oldContext;
bool save_log_statement_stats = log_statement_stats;
bool snapshot_set = false;
@@ -1437,7 +1410,7 @@ exec_bind_message(StringInfo input_message)
}
else
{
- /* Unnamed statements are re-prepared for every bind */
+ /* special-case the unnamed statement */
psrc = unnamed_stmt_psrc;
if (!psrc)
ereport(ERROR,
@@ -1522,7 +1495,7 @@ exec_bind_message(StringInfo input_message)
/*
* Prepare to copy stuff into the portal's memory context. We do all this
* copying first, because it could possibly fail (out-of-memory) and we
- * don't want a failure to occur between RevalidateCachedPlan and
+ * don't want a failure to occur between GetCachedPlan and
* PortalDefineQuery; that would result in leaking our plancache refcount.
*/
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
@@ -1539,7 +1512,9 @@ exec_bind_message(StringInfo input_message)
/*
* Set a snapshot if we have parameters to fetch (since the input
* functions might need it) or the query isn't a utility command (and
- * hence could require redoing parse analysis and planning).
+ * hence could require redoing parse analysis and planning). We keep
+ * the snapshot active till we're done, so that plancache.c doesn't have
+ * to take new ones.
*/
if (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree))
{
@@ -1675,10 +1650,8 @@ exec_bind_message(StringInfo input_message)
params->params[paramno].isnull = isNull;
/*
- * We mark the params as CONST. This has no effect if we already
- * did planning, but if we didn't, it licenses the planner to
- * substitute the parameters directly into the one-shot plan we
- * will generate below.
+ * We mark the params as CONST. This ensures that any custom
+ * plan makes full use of the parameter values.
*/
params->params[paramno].pflags = PARAM_FLAG_CONST;
params->params[paramno].ptype = ptype;
@@ -1703,63 +1676,24 @@ exec_bind_message(StringInfo input_message)
pq_getmsgend(input_message);
- if (psrc->fully_planned)
- {
- /*
- * Revalidate the cached plan; this may result in replanning. Any
- * cruft will be generated in MessageContext. The plan refcount will
- * be assigned to the Portal, so it will be released at portal
- * destruction.
- */
- cplan = RevalidateCachedPlan(psrc, false);
- plan_list = cplan->stmt_list;
- }
- else
- {
- List *query_list;
-
- /*
- * Revalidate the cached plan; this may result in redoing parse
- * analysis and rewriting (but not planning). Any cruft will be
- * generated in MessageContext. The plan refcount is assigned to
- * CurrentResourceOwner.
- */
- cplan = RevalidateCachedPlan(psrc, true);
-
- /*
- * We didn't plan the query before, so do it now. This allows the
- * planner to make use of the concrete parameter values we now have.
- * Because we use PARAM_FLAG_CONST, the plan is good only for this set
- * of param values, and so we generate the plan in the portal's own
- * memory context where it will be thrown away after use. As in
- * exec_parse_message, we make no attempt to recover planner temporary
- * memory until the end of the operation.
- *
- * XXX because the planner has a bad habit of scribbling on its input,
- * we have to make a copy of the parse trees. FIXME someday.
- */
- oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
- query_list = copyObject(cplan->stmt_list);
- plan_list = pg_plan_queries(query_list, 0, params);
- MemoryContextSwitchTo(oldContext);
-
- /* We no longer need the cached plan refcount ... */
- ReleaseCachedPlan(cplan, true);
- /* ... and we don't want the portal to depend on it, either */
- cplan = NULL;
- }
+ /*
+ * Obtain a plan from the CachedPlanSource. Any cruft from (re)planning
+ * 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);
/*
* Now we can define the portal.
*
* DO NOT put any code that could possibly throw an error between the
- * above "RevalidateCachedPlan(psrc, false)" call and here.
+ * above GetCachedPlan call and here.
*/
PortalDefineQuery(portal,
saved_stmt_name,
query_string,
psrc->commandTag,
- plan_list,
+ cplan->stmt_list,
cplan);
/* Done with the snapshot used for parameter I/O and parsing/planning */
@@ -2304,8 +2238,7 @@ exec_describe_statement_message(const char *stmt_name)
/*
* If we are in aborted transaction state, we can't run
- * SendRowDescriptionMessage(), because that needs catalog accesses. (We
- * can't do RevalidateCachedPlan, either, but that's a lesser problem.)
+ * SendRowDescriptionMessage(), because that needs catalog accesses.
* Hence, refuse to Describe statements that return data. (We shouldn't
* just refuse all Describes, since that might break the ability of some
* clients to issue COMMIT or ROLLBACK commands, if they use code that
@@ -2342,18 +2275,12 @@ exec_describe_statement_message(const char *stmt_name)
*/
if (psrc->resultDesc)
{
- CachedPlan *cplan;
List *tlist;
- /* Make sure the plan is up to date */
- cplan = RevalidateCachedPlan(psrc, true);
-
- /* Get the primary statement and find out what it returns */
- tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
+ /* Get the plan's primary targetlist */
+ tlist = CachedPlanGetTargetList(psrc);
SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
-
- ReleaseCachedPlan(cplan, true);
}
else
pq_putemptymessage('n'); /* NoData */
@@ -2536,19 +2463,14 @@ IsTransactionStmtList(List *parseTrees)
static void
drop_unnamed_stmt(void)
{
- /* Release any completed unnamed statement */
+ /* paranoia to avoid a dangling pointer in case of error */
if (unnamed_stmt_psrc)
- DropCachedPlan(unnamed_stmt_psrc);
- unnamed_stmt_psrc = NULL;
+ {
+ CachedPlanSource *psrc = unnamed_stmt_psrc;
- /*
- * If we failed while trying to build a prior unnamed statement, we may
- * have a memory context that wasn't assigned to a completed plancache
- * entry. If so, drop it to avoid a permanent memory leak.
- */
- if (unnamed_stmt_context)
- MemoryContextDelete(unnamed_stmt_context);
- unnamed_stmt_context = NULL;
+ unnamed_stmt_psrc = NULL;
+ DropCachedPlan(psrc);
+ }
}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index af194f0c90f..6b5a5a8e44b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -8,7 +8,7 @@
* across query and transaction boundaries, in fact they live as long as
* the backend does. This works because the hashtable structures
* themselves are allocated by dynahash.c in its permanent DynaHashCxt,
- * and the SPI plans they point to are saved using SPI_saveplan().
+ * and the SPI plans they point to are saved using SPI_keepplan().
* There is not currently any provision for throwing away a no-longer-needed
* plan --- consider improving this someday.
*
@@ -3316,7 +3316,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
/* Save the plan if requested */
if (cache_plan)
{
- qplan = SPI_saveplan(qplan);
+ SPI_keepplan(qplan);
ri_HashPreparedPlan(qkey, qplan);
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e5d5b68084c..c112a9cc163 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -316,7 +316,8 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
- plan_getrulebyoid = SPI_saveplan(plan);
+ SPI_keepplan(plan);
+ plan_getrulebyoid = plan;
}
/*
@@ -450,7 +451,8 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
plan = SPI_prepare(query_getviewrule, 2, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
- plan_getviewrule = SPI_saveplan(plan);
+ SPI_keepplan(plan);
+ plan_getviewrule = plan;
}
/*
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 68b6783e196..62fdf2877db 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -3,19 +3,23 @@
* plancache.c
* Plan cache management.
*
- * We can store a cached plan in either fully-planned format, or just
- * parsed-and-rewritten if the caller wishes to postpone planning until
- * actual parameter values are available. CachedPlanSource has the same
- * contents either way, but CachedPlan contains a list of PlannedStmts
- * and bare utility statements in the first case, or a list of Query nodes
- * in the second case.
+ * The plan cache manager has two principal responsibilities: deciding when
+ * to use a generic plan versus a custom (parameter-value-specific) plan,
+ * and tracking whether cached plans need to be invalidated because of schema
+ * changes in the objects they depend on.
*
- * The plan cache manager itself is principally responsible for tracking
- * whether cached plans should be invalidated because of schema changes in
- * the objects they depend on. When (and if) the next demand for a cached
- * plan occurs, the query will be replanned. Note that this could result
- * in an error, for example if a column referenced by the query is no
- * longer present. The creator of a cached plan can specify whether it
+ * The logic for choosing generic or custom plans is in choose_custom_plan,
+ * which see for comments.
+ *
+ * Cache invalidation is driven off sinval events. Any CachedPlanSource
+ * that matches the event is marked invalid, as is its generic CachedPlan
+ * if it has one. When (and if) the next demand for a cached plan occurs,
+ * parse analysis and rewrite is repeated to build a new valid query tree,
+ * and then planning is performed as normal.
+ *
+ * Note that if the sinval was a result of user DDL actions, parse analysis
+ * could throw an error, for example if a column referenced by the query is
+ * no longer present. The creator of a cached plan can specify whether it
* is allowable for the query to change output tupdesc on replan (this
* could happen with "SELECT *" for example) --- if so, it's up to the
* caller to notice changes and cope with them.
@@ -41,6 +45,8 @@
*/
#include "postgres.h"
+#include <limits.h>
+
#include "access/transam.h"
#include "catalog/namespace.h"
#include "executor/executor.h"
@@ -58,15 +64,28 @@
#include "utils/syscache.h"
-static List *cached_plans_list = NIL;
-
-static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
- MemoryContext plan_context);
+/*
+ * 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
+ * guarantee to save a CachedPlanSource without error.
+ */
+static CachedPlanSource *first_saved_plan = NULL;
+
+static void ReleaseGenericPlan(CachedPlanSource *plansource);
+static List *RevalidateCachedQuery(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource);
+static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
+ ParamListInfo boundParams);
+static bool choose_custom_plan(CachedPlanSource *plansource,
+ ParamListInfo boundParams);
+static double cached_plan_cost(CachedPlan *plan);
static void AcquireExecutorLocks(List *stmt_list, bool acquire);
static void AcquirePlannerLocks(List *stmt_list, bool acquire);
static void ScanQueryForLocks(Query *parsetree, bool acquire);
static bool ScanQueryWalker(Node *node, bool *acquire);
static bool plan_list_is_transient(List *stmt_list);
+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);
@@ -90,32 +109,33 @@ InitPlanCache(void)
/*
* CreateCachedPlan: initially create a plan cache entry.
*
- * The caller must already have successfully parsed/planned the query;
- * about all that we do here is copy it into permanent storage.
+ * Creation of a cached plan is divided into two steps, CreateCachedPlan and
+ * CompleteCachedPlan. CreateCachedPlan should be called after running the
+ * query through raw_parser, but before doing parse analysis and rewrite;
+ * CompleteCachedPlan is called after that. The reason for this arrangement
+ * is that it can save one round of copying of the raw parse tree, since
+ * the parser will normally scribble on the raw parse tree. Callers would
+ * otherwise need to make an extra copy of the parse tree to ensure they
+ * still had a clean copy to present at plan cache creation time.
+ *
+ * All arguments presented to CreateCachedPlan are copied into a memory
+ * context created as a child of the call-time CurrentMemoryContext, which
+ * should be a reasonably short-lived working context that will go away in
+ * event of an error. This ensures that the cached plan data structure will
+ * likewise disappear if an error occurs before we have fully constructed it.
+ * Once constructed, the cached plan can be made longer-lived, if needed,
+ * by calling SaveCachedPlan.
*
* raw_parse_tree: output of raw_parser()
- * query_string: original query text (as of PG 8.4, must not be NULL)
+ * query_string: original query text
* commandTag: compile-time-constant tag for query, or NULL if empty query
- * param_types: array of fixed parameter type OIDs, or NULL if none
- * num_params: number of fixed parameters
- * cursor_options: options bitmask that was/will be passed to planner
- * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
- * fully_planned: are we caching planner or rewriter output?
- * fixed_result: TRUE to disallow changes in result tupdesc
*/
CachedPlanSource *
CreateCachedPlan(Node *raw_parse_tree,
const char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result)
+ const char *commandTag)
{
CachedPlanSource *plansource;
- OverrideSearchPath *search_path;
MemoryContext source_context;
MemoryContext oldcxt;
@@ -123,61 +143,50 @@ CreateCachedPlan(Node *raw_parse_tree,
/*
* Make a dedicated memory context for the CachedPlanSource and its
- * subsidiary data. We expect it can be pretty small.
+ * permanent subsidiary data. It's probably not going to be large, but
+ * just in case, use the default maxsize parameter. Initially it's a
+ * child of the caller's context (which we assume to be transient), so
+ * that it will be cleaned up on error.
*/
- source_context = AllocSetContextCreate(CacheMemoryContext,
+ source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
- ALLOCSET_SMALL_MAXSIZE);
-
- /*
- * Fetch current search_path into new context, but do any recalculation
- * work required in caller's context.
- */
- search_path = GetOverrideSearchPath(source_context);
+ ALLOCSET_DEFAULT_MAXSIZE);
/*
* Create and fill the CachedPlanSource struct within the new context.
+ * Most fields are just left empty for the moment.
*/
oldcxt = MemoryContextSwitchTo(source_context);
- plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
+
+ plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree);
plansource->query_string = pstrdup(query_string);
- plansource->commandTag = commandTag; /* no copying needed */
- if (num_params > 0)
- {
- plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
- memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
- }
- else
- plansource->param_types = NULL;
- plansource->num_params = num_params;
- /* these can be set later with CachedPlanSetParserHook: */
+ plansource->commandTag = commandTag;
+ plansource->param_types = NULL;
+ plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
- plansource->cursor_options = cursor_options;
- plansource->fully_planned = fully_planned;
- plansource->fixed_result = fixed_result;
- plansource->search_path = search_path;
- plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
- plansource->plan = NULL;
+ plansource->cursor_options = 0;
+ plansource->fixed_result = false;
+ plansource->resultDesc = NULL;
+ plansource->search_path = NULL;
plansource->context = source_context;
- plansource->orig_plan = NULL;
-
- /*
- * Copy the current output plans into the plancache entry.
- */
- StoreCachedPlan(plansource, stmt_list, NULL);
-
- /*
- * Now we can add the entry to the list of cached plans. The List nodes
- * live in CacheMemoryContext.
- */
- MemoryContextSwitchTo(CacheMemoryContext);
-
- cached_plans_list = lappend(cached_plans_list, plansource);
+ plansource->query_list = NIL;
+ plansource->relationOids = NIL;
+ plansource->invalItems = NIL;
+ plansource->query_context = NULL;
+ plansource->gplan = NULL;
+ 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;
MemoryContextSwitchTo(oldcxt);
@@ -185,274 +194,465 @@ CreateCachedPlan(Node *raw_parse_tree,
}
/*
- * FastCreateCachedPlan: create a plan cache entry with minimal data copying.
+ * CompleteCachedPlan: second step of creating a plan cache entry.
+ *
+ * Pass in the analyzed-and-rewritten form of the query, as well as the
+ * required subsidiary data about parameters and such. All passed values will
+ * be copied into the CachedPlanSource's memory, except as specified below.
+ * After this is called, GetCachedPlan can be called to obtain a plan, and
+ * optionally the CachedPlanSource can be saved using SaveCachedPlan.
+ *
+ * If querytree_context is not NULL, the querytree_list must be stored in that
+ * context (but the other parameters need not be). The querytree_list is not
+ * copied, rather the given context is kept as the initial query_context of
+ * the CachedPlanSource. (It should have been created as a child of the
+ * caller's working memory context, but it will now be reparented to belong
+ * to the CachedPlanSource.) The querytree_context is normally the context in
+ * which the caller did raw parsing and parse analysis. This approach saves
+ * one tree copying step compared to passing NULL, but leaves lots of extra
+ * cruft in the query_context, namely whatever extraneous stuff parse analysis
+ * created, as well as whatever went unused from the raw parse tree. Using
+ * this option is a space-for-time tradeoff that is appropriate if the
+ * CachedPlanSource is not expected to survive long.
*
- * For plans that aren't expected to live very long, the copying overhead of
- * CreateCachedPlan is annoying. We provide this variant entry point in which
- * the caller has already placed all the data in a suitable memory context.
- * The source data and completed plan are in the same context, since this
- * avoids extra copy steps during plan construction. If the query ever does
- * need replanning, we'll generate a separate new CachedPlan at that time, but
- * the CachedPlanSource and the initial CachedPlan share the caller-provided
- * context and go away together when neither is needed any longer. (Because
- * the parser and planner generate extra cruft in addition to their real
- * output, this approach means that the context probably contains a bunch of
- * useless junk as well as the useful trees. Hence, this method is a
- * space-for-time tradeoff, which is worth making for plans expected to be
- * short-lived.)
+ * plancache.c cannot know how to copy the data referenced by parserSetupArg,
+ * and it would often be inappropriate to do so anyway. When using that
+ * option, it is caller's responsibility that the referenced data remains
+ * valid for as long as the CachedPlanSource exists.
*
- * raw_parse_tree, query_string, param_types, and stmt_list must reside in the
- * given context, which must have adequate lifespan (recommendation: make it a
- * child of CacheMemoryContext). Otherwise the API is the same as
- * CreateCachedPlan.
+ * plansource: structure returned by CreateCachedPlan
+ * querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
+ * querytree_context: memory context containing querytree_list,
+ * or NULL to copy querytree_list into a fresh context
+ * param_types: array of fixed parameter type OIDs, or NULL if none
+ * num_params: number of fixed parameters
+ * parserSetup: alternate method for handling query parameters
+ * parserSetupArg: data to pass to parserSetup
+ * cursor_options: options bitmask to pass to planner
+ * fixed_result: TRUE to disallow future changes in query's result tupdesc
*/
-CachedPlanSource *
-FastCreateCachedPlan(Node *raw_parse_tree,
- char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result,
- MemoryContext context)
+void
+CompleteCachedPlan(CachedPlanSource *plansource,
+ List *querytree_list,
+ MemoryContext querytree_context,
+ Oid *param_types,
+ int num_params,
+ ParserSetupHook parserSetup,
+ void *parserSetupArg,
+ int cursor_options,
+ bool fixed_result)
{
- CachedPlanSource *plansource;
- OverrideSearchPath *search_path;
- MemoryContext oldcxt;
+ MemoryContext source_context = plansource->context;
+ MemoryContext oldcxt = CurrentMemoryContext;
- Assert(query_string != NULL); /* required as of 8.4 */
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(!plansource->is_complete);
+
+ /*
+ * 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);
+ }
+ else
+ {
+ /* Again, it's a good bet the querytree_context can be small */
+ querytree_context = AllocSetContextCreate(source_context,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ MemoryContextSwitchTo(querytree_context);
+ querytree_list = (List *) copyObject(querytree_list);
+ }
+
+ plansource->query_context = querytree_context;
+ plansource->query_list = querytree_list;
/*
- * Fetch current search_path into given context, but do any recalculation
- * work required in caller's context.
+ * 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.)
*/
- search_path = GetOverrideSearchPath(context);
+ extract_query_dependencies((Node *) querytree_list,
+ &plansource->relationOids,
+ &plansource->invalItems);
/*
- * Create and fill the CachedPlanSource struct within the given context.
+ * Save the final parameter types (or other parameter specification data)
+ * into the source_context, as well as our other parameters. Also save
+ * the result tuple descriptor.
*/
- oldcxt = MemoryContextSwitchTo(context);
- plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
- plansource->raw_parse_tree = raw_parse_tree;
- plansource->query_string = query_string;
- plansource->commandTag = commandTag; /* no copying needed */
- plansource->param_types = param_types;
+ MemoryContextSwitchTo(source_context);
+
+ if (num_params > 0)
+ {
+ plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
+ memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
+ }
+ else
+ plansource->param_types = NULL;
plansource->num_params = num_params;
- /* these can be set later with CachedPlanSetParserHook: */
- plansource->parserSetup = NULL;
- plansource->parserSetupArg = NULL;
+ plansource->parserSetup = parserSetup;
+ plansource->parserSetupArg = parserSetupArg;
plansource->cursor_options = cursor_options;
- plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
- plansource->search_path = search_path;
- plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
- plansource->plan = NULL;
- plansource->context = context;
- plansource->orig_plan = NULL;
+ plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list);
+
+ MemoryContextSwitchTo(oldcxt);
/*
- * Store the current output plans into the plancache entry.
+ * Fetch current search_path into dedicated context, but do any
+ * recalculation work required in caller's context.
*/
- StoreCachedPlan(plansource, stmt_list, context);
+ plansource->search_path = GetOverrideSearchPath(source_context);
+
+ plansource->is_complete = true;
+ plansource->is_valid = true;
+}
+
+/*
+ * SaveCachedPlan: save a cached plan permanently
+ *
+ * This function moves the cached plan underneath CacheMemoryContext (making
+ * it live for the life of the backend, unless explicitly dropped), and adds
+ * 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
+ * automatically on transaction abort.
+ */
+void
+SaveCachedPlan(CachedPlanSource *plansource)
+{
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+ Assert(!plansource->is_saved);
/*
- * Since the context is owned by the CachedPlan, advance its refcount.
+ * In typical use, this function would be called before generating any
+ * plans from the CachedPlanSource. If there is a generic plan, moving
+ * it into CacheMemoryContext would be pretty risky since it's unclear
+ * whether the caller has taken suitable care with making references
+ * long-lived. Best thing to do seems to be to discard the plan.
*/
- plansource->orig_plan = plansource->plan;
- plansource->orig_plan->refcount++;
+ ReleaseGenericPlan(plansource);
/*
- * Now we can add the entry to the list of cached plans. The List nodes
- * live in CacheMemoryContext.
+ * Reparent the source memory context under CacheMemoryContext so that
+ * it will live indefinitely. The query_context follows along since it's
+ * already a child of the other one.
*/
- MemoryContextSwitchTo(CacheMemoryContext);
-
- cached_plans_list = lappend(cached_plans_list, plansource);
+ MemoryContextSetParent(plansource->context, CacheMemoryContext);
- MemoryContextSwitchTo(oldcxt);
+ /*
+ * Add the entry to the global list of cached plans.
+ */
+ plansource->next_saved = first_saved_plan;
+ first_saved_plan = plansource;
- return plansource;
+ plansource->is_saved = true;
}
/*
- * CachedPlanSetParserHook: set up to use parser callback hooks
+ * DropCachedPlan: destroy a cached plan.
*
- * Use this when a caller wants to manage parameter information via parser
- * callbacks rather than a fixed parameter-types list. Beware that the
- * information pointed to by parserSetupArg must be valid for as long as
- * the cached plan might be replanned!
+ * Actually this only destroys the CachedPlanSource: any referenced CachedPlan
+ * is released, but not destroyed until its refcount goes to zero. That
+ * handles the situation where DropCachedPlan is called while the plan is
+ * still in use.
*/
void
-CachedPlanSetParserHook(CachedPlanSource *plansource,
- ParserSetupHook parserSetup,
- void *parserSetupArg)
+DropCachedPlan(CachedPlanSource *plansource)
{
- /* Must not have specified a fixed parameter-types list */
- Assert(plansource->param_types == NULL);
- Assert(plansource->num_params == 0);
- /* OK, save hook info */
- plansource->parserSetup = parserSetup;
- plansource->parserSetupArg = parserSetupArg;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+ /* If it's been saved, remove it from the list */
+ if (plansource->is_saved)
+ {
+ if (first_saved_plan == plansource)
+ first_saved_plan = plansource->next_saved;
+ else
+ {
+ CachedPlanSource *psrc;
+
+ for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved)
+ {
+ if (psrc->next_saved == plansource)
+ {
+ psrc->next_saved = plansource->next_saved;
+ break;
+ }
+ }
+ }
+ plansource->is_saved = false;
+ }
+
+ /* 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);
}
/*
- * StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
- *
- * Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
+ * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any.
*/
static void
-StoreCachedPlan(CachedPlanSource *plansource,
- List *stmt_list,
- MemoryContext plan_context)
+ReleaseGenericPlan(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ /* Be paranoid about the possibility that ReleaseCachedPlan fails */
+ if (plansource->gplan)
+ {
+ CachedPlan *plan = plansource->gplan;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+ plansource->gplan = NULL;
+ ReleaseCachedPlan(plan, false);
+ }
+}
+
+/*
+ * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree.
+ *
+ * What we do here is re-acquire locks and redo parse analysis if necessary.
+ * On return, the query_list is valid and we have sufficient locks to begin
+ * planning.
+ *
+ * If any parse analysis activity is required, the caller's memory context is
+ * used for that work.
+ *
+ * The result value is the transient analyzed-and-rewritten query tree if we
+ * had to do re-analysis, and NIL otherwise. (This is returned just to save
+ * a tree copying step in a subsequent BuildCachedPlan call.)
+ */
+static List *
+RevalidateCachedQuery(CachedPlanSource *plansource)
+{
+ bool snapshot_set;
+ Node *rawtree;
+ List *tlist; /* transient query-tree list */
+ List *qlist; /* permanent query-tree list */
+ TupleDesc resultDesc;
+ MemoryContext querytree_context;
MemoryContext oldcxt;
- if (plan_context == NULL)
+ /*
+ * 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.
+ */
+ if (plansource->is_valid)
{
- /*
- * 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.
- */
- plan_context = AllocSetContextCreate(CacheMemoryContext,
- "CachedPlan",
- ALLOCSET_SMALL_MINSIZE,
- ALLOCSET_SMALL_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ AcquirePlannerLocks(plansource->query_list, true);
/*
- * Copy supplied data into the new context.
+ * By now, if any invalidation has happened, the inval callback
+ * functions will have marked the query invalid.
*/
- oldcxt = MemoryContextSwitchTo(plan_context);
+ if (plansource->is_valid)
+ {
+ /* Successfully revalidated and locked the query. */
+ return NIL;
+ }
- stmt_list = (List *) copyObject(stmt_list);
+ /* Ooops, the race case happened. Release useless locks. */
+ AcquirePlannerLocks(plansource->query_list, false);
}
- else
+
+ /*
+ * Discard the no-longer-useful query tree. (Note: we don't want to
+ * do this any earlier, else we'd not have been able to release locks
+ * correctly in the race condition case.)
+ */
+ plansource->is_valid = false;
+ plansource->query_list = NIL;
+ plansource->relationOids = NIL;
+ plansource->invalItems = NIL;
+
+ /*
+ * Free the query_context. We don't really expect MemoryContextDelete to
+ * fail, but just in case, make sure the CachedPlanSource is left in a
+ * reasonably sane state. (The generic plan won't get unlinked yet,
+ * but that's acceptable.)
+ */
+ if (plansource->query_context)
{
- /* Assume subsidiary data is in the given context */
- oldcxt = MemoryContextSwitchTo(plan_context);
+ MemoryContext qcxt = plansource->query_context;
+
+ plansource->query_context = NULL;
+ MemoryContextDelete(qcxt);
}
+ /* Drop the generic plan reference if any */
+ ReleaseGenericPlan(plansource);
+
/*
- * Create and fill the CachedPlan struct within the new context.
+ * Now re-do parse analysis and rewrite. This not incidentally acquires
+ * the locks we need to do planning safely.
*/
- plan = (CachedPlan *) palloc(sizeof(CachedPlan));
- plan->stmt_list = stmt_list;
- plan->fully_planned = plansource->fully_planned;
- plan->dead = false;
- if (plansource->fully_planned && plan_list_is_transient(stmt_list))
+ Assert(plansource->is_complete);
+
+ /*
+ * Restore the search_path that was in use when the plan was made. See
+ * comments for PushOverrideSearchPath about limitations of this.
+ *
+ * (XXX is there anything else we really need to restore?)
+ */
+ PushOverrideSearchPath(plansource->search_path);
+
+ /*
+ * If a snapshot is already set (the normal case), we can just use that
+ * for parsing/planning. But if it isn't, install one. Note: no point in
+ * checking whether parse analysis requires a snapshot; utility commands
+ * don't have invalidatable plans, so we'd not get here for such a
+ * command.
+ */
+ snapshot_set = false;
+ if (!ActiveSnapshotSet())
{
- Assert(TransactionIdIsNormal(TransactionXmin));
- plan->saved_xmin = TransactionXmin;
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
}
+
+ /*
+ * Run parse analysis and rule rewriting. The parser tends to scribble on
+ * its input, so we must copy the raw parse tree to prevent corruption of
+ * the cache.
+ */
+ rawtree = copyObject(plansource->raw_parse_tree);
+ if (plansource->parserSetup != NULL)
+ tlist = pg_analyze_and_rewrite_params(rawtree,
+ plansource->query_string,
+ plansource->parserSetup,
+ plansource->parserSetupArg);
else
- plan->saved_xmin = InvalidTransactionId;
- plan->refcount = 1; /* for the parent's link */
- plan->generation = ++(plansource->generation);
- plan->context = plan_context;
- if (plansource->fully_planned)
- {
- /*
- * Planner already extracted dependencies, we don't have to ... except
- * in the case of EXPLAIN. We assume here that EXPLAIN can't appear
- * in a list with other commands.
- */
- plan->relationOids = plan->invalItems = NIL;
+ tlist = pg_analyze_and_rewrite(rawtree,
+ plansource->query_string,
+ plansource->param_types,
+ plansource->num_params);
- if (list_length(stmt_list) == 1 &&
- IsA(linitial(stmt_list), ExplainStmt))
- {
- ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
+ /* Release snapshot if we got one */
+ if (snapshot_set)
+ PopActiveSnapshot();
- extract_query_dependencies(estmt->query,
- &plan->relationOids,
- &plan->invalItems);
- }
+ /* Now we can restore current search path */
+ PopOverrideSearchPath();
+
+ /*
+ * Check or update the result tupdesc. XXX should we use a weaker
+ * condition than equalTupleDescs() here?
+ *
+ * We assume the parameter types didn't change from the first time, so no
+ * need to update that.
+ */
+ resultDesc = PlanCacheComputeResultDesc(tlist);
+ if (resultDesc == NULL && plansource->resultDesc == NULL)
+ {
+ /* OK, doesn't return tuples */
}
- else
+ else if (resultDesc == NULL || plansource->resultDesc == NULL ||
+ !equalTupleDescs(resultDesc, plansource->resultDesc))
{
- /* Use the planner machinery to extract dependencies */
- extract_query_dependencies((Node *) stmt_list,
- &plan->relationOids,
- &plan->invalItems);
+ /* can we give a better error message? */
+ if (plansource->fixed_result)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cached plan must not change result type")));
+ oldcxt = MemoryContextSwitchTo(plansource->context);
+ if (resultDesc)
+ resultDesc = CreateTupleDescCopy(resultDesc);
+ if (plansource->resultDesc)
+ FreeTupleDesc(plansource->resultDesc);
+ plansource->resultDesc = resultDesc;
+ MemoryContextSwitchTo(oldcxt);
}
- Assert(plansource->plan == NULL);
- plansource->plan = plan;
+ /*
+ * Allocate new query_context and copy the completed querytree into it.
+ * It's transient until we complete the copying and dependency extraction.
+ */
+ querytree_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ oldcxt = MemoryContextSwitchTo(querytree_context);
- MemoryContextSwitchTo(oldcxt);
-}
+ qlist = (List *) copyObject(tlist);
-/*
- * DropCachedPlan: destroy a cached plan.
- *
- * Actually this only destroys the CachedPlanSource: the referenced CachedPlan
- * is released, but not destroyed until its refcount goes to zero. That
- * handles the situation where DropCachedPlan is called while the plan is
- * still in use.
- */
-void
-DropCachedPlan(CachedPlanSource *plansource)
-{
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, plansource));
+ /*
+ * Use the planner machinery to extract dependencies. Data is saved in
+ * query_context. (We assume that not a lot of extra cruft is created
+ * by this call.)
+ */
+ extract_query_dependencies((Node *) qlist,
+ &plansource->relationOids,
+ &plansource->invalItems);
- /* Remove it from the list */
- cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Now reparent the finished query_context and save the links */
+ MemoryContextSetParent(querytree_context, plansource->context);
- /* Decrement child CachePlan's refcount and drop if no longer needed */
- if (plansource->plan)
- ReleaseCachedPlan(plansource->plan, false);
+ plansource->query_context = querytree_context;
+ plansource->query_list = qlist;
/*
- * If CachedPlanSource has independent storage, just drop it. Otherwise
- * decrement the refcount on the CachePlan that owns the storage.
+ * Note: we do not reset generic_cost or total_custom_cost, although
+ * we could choose to do so. If the DDL or statistics change that
+ * prompted the invalidation meant a significant change in the cost
+ * estimates, it would be better to reset those variables and start
+ * fresh; but often it doesn't, and we're better retaining our hard-won
+ * knowledge about the relative costs.
*/
- if (plansource->orig_plan == NULL)
- {
- /* Remove the CachedPlanSource and all subsidiary data */
- MemoryContextDelete(plansource->context);
- }
- else
- {
- Assert(plansource->context == plansource->orig_plan->context);
- ReleaseCachedPlan(plansource->orig_plan, false);
- }
+
+ plansource->is_valid = true;
+
+ /* Return transient copy of querytrees for possible use in planning */
+ return tlist;
}
/*
- * RevalidateCachedPlan: prepare for re-use of a previously cached plan.
- *
- * What we do here is re-acquire locks and rebuild the plan if necessary.
- * On return, the plan is valid and we have sufficient locks to begin
- * execution (or planning, if not fully_planned).
+ * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
*
- * On return, the refcount of the plan has been incremented; a later
- * ReleaseCachedPlan() call is expected. The refcount has been reported
- * to the CurrentResourceOwner if useResOwner is true.
+ * Caller must have already called RevalidateCachedQuery to verify that the
+ * querytree is up to date.
*
- * Note: if any replanning activity is required, the caller's memory context
- * is used for that work.
+ * On a "true" return, we have acquired the locks needed to run the plan.
+ * (We must do this for the "true" result to be race-condition-free.)
*/
-CachedPlan *
-RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
+static bool
+CheckCachedPlan(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ CachedPlan *plan = plansource->gplan;
+
+ /* Assert that caller checked the querytree */
+ Assert(plansource->is_valid);
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, plansource));
+ /* If there's no generic plan, just say "false" */
+ if (!plan)
+ return false;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
/*
- * If the plan currently appears 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 lock.
+ * If it appears valid, acquire locks and recheck; this is much the same
+ * logic as in RevalidateCachedQuery, but for a plan.
*/
- plan = plansource->plan;
- if (plan && !plan->dead)
+ if (plan->is_valid)
{
/*
* Plan must have positive refcount because it is referenced by
@@ -460,164 +660,348 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
*/
Assert(plan->refcount > 0);
- if (plan->fully_planned)
- AcquireExecutorLocks(plan->stmt_list, true);
- else
- AcquirePlannerLocks(plan->stmt_list, true);
+ AcquireExecutorLocks(plan->stmt_list, true);
/*
* If plan was transient, check to see if TransactionXmin has
* advanced, and if so invalidate it.
*/
- if (!plan->dead &&
+ if (plan->is_valid &&
TransactionIdIsValid(plan->saved_xmin) &&
!TransactionIdEquals(plan->saved_xmin, TransactionXmin))
- plan->dead = true;
+ plan->is_valid = false;
/*
* By now, if any invalidation has happened, the inval callback
- * functions will have marked the plan dead.
+ * functions will have marked the plan invalid.
*/
- if (plan->dead)
+ if (plan->is_valid)
{
- /* Ooops, the race case happened. Release useless locks. */
- if (plan->fully_planned)
- AcquireExecutorLocks(plan->stmt_list, false);
- else
- AcquirePlannerLocks(plan->stmt_list, false);
+ /* Successfully revalidated and locked the query. */
+ return true;
}
+
+ /* Ooops, the race case happened. Release useless locks. */
+ AcquireExecutorLocks(plan->stmt_list, false);
}
/*
- * If plan has been invalidated, unlink it from the parent and release it.
+ * Plan has been invalidated, so unlink it from the parent and release it.
*/
- if (plan && plan->dead)
+ ReleaseGenericPlan(plansource);
+
+ return false;
+}
+
+/*
+ * BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource.
+ *
+ * qlist should be the result value from a previous RevalidateCachedQuery.
+ *
+ * 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.
+ *
+ * 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,
+ ParamListInfo boundParams)
+{
+ CachedPlan *plan;
+ List *plist;
+ bool snapshot_set;
+ bool spi_pushed;
+ MemoryContext plan_context;
+ MemoryContext oldcxt;
+
+ /* Assert that caller checked the querytree */
+ Assert(plansource->is_valid);
+
+ /*
+ * 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
+ * comments for PushOverrideSearchPath about limitations of this.
+ *
+ * (XXX is there anything else we really need to restore?)
+ *
+ * Note: it's a bit annoying to do this and snapshot-setting twice in the
+ * case where we have to do both re-analysis and re-planning. However,
+ * until there's some evidence that the cost is actually meaningful
+ * compared to parse analysis + planning, I'm not going to contort the
+ * code enough to avoid that.
+ */
+ PushOverrideSearchPath(plansource->search_path);
+
+ /*
+ * If a snapshot is already set (the normal case), we can just use
+ * that for parsing/planning. But if it isn't, install one. Note: no
+ * point in checking whether parse analysis requires a snapshot;
+ * utility commands don't have invalidatable plans, so we'd not get
+ * here for such a command.
+ */
+ snapshot_set = false;
+ if (!ActiveSnapshotSet())
{
- plansource->plan = NULL;
- ReleaseCachedPlan(plan, false);
- plan = NULL;
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
}
/*
- * Build a new plan if needed.
+ * The planner may try to call SPI-using functions, which causes a
+ * problem if we're already inside one. Rather than expect all
+ * SPI-using code to do SPI_push whenever a replan could happen,
+ * it seems best to take care of the case here.
*/
- if (!plan)
+ spi_pushed = SPI_push_conditional();
+
+ /*
+ * Generate the plan.
+ */
+ plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+
+ /* Clean up SPI state */
+ SPI_pop_conditional(spi_pushed);
+
+ /* Release snapshot if we got one */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ /* Now we can restore current search path */
+ 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.
+ */
+ plan = (CachedPlan *) palloc(sizeof(CachedPlan));
+ plan->magic = CACHEDPLAN_MAGIC;
+ plan->stmt_list = plist;
+ if (plan_list_is_transient(plist))
{
- bool snapshot_set = false;
- Node *rawtree;
- List *slist;
- TupleDesc resultDesc;
+ Assert(TransactionIdIsNormal(TransactionXmin));
+ plan->saved_xmin = TransactionXmin;
+ }
+ else
+ plan->saved_xmin = InvalidTransactionId;
+ plan->refcount = 0;
+ plan->context = plan_context;
+ plan->is_saved = false;
+ plan->is_valid = true;
- /*
- * Restore the search_path that was in use when the plan was made. See
- * comments for PushOverrideSearchPath about limitations of this.
- *
- * (XXX is there anything else we really need to restore?)
- */
- PushOverrideSearchPath(plansource->search_path);
+ /* assign generation number to new plan */
+ plan->generation = ++(plansource->generation);
- /*
- * If a snapshot is already set (the normal case), we can just use
- * that for parsing/planning. But if it isn't, install one. Note: no
- * point in checking whether parse analysis requires a snapshot;
- * utility commands don't have invalidatable plans, so we'd not get
- * here for such a command.
- */
- if (!ActiveSnapshotSet())
- {
- PushActiveSnapshot(GetTransactionSnapshot());
- snapshot_set = true;
- }
+ MemoryContextSwitchTo(oldcxt);
- /*
- * Run parse analysis and rule rewriting. The parser tends to
- * scribble on its input, so we must copy the raw parse tree to
- * prevent corruption of the cache.
- */
- rawtree = copyObject(plansource->raw_parse_tree);
- if (plansource->parserSetup != NULL)
- slist = pg_analyze_and_rewrite_params(rawtree,
- plansource->query_string,
- plansource->parserSetup,
- plansource->parserSetupArg);
- else
- slist = pg_analyze_and_rewrite(rawtree,
- plansource->query_string,
- plansource->param_types,
- plansource->num_params);
+ return plan;
+}
- if (plansource->fully_planned)
- {
- /*
- * Generate plans for queries.
- *
- * The planner may try to call SPI-using functions, which causes a
- * problem if we're already inside one. Rather than expect all
- * SPI-using code to do SPI_push whenever a replan could happen,
- * it seems best to take care of the case here.
- */
- bool pushed;
+/*
+ * choose_custom_plan: choose whether to use custom or generic plan
+ *
+ * This defines the policy followed by GetCachedPlan.
+ */
+static bool
+choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
+{
+ double avg_custom_cost;
- pushed = SPI_push_conditional();
+ /* Never any point in a custom plan if there's no parameters */
+ if (boundParams == NULL)
+ return false;
- slist = pg_plan_queries(slist, plansource->cursor_options, NULL);
+ /* See if caller wants to force the decision */
+ if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)
+ return false;
+ if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN)
+ return true;
- SPI_pop_conditional(pushed);
- }
+ /* Generate custom plans until we have done at least 5 (arbitrary) */
+ if (plansource->num_custom_plans < 5)
+ return true;
- /*
- * Check or update the result tupdesc. XXX should we use a weaker
- * condition than equalTupleDescs() here?
- */
- resultDesc = PlanCacheComputeResultDesc(slist);
- if (resultDesc == NULL && plansource->resultDesc == NULL)
+ avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans;
+
+ /*
+ * Prefer generic plan if it's less than 10% more expensive than average
+ * custom plan. This threshold is a bit arbitrary; it'd be better if we
+ * had some means of comparing planning time to the estimated runtime
+ * cost differential.
+ *
+ * Note that if generic_cost is -1 (indicating we've not yet determined
+ * the generic plan cost), we'll always prefer generic at this point.
+ */
+ if (plansource->generic_cost < avg_custom_cost * 1.1)
+ return false;
+
+ return true;
+}
+
+/*
+ * cached_plan_cost: calculate estimated cost of a plan
+ */
+static double
+cached_plan_cost(CachedPlan *plan)
+{
+ double result = 0;
+ ListCell *lc;
+
+ foreach(lc, plan->stmt_list)
+ {
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
+
+ if (!IsA(plannedstmt, PlannedStmt))
+ continue; /* Ignore utility statements */
+
+ result += plannedstmt->planTree->total_cost;
+ }
+
+ return result;
+}
+
+/*
+ * GetCachedPlan: get a cached plan from a CachedPlanSource.
+ *
+ * This function hides the logic that decides whether to use a generic
+ * plan or a custom plan for the given parameters: the caller does not know
+ * which it will get.
+ *
+ * On return, the plan is valid and we have sufficient locks to begin
+ * execution.
+ *
+ * On return, the refcount of the plan has been incremented; a later
+ * ReleaseCachedPlan() call is expected. The refcount has been reported
+ * to the CurrentResourceOwner if useResOwner is true (note that that must
+ * only be true if it's a "saved" CachedPlanSource).
+ *
+ * Note: if any replanning activity is required, the caller's memory context
+ * is used for that work.
+ */
+CachedPlan *
+GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
+ bool useResOwner)
+{
+ CachedPlan *plan;
+ List *qlist;
+ bool customplan;
+
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+ /* This seems worth a real test, though */
+ if (useResOwner && !plansource->is_saved)
+ elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
+
+ /* Make sure the querytree list is valid and we have parse-time locks */
+ qlist = RevalidateCachedQuery(plansource);
+
+ /* Decide whether to use a custom plan */
+ customplan = choose_custom_plan(plansource, boundParams);
+
+ if (!customplan)
+ {
+ if (CheckCachedPlan(plansource))
{
- /* OK, doesn't return tuples */
+ /* We want a generic plan, and we already have a valid one */
+ plan = plansource->gplan;
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
}
- else if (resultDesc == NULL || plansource->resultDesc == NULL ||
- !equalTupleDescs(resultDesc, plansource->resultDesc))
+ else
{
- MemoryContext oldcxt;
-
- /* can we give a better error message? */
- if (plansource->fixed_result)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cached plan must not change result type")));
- oldcxt = MemoryContextSwitchTo(plansource->context);
- if (resultDesc)
- resultDesc = CreateTupleDescCopy(resultDesc);
- if (plansource->resultDesc)
- FreeTupleDesc(plansource->resultDesc);
- plansource->resultDesc = resultDesc;
- MemoryContextSwitchTo(oldcxt);
- }
-
- /* Release snapshot if we got one */
- if (snapshot_set)
- PopActiveSnapshot();
-
- /* Now we can restore current search path */
- PopOverrideSearchPath();
+ /* Build a new generic plan */
+ plan = BuildCachedPlan(plansource, qlist, NULL);
+ /* Just make real sure plansource->gplan is clear */
+ ReleaseGenericPlan(plansource);
+ /* Link the new generic plan into the plansource */
+ plansource->gplan = plan;
+ plan->refcount++;
+ /* Immediately reparent into appropriate context */
+ if (plansource->is_saved)
+ {
+ /* saved plans all live under CacheMemoryContext */
+ MemoryContextSetParent(plan->context, CacheMemoryContext);
+ plan->is_saved = true;
+ }
+ else
+ {
+ /* otherwise, it should be a sibling of the plansource */
+ MemoryContextSetParent(plan->context,
+ MemoryContextGetParent(plansource->context));
+ }
+ /* Update generic_cost whenever we make a new generic plan */
+ plansource->generic_cost = cached_plan_cost(plan);
- /*
- * Store the plans into the plancache entry, advancing the generation
- * count.
- */
- StoreCachedPlan(plansource, slist, NULL);
+ /*
+ * If, based on the now-known value of generic_cost, we'd not have
+ * chosen to use a generic plan, then forget it and make a custom
+ * plan. This is a bit of a wart but is necessary to avoid a
+ * glitch in behavior when the custom plans are consistently big
+ * winners; at some point we'll experiment with a generic plan and
+ * find it's a loser, but we don't want to actually execute that
+ * plan.
+ */
+ customplan = choose_custom_plan(plansource, boundParams);
+ }
+ }
- plan = plansource->plan;
+ if (customplan)
+ {
+ /* Build a custom plan */
+ plan = BuildCachedPlan(plansource, qlist, boundParams);
+ /* Accumulate total costs of custom plans, but 'ware overflow */
+ if (plansource->num_custom_plans < INT_MAX)
+ {
+ plansource->total_custom_cost += cached_plan_cost(plan);
+ plansource->num_custom_plans++;
+ }
}
- /*
- * Last step: flag the plan as in use by caller.
- */
+ /* Flag the plan as in use by caller */
if (useResOwner)
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
plan->refcount++;
if (useResOwner)
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
+ /*
+ * Saved plans should be under CacheMemoryContext so they will not go away
+ * until their reference count goes to zero. In the generic-plan cases we
+ * already took care of that, but for a custom plan, do it as soon as we
+ * have created a reference-counted link.
+ */
+ if (customplan && plansource->is_saved)
+ {
+ MemoryContextSetParent(plan->context, CacheMemoryContext);
+ plan->is_saved = true;
+ }
+
return plan;
}
@@ -635,8 +1019,12 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
void
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
{
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
if (useResOwner)
+ {
+ Assert(plan->is_saved);
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
+ }
Assert(plan->refcount > 0);
plan->refcount--;
if (plan->refcount == 0)
@@ -644,8 +1032,125 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
}
/*
- * CachedPlanIsValid: test whether the plan within a CachedPlanSource is
- * currently valid (that is, not marked as being in need of revalidation).
+ * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
+ *
+ * This can only be applied to unsaved plans; once saved, a plan always
+ * lives underneath CacheMemoryContext.
+ */
+void
+CachedPlanSetParentContext(CachedPlanSource *plansource,
+ MemoryContext newcontext)
+{
+ /* Assert caller is doing things in a sane order */
+ 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);
+
+ /*
+ * The query_context needs no special handling, since it's a child of
+ * plansource->context. But if there's a generic plan, it should be
+ * maintained as a sibling of plansource->context.
+ */
+ if (plansource->gplan)
+ {
+ Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC);
+ MemoryContextSetParent(plansource->gplan->context, newcontext);
+ }
+}
+
+/*
+ * CopyCachedPlan: make a copy of a CachedPlanSource
+ *
+ * This is a convenience routine that does the equivalent of
+ * CreateCachedPlan + CompleteCachedPlan, using the data stored in the
+ * input CachedPlanSource. The result is therefore "unsaved" (regardless
+ * of the state of the source), and we don't copy any generic plan either.
+ * The result will be currently valid, or not, the same as the source.
+ */
+CachedPlanSource *
+CopyCachedPlan(CachedPlanSource *plansource)
+{
+ CachedPlanSource *newsource;
+ MemoryContext source_context;
+ MemoryContext querytree_context;
+ MemoryContext oldcxt;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+
+ source_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlanSource",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(source_context);
+
+ newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ newsource->magic = CACHEDPLANSOURCE_MAGIC;
+ newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
+ newsource->query_string = pstrdup(plansource->query_string);
+ newsource->commandTag = plansource->commandTag;
+ if (plansource->num_params > 0)
+ {
+ newsource->param_types = (Oid *)
+ palloc(plansource->num_params * sizeof(Oid));
+ memcpy(newsource->param_types, plansource->param_types,
+ plansource->num_params * sizeof(Oid));
+ }
+ else
+ newsource->param_types = NULL;
+ newsource->num_params = plansource->num_params;
+ newsource->parserSetup = plansource->parserSetup;
+ newsource->parserSetupArg = plansource->parserSetupArg;
+ newsource->cursor_options = plansource->cursor_options;
+ newsource->fixed_result = plansource->fixed_result;
+ if (plansource->resultDesc)
+ newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
+ else
+ newsource->resultDesc = NULL;
+ newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
+ newsource->context = source_context;
+
+ querytree_context = AllocSetContextCreate(source_context,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ MemoryContextSwitchTo(querytree_context);
+ newsource->query_list = (List *) copyObject(plansource->query_list);
+ newsource->relationOids = (List *) copyObject(plansource->relationOids);
+ newsource->invalItems = (List *) copyObject(plansource->invalItems);
+ newsource->query_context = querytree_context;
+
+ newsource->gplan = NULL;
+
+ newsource->is_complete = true;
+ newsource->is_saved = false;
+ newsource->is_valid = plansource->is_valid;
+ newsource->generation = plansource->generation;
+ newsource->next_saved = NULL;
+
+ /* We may as well copy any acquired cost knowledge */
+ newsource->generic_cost = plansource->generic_cost;
+ newsource->total_custom_cost = plansource->total_custom_cost;
+ newsource->num_custom_plans = plansource->num_custom_plans;
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return newsource;
+}
+
+/*
+ * CachedPlanIsValid: test whether the rewritten querytree within a
+ * CachedPlanSource is currently valid (that is, not marked as being in need
+ * of revalidation).
*
* This result is only trustworthy (ie, free from race conditions) if
* the caller has acquired locks on all the relations used in the plan.
@@ -653,37 +1158,44 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
bool
CachedPlanIsValid(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ return plansource->is_valid;
+}
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, plansource));
+/*
+ * CachedPlanGetTargetList: return tlist, if any, describing plan's output
+ *
+ * The result is guaranteed up-to-date. However, it is local storage
+ * within the cached plan, and may disappear next time the plan is updated.
+ */
+List *
+CachedPlanGetTargetList(CachedPlanSource *plansource)
+{
+ Node *pstmt;
- plan = plansource->plan;
- if (plan && !plan->dead)
- {
- /*
- * Plan must have positive refcount because it is referenced by
- * plansource; so no need to fear it disappears under us here.
- */
- Assert(plan->refcount > 0);
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
- /*
- * Although we don't want to acquire locks here, it still seems useful
- * to check for expiration of a transient plan.
- */
- if (TransactionIdIsValid(plan->saved_xmin) &&
- !TransactionIdEquals(plan->saved_xmin, TransactionXmin))
- plan->dead = true;
- else
- return true;
- }
+ /*
+ * No work needed if statement doesn't return tuples (we assume this
+ * feature cannot be changed by an invalidation)
+ */
+ if (plansource->resultDesc == NULL)
+ return NIL;
- return false;
+ /* Make sure the querytree list is valid and we have parse-time locks */
+ RevalidateCachedQuery(plansource);
+
+ /* Get the primary statement and find out what it returns */
+ pstmt = PortalListGetPrimaryStmt(plansource->query_list);
+
+ return FetchStatementTargetList(pstmt);
}
/*
- * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned
- * cached plan; or release them if acquire is false.
+ * AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
+ * or release them if acquire is false.
*/
static void
AcquireExecutorLocks(List *stmt_list, bool acquire)
@@ -752,8 +1264,8 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
}
/*
- * AcquirePlannerLocks: acquire locks needed for planning and execution of a
- * not-fully-planned cached plan; or release them if acquire is false.
+ * AcquirePlannerLocks: acquire locks needed for planning of a querytree list;
+ * or release them if acquire is false.
*
* Note that we don't actually try to open the relations, and hence will not
* fail if one has been dropped entirely --- we'll just transiently acquire
@@ -903,64 +1415,36 @@ plan_list_is_transient(List *stmt_list)
}
/*
- * PlanCacheComputeResultDesc: given a list of either fully-planned statements
- * or Queries, determine the result tupledesc it will produce. Returns NULL
- * if the execution will not return tuples.
+ * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries,
+ * determine the result tupledesc it will produce. Returns NULL if the
+ * execution will not return tuples.
*
* Note: the result is created or copied into current memory context.
*/
-TupleDesc
+static TupleDesc
PlanCacheComputeResultDesc(List *stmt_list)
{
- Node *node;
Query *query;
- PlannedStmt *pstmt;
switch (ChoosePortalStrategy(stmt_list))
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_MOD_WITH:
- node = (Node *) linitial(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- return ExecCleanTypeFromTL(query->targetList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
+ query = (Query *) linitial(stmt_list);
+ Assert(IsA(query, Query));
+ return ExecCleanTypeFromTL(query->targetList, false);
case PORTAL_ONE_RETURNING:
- node = PortalListGetPrimaryStmt(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->returningList);
- return ExecCleanTypeFromTL(query->returningList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- Assert(pstmt->hasReturning);
- return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
+ query = (Query *) PortalListGetPrimaryStmt(stmt_list);
+ Assert(IsA(query, Query));
+ Assert(query->returningList);
+ return ExecCleanTypeFromTL(query->returningList, false);
case PORTAL_UTIL_SELECT:
- node = (Node *) linitial(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->utilityStmt);
- return UtilityTupleDescriptor(query->utilityStmt);
- }
- /* else it's a bare utility statement */
- return UtilityTupleDescriptor(node);
+ query = (Query *) linitial(stmt_list);
+ Assert(IsA(query, Query));
+ Assert(query->utilityStmt);
+ return UtilityTupleDescriptor(query->utilityStmt);
case PORTAL_MULTI_QUERY:
/* will not return tuples */
@@ -979,33 +1463,39 @@ PlanCacheComputeResultDesc(List *stmt_list)
static void
PlanCacheRelCallback(Datum arg, Oid relid)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * Check the list we built ourselves; this covers unplanned cases
- * including EXPLAIN.
+ * Check the dependency list for the rewritten querytree.
*/
- if ((relid == InvalidOid) ? plan->relationOids != NIL :
- list_member_oid(plan->relationOids, relid))
- plan->dead = true;
+ if ((relid == InvalidOid) ? plansource->relationOids != NIL :
+ list_member_oid(plansource->relationOids, relid))
+ {
+ /* Invalidate the querytree and generic plan */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
+ }
- if (plan->fully_planned && !plan->dead)
+ /*
+ * The generic plan, if any, could have more dependencies than the
+ * querytree does, so we have to check it too.
+ */
+ if (plansource->gplan && plansource->gplan->is_valid)
{
- /* Have to check the per-PlannedStmt relid lists */
- ListCell *lc2;
+ ListCell *lc;
- foreach(lc2, plan->stmt_list)
+ foreach(lc, plansource->gplan->stmt_list)
{
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
Assert(!IsA(plannedstmt, Query));
if (!IsA(plannedstmt, PlannedStmt))
@@ -1013,8 +1503,8 @@ PlanCacheRelCallback(Datum arg, Oid relid)
if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
list_member_oid(plannedstmt->relationOids, relid))
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the generic plan only */
+ plansource->gplan->is_valid = false;
break; /* out of stmt_list scan */
}
}
@@ -1035,43 +1525,47 @@ PlanCacheRelCallback(Datum arg, Oid relid)
static void
PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
- ListCell *lc2;
+ ListCell *lc;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * Check the list we built ourselves; this covers unplanned cases
- * including EXPLAIN.
+ * Check the dependency list for the rewritten querytree.
*/
- foreach(lc2, plan->invalItems)
+ foreach(lc, plansource->invalItems)
{
- PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
+ PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
if (item->cacheId != cacheid)
continue;
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the querytree and generic plan */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
break;
}
}
- if (plan->fully_planned && !plan->dead)
+ /*
+ * The generic plan, if any, could have more dependencies than the
+ * querytree does, so we have to check it too.
+ */
+ if (plansource->gplan && plansource->gplan->is_valid)
{
- /* Have to check the per-PlannedStmt inval-item lists */
- foreach(lc2, plan->stmt_list)
+ foreach(lc, plansource->gplan->stmt_list)
{
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
ListCell *lc3;
Assert(!IsA(plannedstmt, Query));
@@ -1086,12 +1580,12 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the generic plan only */
+ plansource->gplan->is_valid = false;
break; /* out of invalItems scan */
}
}
- if (plan->dead)
+ if (!plansource->gplan->is_valid)
break; /* out of stmt_list scan */
}
}
@@ -1111,65 +1605,46 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
}
/*
- * ResetPlanCache: drop all cached plans.
+ * ResetPlanCache: invalidate all cached plans.
*/
void
ResetPlanCache(void)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
- ListCell *lc2;
+ ListCell *lc;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * We *must not* mark transaction control statements as dead,
+ * We *must not* mark transaction control statements as invalid,
* particularly not ROLLBACK, because they may need to be executed in
* aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements
- * since they have no plans anyway. So mark it dead only if it
+ * since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement. (EXPLAIN counts as a
* non-utility statement, though, since it contains an analyzed query
* that might have dependencies.)
*/
- if (plan->fully_planned)
+ foreach(lc, plansource->query_list)
{
- /* Search statement list for non-utility statements */
- foreach(lc2, plan->stmt_list)
- {
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ Query *query = (Query *) lfirst(lc);
- Assert(!IsA(plannedstmt, Query));
- if (IsA(plannedstmt, PlannedStmt) ||
- IsA(plannedstmt, ExplainStmt))
- {
- /* non-utility statement, so invalidate */
- plan->dead = true;
- break; /* out of stmt_list scan */
- }
- }
- }
- else
- {
- /* Search Query list for non-utility statements */
- foreach(lc2, plan->stmt_list)
+ Assert(IsA(query, Query));
+ if (query->commandType != CMD_UTILITY ||
+ IsA(query->utilityStmt, ExplainStmt))
{
- Query *query = (Query *) lfirst(lc2);
-
- Assert(IsA(query, Query));
- if (query->commandType != CMD_UTILITY ||
- IsA(query->utilityStmt, ExplainStmt))
- {
- /* non-utility statement, so invalidate */
- plan->dead = true;
- break; /* out of stmt_list scan */
- }
+ /* non-utility statement, so invalidate */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
+ break;
}
}
}
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 4eab24c46c7..466e9334cfb 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -343,6 +343,18 @@ GetMemoryChunkContext(void *pointer)
}
/*
+ * MemoryContextGetParent
+ * Get the parent context (if any) of the specified context
+ */
+MemoryContext
+MemoryContextGetParent(MemoryContext context)
+{
+ AssertArg(MemoryContextIsValid(context));
+
+ return context->parent;
+}
+
+/*
* MemoryContextIsEmpty
* Is a memory context empty of any allocated space?
*/
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 609758ecd23..7fbf1a5efad 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -280,9 +280,9 @@ CreateNewPortal(void)
* (before rewriting) was an empty string. Also, the passed commandTag must
* be a pointer to a constant string, since it is not copied.
*
- * If cplan is provided, then it is a cached plan containing the stmts,
- * and the caller must have done RevalidateCachedPlan(), causing a refcount
- * increment. The refcount will be released when the portal is destroyed.
+ * If cplan is provided, then it is a cached plan containing the stmts, and
+ * the caller must have done GetCachedPlan(), causing a refcount increment.
+ * The refcount will be released when the portal is destroyed.
*
* If cplan is NULL, then it is the caller's responsibility to ensure that
* the passed plan trees have adequate lifetime. Typically this is done by
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 4bcbc20497f..904c6fd97d8 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -118,6 +118,7 @@ extern Oid GetTempToastNamespace(void);
extern void ResetTempTableNamespace(void);
extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
+extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
extern void PushOverrideSearchPath(OverrideSearchPath *newpath);
extern void PopOverrideSearchPath(void);
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index 63c06ad8d69..52362fa933d 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -44,13 +44,7 @@ extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
/* Low-level access to stored prepared statements */
extern void StorePreparedStatement(const char *stmt_name,
- Node *raw_parse_tree,
- const char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
+ CachedPlanSource *plansource,
bool from_sql);
extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
bool throwError);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 7199debb27a..3b1b27ee49e 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -93,6 +93,7 @@ extern SPIPlanPtr SPI_prepare_params(const char *src,
ParserSetupHook parserSetup,
void *parserSetupArg,
int cursorOptions);
+extern int SPI_keepplan(SPIPlanPtr plan);
extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
extern int SPI_freeplan(SPIPlanPtr plan);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 5865f532802..3e7bf860948 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -32,27 +32,32 @@ typedef struct
} _SPI_connection;
/*
- * SPI plans have two states: saved or unsaved.
+ * SPI plans have three states: saved, unsaved, or temporary.
*
- * For an unsaved plan, the _SPI_plan struct and all its subsidiary data are in
- * a dedicated memory context identified by plancxt. An unsaved plan is good
- * at most for the current transaction, since the locks that protect it from
- * schema changes will be lost at end of transaction. Hence the plancxt is
- * always a transient one.
+ * Ordinarily, the _SPI_plan struct itself as well as the argtypes array
+ * are in a dedicated memory context identified by plancxt (which can be
+ * really small). All the other subsidiary state is in plancache entries
+ * identified by plancache_list (note: the list cells themselves are in
+ * plancxt).
*
- * For a saved plan, the _SPI_plan struct and the argument type array are in
- * the plancxt (which can be really small). All the other subsidiary state
- * is in plancache entries identified by plancache_list (note: the list cells
- * themselves are in plancxt). We rely on plancache.c to keep the cache
- * entries up-to-date as needed. The plancxt is a child of CacheMemoryContext
- * since it should persist until explicitly destroyed.
+ * In an unsaved plan, the plancxt as well as the plancache entries' contexts
+ * are children of the SPI procedure context, so they'll all disappear at
+ * function exit. plancache.c also knows that the plancache entries are
+ * "unsaved", so it doesn't link them into its global list; hence they do
+ * not respond to inval events. This is OK since we are presumably holding
+ * adequate locks to prevent other backends from messing with the tables.
*
- * To avoid redundant coding, the representation of unsaved plans matches
- * that of saved plans, ie, plancache_list is a list of CachedPlanSource
- * structs which in turn point to CachedPlan structs. However, in an unsaved
- * plan all these structs are just created by spi.c and are not known to
- * plancache.c. We don't try very hard to make all their fields valid,
- * only the ones spi.c actually uses.
+ * For a saved plan, the plancxt is made a child of CacheMemoryContext
+ * since it should persist until explicitly destroyed. Likewise, the
+ * plancache entries will be under CacheMemoryContext since we tell
+ * plancache.c to save them. We rely on plancache.c to keep the cache
+ * entries up-to-date as needed in the face of invalidation events.
+ *
+ * There are also "temporary" SPI plans, in which the _SPI_plan struct is
+ * not even palloc'd but just exists in some function's local variable.
+ * The plancache entries are unsaved and exist under the SPI executor context,
+ * 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.
*
* 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a4fb3b5f7f6..9998e2f24d6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1996,7 +1996,10 @@ typedef struct SecLabelStmt
#define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */
#define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */
#define CURSOR_OPT_HOLD 0x0010 /* WITH HOLD */
+/* these planner-control flags do not correspond to any SQL grammar: */
#define CURSOR_OPT_FAST_PLAN 0x0020 /* prefer fast-start plan */
+#define CURSOR_OPT_GENERIC_PLAN 0x0040 /* force use of generic plan */
+#define CURSOR_OPT_CUSTOM_PLAN 0x0080 /* force use of custom plan */
typedef struct DeclareCursorStmt
{
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 94c78289b6e..4796f5c7057 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -94,6 +94,7 @@ extern void MemoryContextSetParent(MemoryContext context,
MemoryContext new_parent);
extern Size GetMemoryChunkSpace(void *pointer);
extern MemoryContext GetMemoryChunkContext(void *pointer);
+extern MemoryContext MemoryContextGetParent(MemoryContext context);
extern bool MemoryContextIsEmpty(MemoryContext context);
extern void MemoryContextStats(MemoryContext context);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index b8639a59a0e..c8c27bbb207 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -18,26 +18,47 @@
#include "access/tupdesc.h"
#include "nodes/params.h"
+#define CACHEDPLANSOURCE_MAGIC 195726186
+#define CACHEDPLAN_MAGIC 953717834
+
/*
- * CachedPlanSource represents the portion of a cached plan that persists
- * across invalidation/replan cycles. It stores a raw parse tree (required),
- * the original source text (also required, as of 8.4), and adjunct data.
+ * CachedPlanSource (which might better have been called CachedQuery)
+ * represents a SQL query that we expect to use multiple times. It stores
+ * the query source text, the raw parse tree, and the analyzed-and-rewritten
+ * query tree, as well as adjunct data. Cache invalidation can happen as a
+ * result of DDL affecting objects used by the query. In that case we discard
+ * the analyzed-and-rewritten query tree, and rebuild it when next needed.
+ *
+ * An actual execution plan, represented by CachedPlan, is derived from the
+ * CachedPlanSource when we need to execute the query. The plan could be
+ * either generic (usable with any set of plan parameters) or custom (for a
+ * specific set of parameters). plancache.c contains the logic that decides
+ * which way to do it for any particular execution. If we are using a generic
+ * cached plan then it is meant to be re-used across multiple executions, so
+ * callers must always treat CachedPlans as read-only.
+ *
+ * Once successfully built and "saved", CachedPlanSources typically live
+ * for the life of the backend, although they can be dropped explicitly.
+ * CachedPlans are reference-counted and go away automatically when the last
+ * reference is dropped. A CachedPlan can outlive the CachedPlanSource it
+ * was created from.
*
- * Normally, both the struct itself and the subsidiary data live in the
- * context denoted by the context field, while the linked-to CachedPlan, if
- * any, has its own context. Thus an invalidated CachedPlan can be dropped
- * when no longer needed, and conversely a CachedPlanSource can be dropped
- * without worrying whether any portals depend on particular instances of
- * its plan.
+ * An "unsaved" CachedPlanSource can be used for generating plans, but it
+ * lives in transient storage and will not be updated in response to sinval
+ * events.
*
- * But for entries created by FastCreateCachedPlan, the CachedPlanSource
- * and the initial version of the CachedPlan share the same memory context.
- * In this case, we treat the memory context as belonging to the CachedPlan.
- * The CachedPlanSource has an extra reference-counted link (orig_plan)
- * to the CachedPlan, and the memory context goes away when the CachedPlan's
- * reference count goes to zero. This arrangement saves overhead for plans
- * that aren't expected to live long enough to need replanning, while not
- * losing any flexibility if a replan turns out to be necessary.
+ * CachedPlans made from saved CachedPlanSources are likewise in permanent
+ * storage, so to avoid memory leaks, the reference-counted references to them
+ * must be held in permanent data structures or ResourceOwners. CachedPlans
+ * made from unsaved CachedPlanSources are in children of the caller's
+ * memory context, so references to them should not be longer-lived than
+ * that context. (Reference counting is somewhat pro forma in that case,
+ * though it may be useful if the CachedPlan can be discarded early.)
+ *
+ * A CachedPlanSource has two associated memory contexts: one that holds the
+ * struct itself, the query source text and the raw parse tree, and another
+ * context that holds the rewritten query tree and associated data. This
+ * allows the query tree to be discarded easily when it is invalidated.
*
* Note: the string referenced by commandTag is not subsidiary storage;
* it is assumed to be a compile-time-constant string. As with portals,
@@ -46,78 +67,93 @@
*/
typedef struct CachedPlanSource
{
+ int magic; /* should equal CACHEDPLANSOURCE_MAGIC */
Node *raw_parse_tree; /* output of raw_parser() */
- char *query_string; /* text of query (as of 8.4, never NULL) */
+ 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 */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
int cursor_options; /* cursor options used for planning */
- bool fully_planned; /* do we cache planner or rewriter output? */
bool fixed_result; /* disallow change in result tupdesc? */
- struct OverrideSearchPath *search_path; /* saved search_path */
- int generation; /* counter, starting at 1, for replans */
TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */
- struct CachedPlan *plan; /* link to plan, or NULL if not valid */
- MemoryContext context; /* context containing this CachedPlanSource */
- struct CachedPlan *orig_plan; /* link to plan owning my context */
+ struct OverrideSearchPath *search_path; /* saved search_path */
+ MemoryContext context; /* memory context holding all above */
+ /* These fields describe the current analyzed-and-rewritten query tree: */
+ List *query_list; /* list of Query nodes, or NIL if not valid */
+ List *relationOids; /* OIDs of relations the queries depend on */
+ List *invalItems; /* other dependencies, as PlanInvalItems */
+ MemoryContext query_context; /* context holding the above, or NULL */
+ /* 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_complete; /* has CompleteCachedPlan been done? */
+ bool is_saved; /* has CachedPlanSource been "saved"? */
+ bool is_valid; /* is the query_list currently valid? */
+ int generation; /* increments each time we create a plan */
+ /* If CachedPlanSource has been saved, it is a member of a global list */
+ struct CachedPlanSource *next_saved; /* list link, if so */
+ /* State kept to help decide whether to use custom or generic plans: */
+ double generic_cost; /* cost of generic plan, or -1 if not known */
+ double total_custom_cost; /* total cost of custom plans so far */
+ int num_custom_plans; /* number of plans included in total */
} CachedPlanSource;
/*
- * CachedPlan represents the portion of a cached plan that is discarded when
- * invalidation occurs. The reference count includes both the link(s) from the
- * parent CachedPlanSource, 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.
+ * CachedPlan represents an execution plan derived from a CachedPlanSource.
+ * The reference count includes both the link from the parent CachedPlanSource
+ * (if any), and any active plan executions, so the plan can be discarded
+ * exactly when refcount goes to zero. Both the struct itself and the
+ * subsidiary data live in the context denoted by the context field.
* This makes it easy to free a no-longer-needed cached plan.
*/
typedef struct CachedPlan
{
- List *stmt_list; /* list of statement or Query nodes */
- bool fully_planned; /* do we cache planner or rewriter output? */
- bool dead; /* if true, do not use */
+ 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
* changes from this value */
+ int generation; /* parent's generation number for this plan */
int refcount; /* count of live references to this struct */
- int generation; /* counter, starting at 1, for replans */
MemoryContext context; /* context containing this CachedPlan */
- /* These fields are used only in the not-fully-planned case: */
- List *relationOids; /* OIDs of relations the stmts depend on */
- List *invalItems; /* other dependencies, as PlanInvalItems */
} CachedPlan;
extern void InitPlanCache(void);
+extern void ResetPlanCache(void);
+
extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
const char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result);
-extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree,
- char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result,
- MemoryContext context);
-extern void CachedPlanSetParserHook(CachedPlanSource *plansource,
+ const char *commandTag);
+extern void CompleteCachedPlan(CachedPlanSource *plansource,
+ List *querytree_list,
+ MemoryContext querytree_context,
+ Oid *param_types,
+ int num_params,
ParserSetupHook parserSetup,
- void *parserSetupArg);
+ void *parserSetupArg,
+ int cursor_options,
+ bool fixed_result);
+
+extern void SaveCachedPlan(CachedPlanSource *plansource);
extern void DropCachedPlan(CachedPlanSource *plansource);
-extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
- bool useResOwner);
-extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+
+extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
+ MemoryContext newcontext);
+
+extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);
+
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
-extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
-extern void ResetPlanCache(void);
+extern List *CachedPlanGetTargetList(CachedPlanSource *plansource);
+
+extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
+ ParamListInfo boundParams,
+ bool useResOwner);
+extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
#endif /* PLANCACHE_H */
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 8b5d4dc1915..784e137976c 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -165,7 +165,7 @@ typedef struct plperl_call_data
typedef struct plperl_query_desc
{
char qname[24];
- void *plan;
+ SPIPlanPtr plan;
int nargs;
Oid *argtypes;
FmgrInfo *arginfuncs;
@@ -2951,7 +2951,7 @@ plperl_spi_query(char *query)
PG_TRY();
{
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
/* Make sure the query is validly encoded */
@@ -3118,7 +3118,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
plperl_query_desc *qdesc;
plperl_query_entry *hash_entry;
bool found;
- void *plan;
+ SPIPlanPtr plan;
int i;
MemoryContext oldcontext = CurrentMemoryContext;
@@ -3182,13 +3182,9 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
* Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end).
************************************************************/
- qdesc->plan = SPI_saveplan(plan);
- if (qdesc->plan == NULL)
- elog(ERROR, "SPI_saveplan() failed: %s",
- SPI_result_code_string(SPI_result));
-
- /* Release the procCxt copy to avoid within-function memory leak */
- SPI_freeplan(plan);
+ if (SPI_keepplan(plan))
+ elog(ERROR, "SPI_keepplan() failed");
+ qdesc->plan = plan;
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
@@ -3516,7 +3512,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
void
plperl_spi_freeplan(char *query)
{
- void *plan;
+ SPIPlanPtr plan;
plperl_query_desc *qdesc;
plperl_query_entry *hash_entry;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index de1aece92a1..df785c98511 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -142,6 +142,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions);
static bool exec_simple_check_node(Node *node);
static void exec_simple_check_plan(PLpgSQL_expr *expr);
+static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
Datum *result,
@@ -2020,8 +2021,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, query);
@@ -2991,8 +2991,10 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
expr->query, SPI_result_code_string(SPI_result));
}
}
- expr->plan = SPI_saveplan(plan);
- SPI_freeplan(plan);
+ SPI_keepplan(plan);
+ expr->plan = plan;
+
+ /* Check to see if it's a simple expression */
exec_simple_check_plan(expr);
}
@@ -3011,6 +3013,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr = stmt->sqlstmt;
/*
+ * Set up ParamListInfo (hook function and possibly data values)
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
* On the first call for this statement generate the plan, and detect
* whether the statement is INSERT/UPDATE/DELETE
*/
@@ -3025,16 +3032,17 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
ListCell *l2;
- foreach(l2, plansource->plan->stmt_list)
+ Assert(plansource->is_valid);
+ foreach(l2, plansource->query_list)
{
- PlannedStmt *p = (PlannedStmt *) lfirst(l2);
+ Query *q = (Query *) lfirst(l2);
- if (IsA(p, PlannedStmt) &&
- p->canSetTag)
+ Assert(IsA(q, Query));
+ if (q->canSetTag)
{
- if (p->commandType == CMD_INSERT ||
- p->commandType == CMD_UPDATE ||
- p->commandType == CMD_DELETE)
+ if (q->commandType == CMD_INSERT ||
+ q->commandType == CMD_UPDATE ||
+ q->commandType == CMD_DELETE)
stmt->mod_stmt = true;
}
}
@@ -3042,12 +3050,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
- */
- paramLI = setup_param_list(estate, expr);
-
- /*
* If we have INTO, then we only need one row back ... but if we have INTO
* STRICT, ask for two rows, so that we can verify the statement returns
* only one. INSERT/UPDATE/DELETE are always treated strictly. Without
@@ -3520,8 +3522,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, query);
@@ -4613,8 +4614,7 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr, 0);
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, expr);
@@ -4833,11 +4833,10 @@ loop_exit:
*
* It is possible though unlikely for a simple expression to become non-simple
* (consider for example redefining a trivial view). We must handle that for
- * correctness; fortunately it's normally inexpensive to do
- * RevalidateCachedPlan on a simple expression. We do not consider the other
- * direction (non-simple expression becoming simple) because we'll still give
- * correct results if that happens, and it's unlikely to be worth the cycles
- * to check.
+ * correctness; fortunately it's normally inexpensive to do GetCachedPlan on a
+ * simple expression. We do not consider the other direction (non-simple
+ * expression becoming simple) because we'll still give correct results if
+ * that happens, and it's unlikely to be worth the cycles to check.
*
* Note: if pass-by-reference, the result is in the eval_econtext's
* temporary memory context. It will be freed when exec_eval_cleanup
@@ -4873,17 +4872,21 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Revalidate cached plan, so that we will notice if it became stale. (We
- * also need to hold a refcount while using the plan.) Note that even if
- * replanning occurs, the length of plancache_list can't change, since it
- * is a property of the raw parsetree generated from the query text.
+ * need to hold a refcount while using the plan, anyway.) Note that even
+ * if replanning occurs, the length of plancache_list can't change, since
+ * it is a property of the raw parsetree generated from the query text.
*/
Assert(list_length(expr->plan->plancache_list) == 1);
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
- cplan = RevalidateCachedPlan(plansource, true);
+
+ /* Get the generic plan for the query */
+ cplan = GetCachedPlan(plansource, NULL, true);
+ Assert(cplan == plansource->gplan);
+
if (cplan->generation != expr->expr_simple_generation)
{
/* It got replanned ... is it still simple? */
- exec_simple_check_plan(expr);
+ exec_simple_recheck_plan(expr, cplan);
if (expr->expr_simple_expr == NULL)
{
/* Ooops, release refcount and fail */
@@ -4900,7 +4903,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Prepare the expression for execution, if it's not been done already in
* the current transaction. (This will be forced to happen if we called
- * exec_simple_check_plan above.)
+ * exec_simple_recheck_plan above.)
*/
if (expr->expr_simple_lxid != curlxid)
{
@@ -4931,9 +4934,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* need to free it explicitly, since it will go away at the next reset of
* that context.
*
- * XXX think about avoiding repeated palloc's for param lists? It should
- * be possible --- this routine isn't re-entrant anymore.
- *
* Just for paranoia's sake, save and restore the prior value of
* estate->cur_expr, which setup_param_list() sets.
*/
@@ -4982,10 +4982,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Create a ParamListInfo to pass to SPI
*
- * The ParamListInfo array is initially all zeroes, in particular the
- * ptype values are all InvalidOid. This causes the executor to call the
- * paramFetch hook each time it wants a value. We thus evaluate only the
- * parameters actually demanded.
+ * We fill in the values for any expression parameters that are plain
+ * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting
+ * them with PARAM_FLAG_CONST flags, we allow the planner to use those values
+ * in custom plans. However, parameters that are not plain PLpgSQL_vars
+ * should not be evaluated here, because they could throw errors (for example
+ * "no such record field") and we do not want that to happen in a part of
+ * the expression that might never be evaluated at runtime. To handle those
+ * parameters, we set up a paramFetch hook for the executor to call when it
+ * wants a not-presupplied value.
*
* The result is a locally palloc'd array that should be pfree'd after use;
* but note it can be NULL.
@@ -4997,21 +5002,42 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
/*
* Could we re-use these arrays instead of palloc'ing a new one each time?
- * However, we'd have to zero the array each time anyway, since new values
- * might have been assigned to the variables.
+ * However, we'd have to re-fill the array each time anyway, since new
+ * values might have been assigned to the variables.
*/
if (estate->ndatums > 0)
{
- /* sizeof(ParamListInfoData) includes the first array element */
+ Bitmapset *tmpset;
+ int dno;
+
paramLI = (ParamListInfo)
- palloc0(sizeof(ParamListInfoData) +
- (estate->ndatums - 1) * sizeof(ParamExternData));
+ palloc0(offsetof(ParamListInfoData, params) +
+ estate->ndatums * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
paramLI->parserSetupArg = (void *) expr;
paramLI->numParams = estate->ndatums;
+ /* Instantiate values for "safe" parameters of the expression */
+ tmpset = bms_copy(expr->paramnos);
+ while ((dno = bms_first_member(tmpset)) >= 0)
+ {
+ PLpgSQL_datum *datum = estate->datums[dno];
+
+ if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+ ParamExternData *prm = &paramLI->params[dno];
+
+ prm->value = var->value;
+ prm->isnull = var->isnull;
+ prm->pflags = PARAM_FLAG_CONST;
+ prm->ptype = var->datatype->typoid;
+ }
+ }
+ bms_free(tmpset);
+
/*
* Set up link to active expr where the hook functions can find it.
* Callers must save and restore cur_expr if there is any chance that
@@ -5628,30 +5654,113 @@ static void
exec_simple_check_plan(PLpgSQL_expr *expr)
{
CachedPlanSource *plansource;
- PlannedStmt *stmt;
- Plan *plan;
- TargetEntry *tle;
+ Query *query;
+ CachedPlan *cplan;
/*
* Initialize to "not simple", and remember the plan generation number we
- * last checked. (If the query produces more or less than one parsetree
+ * last checked. (If we don't get as far as obtaining a plan to check,
* we just leave expr_simple_generation set to 0.)
*/
expr->expr_simple_expr = NULL;
expr->expr_simple_generation = 0;
/*
- * 1. We can only evaluate queries that resulted in one single execution
- * plan
+ * We can only test queries that resulted in exactly one CachedPlanSource
*/
if (list_length(expr->plan->plancache_list) != 1)
return;
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
- expr->expr_simple_generation = plansource->generation;
- if (list_length(plansource->plan->stmt_list) != 1)
+
+ /*
+ * Do some checking on the analyzed-and-rewritten form of the query.
+ * These checks are basically redundant with the tests in
+ * exec_simple_recheck_plan, but the point is to avoid building a plan if
+ * possible. Since this function is only
+ * called immediately after creating the CachedPlanSource, we need not
+ * worry about the query being stale.
+ */
+
+ /*
+ * 1. There must be one single querytree.
+ */
+ if (list_length(plansource->query_list) != 1)
return;
+ query = (Query *) linitial(plansource->query_list);
- stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
+ /*
+ * 2. It must be a plain SELECT query without any input tables
+ */
+ if (!IsA(query, Query))
+ return;
+ if (query->commandType != CMD_SELECT || query->intoClause)
+ return;
+ if (query->rtable != NIL)
+ return;
+
+ /*
+ * 3. Can't have any subplans, aggregates, qual clauses either
+ */
+ if (query->hasAggs ||
+ query->hasWindowFuncs ||
+ query->hasSubLinks ||
+ query->hasForUpdate ||
+ query->cteList ||
+ query->jointree->quals ||
+ query->groupClause ||
+ query->havingQual ||
+ query->windowClause ||
+ query->distinctClause ||
+ query->sortClause ||
+ query->limitOffset ||
+ query->limitCount ||
+ query->setOperations)
+ return;
+
+ /*
+ * 4. The query must have a single attribute as result
+ */
+ if (list_length(query->targetList) != 1)
+ return;
+
+ /*
+ * OK, it seems worth constructing a plan for more careful checking.
+ */
+
+ /* Get the generic plan for the query */
+ cplan = GetCachedPlan(plansource, NULL, true);
+ Assert(cplan == plansource->gplan);
+
+ /* Share the remaining work with recheck code path */
+ exec_simple_recheck_plan(expr, cplan);
+
+ /* Release our plan refcount */
+ ReleaseCachedPlan(cplan, true);
+}
+
+/*
+ * exec_simple_recheck_plan --- check for simple plan once we have CachedPlan
+ */
+static void
+exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
+{
+ PlannedStmt *stmt;
+ Plan *plan;
+ TargetEntry *tle;
+
+ /*
+ * Initialize to "not simple", and remember the plan generation number we
+ * last checked.
+ */
+ expr->expr_simple_expr = NULL;
+ expr->expr_simple_generation = cplan->generation;
+
+ /*
+ * 1. There must be one single plantree
+ */
+ if (list_length(cplan->stmt_list) != 1)
+ return;
+ stmt = (PlannedStmt *) linitial(cplan->stmt_list);
/*
* 2. It must be a RESULT plan --> no scan's required
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 17c583ec357..93e8043284e 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -287,7 +287,7 @@ typedef struct PLySubtransactionData
typedef struct PLyPlanObject
{
PyObject_HEAD
- void *plan; /* return of an SPI_saveplan */
+ SPIPlanPtr plan;
int nargs;
Oid *types;
Datum *values;
@@ -3327,7 +3327,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL;
PyObject *volatile optr = NULL;
char *query;
- void *tmpplan;
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
volatile int nargs;
@@ -3431,12 +3430,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
SPI_result_code_string(SPI_result));
/* transfer plan from procCxt to topCxt */
- tmpplan = plan->plan;
- plan->plan = SPI_saveplan(tmpplan);
- SPI_freeplan(tmpplan);
- if (plan->plan == NULL)
- elog(ERROR, "SPI_saveplan failed: %s",
- SPI_result_code_string(SPI_result));
+ if (SPI_keepplan(plan->plan))
+ elog(ERROR, "SPI_keepplan failed");
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index be8fe7a0f21..ecde90626bf 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -128,7 +128,7 @@ typedef struct pltcl_proc_desc
typedef struct pltcl_query_desc
{
char qname[20];
- void *plan;
+ SPIPlanPtr plan;
int nargs;
Oid *argtypes;
FmgrInfo *arginfuncs;
@@ -2024,7 +2024,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
* pltcl_SPI_prepare() - Builtin support for prepared plans
* The Tcl command SPI_prepare
* always saves the plan using
- * SPI_saveplan and returns a key for
+ * SPI_keepplan and returns a key for
* access. There is no chance to prepare
* and not save the plan currently.
**********************************************************************/
@@ -2035,7 +2035,6 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int nargs;
CONST84 char **args;
pltcl_query_desc *qdesc;
- void *plan;
int i;
Tcl_HashEntry *hashent;
int hashnew;
@@ -2103,22 +2102,18 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
* Prepare the plan and check for errors
************************************************************/
UTF_BEGIN;
- plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes);
+ qdesc->plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes);
UTF_END;
- if (plan == NULL)
+ if (qdesc->plan == NULL)
elog(ERROR, "SPI_prepare() failed");
/************************************************************
* Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end).
************************************************************/
- qdesc->plan = SPI_saveplan(plan);
- if (qdesc->plan == NULL)
- elog(ERROR, "SPI_saveplan() failed");
-
- /* Release the procCxt copy to avoid within-function memory leak */
- SPI_freeplan(plan);
+ if (SPI_keepplan(qdesc->plan))
+ elog(ERROR, "SPI_keepplan() failed");
pltcl_subtrans_commit(oldcontext, oldowner);
}
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index daee5af49e4..e5136cfa7c8 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -622,9 +622,8 @@ ttdummy(PG_FUNCTION_ARGS)
if (pplan == NULL)
elog(ERROR, "ttdummy (%s): SPI_prepare returned %d", relname, SPI_result);
- pplan = SPI_saveplan(pplan);
- if (pplan == NULL)
- elog(ERROR, "ttdummy (%s): SPI_saveplan returned %d", relname, SPI_result);
+ if (SPI_keepplan(pplan))
+ elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname);
splan = pplan;
}