Change representation of statement lists, and add statement location info.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 14 Jan 2017 21:02:35 +0000 (16:02 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 14 Jan 2017 21:02:35 +0000 (16:02 -0500)
This patch makes several changes that improve the consistency of
representation of lists of statements.  It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list.  This patch brings
similar consistency to the outputs of raw parsing and planning steps:

* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.

* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements.  In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node.  This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.

Now, every list of statements has a consistent head-node type depending
on how far along it is in processing.  This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.

Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc.  That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way.  It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)

Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list.  While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.

The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement.  This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)

There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.

Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes.  This allows
more intelligent handling of cases where a source query string contains
multiple statements.  This patch doesn't actually do anything with the
information, but a follow-on patch will.  (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)

catversion bump because addition of location fields to struct Query
affects stored rules.

This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.

Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre

53 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
contrib/sepgsql/hooks.c
src/backend/catalog/pg_proc.c
src/backend/commands/copy.c
src/backend/commands/createas.c
src/backend/commands/explain.c
src/backend/commands/extension.c
src/backend/commands/foreigncmds.c
src/backend/commands/portalcmds.c
src/backend/commands/prepare.c
src/backend/commands/schemacmds.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/view.c
src/backend/executor/execParallel.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_type.c
src/backend/parser/parser.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/postgres.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/backend/utils/cache/plancache.c
src/backend/utils/mmgr/portalmem.c
src/include/catalog/catversion.h
src/include/commands/copy.h
src/include/commands/explain.h
src/include/commands/portalcmds.h
src/include/commands/prepare.h
src/include/commands/schemacmds.h
src/include/commands/view.h
src/include/executor/execdesc.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/parser/analyze.h
src/include/tcop/tcopprot.h
src/include/tcop/utility.h
src/include/utils/plancache.h
src/include/utils/portal.h
src/pl/plpgsql/src/pl_exec.c

index 3d1b99f502d2da9bfd58cb90de7f2b30659a6ec4..7eec86fb289b4a3fbccb6b3bfbcedcb52d13d6d2 100644 (file)
@@ -292,7 +292,7 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
                 uint64 count);
 static void pgss_ExecutorFinish(QueryDesc *queryDesc);
 static void pgss_ExecutorEnd(QueryDesc *queryDesc);
-static void pgss_ProcessUtility(Node *parsetree, const char *queryString,
+static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                    ProcessUtilityContext context, ParamListInfo params,
                    DestReceiver *dest, char *completionTag);
 static uint32 pgss_hash_fn(const void *key, Size keysize);
@@ -942,10 +942,12 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
  * ProcessUtility hook
  */
 static void
