Centralize the logic for protective copying of utility statements.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 18 Jun 2021 15:22:58 +0000 (11:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 18 Jun 2021 15:22:58 +0000 (11:22 -0400)
In the "simple Query" code path, it's fine for parse analysis or
execution of a utility statement to scribble on the statement's node
tree, since that'll just be thrown away afterwards.  However it's
not fine if the node tree is in the plan cache, as then it'd be
corrupted for subsequent executions.  Up to now we've dealt with
that by having individual utility-statement functions apply
copyObject() if they were going to modify the tree.  But that's
prone to errors of omission.  Bug #17053 from Charles Samborski
shows that CREATE/ALTER DOMAIN didn't get this memo, and can
crash if executed repeatedly from plan cache.

In the back branches, we'll just apply a narrow band-aid for that,
but in HEAD it seems prudent to have a more principled fix that
will close off the possibility of other similar bugs in future.
Hence, let's hoist the responsibility for doing copyObject up into
ProcessUtility from its children, thus ensuring that it happens for
all utility statement types.

Also, modify ProcessUtility's API so that its callers can tell it
whether a copy step is necessary.  It turns out that in all cases,
the immediate caller knows whether the node tree is transient, so
this doesn't involve a huge amount of code thrashing.  In this way,
while we lose a little bit in the execute-from-cache code path due
to sometimes copying node trees that wouldn't be mutated anyway,
we gain something in the simple-Query code path by not copying
throwaway node trees.  Statements that are complex enough to be
expensive to copy are almost certainly ones that would have to be
copied anyway, so the loss in the cache code path shouldn't be much.

(Note that this whole problem applies only to utility statements.
Optimizable statements don't have the issue because we long ago made
the executor treat Plan trees as read-only.  Perhaps someday we will
make utility statement execution act likewise, but I'm not holding
my breath.)

Discussion: https://postgr.es/m/931771.1623893989@sss.pgh.pa.us
Discussion: https://postgr.es/m/17053-3ca3f501bbc212b4@postgresql.org

19 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
contrib/sepgsql/hooks.c
src/backend/commands/copyto.c
src/backend/commands/createas.c
src/backend/commands/explain.c
src/backend/commands/extension.c
src/backend/commands/foreigncmds.c
src/backend/commands/policy.c
src/backend/commands/portalcmds.c
src/backend/commands/prepare.c
src/backend/commands/schemacmds.c
src/backend/commands/tablecmds.c
src/backend/commands/view.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/parser/parse_utilcmd.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/include/tcop/utility.h

index 09433c8c96c75af4bff8e1fd34ad5c8cce57192e..07fe0e7cdad2c886d6dbfe99a6d670a0e0ec65b1 100644 (file)
@@ -320,6 +320,7 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
 static void pgss_ExecutorFinish(QueryDesc *queryDesc);
 static void pgss_ExecutorEnd(QueryDesc *queryDesc);
 static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+                               bool readOnlyTree,
                                ProcessUtilityContext context, ParamListInfo params,
                                QueryEnvironment *queryEnv,
                                DestReceiver *dest, QueryCompletion *qc);
@@ -1069,6 +1070,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
  */
 static void
 pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+                   bool readOnlyTree,
                    ProcessUtilityContext context,
                    ParamListInfo params, QueryEnvironment *queryEnv,
                    DestReceiver *dest, QueryCompletion *qc)
