aboutsummaryrefslogtreecommitdiff
path: root/src/backend/tcop/postgres.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2007-03-13 00:33:44 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2007-03-13 00:33:44 +0000
commitb9527e984092e838790b543b014c0c2720ea4f11 (patch)
tree60a6063280d446701e1b93e1149eaeb9ce13a128 /src/backend/tcop/postgres.c
parentf84308f1958313f6cd1644d74b6a8ff49a871f8d (diff)
downloadpostgresql-b9527e984092e838790b543b014c0c2720ea4f11.tar.gz
postgresql-b9527e984092e838790b543b014c0c2720ea4f11.zip
First phase of plan-invalidation project: create a plan cache management
module and teach PREPARE and protocol-level prepared statements to use it. In service of this, rearrange utility-statement processing so that parse analysis does not assume table schemas can't change before execution for utility statements (necessary because we don't attempt to re-acquire locks for utility statements when reusing a stored plan). This requires some refactoring of the ProcessUtility API, but it ends up cleaner anyway, for instance we can get rid of the QueryContext global. Still to do: fix up SPI and related code to use the plan cache; I'm tempted to try to make SQL functions use it too. Also, there are at least some aspects of system state that we want to ensure remain the same during a replan as in the original processing; search_path certainly ought to behave that way for instance, and perhaps there are others.
Diffstat (limited to 'src/backend/tcop/postgres.c')
-rw-r--r--src/backend/tcop/postgres.c285
1 files changed, 158 insertions, 127 deletions
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cfb6731b234..f997d524101 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.527 2007/03/03 19:32:54 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.528 2007/03/13 00:33:42 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -140,8 +140,9 @@ static bool ignore_till_sync = false;
* We keep it separate from the hashtable kept by commands/prepare.c
* in order to reduce overhead for short-lived queries.
*/
+static CachedPlanSource *unnamed_stmt_psrc = NULL;
+/* workspace for building a new unnamed statement in */
static MemoryContext unnamed_stmt_context = NULL;
-static PreparedStatement *unnamed_stmt_pstmt = NULL;
static bool EchoQuery = false; /* default don't echo */
@@ -173,6 +174,7 @@ static void finish_xact_command(void);
static bool IsTransactionExitStmt(Node *parsetree);
static bool IsTransactionExitStmtList(List *parseTrees);
static bool IsTransactionStmtList(List *parseTrees);
+static void drop_unnamed_stmt(void);
static void SigHupHandler(SIGNAL_ARGS);
static void log_disconnections(int code, Datum arg);
@@ -794,21 +796,13 @@ exec_simple_query(const char *query_string)
* statement and portal; this ensures we recover any storage used by prior
* unnamed operations.)
*/
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
+ drop_unnamed_stmt();
/*
* Switch to appropriate context for constructing parsetrees.
*/
oldcontext = MemoryContextSwitchTo(MessageContext);
- QueryContext = CurrentMemoryContext;
-
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@@ -906,7 +900,7 @@ exec_simple_query(const char *query_string)
query_string,
commandTag,
plantree_list,
- MessageContext);
+ NULL);
/*
* Start the portal. No parameters here.
@@ -950,6 +944,7 @@ exec_simple_query(const char *query_string)
*/
(void) PortalRun(portal,
FETCH_ALL,
+ true, /* top level */
receiver,
receiver,
completionTag);
@@ -1009,8 +1004,6 @@ exec_simple_query(const char *query_string)
if (!parsetree_list)
NullCommand(dest);
- QueryContext = NULL;
-
/*
* Emit duration logging if appropriate.
*/
@@ -1049,10 +1042,10 @@ exec_parse_message(const char *query_string, /* string to execute */
{
MemoryContext oldcontext;
List *parsetree_list;
+ Node *raw_parse_tree;
const char *commandTag;
List *querytree_list,
- *stmt_list,
- *param_list;
+ *stmt_list;
bool is_named;
bool fully_planned;
bool save_log_statement_stats = log_statement_stats;
@@ -1088,12 +1081,12 @@ exec_parse_message(const char *query_string, /* string to execute */
* We have two strategies depending on whether the prepared statement is
* named or not. For a named prepared statement, we do parsing in
* MessageContext and copy the finished trees into the prepared
- * statement's private context; then the reset of MessageContext releases
+ * statement's plancache entry; then the reset of MessageContext releases
* temporary space used by parsing and planning. 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 set up a special context
- * for the unnamed statement, and do all the parsing work therein.
+ * copying parse/plan trees. So in this case, we create the plancache
+ * entry's context here, and do all the parsing work therein.
*/
is_named = (stmt_name[0] != '\0');
if (is_named)
@@ -1104,16 +1097,10 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Unnamed prepared statement --- release any prior unnamed stmt */
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
- /* create context for parsing/planning */
+ drop_unnamed_stmt();
+ /* Create context for parsing/planning */
unnamed_stmt_context =
- AllocSetContextCreate(TopMemoryContext,
+ AllocSetContextCreate(CacheMemoryContext,
"unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
@@ -1121,8 +1108,6 @@ exec_parse_message(const char *query_string, /* string to execute */
oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
}
- QueryContext = CurrentMemoryContext;
-
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@@ -1141,13 +1126,14 @@ exec_parse_message(const char *query_string, /* string to execute */
if (parsetree_list != NIL)
{
- Node *parsetree = (Node *) linitial(parsetree_list);
int i;
+ raw_parse_tree = (Node *) linitial(parsetree_list);
+
/*
* Get the command name for possible use in status display.
*/
- commandTag = CreateCommandTag(parsetree);
+ commandTag = CreateCommandTag(raw_parse_tree);
/*
* If we are in an aborted transaction, reject all commands except
@@ -1158,7 +1144,7 @@ exec_parse_message(const char *query_string, /* string to execute */
* state, but not many...)
*/
if (IsAbortedTransactionBlockState() &&
- !IsTransactionExitStmt(parsetree))
+ !IsTransactionExitStmt(raw_parse_tree))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@@ -1168,20 +1154,22 @@ 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.
*/
if (log_parser_stats)
ResetUsage();
- querytree_list = parse_analyze_varparams(parsetree,
+ querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree),
query_string,
&paramTypes,
&numParams);
/*
- * Check all parameter types got determined, and convert array
- * representation to a list for storage.
+ * Check all parameter types got determined.
*/
- param_list = NIL;
for (i = 0; i < numParams; i++)
{
Oid ptype = paramTypes[i];
@@ -1191,7 +1179,6 @@ exec_parse_message(const char *query_string, /* string to execute */
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("could not determine data type of parameter $%d",
i + 1)));
- param_list = lappend_oid(param_list, ptype);
}
if (log_parser_stats)
@@ -1217,9 +1204,9 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Empty input string. This is legal. */
+ raw_parse_tree = NULL;
commandTag = NULL;
stmt_list = NIL;
- param_list = NIL;
fully_planned = true;
}
@@ -1232,35 +1219,33 @@ exec_parse_message(const char *query_string, /* string to execute */
if (is_named)
{
StorePreparedStatement(stmt_name,
+ raw_parse_tree,
query_string,
commandTag,
+ paramTypes,
+ numParams,
stmt_list,
- param_list,
- fully_planned,
false);
}
else
{
- PreparedStatement *pstmt;
-
- pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
/* query_string needs to be copied into unnamed_stmt_context */
- pstmt->query_string = pstrdup(query_string);
/* the rest is there already */
- pstmt->commandTag = commandTag;
- pstmt->stmt_list = stmt_list;
- pstmt->argtype_list = param_list;
- pstmt->fully_planned = fully_planned;
- pstmt->from_sql = false;
- pstmt->context = unnamed_stmt_context;
- /* Now the unnamed statement is complete and valid */
- unnamed_stmt_pstmt = pstmt;
+ unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree,
+ pstrdup(query_string),
+ commandTag,
+ paramTypes,
+ numParams,
+ stmt_list,
+ fully_planned,
+ true,
+ unnamed_stmt_context);
+ /* context now belongs to the plancache entry */
+ unnamed_stmt_context = NULL;
}
MemoryContextSwitchTo(oldcontext);
- QueryContext = NULL;
-
/*
* We do NOT close the open transaction command here; that only happens
* when the client sends Sync. Instead, do CommandCounterIncrement just
@@ -1315,12 +1300,11 @@ exec_bind_message(StringInfo input_message)
int numParams;
int numRFormats;
int16 *rformats = NULL;
- PreparedStatement *pstmt;
+ CachedPlanSource *psrc;
+ CachedPlan *cplan;
Portal portal;
ParamListInfo params;
- List *query_list;
List *plan_list;
- MemoryContext qContext;
bool save_log_statement_stats = log_statement_stats;
char msec_str[32];
@@ -1335,12 +1319,17 @@ exec_bind_message(StringInfo input_message)
/* Find prepared statement */
if (stmt_name[0] != '\0')
+ {
+ PreparedStatement *pstmt;
+
pstmt = FetchPreparedStatement(stmt_name, true);
+ psrc = pstmt->plansource;
+ }
else
{
/* special-case the unnamed statement */
- pstmt = unnamed_stmt_pstmt;
- if (!pstmt)
+ psrc = unnamed_stmt_psrc;
+ if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
@@ -1349,7 +1338,7 @@ exec_bind_message(StringInfo input_message)
/*
* Report query to various monitoring facilities.
*/
- debug_query_string = pstmt->query_string ? pstmt->query_string : "<BIND>";
+ debug_query_string = psrc->query_string ? psrc->query_string : "<BIND>";
pgstat_report_activity(debug_query_string);
@@ -1388,11 +1377,11 @@ exec_bind_message(StringInfo input_message)
errmsg("bind message has %d parameter formats but %d parameters",
numPFormats, numParams)));
- if (numParams != list_length(pstmt->argtype_list))
+ if (numParams != psrc->num_params)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
- numParams, stmt_name, list_length(pstmt->argtype_list))));
+ numParams, stmt_name, psrc->num_params)));
/*
* If we are in aborted transaction state, the only portals we can
@@ -1403,7 +1392,7 @@ exec_bind_message(StringInfo input_message)
* functions.
*/
if (IsAbortedTransactionBlockState() &&
- (!IsTransactionExitStmtList(pstmt->stmt_list) ||
+ (!IsTransactionExitStmt(psrc->raw_parse_tree) ||
numParams != 0))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
@@ -1424,7 +1413,6 @@ exec_bind_message(StringInfo input_message)
*/
if (numParams > 0)
{
- ListCell *l;
MemoryContext oldContext;
int paramno;
@@ -1435,10 +1423,9 @@ exec_bind_message(StringInfo input_message)
(numParams - 1) *sizeof(ParamExternData));
params->numParams = numParams;
- paramno = 0;
- foreach(l, pstmt->argtype_list)
+ for (paramno = 0; paramno < numParams; paramno++)
{
- Oid ptype = lfirst_oid(l);
+ Oid ptype = psrc->param_types[paramno];
int32 plength;
Datum pval;
bool isNull;
@@ -1554,8 +1541,6 @@ exec_bind_message(StringInfo input_message)
*/
params->params[paramno].pflags = PARAM_FLAG_CONST;
params->params[paramno].ptype = ptype;
-
- paramno++;
}
MemoryContextSwitchTo(oldContext);
@@ -1576,46 +1561,62 @@ exec_bind_message(StringInfo input_message)
pq_getmsgend(input_message);
- /*
- * If we didn't plan the query before, 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, just in case someone binds and
- * executes an unnamed statement multiple times; this also means that the
- * portal's queryContext becomes its own heap context rather than the
- * prepared statement's context. FIXME someday
- */
- if (pstmt->fully_planned)
+ if (psrc->fully_planned)
{
- plan_list = pstmt->stmt_list;
- qContext = pstmt->context;
+ /*
+ * 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
{
MemoryContext oldContext;
+ 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);
- qContext = PortalGetHeapMemory(portal);
- oldContext = MemoryContextSwitchTo(qContext);
- query_list = copyObject(pstmt->stmt_list);
+ /*
+ * 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, params, true);
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;
}
/*
* Define portal and start execution.
*/
PortalDefineQuery(portal,
- *pstmt->stmt_name ? pstmt->stmt_name : NULL,
- pstmt->query_string,
- pstmt->commandTag,
+ stmt_name[0] ? stmt_name : NULL,
+ psrc->query_string,
+ psrc->commandTag,
plan_list,
- qContext);
+ cplan);
PortalStart(portal, params, InvalidSnapshot);
@@ -1647,7 +1648,7 @@ exec_bind_message(StringInfo input_message)
*stmt_name ? stmt_name : "<unnamed>",
*portal_name ? "/" : "",
*portal_name ? portal_name : "",
- pstmt->query_string ? pstmt->query_string : "<source not stored>"),
+ psrc->query_string ? psrc->query_string : "<source not stored>"),
errhidestmt(true),
errdetail_params(params)));
break;
@@ -1809,6 +1810,7 @@ exec_execute_message(const char *portal_name, long max_rows)
completed = PortalRun(portal,
max_rows,
+ true, /* top level */
receiver,
receiver,
completionTag);
@@ -1981,9 +1983,9 @@ errdetail_execute(List *raw_parsetree_list)
PreparedStatement *pstmt;
pstmt = FetchPreparedStatement(stmt->name, false);
- if (pstmt && pstmt->query_string)
+ if (pstmt && pstmt->plansource->query_string)
{
- errdetail("prepare: %s", pstmt->query_string);
+ errdetail("prepare: %s", pstmt->plansource->query_string);
return 0;
}
}
@@ -2064,10 +2066,9 @@ errdetail_params(ParamListInfo params)
static void
exec_describe_statement_message(const char *stmt_name)
{
- PreparedStatement *pstmt;
- TupleDesc tupdesc;
- ListCell *l;
+ CachedPlanSource *psrc;
StringInfoData buf;
+ int i;
/*
* Start up a transaction command. (Note that this will normally change
@@ -2080,28 +2081,37 @@ exec_describe_statement_message(const char *stmt_name)
/* Find prepared statement */
if (stmt_name[0] != '\0')
+ {
+ PreparedStatement *pstmt;
+
pstmt = FetchPreparedStatement(stmt_name, true);
+ psrc = pstmt->plansource;
+ }
else
{
/* special-case the unnamed statement */
- pstmt = unnamed_stmt_pstmt;
- if (!pstmt)
+ psrc = unnamed_stmt_psrc;
+ if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
}
+ /* Prepared statements shouldn't have changeable result descs */
+ Assert(psrc->fixed_result);
+
/*
- * If we are in aborted transaction state, we can't safely create a result
- * tupledesc, 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 blindly Describes
- * whatever it does.) We can Describe parameters without doing anything
- * dangerous, so we don't restrict that.
+ * 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.)
+ * 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
+ * blindly Describes whatever it does.) We can Describe parameters
+ * without doing anything dangerous, so we don't restrict that.
*/
if (IsAbortedTransactionBlockState() &&
- PreparedStatementReturnsTuples(pstmt))
+ psrc->resultDesc)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@@ -2114,11 +2124,11 @@ exec_describe_statement_message(const char *stmt_name)
* First describe the parameters...
*/
pq_beginmessage(&buf, 't'); /* parameter description message type */
- pq_sendint(&buf, list_length(pstmt->argtype_list), 2);
+ pq_sendint(&buf, psrc->num_params, 2);
- foreach(l, pstmt->argtype_list)
+ for (i = 0; i < psrc->num_params; i++)
{
- Oid ptype = lfirst_oid(l);
+ Oid ptype = psrc->param_types[i];
pq_sendint(&buf, (int) ptype, 4);
}
@@ -2127,11 +2137,21 @@ exec_describe_statement_message(const char *stmt_name)
/*
* Next send RowDescription or NoData to describe the result...
*/
- tupdesc = FetchPreparedStatementResultDesc(pstmt);
- if (tupdesc)
- SendRowDescriptionMessage(tupdesc,
- FetchPreparedStatementTargetList(pstmt),
- NULL);
+ 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));
+
+ SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
+
+ ReleaseCachedPlan(cplan, true);
+ }
else
pq_putemptymessage('n'); /* NoData */
@@ -2308,6 +2328,24 @@ IsTransactionStmtList(List *parseTrees)
return false;
}
+/* Release any existing unnamed prepared statement */
+static void
+drop_unnamed_stmt(void)
+{
+ /* Release any completed unnamed statement */
+ if (unnamed_stmt_psrc)
+ DropCachedPlan(unnamed_stmt_psrc);
+ unnamed_stmt_psrc = NULL;
+ /*
+ * 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;
+}
+
/* --------------------------------
* signal handler routines used in PostgresMain()
@@ -3313,7 +3351,6 @@ PostgresMain(int argc, char *argv[], const char *username)
*/
MemoryContextSwitchTo(TopMemoryContext);
FlushErrorState();
- QueryContext = NULL;
/*
* If we were handling an extended-query-protocol message, initiate
@@ -3558,13 +3595,7 @@ PostgresMain(int argc, char *argv[], const char *username)
else
{
/* special-case the unnamed statement */
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
+ drop_unnamed_stmt();
}
break;
case 'P':