aboutsummaryrefslogtreecommitdiff
path: root/src/backend/tcop/postgres.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/tcop/postgres.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/tcop/postgres.c')
-rw-r--r--src/backend/tcop/postgres.c226
1 files changed, 74 insertions, 152 deletions
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);
+ }
}