Commit 7c337b6b authored by Tom Lane's avatar Tom Lane

Centralize the logic for protective copying of utility statements.

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
parent 0a4efdc7
...@@ -320,6 +320,7 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc, ...@@ -320,6 +320,7 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
static void pgss_ExecutorFinish(QueryDesc *queryDesc); static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc); static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ParamListInfo params, ProcessUtilityContext context, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc); DestReceiver *dest, QueryCompletion *qc);
...@@ -1069,6 +1070,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) ...@@ -1069,6 +1070,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
*/ */
static void static void
pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ProcessUtilityContext context,
ParamListInfo params, QueryEnvironment *queryEnv, ParamListInfo params, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc) DestReceiver *dest, QueryCompletion *qc)
...@@ -1126,11 +1128,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ...@@ -1126,11 +1128,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
PG_TRY(); PG_TRY();
{ {
if (prev_ProcessUtility) if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString, prev_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
else else
standard_ProcessUtility(pstmt, queryString, standard_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
} }
...@@ -1176,11 +1178,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ...@@ -1176,11 +1178,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
else else
{ {
if (prev_ProcessUtility) if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString, prev_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
else else
standard_ProcessUtility(pstmt, queryString, standard_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
} }
......
...@@ -313,6 +313,7 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort) ...@@ -313,6 +313,7 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
static void static void
sepgsql_utility_command(PlannedStmt *pstmt, sepgsql_utility_command(PlannedStmt *pstmt,
const char *queryString, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ProcessUtilityContext context,
ParamListInfo params, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
...@@ -378,11 +379,11 @@ sepgsql_utility_command(PlannedStmt *pstmt, ...@@ -378,11 +379,11 @@ sepgsql_utility_command(PlannedStmt *pstmt,
} }
if (next_ProcessUtility_hook) if (next_ProcessUtility_hook)
(*next_ProcessUtility_hook) (pstmt, queryString, (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
else else
standard_ProcessUtility(pstmt, queryString, standard_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
} }
......
...@@ -438,14 +438,8 @@ BeginCopyTo(ParseState *pstate, ...@@ -438,14 +438,8 @@ BeginCopyTo(ParseState *pstate,
/* /*
* Run parse analysis and rewrite. Note this also acquires sufficient * Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s). * 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, pstate->p_sourcetext, NULL, 0,
NULL); NULL);
......
...@@ -299,14 +299,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, ...@@ -299,14 +299,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
* rewriter. We do not do AcquireRewriteLocks: we assume the query * rewriter. We do not do AcquireRewriteLocks: we assume the query
* either came straight from the parser, or suitable locks were * either came straight from the parser, or suitable locks were
* acquired by plancache.c. * 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 */ /* SELECT should never rewrite to more or less than one SELECT query */
if (list_length(rewritten) != 1) if (list_length(rewritten) != 1)
......
...@@ -256,14 +256,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, ...@@ -256,14 +256,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
* rewriter. We do not do AcquireRewriteLocks: we assume the query either * rewriter. We do not do AcquireRewriteLocks: we assume the query either
* came straight from the parser, or suitable locks were acquired by * came straight from the parser, or suitable locks were acquired by
* plancache.c. * 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 */ /* emit opening boilerplate */
ExplainBeginOutput(es); ExplainBeginOutput(es);
...@@ -427,7 +421,8 @@ ExplainOneQuery(Query *query, int cursorOptions, ...@@ -427,7 +421,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. * "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 * 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 void
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
...@@ -441,8 +436,7 @@ 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 * We have to rewrite the contained SELECT and then pass it back to
* ExplainOneQuery. It's probably not really necessary to copy the * ExplainOneQuery. Copy to be safe in the EXPLAIN EXECUTE case.
* contained parsetree another time, but let's be safe.
*/ */
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt; CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
List *rewritten; List *rewritten;
......
...@@ -786,6 +786,7 @@ execute_sql_string(const char *sql) ...@@ -786,6 +786,7 @@ execute_sql_string(const char *sql)
ProcessUtility(stmt, ProcessUtility(stmt,
sql, sql,
false,
PROCESS_UTILITY_QUERY, PROCESS_UTILITY_QUERY,
NULL, NULL,
NULL, NULL,
......
...@@ -1570,8 +1570,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt) ...@@ -1570,8 +1570,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
pstmt->stmt_len = rs->stmt_len; pstmt->stmt_len = rs->stmt_len;
/* Execute statement */ /* Execute statement */
ProcessUtility(pstmt, ProcessUtility(pstmt, cmd, false,
cmd,
PROCESS_UTILITY_SUBCOMMAND, NULL, NULL, PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
None_Receiver, NULL); None_Receiver, NULL);
......
...@@ -747,12 +747,12 @@ CreatePolicy(CreatePolicyStmt *stmt) ...@@ -747,12 +747,12 @@ CreatePolicy(CreatePolicyStmt *stmt)
addNSItemToQuery(with_check_pstate, nsitem, false, true, true); addNSItemToQuery(with_check_pstate, nsitem, false, true, true);
qual = transformWhereClause(qual_pstate, qual = transformWhereClause(qual_pstate,
copyObject(stmt->qual), stmt->qual,
EXPR_KIND_POLICY, EXPR_KIND_POLICY,
"POLICY"); "POLICY");
with_check_qual = transformWhereClause(with_check_pstate, with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check), stmt->with_check,
EXPR_KIND_POLICY, EXPR_KIND_POLICY,
"POLICY"); "POLICY");
...@@ -922,7 +922,7 @@ AlterPolicy(AlterPolicyStmt *stmt) ...@@ -922,7 +922,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
addNSItemToQuery(qual_pstate, nsitem, false, true, true); addNSItemToQuery(qual_pstate, nsitem, false, true, true);
qual = transformWhereClause(qual_pstate, copyObject(stmt->qual), qual = transformWhereClause(qual_pstate, stmt->qual,
EXPR_KIND_POLICY, EXPR_KIND_POLICY,
"POLICY"); "POLICY");
...@@ -946,7 +946,7 @@ AlterPolicy(AlterPolicyStmt *stmt) ...@@ -946,7 +946,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
addNSItemToQuery(with_check_pstate, nsitem, false, true, true); addNSItemToQuery(with_check_pstate, nsitem, false, true, true);
with_check_qual = transformWhereClause(with_check_pstate, with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check), stmt->with_check,
EXPR_KIND_POLICY, EXPR_KIND_POLICY,
"POLICY"); "POLICY");
......
...@@ -76,14 +76,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa ...@@ -76,14 +76,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa
* rewriter. We do not do AcquireRewriteLocks: we assume the query either * rewriter. We do not do AcquireRewriteLocks: we assume the query either
* came straight from the parser, or suitable locks were acquired by * came straight from the parser, or suitable locks were acquired by
* plancache.c. * 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 */ /* SELECT should never rewrite to more or less than one query */
if (list_length(rewritten) != 1) if (list_length(rewritten) != 1)
......
...@@ -78,12 +78,9 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, ...@@ -78,12 +78,9 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
/* /*
* Need to wrap the contained statement in a RawStmt node to pass it to * Need to wrap the contained statement in a RawStmt node to pass it to
* parse analysis. * 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 = makeNode(RawStmt);
rawstmt->stmt = (Node *) copyObject(stmt->query); rawstmt->stmt = stmt->query;
rawstmt->stmt_location = stmt_location; rawstmt->stmt_location = stmt_location;
rawstmt->stmt_len = stmt_len; rawstmt->stmt_len = stmt_len;
......
...@@ -191,6 +191,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString, ...@@ -191,6 +191,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
/* do this step */ /* do this step */
ProcessUtility(wrapper, ProcessUtility(wrapper,
queryString, queryString,
false,
PROCESS_UTILITY_SUBCOMMAND, PROCESS_UTILITY_SUBCOMMAND,
NULL, NULL,
NULL, NULL,
......
...@@ -4408,8 +4408,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ...@@ -4408,8 +4408,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* Copy the original subcommand for each table. This avoids conflicts * Copy the original subcommand for each table. This avoids conflicts
* when different child tables need to make different parse * when different child tables need to make different parse
* transformations (for example, the same column may have different column * transformations (for example, the same column may have different column
* numbers in different children). It also ensures that we don't corrupt * numbers in different children).
* the original parse tree, in case it is saved in plancache.
*/ */
cmd = copyObject(cmd); cmd = copyObject(cmd);
......
...@@ -417,12 +417,9 @@ DefineView(ViewStmt *stmt, const char *queryString, ...@@ -417,12 +417,9 @@ DefineView(ViewStmt *stmt, const char *queryString,
/* /*
* Run parse analysis to convert the raw parse tree to a Query. Note this * Run parse analysis to convert the raw parse tree to a Query. Note this
* also acquires sufficient locks on the source table(s). * 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 = makeNode(RawStmt);
rawstmt->stmt = (Node *) copyObject(stmt->query); rawstmt->stmt = stmt->query;
rawstmt->stmt_location = stmt_location; rawstmt->stmt_location = stmt_location;
rawstmt->stmt_len = stmt_len; rawstmt->stmt_len = stmt_len;
......
...@@ -886,6 +886,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) ...@@ -886,6 +886,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{ {
ProcessUtility(es->qd->plannedstmt, ProcessUtility(es->qd->plannedstmt,
fcache->src, fcache->src,
false,
PROCESS_UTILITY_QUERY, PROCESS_UTILITY_QUERY,
es->qd->params, es->qd->params,
es->qd->queryEnv, es->qd->queryEnv,
......
...@@ -2545,6 +2545,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -2545,6 +2545,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
InitializeQueryCompletion(&qc); InitializeQueryCompletion(&qc);
ProcessUtility(stmt, ProcessUtility(stmt,
plansource->query_string, plansource->query_string,
true, /* protect plancache's node tree */
context, context,
paramLI, paramLI,
_SPI_current->queryEnv, _SPI_current->queryEnv,
......
...@@ -11,10 +11,6 @@ ...@@ -11,10 +11,6 @@
* Hence these functions are now called at the start of execution of their * Hence these functions are now called at the start of execution of their
* respective utility commands. * 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) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
...@@ -177,12 +173,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) ...@@ -177,12 +173,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
Oid existing_relid; Oid existing_relid;
ParseCallbackState pcbstate; 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 */ /* Set up pstate */
pstate = make_parsestate(NULL); pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString; pstate->p_sourcetext = queryString;
...@@ -2824,12 +2814,6 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) ...@@ -2824,12 +2814,6 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
if (stmt->transformed) if (stmt->transformed)
return stmt; 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 */ /* Set up pstate */
pstate = make_parsestate(NULL); pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString; pstate->p_sourcetext = queryString;
...@@ -2925,12 +2909,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) ...@@ -2925,12 +2909,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed) if (stmt->transformed)
return stmt; 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 */ /* Set up pstate */
pstate = make_parsestate(NULL); pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString; pstate->p_sourcetext = queryString;
...@@ -2993,9 +2971,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) ...@@ -2993,9 +2971,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* *
* actions and whereClause are output parameters that receive the * actions and whereClause are output parameters that receive the
* transformed results. * transformed results.
*
* Note that we must not scribble on the passed-in RuleStmt, so we do
* copyObject() on the actions and WHERE clause.
*/ */
void void
transformRuleStmt(RuleStmt *stmt, const char *queryString, transformRuleStmt(RuleStmt *stmt, const char *queryString,
...@@ -3070,7 +3045,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString, ...@@ -3070,7 +3045,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
/* take care of the where clause */ /* take care of the where clause */
*whereClause = transformWhereClause(pstate, *whereClause = transformWhereClause(pstate,
(Node *) copyObject(stmt->whereClause), stmt->whereClause,
EXPR_KIND_WHERE, EXPR_KIND_WHERE,
"WHERE"); "WHERE");
/* we have to fix its collations too */ /* we have to fix its collations too */
...@@ -3142,8 +3117,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString, ...@@ -3142,8 +3117,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
addNSItemToQuery(sub_pstate, newnsitem, false, true, false); addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
/* Transform the rule action statement */ /* Transform the rule action statement */
top_subqry = transformStmt(sub_pstate, top_subqry = transformStmt(sub_pstate, action);
(Node *) copyObject(action));
/* /*
* We cannot support utility-statement actions (eg NOTIFY) with * We cannot support utility-statement actions (eg NOTIFY) with
...@@ -3325,12 +3299,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, ...@@ -3325,12 +3299,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
AlterTableCmd *newcmd; AlterTableCmd *newcmd;
ParseNamespaceItem *nsitem; 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 */ /* Caller is responsible for locking the relation */
rel = relation_open(relid, NoLock); rel = relation_open(relid, NoLock);
tupdesc = RelationGetDescr(rel); tupdesc = RelationGetDescr(rel);
......
...@@ -1146,6 +1146,7 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt, ...@@ -1146,6 +1146,7 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt,
ProcessUtility(pstmt, ProcessUtility(pstmt,
portal->sourceText, portal->sourceText,
(portal->cplan != NULL), /* protect tree if in plancache */
isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY, isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
portal->portalParams, portal->portalParams,
portal->queryEnv, portal->queryEnv,
......
...@@ -476,6 +476,7 @@ CheckRestrictedOperation(const char *cmdname) ...@@ -476,6 +476,7 @@ CheckRestrictedOperation(const char *cmdname)
* *
* pstmt: PlannedStmt wrapper for the utility statement * pstmt: PlannedStmt wrapper for the utility statement
* queryString: original source text of command * 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, * context: identifies source of statement (toplevel client command,
* non-toplevel client command, subcommand of a larger utility command) * non-toplevel client command, subcommand of a larger utility command)
* params: parameters to use during execution * params: parameters to use during execution
...@@ -501,6 +502,7 @@ CheckRestrictedOperation(const char *cmdname) ...@@ -501,6 +502,7 @@ CheckRestrictedOperation(const char *cmdname)
void void
ProcessUtility(PlannedStmt *pstmt, ProcessUtility(PlannedStmt *pstmt,
const char *queryString, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ProcessUtilityContext context,
ParamListInfo params, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
...@@ -518,11 +520,11 @@ ProcessUtility(PlannedStmt *pstmt, ...@@ -518,11 +520,11 @@ ProcessUtility(PlannedStmt *pstmt,
* call standard_ProcessUtility(). * call standard_ProcessUtility().
*/ */
if (ProcessUtility_hook) if (ProcessUtility_hook)
(*ProcessUtility_hook) (pstmt, queryString, (*ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
else else
standard_ProcessUtility(pstmt, queryString, standard_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv, context, params, queryEnv,
dest, qc); dest, qc);
} }
...@@ -541,13 +543,14 @@ ProcessUtility(PlannedStmt *pstmt, ...@@ -541,13 +543,14 @@ ProcessUtility(PlannedStmt *pstmt,
void void
standard_ProcessUtility(PlannedStmt *pstmt, standard_ProcessUtility(PlannedStmt *pstmt,
const char *queryString, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ProcessUtilityContext context,
ParamListInfo params, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, DestReceiver *dest,
QueryCompletion *qc) QueryCompletion *qc)
{ {
Node *parsetree = pstmt->utilityStmt; Node *parsetree;
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
bool isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock()); bool isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
ParseState *pstate; ParseState *pstate;
...@@ -556,6 +559,18 @@ standard_ProcessUtility(PlannedStmt *pstmt, ...@@ -556,6 +559,18 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* This can recurse, so check for excessive recursion */ /* This can recurse, so check for excessive recursion */
check_stack_depth(); 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. */ /* Prohibit read/write commands in read-only states. */
readonly_flags = ClassifyUtilityCommandAsReadOnly(parsetree); readonly_flags = ClassifyUtilityCommandAsReadOnly(parsetree);
if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY && if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY &&
...@@ -1211,6 +1226,7 @@ ProcessUtilitySlow(ParseState *pstate, ...@@ -1211,6 +1226,7 @@ ProcessUtilitySlow(ParseState *pstate,
ProcessUtility(wrapper, ProcessUtility(wrapper,
queryString, queryString,
false,
PROCESS_UTILITY_SUBCOMMAND, PROCESS_UTILITY_SUBCOMMAND,
params, params,
NULL, NULL,
...@@ -1918,6 +1934,7 @@ ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context) ...@@ -1918,6 +1934,7 @@ ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
ProcessUtility(wrapper, ProcessUtility(wrapper,
context->queryString, context->queryString,
false,
PROCESS_UTILITY_SUBCOMMAND, PROCESS_UTILITY_SUBCOMMAND,
context->params, context->params,
context->queryEnv, context->queryEnv,
......
...@@ -69,17 +69,21 @@ typedef struct AlterTableUtilityContext ...@@ -69,17 +69,21 @@ typedef struct AlterTableUtilityContext
/* Hook for plugins to get control in ProcessUtility() */ /* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt, typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context,
ParamListInfo params, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc); DestReceiver *dest, QueryCompletion *qc);
extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook; extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString, extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ParamListInfo params, ProcessUtilityContext context, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc); DestReceiver *dest, QueryCompletion *qc);
extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString, extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
bool readOnlyTree,
ProcessUtilityContext context, ParamListInfo params, ProcessUtilityContext context, ParamListInfo params,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc); DestReceiver *dest, QueryCompletion *qc);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment