Do parse analysis of an EXPLAIN's contained statement during the normal

parse analysis phase, rather than at execution time.  This makes parameter
handling work the same as it does in ordinary plannable queries, and in
particular fixes the incompatibility that Pavel pointed out with plpgsql's
new handling of variable references.  plancache.c gets a little bit
grottier, but the alternatives seem worse.
This commit is contained in:
Tom Lane 2010-01-15 22:36:35 +00:00
parent 00b5ccebdd
commit 08f8d478eb
9 changed files with 141 additions and 128 deletions

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.198 2010/01/02 16:57:37 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.199 2010/01/15 22:36:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -158,19 +158,19 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
* Parse analysis was done already, but we still have to run the rule
* rewriter. We do not do AcquireRewriteLocks: we assume the query
* either came straight from the parser, or suitable locks were
* acquired by plancache.c.
*
* Because the parser and planner tend to scribble on their input, we make
* Because the rewriter and planner tend to scribble on the input, we make
* a preliminary copy of the source querytree. This prevents problems in
* the case that the EXPLAIN is in a portal or plpgsql function and is
* executed repeatedly. (See also the same hack in DECLARE CURSOR and
* PREPARE.) XXX FIXME someday.
*/
rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query),
queryString,
(ParserSetupHook) setupParserWithParamList,
params);
Assert(IsA(stmt->query, Query));
rewritten = QueryRewrite((Query *) copyObject(stmt->query));
/* emit opening boilerplate */
ExplainBeginOutput(&es);
@ -248,6 +248,7 @@ ExplainResultDesc(ExplainStmt *stmt)
char *p = defGetString(opt);
xml = (strcmp(p, "xml") == 0);
/* don't "break", as ExplainQuery will use the last value */
}
}

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.13 2010/01/02 16:57:46 momjian Exp $
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.14 2010/01/15 22:36:31 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -75,47 +75,3 @@ copyParamList(ParamListInfo from)
return retval;
}
/*
* Set up the parser to treat the given list of run-time parameters
* as available external parameters during parsing of a new query.
*
* Note that the parser doesn't actually care about the *values* of the given
* parameters, only about their *types*. Also, the code that originally
* provided the ParamListInfo may have provided a setupHook, which should
* override applying parse_fixed_parameters().
*/
void
setupParserWithParamList(struct ParseState *pstate,
ParamListInfo params)
{
if (params == NULL) /* no params, nothing to do */
return;
/* If there is a parserSetup hook, it gets to do this */
if (params->parserSetup != NULL)
{
(*params->parserSetup) (pstate, params->parserSetupArg);
return;
}
/* Else, treat any available parameters as being of fixed type */
if (params->numParams > 0)
{
Oid *ptypes;
int i;
ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
for (i = 0; i < params->numParams; i++)
{
ParamExternData *prm = &params->params[i];
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
(*params->paramFetch) (params, i+1);
ptypes[i] = prm->ptype;
}
parse_fixed_parameters(pstate, ptypes, params->numParams);
}
}

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.156 2010/01/02 16:57:47 momjian Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.157 2010/01/15 22:36:32 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1905,14 +1905,15 @@ record_plan_function_dependency(PlannerGlobal *glob, Oid funcid)
/*
* extract_query_dependencies
* Given a list of not-yet-planned queries (i.e. Query nodes),
* extract their dependencies just as set_plan_references would do.
* Given a not-yet-planned query or queries (i.e. a Query node or list
* of Query nodes), extract dependencies just as set_plan_references
* would do.
*
* This is needed by plancache.c to handle invalidation of cached unplanned
* queries.
*/
void
extract_query_dependencies(List *queries,
extract_query_dependencies(Node *query,
List **relationOids,
List **invalItems)
{
@ -1924,7 +1925,7 @@ extract_query_dependencies(List *queries,
glob.relationOids = NIL;
glob.invalItems = NIL;
(void) extract_query_dependencies_walker((Node *) queries, &glob);
(void) extract_query_dependencies_walker(query, &glob);
*relationOids = glob.relationOids;
*invalItems = glob.invalItems;
@ -1943,6 +1944,19 @@ extract_query_dependencies_walker(Node *node, PlannerGlobal *context)
Query *query = (Query *) node;
ListCell *lc;
if (query->commandType == CMD_UTILITY)
{
/* Ignore utility statements, except EXPLAIN */
if (IsA(query->utilityStmt, ExplainStmt))
{
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
Assert(IsA(query, Query));
Assert(query->commandType != CMD_UTILITY);
}
else
return false;
}
/* Collect relation OIDs in this Query's rtable */
foreach(lc, query->rtable)
{

View File

@ -17,7 +17,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.399 2010/01/02 16:57:48 momjian Exp $
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.400 2010/01/15 22:36:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -257,16 +257,12 @@ analyze_requires_snapshot(Node *parseTree)
break;
case T_ExplainStmt:
/*
* We only need a snapshot in varparams case, but it doesn't seem
* worth complicating this function's API to distinguish that.
*/
/* yes, because we must analyze the contained statement */
result = true;
break;
default:
/* utility statements don't have any active parse analysis */
/* other utility statements don't have any real parse analysis */
result = false;
break;
}
@ -1993,29 +1989,21 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
* transformExplainStmt -
* transform an EXPLAIN Statement
*
* EXPLAIN is just like other utility statements in that we emit it as a
* CMD_UTILITY Query node with no transformation of the raw parse tree.
* However, if p_coerce_param_hook is set, it could be that the client is
* expecting us to resolve parameter types in something like
* EXPLAIN SELECT * FROM tab WHERE col = $1
* To deal with such cases, we run parse analysis and throw away the result;
* this is a bit grotty but not worth contorting the rest of the system for.
* (The approach we use for DECLARE CURSOR won't work because the statement
* being explained isn't necessarily a SELECT, and in particular might rewrite
* to multiple parsetrees.)
* EXPLAIN is like other utility statements in that we emit it as a
* CMD_UTILITY Query node; however, we must first transform the contained
* query. We used to postpone that until execution, but it's really necessary
* to do it during the normal parse analysis phase to ensure that side effects
* of parser hooks happen at the expected time.
*/
static Query *
transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
{
Query *result;
if (pstate->p_coerce_param_hook != NULL)
{
/* Since parse analysis scribbles on its input, copy the tree first! */
(void) transformStmt(pstate, copyObject(stmt->query));
}
/* transform contained query */
stmt->query = (Node *) transformStmt(pstate, stmt->query);
/* Now return the untransformed command as a utility Query */
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.328 2010/01/06 03:04:01 momjian Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.329 2010/01/15 22:36:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2438,6 +2438,7 @@ GetCommandLogLevel(Node *parsetree)
if (strcmp(opt->defname, "analyze") == 0)
analyze = defGetBoolean(opt);
/* don't "break", as explain.c will use the last value */
}
if (analyze)
return GetCommandLogLevel(stmt->query);

View File

@ -35,7 +35,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.33 2010/01/13 16:56:56 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.34 2010/01/15 22:36:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -359,13 +359,27 @@ StoreCachedPlan(CachedPlanSource *plansource,
plan->context = plan_context;
if (plansource->fully_planned)
{
/* Planner already extracted dependencies, we don't have to */
/*
* 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;
if (list_length(stmt_list) == 1 &&
IsA(linitial(stmt_list), ExplainStmt))
{
ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
extract_query_dependencies(estmt->query,
&plan->relationOids,
&plan->invalItems);
}
}
else
{
/* Use the planner machinery to extract dependencies */
extract_query_dependencies(stmt_list,
extract_query_dependencies((Node *) stmt_list,
&plan->relationOids,
&plan->invalItems);
}
@ -685,7 +699,24 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
Assert(!IsA(plannedstmt, Query));
if (!IsA(plannedstmt, PlannedStmt))
continue; /* Ignore utility statements */
{
/*
* Ignore utility statements, except EXPLAIN which contains a
* parsed-but-not-planned query. Note: it's okay to use
* ScanQueryForLocks, even though the query hasn't been through
* rule rewriting, because rewriting doesn't change the query
* representation.
*/
if (IsA(plannedstmt, ExplainStmt))
{
Query *query;
query = (Query *) ((ExplainStmt *) plannedstmt)->query;
Assert(IsA(query, Query));
ScanQueryForLocks(query, acquire);
}
continue;
}
rt_index = 0;
foreach(lc2, plannedstmt->rtable)
@ -739,6 +770,19 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
Query *query = (Query *) lfirst(lc);
Assert(IsA(query, Query));
if (query->commandType == CMD_UTILITY)
{
/* Ignore utility statements, except EXPLAIN */
if (IsA(query->utilityStmt, ExplainStmt))
{
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
Assert(IsA(query, Query));
ScanQueryForLocks(query, acquire);
}
continue;
}
ScanQueryForLocks(query, acquire);
}
}
@ -752,6 +796,9 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
ListCell *lc;
int rt_index;
/* Shouldn't get called on utility commands */
Assert(parsetree->commandType != CMD_UTILITY);
/*
* First, process RTEs of the current query level.
*/
@ -942,7 +989,16 @@ PlanCacheRelCallback(Datum arg, Oid relid)
/* No work if it's already invalidated */
if (!plan || plan->dead)
continue;
if (plan->fully_planned)
/*
* Check the list we built ourselves; this covers unplanned cases
* including EXPLAIN.
*/
if ((relid == InvalidOid) ? plan->relationOids != NIL :
list_member_oid(plan->relationOids, relid))
plan->dead = true;
if (plan->fully_planned && !plan->dead)
{
/* Have to check the per-PlannedStmt relid lists */
ListCell *lc2;
@ -963,13 +1019,6 @@ PlanCacheRelCallback(Datum arg, Oid relid)
}
}
}
else
{
/* Otherwise check the single list we built ourselves */
if ((relid == InvalidOid) ? plan->relationOids != NIL :
list_member_oid(plan->relationOids, relid))
plan->dead = true;
}
}
}
@ -992,15 +1041,34 @@ PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
CachedPlan *plan = plansource->plan;
ListCell *lc2;
/* No work if it's already invalidated */
if (!plan || plan->dead)
continue;
if (plan->fully_planned)
/*
* Check the list we built ourselves; this covers unplanned cases
* including EXPLAIN.
*/
foreach(lc2, plan->invalItems)
{
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
if (item->cacheId != cacheid)
continue;
if (tuplePtr == NULL ||
ItemPointerEquals(tuplePtr, &item->tupleId))
{
/* Invalidate the plan! */
plan->dead = true;
break;
}
}
if (plan->fully_planned && !plan->dead)
{
/* Have to check the per-PlannedStmt inval-item lists */
ListCell *lc2;
foreach(lc2, plan->stmt_list)
{
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
@ -1027,26 +1095,6 @@ PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
break; /* out of stmt_list scan */
}
}
else
{
/* Otherwise check the single list we built ourselves */
ListCell *lc2;
foreach(lc2, plan->invalItems)
{
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
if (item->cacheId != cacheid)
continue;
if (tuplePtr == NULL ||
ItemPointerEquals(tuplePtr, &item->tupleId))
{
/* Invalidate the plan! */
plan->dead = true;
break;
}
}
}
}
}
@ -1086,7 +1134,9 @@ ResetPlanCache(void)
* 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
* contains at least one non-utility statement.
* 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)
{
@ -1096,7 +1146,8 @@ ResetPlanCache(void)
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
Assert(!IsA(plannedstmt, Query));
if (IsA(plannedstmt, PlannedStmt))
if (IsA(plannedstmt, PlannedStmt) ||
IsA(plannedstmt, ExplainStmt))
{
/* non-utility statement, so invalidate */
plan->dead = true;
@ -1112,7 +1163,8 @@ ResetPlanCache(void)
Query *query = (Query *) lfirst(lc2);
Assert(IsA(query, Query));
if (query->commandType != CMD_UTILITY)
if (query->commandType != CMD_UTILITY ||
IsA(query->utilityStmt, ExplainStmt))
{
/* non-utility statement, so invalidate */
plan->dead = true;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.40 2010/01/02 16:58:04 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.41 2010/01/15 22:36:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -103,7 +103,4 @@ typedef struct ParamExecData
/* Functions found in src/backend/nodes/params.c */
extern ParamListInfo copyParamList(ParamListInfo from);
extern void setupParserWithParamList(struct ParseState *pstate,
ParamListInfo params);
#endif /* PARAMS_H */

View File

@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.423 2010/01/06 05:31:14 itagaki Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.424 2010/01/15 22:36:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2260,12 +2260,16 @@ typedef struct VacuumStmt
/* ----------------------
* Explain Statement
*
* The "query" field is either a raw parse tree (SelectStmt, InsertStmt, etc)
* or a Query node if parse analysis has been done. Note that rewriting and
* planning of the query are always postponed until execution of EXPLAIN.
* ----------------------
*/
typedef struct ExplainStmt
{
NodeTag type;
Node *query; /* the query (as a raw parse tree) */
Node *query; /* the query (see comments above) */
List *options; /* list of DefElem nodes */
} ExplainStmt;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.123 2010/01/02 16:58:07 momjian Exp $
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.124 2010/01/15 22:36:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -122,7 +122,7 @@ extern void fix_opfuncids(Node *node);
extern void set_opfuncid(OpExpr *opexpr);
extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
extern void record_plan_function_dependency(PlannerGlobal *glob, Oid funcid);
extern void extract_query_dependencies(List *queries,
extern void extract_query_dependencies(Node *query,
List **relationOids,
List **invalItems);