Do parse analysis of an EXPLAIN's contained statement during the normal
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 15 Jan 2010 22:36:35 +0000 (22:36 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 15 Jan 2010 22:36:35 +0000 (22:36 +0000)
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.

src/backend/commands/explain.c
src/backend/nodes/params.c
src/backend/optimizer/plan/setrefs.c
src/backend/parser/analyze.c
src/backend/tcop/utility.c
src/backend/utils/cache/plancache.c
src/include/nodes/params.h
src/include/nodes/parsenodes.h
src/include/optimizer/planmain.h

index ba2bd86467f99cb37fc8003c3c1b792cd19026e8..18ddeec76dc4322dfd7b9b163cfea42066ea7a0a 100644 (file)
@@ -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 */
        }
    }
 
index 4800466782a7ff8a9bb130e6aaabfe2d555b807c..ace502faa6c2e24d6549b9b90ac6c0ba50b9cb0d 100644 (file)
@@ -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);
-   }
-}
index 0dac6d424cf8c7ab409789f824fcd2a6c60c9cf5..c75d1d89670a0b1b739f208fe3369a0f1be68f4f 100644 (file)
@@ -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)
        {
index 63c1029fc4c0f5c1bc8785902bea011f0cfc65b7..2a085e73a65f4f094593d9915aef69abd4a747bc 100644 (file)
@@ -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;
index 1f90c09a99c0f9348c1d884ad0cace387027f322..c3b418d562b82442b5cff0e1e4e6cbb89361670b 100644 (file)
@@ -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);
index 8d28c1ee09bfd8381cc06499f1092227b0de792a..31552b0287422f8e078bcae1518584f9c5dc8767 100644 (file)
@@ -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)
        {
-           /* Have to check the per-PlannedStmt inval-item lists */
-           ListCell   *lc2;
+           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 */
            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;
index b00d233e272056636e4c303cb1b312a0765f5f76..50c9c7641ba34b1c7f854084f755f15e4501a7b9 100644 (file)
@@ -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 */
index ba51833c57c594731babcfa2bce4427ba7388066..a32392dfd33c9be4aaffb0ce80e490c1b39779c0 100644 (file)
@@ -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;
 
index 318aa6cbddfde7ff7c63ce5361d9cb70206cc3e2..c10f2d875463836b7ce175bdeff3328f53b73aff 100644 (file)
@@ -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);