@@ -1126,11 +1128,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
        PG_TRY();
        {
            if (prev_ProcessUtility)
-               prev_ProcessUtility(pstmt, queryString,
+               prev_ProcessUtility(pstmt, queryString, readOnlyTree,
                                    context, params, queryEnv,
                                    dest, qc);
            else
-               standard_ProcessUtility(pstmt, queryString,
+               standard_ProcessUtility(pstmt, queryString, readOnlyTree,
                                        context, params, queryEnv,
                                        dest, qc);
        }
@@ -1176,11 +1178,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
    else
    {
        if (prev_ProcessUtility)
-           prev_ProcessUtility(pstmt, queryString,
+           prev_ProcessUtility(pstmt, queryString, readOnlyTree,
                                context, params, queryEnv,
                                dest, qc);
        else
-           standard_ProcessUtility(pstmt, queryString,
+           standard_ProcessUtility(pstmt, queryString, readOnlyTree,
                                    context, params, queryEnv,
                                    dest, qc);
    }
index 34de6158d6047f79850d4ad05522f7d9e4dc1607..19a3ffb7ffae7ff550492b7257634b7ddffc7b46 100644 (file)
@@ -313,6 +313,7 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
 static void
 sepgsql_utility_command(PlannedStmt *pstmt,
                        const char *queryString,
+                       bool readOnlyTree,
                        ProcessUtilityContext context,
                        ParamListInfo params,
                        QueryEnvironment *queryEnv,
@@ -378,11 +379,11 @@ sepgsql_utility_command(PlannedStmt *pstmt,
        }
 
        if (next_ProcessUtility_hook)
-           (*next_ProcessUtility_hook) (pstmt, queryString,
+           (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
                                         context, params, queryEnv,
                                         dest, qc);
        else
-           standard_ProcessUtility(pstmt, queryString,
+           standard_ProcessUtility(pstmt, queryString, readOnlyTree,
                                    context, params, queryEnv,
                                    dest, qc);
    }
index 89a4f8f810e5a15583a28571cc0dc952c9ea7b8f..b6eacd5baa8932afe4b0eee7e04c18c1de22f437 100644 (file)
@@ -438,14 +438,8 @@ BeginCopyTo(ParseState *pstate,
        /*
         * Run parse analysis and rewrite.  Note this also acquires sufficient
         * locks on the source table(s).
-        *
-        * Because the parser and planner tend to scribble on their input, we
-        * make a preliminary copy of the source querytree.  This prevents
-        * problems in the case that the COPY 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(copyObject(raw_query),
+       rewritten = pg_analyze_and_rewrite(raw_query,
                                           pstate->p_sourcetext, NULL, 0,
                                           NULL);
 
index dce882012e6ff73e1c0ee252a291365229c579c2..09828517153b338e0b324a25a799e677f4a3372e 100644 (file)
@@ -299,14 +299,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
         * 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 CTAS is in a portal or plpgsql function
-        * and is executed repeatedly.  (See also the same hack in EXPLAIN and
-        * PREPARE.)
         */
-       rewritten = QueryRewrite(copyObject(query));
+       rewritten = QueryRewrite(query);
 
        /* SELECT should never rewrite to more or less than one SELECT query */
        if (list_length(rewritten) != 1)
index 9a60865d19111d752e287d48a46cbce4ac3c3eee..e81b990092594d15acc0be0fa4a74d8719f63a1e 100644 (file)
@@ -256,14 +256,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
     * 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 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 = QueryRewrite(castNode(Query, copyObject(stmt->query)));
+   rewritten = QueryRewrite(castNode(Query, stmt->query));
 
    /* emit opening boilerplate */
    ExplainBeginOutput(es);
@@ -427,7 +421,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
  *
  * This is exported because it's called back from prepare.c in the
- * EXPLAIN EXECUTE case.
+ * EXPLAIN EXECUTE case.  In that case, we'll be dealing with a statement
+ * that's in the plan cache, so we have to ensure we don't modify it.
  */
 void
 ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
@@ -441,8 +436,7 @@ 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.
+        * ExplainOneQuery.  Copy to be safe in the EXPLAIN EXECUTE case.
         */
        CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
        List       *rewritten;
index 008505368c404c42eecaa30e53226dd869a3932a..41857feda9f5c69b49396b291626ac401696ccc4 100644 (file)
@@ -786,6 +786,7 @@ execute_sql_string(const char *sql)
 
                ProcessUtility(stmt,
                               sql,
+                              false,
                               PROCESS_UTILITY_QUERY,
                               NULL,
                               NULL,
index eb7103fd3b11faf45685ab22ca2cfba792eb1f64..bc36311d3834fa9fde04aee10e5cd275526333d3 100644 (file)
@@ -1570,8 +1570,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
            pstmt->stmt_len = rs->stmt_len;
 
            /* Execute statement */
-           ProcessUtility(pstmt,
-                          cmd,
+           ProcessUtility(pstmt, cmd, false,
                           PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
                           None_Receiver, NULL);
 
index 5cacc088cd877c108860208c2981eb4fcdb22cc5..5d469309ce1d08b10115d5b614bf22c68d6861a4 100644 (file)
@@ -747,12 +747,12 @@ CreatePolicy(CreatePolicyStmt *stmt)
    addNSItemToQuery(with_check_pstate, nsitem, false, true, true);
 
    qual = transformWhereClause(qual_pstate,
-                               copyObject(stmt->qual),
+                               stmt->qual,
                                EXPR_KIND_POLICY,
                                "POLICY");
 
    with_check_qual = transformWhereClause(with_check_pstate,
-                                          copyObject(stmt->with_check),
+                                          stmt->with_check,
                                           EXPR_KIND_POLICY,
                                           "POLICY");
 
@@ -922,7 +922,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
 
        addNSItemToQuery(qual_pstate, nsitem, false, true, true);
 
-       qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
+       qual = transformWhereClause(qual_pstate, stmt->qual,
                                    EXPR_KIND_POLICY,
                                    "POLICY");
 
@@ -946,7 +946,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
        addNSItemToQuery(with_check_pstate, nsitem, false, true, true);
 
        with_check_qual = transformWhereClause(with_check_pstate,
-                                              copyObject(stmt->with_check),
+                                              stmt->with_check,
                                               EXPR_KIND_POLICY,
                                               "POLICY");
 
index d34cc39fdea1718d66699daae5fd9159a45cb5e3..3ea30bcbc99850547a7344e1621f011d300bdd00 100644 (file)
@@ -76,14 +76,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa
     * 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));
+   rewritten = QueryRewrite(query);
 
    /* SELECT should never rewrite to more or less than one query */
    if (list_length(rewritten) != 1)
index 65d8ac65b1843d751914856de42efda48dd5f0f9..5e03c7c5aaacf098888914794ecf9681d5a79b17 100644 (file)
@@ -78,12 +78,9 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
    /*
     * 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 = stmt->query;
    rawstmt->stmt_location = stmt_location;
    rawstmt->stmt_len = stmt_len;
 
index a60eb161e4aae8b89050e0f66f5d490be69af98d..6c6ab9ee349f0477f9680872618702597ed31874 100644 (file)
@@ -191,6 +191,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
        /* do this step */
        ProcessUtility(wrapper,
                       queryString,
+                      false,
                       PROCESS_UTILITY_SUBCOMMAND,
                       NULL,
                       NULL,
index 028e8ac46b33516ed75a974777e4078aa40db18b..4e23c7fce5f4981a74d986f594e47936c69e1030 100644 (file)
@@ -4408,8 +4408,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
     * Copy the original subcommand for each table.  This avoids conflicts
     * when different child tables need to make different parse
     * transformations (for example, the same column may have different column
-    * numbers in different children).  It also ensures that we don't corrupt
-    * the original parse tree, in case it is saved in plancache.
+    * numbers in different children).
     */
    cmd = copyObject(cmd);
 
index f2642dba6c98237f26849f4d3f89c77f5136b947..4df05a0b33d375194c4b2bcb6731acac32654f1a 100644 (file)
@@ -417,12 +417,9 @@ DefineView(ViewStmt *stmt, const char *queryString,
    /*
     * Run parse analysis to convert the raw parse tree to a Query.  Note this
     * also acquires sufficient locks on the source table(s).
-    *
-    * Since parse analysis scribbles on its input, copy the raw parse tree;
-    * this ensures we don't corrupt a prepared statement, for example.
     */
    rawstmt = makeNode(RawStmt);
-   rawstmt->stmt = (Node *) copyObject(stmt->query);
+   rawstmt->stmt = stmt->query;
    rawstmt->stmt_location = stmt_location;
    rawstmt->stmt_len = stmt_len;
 
index e2ea51aafe52086898afc1367725dd423f03cc8a..296e54e60a445b38d57772b6676d30f71d0314ce 100644 (file)
@@ -886,6 +886,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
    {
        ProcessUtility(es->qd->plannedstmt,
                       fcache->src,
+                      false,
                       PROCESS_UTILITY_QUERY,
                       es->qd->params,
                       es->qd->queryEnv,
index b8bd05e8942158e0bb07bfa00e622ba16c6925b7..bf619d3a65a5bb934d8a53202ce2de2212fa34dc 100644 (file)
@@ -2545,6 +2545,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                InitializeQueryCompletion(&qc);
                ProcessUtility(stmt,
                               plansource->query_string,
+                              true,    /* protect plancache's node tree */
                               context,
                               paramLI,
                               _SPI_current->queryEnv,
index c9708e38f4680d1d4f3f496acb23bfd02d5b904b..81d3e7990c6b75510c6754ddeb6a075859c96288 100644 (file)
  * Hence these functions are now called at the start of execution of their
  * respective utility commands.
  *
- * NOTE: in general we must avoid scribbling on the passed-in raw parse
- * tree, since it might be in a plan cache.  The simplest solution is
- * a quick copyObject() call before manipulating the query tree.
- *
  *
  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -177,12 +173,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
    Oid         existing_relid;
    ParseCallbackState pcbstate;
 
-   /*
-    * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
-    * overkill, but easy.)
-    */
-   stmt = copyObject(stmt);
-
    /* Set up pstate */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;
@@ -2824,12 +2814,6 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
    if (stmt->transformed)
        return stmt;
 
-   /*
-    * We must not scribble on the passed-in IndexStmt, so copy it.  (This is
-    * overkill, but easy.)
-    */
-   stmt = copyObject(stmt);
-
    /* Set up pstate */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;
@@ -2925,12 +2909,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
    if (stmt->transformed)
        return stmt;
 
-   /*
-    * We must not scribble on the passed-in CreateStatsStmt, so copy it.
-    * (This is overkill, but easy.)
-    */
-   stmt = copyObject(stmt);
-
    /* Set up pstate */
    pstate = make_parsestate(NULL);
    pstate->p_sourcetext = queryString;
@@ -2993,9 +2971,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
  *
  * actions and whereClause are output parameters that receive the
  * transformed results.
- *
- * Note that we must not scribble on the passed-in RuleStmt, so we do
- * copyObject() on the actions and WHERE clause.
  */
 void
 transformRuleStmt(RuleStmt *stmt, const char *queryString,
@@ -3070,7 +3045,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
    /* take care of the where clause */
    *whereClause = transformWhereClause(pstate,
-                                       (Node *) copyObject(stmt->whereClause),
+                                       stmt->whereClause,
                                        EXPR_KIND_WHERE,
                                        "WHERE");
    /* we have to fix its collations too */
@@ -3142,8 +3117,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
            addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
            /* Transform the rule action statement */
-           top_subqry = transformStmt(sub_pstate,
-                                      (Node *) copyObject(action));
+           top_subqry = transformStmt(sub_pstate, action);
 
            /*
             * We cannot support utility-statement actions (eg NOTIFY) with
@@ -3325,12 +3299,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
    AlterTableCmd *newcmd;
    ParseNamespaceItem *nsitem;
 
-   /*
-    * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
-    * is overkill, but easy.)
-    */
-   stmt = copyObject(stmt);
-
    /* Caller is responsible for locking the relation */
    rel = relation_open(relid, NoLock);
    tupdesc = RelationGetDescr(rel);
index f7f08e6c05f8b2135284bcad3f431cc0f8c151b9..ed43920264a1ec3a9c42a3ca38ca533f76503860 100644 (file)
@@ -1146,6 +1146,7 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
 
    ProcessUtility(pstmt,
                   portal->sourceText,
+                  (portal->cplan != NULL), /* protect tree if in plancache */
                   isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
                   portal->portalParams,
                   portal->queryEnv,
index 1a8fc16773322375a962f843b57ef115a7f1ae14..7a2da9dab431f499ff843ba6586b803c60a3350c 100644 (file)
@@ -476,6 +476,7 @@ CheckRestrictedOperation(const char *cmdname)
  *
  * pstmt: PlannedStmt wrapper for the utility statement
  * queryString: original source text of command
+ * readOnlyTree: if true, pstmt's node tree must not be modified
  * context: identifies source of statement (toplevel client command,
  *     non-toplevel client command, subcommand of a larger utility command)
  * params: parameters to use during execution
@@ -501,6 +502,7 @@ CheckRestrictedOperation(const char *cmdname)
 void
 ProcessUtility(PlannedStmt *pstmt,
               const char *queryString,
+              bool readOnlyTree,
               ProcessUtilityContext context,
               ParamListInfo params,
               QueryEnvironment *queryEnv,
@@ -518,11 +520,11 @@ ProcessUtility(PlannedStmt *pstmt,
     * call standard_ProcessUtility().
     */
    if (ProcessUtility_hook)
-       (*ProcessUtility_hook) (pstmt, queryString,
+       (*ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
                                context, params, queryEnv,
                                dest, qc);
    else
-       standard_ProcessUtility(pstmt, queryString,
+       standard_ProcessUtility(pstmt, queryString, readOnlyTree,
                                context, params, queryEnv,
                                dest, qc);
 }
@@ -541,13 +543,14 @@ ProcessUtility(PlannedStmt *pstmt,
 void
 standard_ProcessUtility(PlannedStmt *pstmt,
                        const char *queryString,
+                       bool readOnlyTree,
                        ProcessUtilityContext context,
                        ParamListInfo params,
                        QueryEnvironment *queryEnv,
                        DestReceiver *dest,
                        QueryCompletion *qc)
 {
-   Node       *parsetree = pstmt->utilityStmt;
+   Node       *parsetree;
    bool        isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
    bool        isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
    ParseState *pstate;
@@ -556,6 +559,18 @@ standard_ProcessUtility(PlannedStmt *pstmt,
    /* This can recurse, so check for excessive recursion */
    check_stack_depth();
 
+   /*
+    * If the given node tree is read-only, make a copy to ensure that parse
+    * transformations don't damage the original tree.  This could be
+    * refactored to avoid making unnecessary copies in more cases, but it's
+    * not clear that it's worth a great deal of trouble over.  Statements
+    * that are complex enough to be expensive to copy are exactly the ones
+    * we'd need to copy, so that only marginal savings seem possible.
+    */
+   if (readOnlyTree)
+       pstmt = copyObject(pstmt);
+   parsetree = pstmt->utilityStmt;
+
    /* Prohibit read/write commands in read-only states. */
    readonly_flags = ClassifyUtilityCommandAsReadOnly(parsetree);
    if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY &&
@@ -1211,6 +1226,7 @@ ProcessUtilitySlow(ParseState *pstate,
 
                            ProcessUtility(wrapper,
                                           queryString,
+                                          false,
                                           PROCESS_UTILITY_SUBCOMMAND,
                                           params,
                                           NULL,
@@ -1918,6 +1934,7 @@ ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
 
    ProcessUtility(wrapper,
                   context->queryString,
+                  false,
                   PROCESS_UTILITY_SUBCOMMAND,
                   context->params,
                   context->queryEnv,
index 841062b4b35bb3d159737a6e826a540a7dcaff8c..212e9b32806c74ff49b18d55394dfcb0257cae4c 100644 (file)
@@ -69,17 +69,21 @@ typedef struct AlterTableUtilityContext
 
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
-                                         const char *queryString, ProcessUtilityContext context,
+                                         const char *queryString,
+                                         bool readOnlyTree,
+                                         ProcessUtilityContext context,
                                          ParamListInfo params,
                                          QueryEnvironment *queryEnv,
                                          DestReceiver *dest, QueryCompletion *qc);
 extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
 
 extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+                          bool readOnlyTree,
                           ProcessUtilityContext context, ParamListInfo params,
                           QueryEnvironment *queryEnv,
                           DestReceiver *dest, QueryCompletion *qc);
 extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+                                   bool readOnlyTree,
                                    ProcessUtilityContext context, ParamListInfo params,
                                    QueryEnvironment *queryEnv,
                                    DestReceiver *dest, QueryCompletion *qc);