Do parse analysis of an EXPLAIN's contained statement during the normal
authorTom Lane
Fri, 15 Jan 2010 22:36:35 +0000 (22:36 +0000)
committerTom Lane
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 1617dedcef8596e1cb82bc8bf6e418fe1dafe0f4..37647e57398ff53c513b61b22ca5c0fab37399bb 100644 (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 */
        }
    }
 
index 136f40ea549a3dc4babdbf0dbc646f906d1cf836..ef17a9bb32184b445c9b36df3cd782099bd0c7b7 100644 (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 = ¶ms->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 2c33e6acb980a8a1bdb63b9c61098160a8fd061d..aa4fd4e1ebe71b0eabf0eb826c50fe81119de8ea 100644 (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)
        {
index a0e565b187cad832f11e80437a5618c4fba919fe..efa4e47b1a46fb7972ac6f482dec9a1419df7df9 100644 (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;
index acacbec094a2f1216453b7ba4377175f6958231e..96b0aa735f40f2e8673195b1728f359704b7b657 100644 (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);
index ffa117b66cd2cf2a9fb787392711ef1b3700e010..114cd9b9756c2e9282d244b89948a395e50b9d0b 100644 (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)
        {
-           /* 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 93b2c03cee9abd4b00eee0af52fe4a9bf79f4a99..12ef269e610608a7fea7b7a49c523423de321e4e 100644 (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 */
index 9a0bb8eec3f8a734f46e7fc1390f0d369e2b47ac..a03597c9f375fa4c093d4e0c188ffabb364ecf19 100644 (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;
 
index 7e59cd4a6bef4eac37998f4625182c42c18e3c2c..024142250ac3e0c1b2cd3515fd83793908e0bc12 100644 (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);