aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/prepare.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2011-09-16 00:42:53 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2011-09-16 00:43:52 -0400
commite6faf910d75027bdce7cd0f2033db4e912592bcc (patch)
treeb5fdc2340cc1cdf27dd473e23a09cb2953b5053c /src/backend/commands/prepare.c
parent09e98a3e170ecdeb25a0e1afe81bdbeeeaf21f48 (diff)
downloadpostgresql-e6faf910d75027bdce7cd0f2033db4e912592bcc.tar.gz
postgresql-e6faf910d75027bdce7cd0f2033db4e912592bcc.zip
Redesign the plancache mechanism for more flexibility and efficiency.
Rewrite plancache.c so that a "cached plan" (which is rather a misnomer at this point) can support generation of custom, parameter-value-dependent plans, and can make an intelligent choice between using custom plans and the traditional generic-plan approach. The specific choice algorithm implemented here can probably be improved in future, but this commit is all about getting the mechanism in place, not the policy. In addition, restructure the API to greatly reduce the amount of extraneous data copying needed. The main compromise needed to make that possible was to split the initial creation of a CachedPlanSource into two steps. It's worth noting in particular that SPI_saveplan is now deprecated in favor of SPI_keepplan, which accomplishes the same end result with zero data copying, and no need to then spend even more cycles throwing away the original SPIPlan. The risk of long-term memory leaks while manipulating SPIPlans has also been greatly reduced. Most of this improvement is based on use of the recently-added MemoryContextSetParent primitive.
Diffstat (limited to 'src/backend/commands/prepare.c')
-rw-r--r--src/backend/commands/prepare.c144
1 files changed, 57 insertions, 87 deletions
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 */