-pgss_ProcessUtility(Node *parsetree, const char *queryString,
+pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                    ProcessUtilityContext context, ParamListInfo params,
                    DestReceiver *dest, char *completionTag)
 {
+   Node       *parsetree = pstmt->utilityStmt;
+
    /*
     * If it's an EXECUTE statement, we don't track it and don't increment the
     * nesting level.  This allows the cycles to be charged to the underlying
@@ -979,11 +981,11 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
        PG_TRY();
        {
            if (prev_ProcessUtility)
-               prev_ProcessUtility(parsetree, queryString,
+               prev_ProcessUtility(pstmt, queryString,
                                    context, params,
                                    dest, completionTag);
            else
-               standard_ProcessUtility(parsetree, queryString,
+               standard_ProcessUtility(pstmt, queryString,
                                        context, params,
                                        dest, completionTag);
            nested_level--;
@@ -1044,11 +1046,11 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
    else
    {
        if (prev_ProcessUtility)
-           prev_ProcessUtility(parsetree, queryString,
+           prev_ProcessUtility(pstmt, queryString,
                                context, params,
                                dest, completionTag);
        else
-           standard_ProcessUtility(parsetree, queryString,
+           standard_ProcessUtility(pstmt, queryString,
                                    context, params,
                                    dest, completionTag);
    }
index 15f40d83f11521001e2b9fcd960f09bf1efc32e3..93cc8debaa7e6ce6a3f9f354749e668819d2e67e 100644 (file)
@@ -297,13 +297,14 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
  * break whole of the things if nefarious user would use.
  */
 static void
-sepgsql_utility_command(Node *parsetree,
+sepgsql_utility_command(PlannedStmt *pstmt,
                        const char *queryString,
                        ProcessUtilityContext context,
                        ParamListInfo params,
                        DestReceiver *dest,
                        char *completionTag)
 {
+   Node       *parsetree = pstmt->utilityStmt;
    sepgsql_context_info_t saved_context_info = sepgsql_context_info;
    ListCell   *cell;
 
@@ -362,11 +363,11 @@ sepgsql_utility_command(Node *parsetree,
        }
 
        if (next_ProcessUtility_hook)
-           (*next_ProcessUtility_hook) (parsetree, queryString,
+           (*next_ProcessUtility_hook) (pstmt, queryString,
                                         context, params,
                                         dest, completionTag);
        else
-           standard_ProcessUtility(parsetree, queryString,
+           standard_ProcessUtility(pstmt, queryString,
                                    context, params,
                                    dest, completionTag);
    }
index 6d8f17db2d14641618ac1c52e6374cf45c37d4b0..85cec0cb1cbeb1770cb4f120a6bc6bb188421aa6 100644 (file)
@@ -934,7 +934,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
            querytree_list = NIL;
            foreach(lc, raw_parsetree_list)
            {
-               Node       *parsetree = (Node *) lfirst(lc);
+               RawStmt    *parsetree = (RawStmt *) lfirst(lc);
                List       *querytree_sublist;
 
                querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
index f56b2ac49b0c5c3fd4b7dccb7a0c4da33c2ec58c..1fd2162794832c76d8c2f0e275724d0cd7cf4b07 100644 (file)
@@ -287,13 +287,13 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
 
 
 /* non-export function prototypes */
-static CopyState BeginCopy(ParseState *pstate, bool is_from, Relation rel, Node *raw_query,
-         const Oid queryRelId, List *attnamelist,
+static CopyState BeginCopy(ParseState *pstate, bool is_from, Relation rel,
+         RawStmt *raw_query, Oid queryRelId, List *attnamelist,
          List *options);
 static void EndCopy(CopyState cstate);
 static void ClosePipeToProgram(CopyState cstate);
-static CopyState BeginCopyTo(ParseState *pstate, Relation rel, Node *query,
-           const Oid queryRelId, const char *filename, bool is_program,
+static CopyState BeginCopyTo(ParseState *pstate, Relation rel, RawStmt *query,
+           Oid queryRelId, const char *filename, bool is_program,
            List *attnamelist, List *options);
 static void EndCopyTo(CopyState cstate);
 static uint64 DoCopyTo(CopyState cstate);
@@ -770,15 +770,17 @@ CopyLoadRawBuf(CopyState cstate)
  * Do not allow the copy if user doesn't have proper permission to access
  * the table or the specifically requested columns.
  */
-Oid
-DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
+void
+DoCopy(ParseState *pstate, const CopyStmt *stmt,
+      int stmt_location, int stmt_len,
+      uint64 *processed)
 {
    CopyState   cstate;
    bool        is_from = stmt->is_from;
    bool        pipe = (stmt->filename == NULL);
    Relation    rel;
    Oid         relid;
-   Node       *query = NULL;
+   RawStmt    *query = NULL;
    List       *range_table = NIL;
 
    /* Disallow COPY to/from file or program except to superusers. */
@@ -929,7 +931,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
            select->targetList = targetList;
            select->fromClause = list_make1(from);
 
-           query = (Node *) select;
+           query = makeNode(RawStmt);
+           query->stmt = (Node *) select;
+           query->stmt_location = stmt_location;
+           query->stmt_len = stmt_len;
 
            /*
             * Close the relation for now, but keep the lock on it to prevent
@@ -945,7 +950,11 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
    {
        Assert(stmt->query);
 
-       query = stmt->query;
+       query = makeNode(RawStmt);
+       query->stmt = stmt->query;
+       query->stmt_location = stmt_location;
+       query->stmt_len = stmt_len;
+
        relid = InvalidOid;
        rel = NULL;
    }
@@ -981,8 +990,6 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
     */
    if (rel != NULL)
        heap_close(rel, (is_from ? NoLock : AccessShareLock));
-
-   return relid;
 }
 
 /*
@@ -1364,8 +1371,8 @@ static CopyState
 BeginCopy(ParseState *pstate,
          bool is_from,
          Relation rel,
-         Node *raw_query,
-         const Oid queryRelId,
+         RawStmt *raw_query,
+         Oid queryRelId,
          List *attnamelist,
          List *options)
 {
@@ -1456,7 +1463,7 @@ BeginCopy(ParseState *pstate,
         * function and is executed repeatedly.  (See also the same hack in
         * DECLARE CURSOR and PREPARE.)  XXX FIXME someday.
         */
-       rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
+       rewritten = pg_analyze_and_rewrite((RawStmt *) copyObject(raw_query),
                                           pstate->p_sourcetext, NULL, 0);
 
        /* check that we got back something we can work with */
@@ -1747,8 +1754,8 @@ EndCopy(CopyState cstate)
 static CopyState
 BeginCopyTo(ParseState *pstate,
            Relation rel,
-           Node *query,
-           const Oid queryRelId,
+           RawStmt *query,
+           Oid queryRelId,
            const char *filename,
            bool is_program,
            List *attnamelist,
index 57ef9817653a1e9bed718c2b3053a5ee4115201c..cee3b4d50b5d5a906c24c000eec005bcd7ed202c 100644 (file)
@@ -326,7 +326,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
        query = (Query *) linitial(rewritten);
        Assert(query->commandType == CMD_SELECT);
 
-       /* plan the query */
+       /* plan the query --- note we disallow parallelism */
        plan = pg_plan_query(query, 0, params);
 
        /*
index c762fb07d4db0ee3c5f1984e20915e827813910e..ee7046c47b922e059b7d191dbaf6872ed82bea9a 100644 (file)
@@ -53,7 +53,8 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
 #define X_CLOSE_IMMEDIATE 2
 #define X_NOWHITESPACE 4
 
-static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
+static void ExplainOneQuery(Query *query, int cursorOptions,
+               IntoClause *into, ExplainState *es,
                const char *queryString, ParamListInfo params);
 static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
                ExplainState *es);
@@ -245,7 +246,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
        /* Explain every plan */
        foreach(l, rewritten)
        {
-           ExplainOneQuery((Query *) lfirst(l), NULL, es,
+           ExplainOneQuery((Query *) lfirst(l),
+                           CURSOR_OPT_PARALLEL_OK, NULL, es,
                            queryString, params);
 
            /* Separate plans with an appropriate separator */
@@ -329,7 +331,8 @@ ExplainResultDesc(ExplainStmt *stmt)
  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
  */
 static void
-ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
+ExplainOneQuery(Query *query, int cursorOptions,
+               IntoClause *into, ExplainState *es,
                const char *queryString, ParamListInfo params)
 {
    /* planner will not cope with utility statements */
@@ -341,7 +344,8 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
 
    /* if an advisor plugin is present, let it manage things */
    if (ExplainOneQuery_hook)
-       (*ExplainOneQuery_hook) (query, into, es, queryString, params);
+       (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
+                                queryString, params);
    else
    {
        PlannedStmt *plan;
@@ -351,7 +355,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
        INSTR_TIME_SET_CURRENT(planstart);
 
        /* plan the query */
-       plan = pg_plan_query(query, into ? 0 : CURSOR_OPT_PARALLEL_OK, params);
+       plan = pg_plan_query(query, cursorOptions, params);
 
        INSTR_TIME_SET_CURRENT(planduration);
        INSTR_TIME_SUBTRACT(planduration, planstart);
@@ -385,6 +389,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
         * We have to rewrite the contained SELECT and then pass it back to
         * ExplainOneQuery.  It's probably not really necessary to copy the
         * contained parsetree another time, but let's be safe.
+        *
+        * Like ExecCreateTableAs, disallow parallelism in the plan.
         */
        CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
        List       *rewritten;
@@ -392,7 +398,28 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
        Assert(IsA(ctas->query, Query));
        rewritten = QueryRewrite((Query *) copyObject(ctas->query));
        Assert(list_length(rewritten) == 1);
-       ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
+       ExplainOneQuery((Query *) linitial(rewritten),
+                       0, ctas->into, es,
+                       queryString, params);
+   }
+   else if (IsA(utilityStmt, DeclareCursorStmt))
+   {
+       /*
+        * Likewise for DECLARE CURSOR.
+        *
+        * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
+        * actually run the query.  This is different from pre-8.3 behavior
+        * but seems more useful than not running the query.  No cursor will
+        * be created, however.
+        */
+       DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
+       List       *rewritten;
+
+       Assert(IsA(dcs->query, Query));
+       rewritten = QueryRewrite((Query *) copyObject(dcs->query));
+       Assert(list_length(rewritten) == 1);
+       ExplainOneQuery((Query *) linitial(rewritten),
+                       dcs->options, NULL, es,
                        queryString, params);
    }
    else if (IsA(utilityStmt, ExecuteStmt))
@@ -423,11 +450,6 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
  * in which case executing the query should result in creating that table.
  *
- * Since we ignore any DeclareCursorStmt that might be attached to the query,
- * if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
- * query.  This is different from pre-8.3 behavior but seems more useful than
- * not running the query.  No cursor will be created, however.
- *
  * This is exported because it's called back from prepare.c in the
  * EXPLAIN EXECUTE case, and because an index advisor plugin would need
  * to call it.
@@ -444,6 +466,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
    int         eflags;
    int         instrument_option = 0;
 
+   Assert(plannedstmt->commandType != CMD_UTILITY);
+
    if (es->analyze && es->timing)
        instrument_option |= INSTRUMENT_TIMER;
    else if (es->analyze)
index be521484d0895ee56ab610636cd0fe34da9b77a8..967b52a133fe3dfd4d91672ea043a35f8a0215d1 100644 (file)
@@ -712,7 +712,7 @@ execute_sql_string(const char *sql, const char *filename)
     */
    foreach(lc1, raw_parsetree_list)
    {
-       Node       *parsetree = (Node *) lfirst(lc1);
+       RawStmt    *parsetree = (RawStmt *) lfirst(lc1);
        List       *stmt_list;
        ListCell   *lc2;
 
@@ -724,23 +724,17 @@ execute_sql_string(const char *sql, const char *filename)
 
        foreach(lc2, stmt_list)
        {
-           Node       *stmt = (Node *) lfirst(lc2);
-
-           if (IsA(stmt, TransactionStmt))
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("transaction control statements are not allowed within an extension script")));
+           PlannedStmt *stmt = (PlannedStmt *) lfirst(lc2);
 
            CommandCounterIncrement();
 
            PushActiveSnapshot(GetTransactionSnapshot());
 
-           if (IsA(stmt, PlannedStmt) &&
-               ((PlannedStmt *) stmt)->utilityStmt == NULL)
+           if (stmt->utilityStmt == NULL)
            {
                QueryDesc  *qdesc;
 
-               qdesc = CreateQueryDesc((PlannedStmt *) stmt,
+               qdesc = CreateQueryDesc(stmt,
                                        sql,
                                        GetActiveSnapshot(), NULL,
                                        dest, NULL, 0);
@@ -754,6 +748,11 @@ execute_sql_string(const char *sql, const char *filename)
            }
            else
            {
+               if (IsA(stmt->utilityStmt, TransactionStmt))
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("transaction control statements are not allowed within an extension script")));
+
                ProcessUtility(stmt,
                               sql,
                               PROCESS_UTILITY_QUERY,
@@ -1434,7 +1433,8 @@ CreateExtensionInternal(char *extensionName,
            csstmt->authrole = NULL;    /* will be created by current user */
            csstmt->schemaElts = NIL;
            csstmt->if_not_exists = false;
-           CreateSchemaCommand(csstmt, NULL);
+           CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
+                               -1, -1);
 
            /*
             * CreateSchemaCommand includes CommandCounterIncrement, so new
index 06b4bc3ba9a05d0f76fa8ed4ab9c659d906c5878..476a023ec54c91f4eec31a8b7c54692d4ff97f26 100644 (file)
@@ -1572,7 +1572,9 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
         */
        foreach(lc2, raw_parsetree_list)
        {
-           CreateForeignTableStmt *cstmt = lfirst(lc2);
+           RawStmt    *rs = (RawStmt *) lfirst(lc2);
+           CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) rs->stmt;
+           PlannedStmt *pstmt;
 
            /*
             * Because we only allow CreateForeignTableStmt, we can skip parse
@@ -1593,8 +1595,16 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
            /* Ensure creation schema is the one given in IMPORT statement */
            cstmt->base.relation->schemaname = pstrdup(stmt->local_schema);
 
+           /* No planning needed, just make a wrapper PlannedStmt */
+           pstmt = makeNode(PlannedStmt);
+           pstmt->commandType = CMD_UTILITY;
+           pstmt->canSetTag = false;
+           pstmt->utilityStmt = (Node *) cstmt;
+           pstmt->stmt_location = rs->stmt_location;
+           pstmt->stmt_len = rs->stmt_len;
+
            /* Execute statement */
-           ProcessUtility((Node *) cstmt,
+           ProcessUtility(pstmt,
                           cmd,
                           PROCESS_UTILITY_SUBCOMMAND, NULL,
                           None_Receiver, NULL);
index 71640b7748f31908b4baf0f1992a8504ffa44f29..1d3e39299b9d405751a96286b6c22201e48a129d 100644 (file)
@@ -27,7 +27,9 @@
 #include "commands/portalcmds.h"
 #include "executor/executor.h"
 #include "executor/tstoreReceiver.h"
+#include "rewrite/rewriteHandler.h"
 #include "tcop/pquery.h"
+#include "tcop/tcopprot.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 
 /*
  * PerformCursorOpen
  *     Execute SQL DECLARE CURSOR command.
- *
- * The query has already been through parse analysis, rewriting, and planning.
- * When it gets here, it looks like a SELECT PlannedStmt, except that the
- * utilityStmt field is set.
  */
 void
-PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
+PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params,
                  const char *queryString, bool isTopLevel)
 {
-   DeclareCursorStmt *cstmt = (DeclareCursorStmt *) stmt->utilityStmt;
+   Query      *query = (Query *) cstmt->query;
+   List       *rewritten;
+   PlannedStmt *plan;
    Portal      portal;
    MemoryContext oldContext;
 
-   if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
-       elog(ERROR, "PerformCursorOpen called for non-cursor query");
+   Assert(IsA(query, Query));  /* else parse analysis wasn't done */
 
    /*
     * Disallow empty-string cursor name (conflicts with protocol-level
@@ -68,6 +67,32 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
    if (!(cstmt->options & CURSOR_OPT_HOLD))
        RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
 
+   /*
+    * 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 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 DECLARE CURSOR is in a portal or plpgsql function and
+    * is executed repeatedly.  (See also the same hack in EXPLAIN and
+    * PREPARE.)  XXX FIXME someday.
+    */
+   rewritten = QueryRewrite((Query *) copyObject(query));
+
+   /* SELECT should never rewrite to more or less than one query */
+   if (list_length(rewritten) != 1)
+       elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
+
+   query = (Query *) linitial(rewritten);
+
+   if (query->commandType != CMD_SELECT)
+       elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
+
+   /* Plan the query, applying the specified options */
+   plan = pg_plan_query(query, cstmt->options, params);
+
    /*
     * Create a portal and copy the plan and queryString into its memory.
     */
@@ -75,8 +100,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
 
    oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
-   stmt = copyObject(stmt);
-   stmt->utilityStmt = NULL;   /* make it look like plain SELECT */
+   plan = copyObject(plan);
 
    queryString = pstrdup(queryString);
 
@@ -84,7 +108,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
                      NULL,
                      queryString,
                      "SELECT", /* cursor's query is always a SELECT */
-                     list_make1(stmt),
+                     list_make1(plan),
                      NULL);
 
    /*----------
@@ -111,8 +135,8 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
    portal->cursorOptions = cstmt->options;
    if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
    {
-       if (stmt->rowMarks == NIL &&
-           ExecSupportsBackwardScan(stmt->planTree))
+       if (plan->rowMarks == NIL &&
+           ExecSupportsBackwardScan(plan->planTree))
            portal->cursorOptions |= CURSOR_OPT_SCROLL;
        else
            portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
index d768cf8dda39a3418bba29094551eb15af449cba..1ff41661a551c586b4198e3dc14bf3deb317f384 100644 (file)
@@ -52,8 +52,10 @@ static Datum build_regtype_array(Oid *param_types, int num_params);
  * Implements the 'PREPARE' utility statement.
  */
 void
-PrepareQuery(PrepareStmt *stmt, const char *queryString)
+PrepareQuery(PrepareStmt *stmt, const char *queryString,
+            int stmt_location, int stmt_len)
 {
+   RawStmt    *rawstmt;
    CachedPlanSource *plansource;
    Oid        *argtypes = NULL;
    int         nargs;
@@ -70,11 +72,23 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
                (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
                 errmsg("invalid statement name: must not be empty")));
 
+   /*
+    * Need to wrap the contained statement in a RawStmt node to pass it to
+    * parse analysis.
+    *
+    * Because parse analysis scribbles on the raw querytree, we must make a
+    * copy to ensure we don't modify the passed-in tree.  FIXME someday.
+    */
+   rawstmt = makeNode(RawStmt);
+   rawstmt->stmt = (Node *) copyObject(stmt->query);
+   rawstmt->stmt_location = stmt_location;
+   rawstmt->stmt_len = stmt_len;
+
    /*
     * Create the CachedPlanSource before we do parse analysis, since it needs
     * to see the unmodified raw parse tree.
     */
-   plansource = CreateCachedPlan(stmt->query, queryString,
+   plansource = CreateCachedPlan(rawstmt, queryString,
                                  CreateCommandTag(stmt->query));
 
    /* Transform list of TypeNames to array of type OIDs */
@@ -108,12 +122,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
     * Analyze the statement using these parameter types (any parameters
     * passed in from above us will not be visible to it), allowing
     * information about unknown parameters to be deduced from context.
-    *
-    * Because parse analysis scribbles on the raw querytree, we must make a
-    * copy to ensure we don't modify the passed-in tree.  FIXME someday.
     */
-   query = parse_analyze_varparams((Node *) copyObject(stmt->query),
-                                   queryString,
+   query = parse_analyze_varparams(rawstmt, queryString,
                                    &argtypes, &nargs);
 
    /*
@@ -256,9 +266,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("prepared statement is not a SELECT")));
        pstmt = (PlannedStmt *) linitial(plan_list);
-       if (!IsA(pstmt, PlannedStmt) ||
-           pstmt->commandType != CMD_SELECT ||
-           pstmt->utilityStmt != NULL)
+       Assert(IsA(pstmt, PlannedStmt));
+       if (pstmt->commandType != CMD_SELECT)
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("prepared statement is not a SELECT")));
@@ -664,10 +673,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
    {
        PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
 
-       if (IsA(pstmt, PlannedStmt))
+       Assert(IsA(pstmt, PlannedStmt));
+       if (pstmt->commandType != CMD_UTILITY)
            ExplainOnePlan(pstmt, into, es, query_string, paramLI, NULL);
        else
-           ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
+           ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI);
 
        /* No need for CommandCounterIncrement, as ExplainOnePlan did it */
 
index c475613b1c5cd1cfc6b311991798589e03a3ad06..c3b37b2625901f1a7756b4332c37d856d830b6fc 100644 (file)
@@ -40,9 +40,16 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
 
 /*
  * CREATE SCHEMA
+ *
+ * Note: caller should pass in location information for the whole
+ * CREATE SCHEMA statement, which in turn we pass down as the location
+ * of the component commands.  This comports with our general plan of
+ * reporting location/len for the whole command even when executing
+ * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
+CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+                   int stmt_location, int stmt_len)
 {
    const char *schemaName = stmt->schemaname;
    Oid         namespaceId;
@@ -172,14 +179,24 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
    foreach(parsetree_item, parsetree_list)
    {
        Node       *stmt = (Node *) lfirst(parsetree_item);
+       PlannedStmt *wrapper;
+
+       /* need to make a wrapper PlannedStmt */
+       wrapper = makeNode(PlannedStmt);
+       wrapper->commandType = CMD_UTILITY;
+       wrapper->canSetTag = false;
+       wrapper->utilityStmt = stmt;
+       wrapper->stmt_location = stmt_location;
+       wrapper->stmt_len = stmt_len;
 
        /* do this step */
-       ProcessUtility(stmt,
+       ProcessUtility(wrapper,
                       queryString,
                       PROCESS_UTILITY_SUBCOMMAND,
                       NULL,
                       None_Receiver,
                       NULL);
+
        /* make sure later steps can see the object created here */
        CommandCounterIncrement();
    }
index f913e87bc8b9d32d87e4b6ff28294fcbd2695568..e633a50dd2db4f08f3edb7b05273bd9648e4992b 100644 (file)
@@ -9285,7 +9285,8 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
    querytree_list = NIL;
    foreach(list_item, raw_parsetree_list)
    {
-       Node       *stmt = (Node *) lfirst(list_item);
+       RawStmt    *rs = (RawStmt *) lfirst(list_item);
+       Node       *stmt = rs->stmt;
 
        if (IsA(stmt, IndexStmt))
            querytree_list = lappend(querytree_list,
index 61666ad19238cc4bc7f8e5a2658f0355471229da..b404d1ea16ff99ef1198eb208aab28f913fa00cc 100644 (file)
@@ -1078,6 +1078,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
        AlterTableStmt *atstmt = makeNode(AlterTableStmt);
        AlterTableCmd *atcmd = makeNode(AlterTableCmd);
        Constraint *fkcon = makeNode(Constraint);
+       PlannedStmt *wrapper = makeNode(PlannedStmt);
 
        ereport(NOTICE,
                (errmsg("converting trigger group into constraint \"%s\" %s",
@@ -1167,8 +1168,15 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
        fkcon->skip_validation = false;
        fkcon->initially_valid = true;
 
+       /* finally, wrap it in a dummy PlannedStmt */
+       wrapper->commandType = CMD_UTILITY;
+       wrapper->canSetTag = false;
+       wrapper->utilityStmt = (Node *) atstmt;
+       wrapper->stmt_location = -1;
+       wrapper->stmt_len = -1;
+
        /* ... and execute it */
-       ProcessUtility((Node *) atstmt,
+       ProcessUtility(wrapper,
                       "(generated ALTER TABLE ADD FOREIGN KEY command)",
                       PROCESS_UTILITY_SUBCOMMAND, NULL,
                       None_Receiver, NULL);
index e4be5c533205bfb2a080dd7401fd23a410ecf2ec..1f008b075666342c0cbae3486c3fbc4ea9080d3a 100644 (file)
@@ -414,8 +414,10 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
  *     Execute a CREATE VIEW command.
  */
 ObjectAddress
-DefineView(ViewStmt *stmt, const char *queryString)
+DefineView(ViewStmt *stmt, const char *queryString,
+          int stmt_location, int stmt_len)
 {
+   RawStmt    *rawstmt;
    Query      *viewParse;
    RangeVar   *view;
    ListCell   *cell;
@@ -429,8 +431,12 @@ DefineView(ViewStmt *stmt, const char *queryString)
     * Since parse analysis scribbles on its input, copy the raw parse tree;
     * this ensures we don't corrupt a prepared statement, for example.
     */
-   viewParse = parse_analyze((Node *) copyObject(stmt->query),
-                             queryString, NULL, 0);
+   rawstmt = makeNode(RawStmt);
+   rawstmt->stmt = (Node *) copyObject(stmt->query);
+   rawstmt->stmt_location = stmt_location;
+   rawstmt->stmt_len = stmt_len;
+
+   viewParse = parse_analyze(rawstmt, queryString, NULL, 0);
 
    /*
     * The grammar should ensure that the result is a single SELECT Query.
@@ -443,8 +449,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("views must not contain SELECT INTO")));
-   if (viewParse->commandType != CMD_SELECT ||
-       viewParse->utilityStmt != NULL)
+   if (viewParse->commandType != CMD_SELECT)
        elog(ERROR, "unexpected parse analysis result");
 
    /*
index 6cf62daab8a58e384f88f53127de1fa5b3087ca6..e01fe6da96492f46d034c9522f88062646437d1c 100644 (file)
@@ -156,13 +156,15 @@ ExecSerializePlan(Plan *plan, EState *estate)
    pstmt->planTree = plan;
    pstmt->rtable = estate->es_range_table;
    pstmt->resultRelations = NIL;
-   pstmt->utilityStmt = NULL;
    pstmt->subplans = NIL;
    pstmt->rewindPlanIDs = NULL;
    pstmt->rowMarks = NIL;
    pstmt->relationOids = NIL;
    pstmt->invalItems = NIL;    /* workers can't replan anyway... */
    pstmt->nParamExec = estate->es_plannedstmt->nParamExec;
+   pstmt->utilityStmt = NULL;
+   pstmt->stmt_location = -1;
+   pstmt->stmt_len = -1;
 
    /* Return serialized copy of our dummy PlannedStmt. */
    return nodeToString(pstmt);
index 039defa7b85f681f5398838b7946fd9941ce9402..e4a1da4dbbf4b2be0475fedfcfe64be498f13094 100644 (file)
@@ -66,7 +66,7 @@ typedef struct execution_state
    ExecStatus  status;
    bool        setsResult;     /* true if this query produces func's result */
    bool        lazyEval;       /* true if should fetch one row at a time */
-   Node       *stmt;           /* PlannedStmt or utility statement */
+   PlannedStmt *stmt;          /* plan for this query */
    QueryDesc  *qd;             /* null unless status == RUN */
 } execution_state;
 
@@ -487,45 +487,56 @@ init_execution_state(List *queryTree_list,
        foreach(lc2, qtlist)
        {
            Query      *queryTree = (Query *) lfirst(lc2);
-           Node       *stmt;
+           PlannedStmt *stmt;
            execution_state *newes;
 
            Assert(IsA(queryTree, Query));
 
            /* Plan the query if needed */
            if (queryTree->commandType == CMD_UTILITY)
-               stmt = queryTree->utilityStmt;
+           {
+               /* Utility commands require no planning. */
+               stmt = makeNode(PlannedStmt);
+               stmt->commandType = CMD_UTILITY;
+               stmt->canSetTag = queryTree->canSetTag;
+               stmt->utilityStmt = queryTree->utilityStmt;
+               stmt->stmt_location = queryTree->stmt_location;
+               stmt->stmt_len = queryTree->stmt_len;
+           }
            else
-               stmt = (Node *) pg_plan_query(queryTree,
+               stmt = pg_plan_query(queryTree,
                          fcache->readonly_func ? CURSOR_OPT_PARALLEL_OK : 0,
-                                             NULL);
+                                    NULL);
 
            /*
             * Precheck all commands for validity in a function.  This should
             * generally match the restrictions spi.c applies.
             */
-           if (IsA(stmt, CopyStmt) &&
-               ((CopyStmt *) stmt)->filename == NULL)
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+           if (stmt->commandType == CMD_UTILITY)
+           {
+               if (IsA(stmt->utilityStmt, CopyStmt) &&
+                   ((CopyStmt *) stmt->utilityStmt)->filename == NULL)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("cannot COPY to/from client in a SQL function")));
 
-           if (IsA(stmt, TransactionStmt))
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-               /* translator: %s is a SQL statement name */
-                        errmsg("%s is not allowed in a SQL function",
-                               CreateCommandTag(stmt))));
+               if (IsA(stmt->utilityStmt, TransactionStmt))
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                   /* translator: %s is a SQL statement name */
+                            errmsg("%s is not allowed in a SQL function",
+                                   CreateCommandTag(stmt->utilityStmt))));
+           }
 
            if (fcache->readonly_func && !CommandIsReadOnly(stmt))
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                /* translator: %s is a SQL statement name */
                       errmsg("%s is not allowed in a non-volatile function",
-                             CreateCommandTag(stmt))));
+                             CreateCommandTag((Node *) stmt))));
 
            if (IsInParallelMode() && !CommandIsReadOnly(stmt))
-               PreventCommandIfParallelMode(CreateCommandTag(stmt));
+               PreventCommandIfParallelMode(CreateCommandTag((Node *) stmt));
 
            /* OK, build the execution_state for this query */
            newes = (execution_state *) palloc(sizeof(execution_state));
@@ -569,15 +580,9 @@ init_execution_state(List *queryTree_list,
    {
        lasttages->setsResult = true;
        if (lazyEvalOK &&
-           IsA(lasttages->stmt, PlannedStmt))
-       {
-           PlannedStmt *ps = (PlannedStmt *) lasttages->stmt;
-
-           if (ps->commandType == CMD_SELECT &&
-               ps->utilityStmt == NULL &&
-               !ps->hasModifyingCTE)
-               fcache->lazyEval = lasttages->lazyEval = true;
-       }
+           lasttages->stmt->commandType == CMD_SELECT &&
+           !lasttages->stmt->hasModifyingCTE)
+           fcache->lazyEval = lasttages->lazyEval = true;
    }
 
    return eslist;
@@ -704,7 +709,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
    flat_query_list = NIL;
    foreach(lc, raw_parsetree_list)
    {
-       Node       *parsetree = (Node *) lfirst(lc);
+       RawStmt    *parsetree = (RawStmt *) lfirst(lc);
        List       *queryTree_sublist;
 
        queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
@@ -801,22 +806,15 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
    else
        dest = None_Receiver;
 
-   if (IsA(es->stmt, PlannedStmt))
-       es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
-                                fcache->src,
-                                GetActiveSnapshot(),
-                                InvalidSnapshot,
-                                dest,
-                                fcache->paramLI, 0);
-   else
-       es->qd = CreateUtilityQueryDesc(es->stmt,
-                                       fcache->src,
-                                       GetActiveSnapshot(),
-                                       dest,
-                                       fcache->paramLI);
+   es->qd = CreateQueryDesc(es->stmt,
+                            fcache->src,
+                            GetActiveSnapshot(),
+                            InvalidSnapshot,
+                            dest,
+                            fcache->paramLI, 0);
 
    /* Utility commands don't need Executor. */
-   if (es->qd->utilitystmt == NULL)
+   if (es->qd->operation != CMD_UTILITY)
    {
        /*
         * In lazyEval mode, do not let the executor set up an AfterTrigger
@@ -844,12 +842,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
 {
    bool        result;
 
-   if (es->qd->utilitystmt)
+   if (es->qd->operation == CMD_UTILITY)
    {
-       /* ProcessUtility needs the PlannedStmt for DECLARE CURSOR */
-       ProcessUtility((es->qd->plannedstmt ?
-                       (Node *) es->qd->plannedstmt :
-                       es->qd->utilitystmt),
+       ProcessUtility(es->qd->plannedstmt,
                       fcache->src,
                       PROCESS_UTILITY_QUERY,
                       es->qd->params,
@@ -882,7 +877,7 @@ postquel_end(execution_state *es)
    es->status = F_EXEC_DONE;
 
    /* Utility commands don't need Executor. */
-   if (es->qd->utilitystmt == NULL)
+   if (es->qd->operation != CMD_UTILITY)
    {
        ExecutorFinish(es->qd);
        ExecutorEnd(es->qd);
@@ -1576,8 +1571,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
     * entities.
     */
    if (parse &&
-       parse->commandType == CMD_SELECT &&
-       parse->utilityStmt == NULL)
+       parse->commandType == CMD_SELECT)
    {
        tlist_ptr = &parse->targetList;
        tlist = parse->targetList;
index ee7a7e2d78c2e4708ec507ec2bba1ef6c6dd5066..7bd37283b7f809560ed314078dabc4838520038c 100644 (file)
@@ -1232,7 +1232,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
    if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
    {
        if (list_length(stmt_list) == 1 &&
-           IsA((Node *) linitial(stmt_list), PlannedStmt) &&
+        ((PlannedStmt *) linitial(stmt_list))->commandType != CMD_UTILITY &&
            ((PlannedStmt *) linitial(stmt_list))->rowMarks == NIL &&
            ExecSupportsBackwardScan(((PlannedStmt *) linitial(stmt_list))->planTree))
            portal->cursorOptions |= CURSOR_OPT_SCROLL;
@@ -1248,7 +1248,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
    if (portal->cursorOptions & CURSOR_OPT_SCROLL)
    {
        if (list_length(stmt_list) == 1 &&
-           IsA((Node *) linitial(stmt_list), PlannedStmt) &&
+        ((PlannedStmt *) linitial(stmt_list))->commandType != CMD_UTILITY &&
            ((PlannedStmt *) linitial(stmt_list))->rowMarks != NIL)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1270,7 +1270,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 
        foreach(lc, stmt_list)
        {
-           Node       *pstmt = (Node *) lfirst(lc);
+           PlannedStmt *pstmt = (PlannedStmt *) lfirst(lc);
 
            if (!CommandIsReadOnly(pstmt))
            {
@@ -1279,9 +1279,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    /* translator: %s is a SQL statement name */
                       errmsg("%s is not allowed in a non-volatile function",
-                             CreateCommandTag(pstmt))));
+                             CreateCommandTag((Node *) pstmt))));
                else
-                   PreventCommandIfParallelMode(CreateCommandTag(pstmt));
+                   PreventCommandIfParallelMode(CreateCommandTag((Node *) pstmt));
            }
        }
    }
@@ -1757,7 +1757,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
 
    foreach(list_item, raw_parsetree_list)
    {
-       Node&nbs