diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 3d1b99f502..7eec86fb28 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -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); } diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 15f40d83f1..93cc8debaa 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -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); } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 6d8f17db2d..85cec0cb1c 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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, diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index f56b2ac49b..1fd2162794 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -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, diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 57ef981765..cee3b4d50b 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -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); /* diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index c762fb07d4..ee7046c47b 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -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) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index be521484d0..967b52a133 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -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 diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index 06b4bc3ba9..476a023ec5 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -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); diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 71640b7748..1d3e39299b 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -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" @@ -35,21 +37,18 @@ /* * 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; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index d768cf8dda..1ff41661a5 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -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 */ diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index c475613b1c..c3b37b2625 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -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(); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f913e87bc8..e633a50dd2 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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, diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 61666ad192..b404d1ea16 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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); diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index e4be5c5332..1f008b0756 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -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"); /* diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 6cf62daab8..e01fe6da96 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -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); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 039defa7b8..e4a1da4dbb 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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; diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index ee7a7e2d78..7bd37283b7 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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 *parsetree = (Node *) lfirst(list_item); + RawStmt *parsetree = (RawStmt *) lfirst(list_item); List *stmt_list; CachedPlanSource *plansource; @@ -1767,7 +1767,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan) */ plansource = CreateCachedPlan(parsetree, src, - CreateCommandTag(parsetree)); + CreateCommandTag(parsetree->stmt)); /* * Parameter datatypes are driven by parserSetup hook if provided, @@ -1859,12 +1859,12 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan) foreach(list_item, raw_parsetree_list) { - Node *parsetree = (Node *) lfirst(list_item); + RawStmt *parsetree = (RawStmt *) lfirst(list_item); CachedPlanSource *plansource; plansource = CreateOneShotCachedPlan(parsetree, src, - CreateCommandTag(parsetree)); + CreateCommandTag(parsetree->stmt)); plancache_list = lappend(plancache_list, plansource); } @@ -1959,7 +1959,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, */ if (plan->oneshot) { - Node *parsetree = plansource->raw_parse_tree; + RawStmt *parsetree = plansource->raw_parse_tree; const char *src = plansource->query_string; List *stmt_list; @@ -2018,26 +2018,19 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, foreach(lc2, stmt_list) { - Node *stmt = (Node *) lfirst(lc2); - bool canSetTag; + PlannedStmt *stmt = (PlannedStmt *) lfirst(lc2); + bool canSetTag = stmt->canSetTag; DestReceiver *dest; _SPI_current->processed = 0; _SPI_current->lastoid = InvalidOid; _SPI_current->tuptable = NULL; - if (IsA(stmt, PlannedStmt)) + if (stmt->utilityStmt) { - canSetTag = ((PlannedStmt *) stmt)->canSetTag; - } - else - { - /* utilities are canSetTag if only thing in list */ - canSetTag = (list_length(stmt_list) == 1); - - if (IsA(stmt, CopyStmt)) + if (IsA(stmt->utilityStmt, CopyStmt)) { - CopyStmt *cstmt = (CopyStmt *) stmt; + CopyStmt *cstmt = (CopyStmt *) stmt->utilityStmt; if (cstmt->filename == NULL) { @@ -2045,7 +2038,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, goto fail; } } - else if (IsA(stmt, TransactionStmt)) + else if (IsA(stmt->utilityStmt, TransactionStmt)) { my_res = SPI_ERROR_TRANSACTION; goto fail; @@ -2057,10 +2050,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, (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)); /* * If not read-only mode, advance the command counter before each @@ -2074,8 +2067,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, dest = CreateDestReceiver(canSetTag ? DestSPI : DestNone); - if (IsA(stmt, PlannedStmt) && - ((PlannedStmt *) stmt)->utilityStmt == NULL) + if (stmt->utilityStmt == NULL) { QueryDesc *qdesc; Snapshot snap; @@ -2085,7 +2077,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, else snap = InvalidSnapshot; - qdesc = CreateQueryDesc((PlannedStmt *) stmt, + qdesc = CreateQueryDesc(stmt, plansource->query_string, snap, crosscheck_snapshot, dest, @@ -2116,9 +2108,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, * Some utility statements return a row count, even though the * tuples are not returned to the caller. */ - if (IsA(stmt, CreateTableAsStmt)) + if (IsA(stmt->utilityStmt, CreateTableAsStmt)) { - CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt; + CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt->utilityStmt; if (strncmp(completionTag, "SELECT ", 7) == 0) _SPI_current->processed = @@ -2141,7 +2133,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, if (ctastmt->is_select_into) res = SPI_OK_SELINTO; } - else if (IsA(stmt, CopyStmt)) + else if (IsA(stmt->utilityStmt, CopyStmt)) { Assert(strncmp(completionTag, "COPY ", 5) == 0); _SPI_current->processed = pg_strtouint64(completionTag + 5, @@ -2270,7 +2262,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) switch (operation) { case CMD_SELECT: - Assert(queryDesc->plannedstmt->utilityStmt == NULL); if (queryDesc->dest->mydest != DestSPI) { /* Don't return SPI_OK_SELECT if we're discarding result */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 930f2f1d5f..7107bbf164 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -90,13 +90,15 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(planTree); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); - COPY_NODE_FIELD(utilityStmt); COPY_NODE_FIELD(subplans); COPY_BITMAPSET_FIELD(rewindPlanIDs); COPY_NODE_FIELD(rowMarks); COPY_NODE_FIELD(relationOids); COPY_NODE_FIELD(invalItems); COPY_SCALAR_FIELD(nParamExec); + COPY_NODE_FIELD(utilityStmt); + COPY_LOCATION_FIELD(stmt_location); + COPY_LOCATION_FIELD(stmt_len); return newnode; } @@ -2767,6 +2769,20 @@ _copyQuery(const Query *from) COPY_NODE_FIELD(setOperations); COPY_NODE_FIELD(constraintDeps); COPY_NODE_FIELD(withCheckOptions); + COPY_LOCATION_FIELD(stmt_location); + COPY_LOCATION_FIELD(stmt_len); + + return newnode; +} + +static RawStmt * +_copyRawStmt(const RawStmt *from) +{ + RawStmt *newnode = makeNode(RawStmt); + + COPY_NODE_FIELD(stmt); + COPY_LOCATION_FIELD(stmt_location); + COPY_LOCATION_FIELD(stmt_len); return newnode; } @@ -4728,6 +4744,9 @@ copyObject(const void *from) case T_Query: retval = _copyQuery(from); break; + case T_RawStmt: + retval = _copyRawStmt(from); + break; case T_InsertStmt: retval = _copyInsertStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a27e5edf37..ec4bbfc770 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -946,6 +946,18 @@ _equalQuery(const Query *a, const Query *b) COMPARE_NODE_FIELD(setOperations); COMPARE_NODE_FIELD(constraintDeps); COMPARE_NODE_FIELD(withCheckOptions); + COMPARE_LOCATION_FIELD(stmt_location); + COMPARE_LOCATION_FIELD(stmt_len); + + return true; +} + +static bool +_equalRawStmt(const RawStmt *a, const RawStmt *b) +{ + COMPARE_NODE_FIELD(stmt); + COMPARE_LOCATION_FIELD(stmt_location); + COMPARE_LOCATION_FIELD(stmt_len); return true; } @@ -3015,6 +3027,9 @@ equal(const void *a, const void *b) case T_Query: retval = _equalQuery(a, b); break; + case T_RawStmt: + retval = _equalRawStmt(a, b); + break; case T_InsertStmt: retval = _equalInsertStmt(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 806d0a93aa..cf0a6059e9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -252,13 +252,15 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(planTree); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); - WRITE_NODE_FIELD(utilityStmt); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); WRITE_INT_FIELD(nParamExec); + WRITE_NODE_FIELD(utilityStmt); + WRITE_LOCATION_FIELD(stmt_location); + WRITE_LOCATION_FIELD(stmt_len); } /* @@ -2705,6 +2707,9 @@ _outQuery(StringInfo str, const Query *node) WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(setOperations); WRITE_NODE_FIELD(constraintDeps); + /* withCheckOptions intentionally omitted, see comment in parsenodes.h */ + WRITE_LOCATION_FIELD(stmt_location); + WRITE_LOCATION_FIELD(stmt_len); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index dc40d0181e..e02dd94f05 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -262,6 +262,9 @@ _readQuery(void) READ_NODE_FIELD(rowMarks); READ_NODE_FIELD(setOperations); READ_NODE_FIELD(constraintDeps); + /* withCheckOptions intentionally omitted, see comment in parsenodes.h */ + READ_LOCATION_FIELD(stmt_location); + READ_LOCATION_FIELD(stmt_len); READ_DONE(); } @@ -1415,13 +1418,15 @@ _readPlannedStmt(void) READ_NODE_FIELD(planTree); READ_NODE_FIELD(rtable); READ_NODE_FIELD(resultRelations); - READ_NODE_FIELD(utilityStmt); READ_NODE_FIELD(subplans); READ_BITMAPSET_FIELD(rewindPlanIDs); READ_NODE_FIELD(rowMarks); READ_NODE_FIELD(relationOids); READ_NODE_FIELD(invalItems); READ_INT_FIELD(nParamExec); + READ_NODE_FIELD(utilityStmt); + READ_LOCATION_FIELD(stmt_location); + READ_LOCATION_FIELD(stmt_len); READ_DONE(); } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 207290fd26..f936710171 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -193,11 +193,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) ListCell *lp, *lr; - /* Cursor options may come from caller or from DECLARE CURSOR stmt */ - if (parse->utilityStmt && - IsA(parse->utilityStmt, DeclareCursorStmt)) - cursorOptions |= ((DeclareCursorStmt *) parse->utilityStmt)->options; - /* * Set up global state for this planner invocation. This data is needed * across all levels of sub-Query that might exist in the given command, @@ -246,7 +241,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) IsUnderPostmaster && dynamic_shared_memory_type != DSM_IMPL_NONE && parse->commandType == CMD_SELECT && - parse->utilityStmt == NULL && !parse->hasModifyingCTE && max_parallel_workers_per_gather > 0 && !IsParallelWorker() && @@ -421,13 +415,16 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->planTree = top_plan; result->rtable = glob->finalrtable; result->resultRelations = glob->resultRelations; - result->utilityStmt = parse->utilityStmt; result->subplans = glob->subplans; result->rewindPlanIDs = glob->rewindPlanIDs; result->rowMarks = glob->finalrowmarks; result->relationOids = glob->relationOids; result->invalItems = glob->invalItems; result->nParamExec = glob->nParamExec; + /* utilityStmt should be null, but we might as well copy it */ + result->utilityStmt = parse->utilityStmt; + result->stmt_location = parse->stmt_location; + result->stmt_len = parse->stmt_len; return result; } diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index be5d3d1104..7cb1bc9a62 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1409,8 +1409,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, * Let's just make sure it's a valid subselect ... */ if (!IsA(subquery, Query) || - subquery->commandType != CMD_SELECT || - subquery->utilityStmt != NULL) + subquery->commandType != CMD_SELECT) elog(ERROR, "subquery is bogus"); /* @@ -1744,8 +1743,7 @@ is_simple_union_all(Query *subquery) /* Let's just make sure it's a valid subselect ... */ if (!IsA(subquery, Query) || - subquery->commandType != CMD_SELECT || - subquery->utilityStmt != NULL) + subquery->commandType != CMD_SELECT) elog(ERROR, "subquery is bogus"); /* Is it a set-operation query at all? */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 10abdc3aff..59ccdf43d4 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4479,7 +4479,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, */ if (!IsA(querytree, Query) || querytree->commandType != CMD_SELECT || - querytree->utilityStmt || querytree->hasAggs || querytree->hasWindowFuncs || querytree->hasTargetSRFs || @@ -5006,8 +5005,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * The single command must be a plain SELECT. */ if (!IsA(querytree, Query) || - querytree->commandType != CMD_SELECT || - querytree->utilityStmt) + querytree->commandType != CMD_SELECT) goto fail; /* diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 5116cbb71a..8278e742f8 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -48,6 +48,7 @@ /* Hook for plugins to get control at end of parse analysis */ post_parse_analyze_hook_type post_parse_analyze_hook = NULL; +static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); static List *transformInsertRow(ParseState *pstate, List *exprlist, @@ -92,7 +93,7 @@ static bool test_raw_expression_coverage(Node *node, void *context); * a dummy CMD_UTILITY Query node. */ Query * -parse_analyze(Node *parseTree, const char *sourceText, +parse_analyze(RawStmt *parseTree, const char *sourceText, Oid *paramTypes, int numParams) { ParseState *pstate = make_parsestate(NULL); @@ -123,7 +124,7 @@ parse_analyze(Node *parseTree, const char *sourceText, * be modified or enlarged (via repalloc). */ Query * -parse_analyze_varparams(Node *parseTree, const char *sourceText, +parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, Oid **paramTypes, int *numParams) { ParseState *pstate = make_parsestate(NULL); @@ -174,14 +175,35 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState, * transformTopLevelStmt - * transform a Parse tree into a Query tree. * + * This function is just responsible for transferring statement location data + * from the RawStmt into the finished Query. + */ +Query * +transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree) +{ + Query *result; + + /* We're at top level, so allow SELECT INTO */ + result = transformOptionalSelectInto(pstate, parseTree->stmt); + + result->stmt_location = parseTree->stmt_location; + result->stmt_len = parseTree->stmt_len; + + return result; +} + +/* + * transformOptionalSelectInto - + * If SELECT has INTO, convert it to CREATE TABLE AS. + * * The only thing we do here that we don't do in transformStmt() is to * convert SELECT ... INTO into CREATE TABLE AS. Since utility statements * aren't allowed within larger statements, this is only allowed at the top * of the parse tree, and so we only try it before entering the recursive * transformStmt() processing. */ -Query * -transformTopLevelStmt(ParseState *pstate, Node *parseTree) +static Query * +transformOptionalSelectInto(ParseState *pstate, Node *parseTree) { if (IsA(parseTree, SelectStmt)) { @@ -318,11 +340,11 @@ transformStmt(ParseState *pstate, Node *parseTree) * Classification here should match transformStmt(). */ bool -analyze_requires_snapshot(Node *parseTree) +analyze_requires_snapshot(RawStmt *parseTree) { bool result; - switch (nodeTag(parseTree)) + switch (nodeTag(parseTree->stmt)) { /* * Optimizable statements @@ -338,10 +360,6 @@ analyze_requires_snapshot(Node *parseTree) * Special cases */ case T_DeclareCursorStmt: - /* yes, because it's analyzed just like SELECT */ - result = true; - break; - case T_ExplainStmt: case T_CreateTableAsStmt: /* yes, because we must analyze the contained statement */ @@ -563,8 +581,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* The grammar should have produced a SELECT */ if (!IsA(selectQuery, Query) || - selectQuery->commandType != CMD_SELECT || - selectQuery->utilityStmt != NULL) + selectQuery->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT"); /* @@ -2344,17 +2361,17 @@ transformReturningList(ParseState *pstate, List *returningList) * transformDeclareCursorStmt - * transform a DECLARE CURSOR Statement * - * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not - * significantly different from a SELECT) as far as parsing/rewriting/planning - * are concerned, but it's not passed to the executor and so in that sense is - * a utility statement. We transform it into a Query exactly as if it were - * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt - * field to carry the cursor name and options. + * DECLARE CURSOR is like other utility statements in that we emit it as a + * CMD_UTILITY Query node; however, we must first transform the contained + * query. We used to postpone that until execution, but it's really necessary + * to do it during the normal parse analysis phase to ensure that side effects + * of parser hooks happen at the expected time. */ static Query * transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) { Query *result; + Query *query; /* * Don't allow both SCROLL and NO SCROLL to be specified @@ -2365,12 +2382,13 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("cannot specify both SCROLL and NO SCROLL"))); - result = transformStmt(pstate, stmt->query); + /* Transform contained query, not allowing SELECT INTO */ + query = transformStmt(pstate, stmt->query); + stmt->query = (Node *) query; /* Grammar should not have allowed anything but SELECT */ - if (!IsA(result, Query) || - result->commandType != CMD_SELECT || - result->utilityStmt != NULL) + if (!IsA(query, Query) || + query->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR"); /* @@ -2378,47 +2396,47 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) * allowed, but the semantics of when the updates occur might be * surprising.) */ - if (result->hasModifyingCTE) + if (query->hasModifyingCTE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DECLARE CURSOR must not contain data-modifying statements in WITH"))); /* FOR UPDATE and WITH HOLD are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Holdable cursors must be READ ONLY."))); /* FOR UPDATE and SCROLL are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE SCROLL CURSOR ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Scrollable cursors must be READ ONLY."))); /* FOR UPDATE and INSENSITIVE are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE INSENSITIVE CURSOR ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Insensitive cursors must be READ ONLY."))); - /* We won't need the raw querytree any more */ - stmt->query = NULL; - + /* represent the command as a utility Query */ + result = makeNode(Query); + result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) stmt; return result; @@ -2441,7 +2459,7 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) Query *result; /* transform contained query, allowing SELECT INTO */ - stmt->query = (Node *) transformTopLevelStmt(pstate, stmt->query); + stmt->query = (Node *) transformOptionalSelectInto(pstate, stmt->query); /* represent the command as a utility Query */ result = makeNode(Query); @@ -2457,7 +2475,7 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) * transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW * Statement * - * As with EXPLAIN, transform the contained statement now. + * As with DECLARE CURSOR and EXPLAIN, transform the contained statement now. */ static Query * transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) @@ -2465,7 +2483,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) Query *result; Query *query; - /* transform contained query */ + /* transform contained query, not allowing SELECT INTO */ query = transformStmt(pstate, stmt->query); stmt->query = (Node *) query; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9eef550d2a..e61ba06efe 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -80,7 +80,8 @@ /* * The above macro assigns -1 (unknown) as the parse location of any - * nonterminal that was reduced from an empty rule. This is problematic + * nonterminal that was reduced from an empty rule, or whose leftmost + * component was reduced from an empty rule. This is problematic * for nonterminals defined like * OptFooList: / * EMPTY * / { ... } | OptFooList Foo { ... } ; * because we'll set -1 as the location during the first reduction and then @@ -91,6 +92,12 @@ * (Although we have many nonterminals that follow this pattern, we only * bother with fixing @$ like this when the nonterminal's parse location * is actually referenced in some rule.) + * + * A cleaner answer would be to make YYLLOC_DEFAULT scan all the Rhs + * locations until it's found one that's not -1. Then we'd get a correct + * location for any nonterminal that isn't entirely empty. But this way + * would add overhead to every rule reduction, and so far there's not been + * a compelling reason to pay that overhead. */ /* @@ -133,6 +140,8 @@ typedef struct ImportQual static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg); +static RawStmt *makeRawStmt(Node *stmt, int stmt_location); +static void updateRawStmtEnd(RawStmt *rs, int end_location); static Node *makeColumnRef(char *colname, List *indirection, int location, core_yyscan_t yyscanner); static Node *makeTypeCast(Node *arg, TypeName *typename, int location); @@ -758,18 +767,32 @@ stmtblock: stmtmulti } ; -/* the thrashing around here is to discard "empty" statements... */ +/* + * At top level, we wrap each stmt with a RawStmt node carrying start location + * and length of the stmt's text. Notice that the start loc/len are driven + * entirely from semicolon locations (@2). It would seem natural to use + * @1 or @3 to get the true start location of a stmt, but that doesn't work + * for statements that can start with empty nonterminals (opt_with_clause is + * the main offender here); as noted in the comments for YYLLOC_DEFAULT, + * we'd get -1 for the location in such cases. + * We also take care to discard empty statements entirely. + */ stmtmulti: stmtmulti ';' stmt { + if ($1 != NIL) + { + /* update length of previous stmt */ + updateRawStmtEnd((RawStmt *) llast($1), @2); + } if ($3 != NULL) - $$ = lappend($1, $3); + $$ = lappend($1, makeRawStmt($3, @2 + 1)); else $$ = $1; } | stmt { if ($1 != NULL) - $$ = list_make1($1); + $$ = list_make1(makeRawStmt($1, 0)); else $$ = NIL; } @@ -14474,6 +14497,33 @@ base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg) parser_yyerror(msg); } +static RawStmt * +makeRawStmt(Node *stmt, int stmt_location) +{ + RawStmt *rs = makeNode(RawStmt); + + rs->stmt = stmt; + rs->stmt_location = stmt_location; + rs->stmt_len = 0; /* might get changed later */ + return rs; +} + +/* Adjust a RawStmt to reflect that it doesn't run to the end of the string */ +static void +updateRawStmtEnd(RawStmt *rs, int end_location) +{ + /* + * If we already set the length, don't change it. This is for situations + * like "select foo ;; select bar" where the same statement will be last + * in the string for more than one semicolon. + */ + if (rs->stmt_len > 0) + return; + + /* OK, update length of RawStmt */ + rs->stmt_len = end_location - rs->stmt_location; +} + static Node * makeColumnRef(char *colname, List *indirection, int location, core_yyscan_t yyscanner) diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d788ffd799..624ab41371 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -478,12 +478,11 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) pstate->p_expr_kind = EXPR_KIND_NONE; /* - * Check that we got something reasonable. Many of these conditions are - * impossible given restrictions of the grammar, but check 'em anyway. + * Check that we got a SELECT. Anything else should be impossible given + * restrictions of the grammar, but check anyway. */ if (!IsA(query, Query) || - query->commandType != CMD_SELECT || - query->utilityStmt != NULL) + query->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in subquery in FROM"); /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f62e45f8ac..73521b93fc 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1848,12 +1848,11 @@ transformSubLink(ParseState *pstate, SubLink *sublink) qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false); /* - * Check that we got something reasonable. Many of these conditions are - * impossible given restrictions of the grammar, but check 'em anyway. + * Check that we got a SELECT. Anything else should be impossible given + * restrictions of the grammar, but check anyway. */ if (!IsA(qtree, Query) || - qtree->commandType != CMD_SELECT || - qtree->utilityStmt != NULL) + qtree->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in SubLink"); sublink->subselect = (Node *) qtree; diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index e2c884caae..6660929dec 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -720,7 +720,7 @@ typeStringToTypeName(const char *str) */ if (list_length(raw_parsetree_list) != 1) goto fail; - stmt = (SelectStmt *) linitial(raw_parsetree_list); + stmt = (SelectStmt *) ((RawStmt *) linitial(raw_parsetree_list))->stmt; if (stmt == NULL || !IsA(stmt, SelectStmt) || stmt->distinctClause != NIL || diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index c870229817..245b4cda3b 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -29,7 +29,8 @@ * raw_parser * Given a query in string form, do lexical and grammatical analysis. * - * Returns a list of raw (un-analyzed) parse trees. + * Returns a list of raw (un-analyzed) parse trees. The immediate elements + * of the list are always RawStmt nodes. */ List * raw_parser(const char *str) diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 55900d4b0e..864d45ff12 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -330,8 +330,7 @@ DefineQueryRewrite(char *rulename, */ query = (Query *) linitial(action); if (!is_instead || - query->commandType != CMD_SELECT || - query->utilityStmt != NULL) + query->commandType != CMD_SELECT) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("rules on SELECT must have action INSTEAD SELECT"))); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 05b2e57b71..bb89cce8cb 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -183,8 +183,8 @@ static int errdetail_recovery_conflict(void); static void start_xact_command(void); static void finish_xact_command(void); static bool IsTransactionExitStmt(Node *parsetree); -static bool IsTransactionExitStmtList(List *parseTrees); -static bool IsTransactionStmtList(List *parseTrees); +static bool IsTransactionExitStmtList(List *pstmts); +static bool IsTransactionStmtList(List *pstmts); static void drop_unnamed_stmt(void); static void SigHupHandler(SIGNAL_ARGS); static void log_disconnections(int code, Datum arg); @@ -588,8 +588,8 @@ ProcessClientWriteInterrupt(bool blocked) /* * Do raw parsing (only). * - * A list of parsetrees is returned, since there might be multiple - * commands in the given string. + * A list of parsetrees (RawStmt nodes) is returned, since there might be + * multiple commands in the given string. * * NOTE: for interactive queries, it is important to keep this routine * separate from the analysis & rewrite stages. Analysis and rewriting @@ -641,7 +641,7 @@ pg_parse_query(const char *query_string) * NOTE: for reasons mentioned above, this must be separate from raw parsing. */ List * -pg_analyze_and_rewrite(Node *parsetree, const char *query_string, +pg_analyze_and_rewrite(RawStmt *parsetree, const char *query_string, Oid *paramTypes, int numParams) { Query *query; @@ -676,7 +676,7 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string, * hooks instead of a fixed list of parameter datatypes. */ List * -pg_analyze_and_rewrite_params(Node *parsetree, +pg_analyze_and_rewrite_params(RawStmt *parsetree, const char *query_string, ParserSetupHook parserSetup, void *parserSetupArg) @@ -833,8 +833,10 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams) /* * Generate plans for a list of already-rewritten queries. * - * Normal optimizable statements generate PlannedStmt entries in the result - * list. Utility statements are simply represented by their statement nodes. + * For normal optimizable statements, invoke the planner. For utility + * statements, just make a wrapper PlannedStmt node. + * + * The result is a list of PlannedStmt nodes. */ List * pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams) @@ -845,16 +847,21 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams) foreach(query_list, querytrees) { Query *query = (Query *) lfirst(query_list); - Node *stmt; + PlannedStmt *stmt; if (query->commandType == CMD_UTILITY) { - /* Utility commands have no plans. */ - stmt = query->utilityStmt; + /* Utility commands require no planning. */ + stmt = makeNode(PlannedStmt); + stmt->commandType = CMD_UTILITY; + stmt->canSetTag = query->canSetTag; + stmt->utilityStmt = query->utilityStmt; + stmt->stmt_location = query->stmt_location; + stmt->stmt_len = query->stmt_len; } else { - stmt = (Node *) pg_plan_query(query, cursorOptions, boundParams); + stmt = pg_plan_query(query, cursorOptions, boundParams); } stmt_list = lappend(stmt_list, stmt); @@ -955,7 +962,7 @@ exec_simple_query(const char *query_string) */ foreach(parsetree_item, parsetree_list) { - Node *parsetree = (Node *) lfirst(parsetree_item); + RawStmt *parsetree = (RawStmt *) lfirst(parsetree_item); bool snapshot_set = false; const char *commandTag; char completionTag[COMPLETION_TAG_BUFSIZE]; @@ -971,7 +978,7 @@ exec_simple_query(const char *query_string) * do any special start-of-SQL-command processing needed by the * destination. */ - commandTag = CreateCommandTag(parsetree); + commandTag = CreateCommandTag(parsetree->stmt); set_ps_display(commandTag, false); @@ -986,7 +993,7 @@ exec_simple_query(const char *query_string) * state, but not many...) */ if (IsAbortedTransactionBlockState() && - !IsTransactionExitStmt(parsetree)) + !IsTransactionExitStmt(parsetree->stmt)) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), errmsg("current transaction is aborted, " @@ -1061,9 +1068,9 @@ exec_simple_query(const char *query_string) * backward compatibility...) */ format = 0; /* TEXT is default */ - if (IsA(parsetree, FetchStmt)) + if (IsA(parsetree->stmt, FetchStmt)) { - FetchStmt *stmt = (FetchStmt *) parsetree; + FetchStmt *stmt = (FetchStmt *) parsetree->stmt; if (!stmt->ismove) { @@ -1102,7 +1109,7 @@ exec_simple_query(const char *query_string) PortalDrop(portal, false); - if (IsA(parsetree, TransactionStmt)) + if (IsA(parsetree->stmt, TransactionStmt)) { /* * If this was a transaction control statement, commit it. We will @@ -1194,7 +1201,7 @@ exec_parse_message(const char *query_string, /* string to execute */ MemoryContext unnamed_stmt_context = NULL; MemoryContext oldcontext; List *parsetree_list; - Node *raw_parse_tree; + RawStmt *raw_parse_tree; const char *commandTag; List *querytree_list; CachedPlanSource *psrc; @@ -1279,12 +1286,12 @@ exec_parse_message(const char *query_string, /* string to execute */ bool snapshot_set = false; int i; - raw_parse_tree = (Node *) linitial(parsetree_list); + raw_parse_tree = (RawStmt *) linitial(parsetree_list); /* * Get the command name for possible use in status display. */ - commandTag = CreateCommandTag(raw_parse_tree); + commandTag = CreateCommandTag(raw_parse_tree->stmt); /* * If we are in an aborted transaction, reject all commands except @@ -1295,7 +1302,7 @@ exec_parse_message(const char *query_string, /* string to execute */ * state, but not many...) */ if (IsAbortedTransactionBlockState() && - !IsTransactionExitStmt(raw_parse_tree)) + !IsTransactionExitStmt(raw_parse_tree->stmt)) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), errmsg("current transaction is aborted, " @@ -1552,7 +1559,7 @@ exec_bind_message(StringInfo input_message) * functions. */ if (IsAbortedTransactionBlockState() && - (!IsTransactionExitStmt(psrc->raw_parse_tree) || + (!IsTransactionExitStmt(psrc->raw_parse_tree->stmt) || numParams != 0)) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), @@ -2140,11 +2147,11 @@ errdetail_execute(List *raw_parsetree_list) foreach(parsetree_item, raw_parsetree_list) { - Node *parsetree = (Node *) lfirst(parsetree_item); + RawStmt *parsetree = (RawStmt *) lfirst(parsetree_item); - if (IsA(parsetree, ExecuteStmt)) + if (IsA(parsetree->stmt, ExecuteStmt)) { - ExecuteStmt *stmt = (ExecuteStmt *) parsetree; + ExecuteStmt *stmt = (ExecuteStmt *) parsetree->stmt; PreparedStatement *pstmt; pstmt = FetchPreparedStatement(stmt->name, false); @@ -2488,45 +2495,33 @@ IsTransactionExitStmt(Node *parsetree) return false; } -/* Test a list that might contain Query nodes or bare parsetrees */ +/* Test a list that contains PlannedStmt nodes */ static bool -IsTransactionExitStmtList(List *parseTrees) +IsTransactionExitStmtList(List *pstmts) { - if (list_length(parseTrees) == 1) + if (list_length(pstmts) == 1) { - Node *stmt = (Node *) linitial(parseTrees); + PlannedStmt *pstmt = (PlannedStmt *) linitial(pstmts); - if (IsA(stmt, Query)) - { - Query *query = (Query *) stmt; - - if (query->commandType == CMD_UTILITY && - IsTransactionExitStmt(query->utilityStmt)) - return true; - } - else if (IsTransactionExitStmt(stmt)) + Assert(IsA(pstmt, PlannedStmt)); + if (pstmt->commandType == CMD_UTILITY && + IsTransactionExitStmt(pstmt->utilityStmt)) return true; } return false; } -/* Test a list that might contain Query nodes or bare parsetrees */ +/* Test a list that contains PlannedStmt nodes */ static bool -IsTransactionStmtList(List *parseTrees) +IsTransactionStmtList(List *pstmts) { - if (list_length(parseTrees) == 1) + if (list_length(pstmts) == 1) { - Node *stmt = (Node *) linitial(parseTrees); + PlannedStmt *pstmt = (PlannedStmt *) linitial(pstmts); - if (IsA(stmt, Query)) - { - Query *query = (Query *) stmt; - - if (query->commandType == CMD_UTILITY && - IsA(query->utilityStmt, TransactionStmt)) - return true; - } - else if (IsA(stmt, TransactionStmt)) + Assert(IsA(pstmt, PlannedStmt)); + if (pstmt->commandType == CMD_UTILITY && + IsA(pstmt->utilityStmt, TransactionStmt)) return true; } return false; diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 3b5da73051..704be399cf 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -43,7 +43,7 @@ static uint64 RunFromStore(Portal portal, ScanDirection direction, uint64 count, DestReceiver *dest); static uint64 PortalRunSelect(Portal portal, bool forward, long count, DestReceiver *dest); -static void PortalRunUtility(Portal portal, Node *utilityStmt, +static void PortalRunUtility(Portal portal, PlannedStmt *pstmt, bool isTopLevel, bool setHoldSnapshot, DestReceiver *dest, char *completionTag); static void PortalRunMulti(Portal portal, @@ -73,7 +73,6 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->operation = plannedstmt->commandType; /* operation */ qd->plannedstmt = plannedstmt; /* plan */ - qd->utilitystmt = plannedstmt->utilityStmt; /* in case DECLARE CURSOR */ qd->sourceText = sourceText; /* query text */ qd->snapshot = RegisterSnapshot(snapshot); /* snapshot */ /* RI check snapshot */ @@ -92,37 +91,6 @@ CreateQueryDesc(PlannedStmt *plannedstmt, return qd; } -/* - * CreateUtilityQueryDesc - */ -QueryDesc * -CreateUtilityQueryDesc(Node *utilitystmt, - const char *sourceText, - Snapshot snapshot, - DestReceiver *dest, - ParamListInfo params) -{ - QueryDesc *qd = (QueryDesc *) palloc(sizeof(QueryDesc)); - - qd->operation = CMD_UTILITY; /* operation */ - qd->plannedstmt = NULL; - qd->utilitystmt = utilitystmt; /* utility command */ - qd->sourceText = sourceText; /* query text */ - qd->snapshot = RegisterSnapshot(snapshot); /* snapshot */ - qd->crosscheck_snapshot = InvalidSnapshot; /* RI check snapshot */ - qd->dest = dest; /* output dest */ - qd->params = params; /* parameter values passed into query */ - qd->instrument_options = false; /* uninteresting for utilities */ - - /* null these fields until set by ExecutorStart */ - qd->tupDesc = NULL; - qd->estate = NULL; - qd->planstate = NULL; - qd->totaltime = NULL; - - return qd; -} - /* * FreeQueryDesc */ @@ -236,7 +204,7 @@ ProcessQuery(PlannedStmt *plan, * ChoosePortalStrategy * Select portal execution strategy given the intended statement list. * - * The list elements can be Querys, PlannedStmts, or utility statements. + * The list elements can be Querys or PlannedStmts. * That's more general than portals need, but plancache.c uses this too. * * See the comments in portal.h. @@ -263,16 +231,14 @@ ChoosePortalStrategy(List *stmts) if (query->canSetTag) { - if (query->commandType == CMD_SELECT && - query->utilityStmt == NULL) + if (query->commandType == CMD_SELECT) { if (query->hasModifyingCTE) return PORTAL_ONE_MOD_WITH; else return PORTAL_ONE_SELECT; } - if (query->commandType == CMD_UTILITY && - query->utilityStmt != NULL) + if (query->commandType == CMD_UTILITY) { if (UtilityReturnsTuples(query->utilityStmt)) return PORTAL_UTIL_SELECT; @@ -287,24 +253,24 @@ ChoosePortalStrategy(List *stmts) if (pstmt->canSetTag) { - if (pstmt->commandType == CMD_SELECT && - pstmt->utilityStmt == NULL) + if (pstmt->commandType == CMD_SELECT) { if (pstmt->hasModifyingCTE) return PORTAL_ONE_MOD_WITH; else return PORTAL_ONE_SELECT; } + if (pstmt->commandType == CMD_UTILITY) + { + if (UtilityReturnsTuples(pstmt->utilityStmt)) + return PORTAL_UTIL_SELECT; + /* it can't be ONE_RETURNING, so give up */ + return PORTAL_MULTI_QUERY; + } } } else - { - /* must be a utility command; assume it's canSetTag */ - if (UtilityReturnsTuples(stmt)) - return PORTAL_UTIL_SELECT; - /* it can't be ONE_RETURNING, so give up */ - return PORTAL_MULTI_QUERY; - } + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt)); } /* @@ -325,7 +291,8 @@ ChoosePortalStrategy(List *stmts) { if (++nSetTag > 1) return PORTAL_MULTI_QUERY; /* no need to look further */ - if (query->returningList == NIL) + if (query->commandType == CMD_UTILITY || + query->returningList == NIL) return PORTAL_MULTI_QUERY; /* no need to look further */ } } @@ -337,11 +304,13 @@ ChoosePortalStrategy(List *stmts) { if (++nSetTag > 1) return PORTAL_MULTI_QUERY; /* no need to look further */ - if (!pstmt->hasReturning) + if (pstmt->commandType == CMD_UTILITY || + !pstmt->hasReturning) return PORTAL_MULTI_QUERY; /* no need to look further */ } } - /* otherwise, utility command, assumed not canSetTag */ + else + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt)); } if (nSetTag == 1) return PORTAL_ONE_RETURNING; @@ -364,7 +333,7 @@ FetchPortalTargetList(Portal portal) if (portal->strategy == PORTAL_MULTI_QUERY) return NIL; /* get the primary statement and find out what it returns */ - return FetchStatementTargetList(PortalGetPrimaryStmt(portal)); + return FetchStatementTargetList((Node *) PortalGetPrimaryStmt(portal)); } /* @@ -372,7 +341,7 @@ FetchPortalTargetList(Portal portal) * Given a statement that returns tuples, extract the query targetlist. * Returns NIL if the statement doesn't have a determinable targetlist. * - * This can be applied to a Query, a PlannedStmt, or a utility statement. + * This can be applied to a Query or a PlannedStmt. * That's more general than portals need, but plancache.c uses this too. * * Note: do not modify the result. @@ -388,16 +357,14 @@ FetchStatementTargetList(Node *stmt) { Query *query = (Query *) stmt; - if (query->commandType == CMD_UTILITY && - query->utilityStmt != NULL) + if (query->commandType == CMD_UTILITY) { /* transfer attention to utility statement */ stmt = query->utilityStmt; } else { - if (query->commandType == CMD_SELECT && - query->utilityStmt == NULL) + if (query->commandType == CMD_SELECT) return query->targetList; if (query->returningList) return query->returningList; @@ -408,12 +375,19 @@ FetchStatementTargetList(Node *stmt) { PlannedStmt *pstmt = (PlannedStmt *) stmt; - if (pstmt->commandType == CMD_SELECT && - pstmt->utilityStmt == NULL) - return pstmt->planTree->targetlist; - if (pstmt->hasReturning) - return pstmt->planTree->targetlist; - return NIL; + if (pstmt->commandType == CMD_UTILITY) + { + /* transfer attention to utility statement */ + stmt = pstmt->utilityStmt; + } + else + { + if (pstmt->commandType == CMD_SELECT) + return pstmt->planTree->targetlist; + if (pstmt->hasReturning) + return pstmt->planTree->targetlist; + return NIL; + } } if (IsA(stmt, FetchStmt)) { @@ -566,8 +540,7 @@ PortalStart(Portal portal, ParamListInfo params, { PlannedStmt *pstmt; - pstmt = (PlannedStmt *) PortalGetPrimaryStmt(portal); - Assert(IsA(pstmt, PlannedStmt)); + pstmt = PortalGetPrimaryStmt(portal); portal->tupDesc = ExecCleanTypeFromTL(pstmt->planTree->targetlist, false); @@ -588,10 +561,10 @@ PortalStart(Portal portal, ParamListInfo params, * take care of it if needed. */ { - Node *ustmt = PortalGetPrimaryStmt(portal); + PlannedStmt *pstmt = PortalGetPrimaryStmt(portal); - Assert(!IsA(ustmt, PlannedStmt)); - portal->tupDesc = UtilityTupleDescriptor(ustmt); + Assert(pstmt->commandType == CMD_UTILITY); + portal->tupDesc = UtilityTupleDescriptor(pstmt->utilityStmt); } /* @@ -1047,7 +1020,7 @@ FillPortalStore(Portal portal, bool isTopLevel) break; case PORTAL_UTIL_SELECT: - PortalRunUtility(portal, (Node *) linitial(portal->stmts), + PortalRunUtility(portal, (PlannedStmt *) linitial(portal->stmts), isTopLevel, true, treceiver, completionTag); break; @@ -1143,10 +1116,11 @@ RunFromStore(Portal portal, ScanDirection direction, uint64 count, * Execute a utility statement inside a portal. */ static void -PortalRunUtility(Portal portal, Node *utilityStmt, +PortalRunUtility(Portal portal, PlannedStmt *pstmt, bool isTopLevel, bool setHoldSnapshot, DestReceiver *dest, char *completionTag) { + Node *utilityStmt = pstmt->utilityStmt; Snapshot snapshot; /* @@ -1186,7 +1160,7 @@ PortalRunUtility(Portal portal, Node *utilityStmt, else snapshot = NULL; - ProcessUtility(utilityStmt, + ProcessUtility(pstmt, portal->sourceText, isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY, portal->portalParams, @@ -1241,21 +1215,18 @@ PortalRunMulti(Portal portal, */ foreach(stmtlist_item, portal->stmts) { - Node *stmt = (Node *) lfirst(stmtlist_item); + PlannedStmt *pstmt = (PlannedStmt *) lfirst(stmtlist_item); /* * If we got a cancel signal in prior command, quit */ CHECK_FOR_INTERRUPTS(); - if (IsA(stmt, PlannedStmt) && - ((PlannedStmt *) stmt)->utilityStmt == NULL) + if (pstmt->utilityStmt == NULL) { /* * process a plannable query. */ - PlannedStmt *pstmt = (PlannedStmt *) stmt; - TRACE_POSTGRESQL_QUERY_EXECUTE_START(); if (log_executor_stats) @@ -1320,9 +1291,6 @@ PortalRunMulti(Portal portal, /* * process utility functions (create, destroy, etc..) * - * These are assumed canSetTag if they're the only stmt in the - * portal. - * * We must not set a snapshot here for utility commands (if one is * needed, PortalRunUtility will do it). If a utility command is * alone in a portal then everything's fine. The only case where @@ -1331,18 +1299,18 @@ PortalRunMulti(Portal portal, * whether it has a snapshot or not, so we just leave the current * snapshot alone if we have one. */ - if (list_length(portal->stmts) == 1) + if (pstmt->canSetTag) { Assert(!active_snapshot_set); /* statement can set tag string */ - PortalRunUtility(portal, stmt, isTopLevel, false, + PortalRunUtility(portal, pstmt, isTopLevel, false, dest, completionTag); } else { - Assert(IsA(stmt, NotifyStmt)); + Assert(IsA(pstmt->utilityStmt, NotifyStmt)); /* stmt added by rewrite cannot set tag */ - PortalRunUtility(portal, stmt, isTopLevel, false, + PortalRunUtility(portal, pstmt, isTopLevel, false, altdest, NULL); } } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 127dc862e8..1492101336 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -72,7 +72,7 @@ ProcessUtility_hook_type ProcessUtility_hook = NULL; /* local function declarations */ static void ProcessUtilitySlow(ParseState *pstate, - Node *parsetree, + PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, @@ -88,35 +88,33 @@ static void ExecDropStmt(DropStmt *stmt, bool isTopLevel); * the query must be *in truth* read-only, because the caller wishes * not to do CommandCounterIncrement for it. * - * Note: currently no need to support Query nodes here + * Note: currently no need to support raw or analyzed queries here */ bool -CommandIsReadOnly(Node *parsetree) +CommandIsReadOnly(PlannedStmt *pstmt) { - if (IsA(parsetree, PlannedStmt)) + Assert(IsA(pstmt, PlannedStmt)); + switch (pstmt->commandType) { - PlannedStmt *stmt = (PlannedStmt *) parsetree; - - switch (stmt->commandType) - { - case CMD_SELECT: - if (stmt->rowMarks != NIL) - return false; /* SELECT FOR [KEY] UPDATE/SHARE */ - else if (stmt->hasModifyingCTE) - return false; /* data-modifying CTE */ - else - return true; - case CMD_UPDATE: - case CMD_INSERT: - case CMD_DELETE: - return false; - default: - elog(WARNING, "unrecognized commandType: %d", - (int) stmt->commandType); - break; - } + case CMD_SELECT: + if (pstmt->rowMarks != NIL) + return false; /* SELECT FOR [KEY] UPDATE/SHARE */ + else if (pstmt->hasModifyingCTE) + return false; /* data-modifying CTE */ + else + return true; + case CMD_UPDATE: + case CMD_INSERT: + case CMD_DELETE: + return false; + case CMD_UTILITY: + /* For now, treat all utility commands as read/write */ + return false; + default: + elog(WARNING, "unrecognized commandType: %d", + (int) pstmt->commandType); + break; } - /* For now, treat all utility commands as read/write */ return false; } @@ -297,7 +295,7 @@ CheckRestrictedOperation(const char *cmdname) * ProcessUtility * general utility function invoker * - * parsetree: the parse tree for the utility statement + * pstmt: PlannedStmt wrapper for the utility statement * queryString: original source text of command * context: identifies source of statement (toplevel client command, * non-toplevel client command, subcommand of a larger utility command) @@ -315,13 +313,15 @@ CheckRestrictedOperation(const char *cmdname) * completionTag may be NULL if caller doesn't want a status string. */ void -ProcessUtility(Node *parsetree, +ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag) { + Assert(IsA(pstmt, PlannedStmt)); + Assert(pstmt->commandType == CMD_UTILITY); Assert(queryString != NULL); /* required as of 8.4 */ /* @@ -330,11 +330,11 @@ ProcessUtility(Node *parsetree, * call standard_ProcessUtility(). */ if (ProcessUtility_hook) - (*ProcessUtility_hook) (parsetree, queryString, + (*ProcessUtility_hook) (pstmt, queryString, context, params, dest, completionTag); else - standard_ProcessUtility(parsetree, queryString, + standard_ProcessUtility(pstmt, queryString, context, params, dest, completionTag); } @@ -351,13 +351,14 @@ ProcessUtility(Node *parsetree, * which requires being in a valid transaction. */ void -standard_ProcessUtility(Node *parsetree, +standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag) { + Node *parsetree = pstmt->utilityStmt; bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); ParseState *pstate; @@ -486,20 +487,10 @@ standard_ProcessUtility(Node *parsetree, /* * Portal (cursor) manipulation - * - * Note: DECLARE CURSOR is processed mostly as a SELECT, and - * therefore what we will get here is a PlannedStmt not a bare - * DeclareCursorStmt. */ - case T_PlannedStmt: - { - PlannedStmt *stmt = (PlannedStmt *) parsetree; - - if (stmt->utilityStmt == NULL || - !IsA(stmt->utilityStmt, DeclareCursorStmt)) - elog(ERROR, "non-DECLARE CURSOR PlannedStmt passed to ProcessUtility"); - PerformCursorOpen(stmt, params, queryString, isTopLevel); - } + case T_DeclareCursorStmt: + PerformCursorOpen((DeclareCursorStmt *) parsetree, params, + queryString, isTopLevel); break; case T_ClosePortalStmt: @@ -545,7 +536,9 @@ standard_ProcessUtility(Node *parsetree, { uint64 processed; - DoCopy(pstate, (CopyStmt *) parsetree, &processed); + DoCopy(pstate, (CopyStmt *) parsetree, + pstmt->stmt_location, pstmt->stmt_len, + &processed); if (completionTag) snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "COPY " UINT64_FORMAT, processed); @@ -554,7 +547,8 @@ standard_ProcessUtility(Node *parsetree, case T_PrepareStmt: CheckRestrictedOperation("PREPARE"); - PrepareQuery((PrepareStmt *) parsetree, queryString); + PrepareQuery((PrepareStmt *) parsetree, queryString, + pstmt->stmt_location, pstmt->stmt_len); break; case T_ExecuteStmt: @@ -808,11 +802,11 @@ standard_ProcessUtility(Node *parsetree, GrantStmt *stmt = (GrantStmt *) parsetree; if (EventTriggerSupportsGrantObjectType(stmt->objtype)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else - ExecuteGrantStmt((GrantStmt *) parsetree); + ExecuteGrantStmt(stmt); } break; @@ -821,7 +815,7 @@ standard_ProcessUtility(Node *parsetree, DropStmt *stmt = (DropStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->removeType)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -834,7 +828,7 @@ standard_ProcessUtility(Node *parsetree, RenameStmt *stmt = (RenameStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->renameType)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -847,7 +841,7 @@ standard_ProcessUtility(Node *parsetree, AlterObjectDependsStmt *stmt = (AlterObjectDependsStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->objectType)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -860,7 +854,7 @@ standard_ProcessUtility(Node *parsetree, AlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->objectType)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -873,7 +867,7 @@ standard_ProcessUtility(Node *parsetree, AlterOwnerStmt *stmt = (AlterOwnerStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->objectType)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -886,11 +880,11 @@ standard_ProcessUtility(Node *parsetree, CommentStmt *stmt = (CommentStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->objtype)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else - CommentObject((CommentStmt *) parsetree); + CommentObject(stmt); break; } @@ -899,7 +893,7 @@ standard_ProcessUtility(Node *parsetree, SecLabelStmt *stmt = (SecLabelStmt *) parsetree; if (EventTriggerSupportsObjectType(stmt->objtype)) - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); else @@ -909,7 +903,7 @@ standard_ProcessUtility(Node *parsetree, default: /* All other statement types have event trigger support */ - ProcessUtilitySlow(pstate, parsetree, queryString, + ProcessUtilitySlow(pstate, pstmt, queryString, context, params, dest, completionTag); break; @@ -925,13 +919,14 @@ standard_ProcessUtility(Node *parsetree, */ static void ProcessUtilitySlow(ParseState *pstate, - Node *parsetree, + PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag) { + Node *parsetree = pstmt->utilityStmt; bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY); bool needCleanup; @@ -955,7 +950,9 @@ ProcessUtilitySlow(ParseState *pstate, */ case T_CreateSchemaStmt: CreateSchemaCommand((CreateSchemaStmt *) parsetree, - queryString); + queryString, + pstmt->stmt_location, + pstmt->stmt_len); /* * EventTriggerCollectSimpleCommand called by @@ -1036,7 +1033,16 @@ ProcessUtilitySlow(ParseState *pstate, * call will stash the objects so created into our * event trigger context. */ - ProcessUtility(stmt, + PlannedStmt *wrapper; + + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = stmt; + wrapper->stmt_location = pstmt->stmt_location; + wrapper->stmt_len = pstmt->stmt_len; + + ProcessUtility(wrapper, queryString, PROCESS_UTILITY_SUBCOMMAND, params, @@ -1105,8 +1111,16 @@ ProcessUtilitySlow(ParseState *pstate, * queued commands is consistent with the way * they are executed here. */ + PlannedStmt *wrapper; + EventTriggerAlterTableEnd(); - ProcessUtility(stmt, + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = stmt; + wrapper->stmt_location = pstmt->stmt_location; + wrapper->stmt_len = pstmt->stmt_len; + ProcessUtility(wrapper, queryString, PROCESS_UTILITY_SUBCOMMAND, params, @@ -1376,7 +1390,8 @@ ProcessUtilitySlow(ParseState *pstate, case T_ViewStmt: /* CREATE VIEW */ EventTriggerAlterTableStart(parsetree); - address = DefineView((ViewStmt *) parsetree, queryString); + address = DefineView((ViewStmt *) parsetree, queryString, + pstmt->stmt_location, pstmt->stmt_len); EventTriggerCollectSimpleCommand(address, secondaryObject, parsetree); /* stashed internally */ @@ -1480,6 +1495,7 @@ ProcessUtilitySlow(ParseState *pstate, case T_AlterTSConfigurationStmt: AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); + /* * Commands are stashed in MakeConfigurationMapping and * DropConfigurationMapping, which are called from @@ -1736,10 +1752,8 @@ QueryReturnsTuples(Query *parsetree) switch (parsetree->commandType) { case CMD_SELECT: - /* returns tuples ... unless it's DECLARE CURSOR */ - if (parsetree->utilityStmt == NULL) - return true; - break; + /* returns tuples */ + return true; case CMD_INSERT: case CMD_UPDATE: case CMD_DELETE: @@ -1780,6 +1794,13 @@ UtilityContainsQuery(Node *parsetree) switch (nodeTag(parsetree)) { + case T_DeclareCursorStmt: + qry = (Query *) ((DeclareCursorStmt *) parsetree)->query; + Assert(IsA(qry, Query)); + if (qry->commandType == CMD_UTILITY) + return UtilityContainsQuery(qry->utilityStmt); + return qry; + case T_ExplainStmt: qry = (Query *) ((ExplainStmt *) parsetree)->query; Assert(IsA(qry, Query)); @@ -1931,7 +1952,8 @@ AlterObjectTypeCommandTag(ObjectType objtype) /* * CreateCommandTag * utility to get a string representation of the command operation, - * given either a raw (un-analyzed) parsetree or a planned query. + * given either a raw (un-analyzed) parsetree, an analyzed Query, + * or a PlannedStmt. * * This must handle all command types, but since the vast majority * of 'em are utility commands, it seems sensible to keep it here. @@ -1946,6 +1968,11 @@ CreateCommandTag(Node *parsetree) switch (nodeTag(parsetree)) { + /* recurse if we're given a RawStmt */ + case T_RawStmt: + tag = CreateCommandTag(((RawStmt *) parsetree)->stmt); + break; + /* raw plannable queries */ case T_InsertStmt: tag = "INSERT"; @@ -2608,12 +2635,7 @@ CreateCommandTag(Node *parsetree) * will be useful for complaints about read-only * statements */ - if (stmt->utilityStmt != NULL) - { - Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); - tag = "DECLARE CURSOR"; - } - else if (stmt->rowMarks != NIL) + if (stmt->rowMarks != NIL) { /* not 100% but probably close enough */ switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength) @@ -2647,6 +2669,9 @@ CreateCommandTag(Node *parsetree) case CMD_DELETE: tag = "DELETE"; break; + case CMD_UTILITY: + tag = CreateCommandTag(stmt->utilityStmt); + break; default: elog(WARNING, "unrecognized commandType: %d", (int) stmt->commandType); @@ -2670,12 +2695,7 @@ CreateCommandTag(Node *parsetree) * will be useful for complaints about read-only * statements */ - if (stmt->utilityStmt != NULL) - { - Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); - tag = "DECLARE CURSOR"; - } - else if (stmt->rowMarks != NIL) + if (stmt->rowMarks != NIL) { /* not 100% but probably close enough */ switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength) @@ -2735,7 +2755,8 @@ CreateCommandTag(Node *parsetree) /* * GetCommandLogLevel * utility to get the minimum log_statement level for a command, - * given either a raw (un-analyzed) parsetree or a planned query. + * given either a raw (un-analyzed) parsetree, an analyzed Query, + * or a PlannedStmt. * * This must handle all command types, but since the vast majority * of 'em are utility commands, it seems sensible to keep it here. @@ -2747,6 +2768,11 @@ GetCommandLogLevel(Node *parsetree) switch (nodeTag(parsetree)) { + /* recurse if we're given a RawStmt */ + case T_RawStmt: + lev = GetCommandLogLevel(((RawStmt *) parsetree)->stmt); + break; + /* raw plannable queries */ case T_InsertStmt: case T_DeleteStmt: @@ -2850,7 +2876,7 @@ GetCommandLogLevel(Node *parsetree) /* Look through an EXECUTE to the referenced stmt */ ps = FetchPreparedStatement(stmt->name, false); if (ps && ps->plansource->raw_parse_tree) - lev = GetCommandLogLevel(ps->plansource->raw_parse_tree); + lev = GetCommandLogLevel(ps->plansource->raw_parse_tree->stmt); else lev = LOGSTMT_ALL; } @@ -3157,6 +3183,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_MOD; break; + case CMD_UTILITY: + lev = GetCommandLogLevel(stmt->utilityStmt); + break; + default: elog(WARNING, "unrecognized commandType: %d", (int) stmt->commandType); diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index e48a878dc2..c31c603fbf 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -77,7 +77,7 @@ */ #define IsTransactionStmtPlan(plansource) \ ((plansource)->raw_parse_tree && \ - IsA((plansource)->raw_parse_tree, TransactionStmt)) + IsA((plansource)->raw_parse_tree->stmt, TransactionStmt)) /* * This is the head of the backend's list of "saved" CachedPlanSources (i.e., @@ -95,6 +95,7 @@ static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, static bool choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams); static double cached_plan_cost(CachedPlan *plan, bool include_planner); +static Query *QueryListGetPrimaryStmt(List *stmts); static void AcquireExecutorLocks(List *stmt_list, bool acquire); static void AcquirePlannerLocks(List *stmt_list, bool acquire); static void ScanQueryForLocks(Query *parsetree, bool acquire); @@ -147,7 +148,7 @@ InitPlanCache(void) * commandTag: compile-time-constant tag for query, or NULL if empty query */ CachedPlanSource * -CreateCachedPlan(Node *raw_parse_tree, +CreateCachedPlan(RawStmt *raw_parse_tree, const char *query_string, const char *commandTag) { @@ -230,7 +231,7 @@ CreateCachedPlan(Node *raw_parse_tree, * commandTag: compile-time-constant tag for query, or NULL if empty query */ CachedPlanSource * -CreateOneShotCachedPlan(Node *raw_parse_tree, +CreateOneShotCachedPlan(RawStmt *raw_parse_tree, const char *query_string, const char *commandTag) { @@ -555,7 +556,7 @@ static List * RevalidateCachedQuery(CachedPlanSource *plansource) { bool snapshot_set; - Node *rawtree; + RawStmt *rawtree; List *tlist; /* transient query-tree list */ List *qlist; /* permanent query-tree list */ TupleDesc resultDesc; @@ -976,7 +977,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, { PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); - if (!IsA(plannedstmt, PlannedStmt)) + if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ if (plannedstmt->transientPlan) @@ -1071,7 +1072,7 @@ cached_plan_cost(CachedPlan *plan, bool include_planner) { PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); - if (!IsA(plannedstmt, PlannedStmt)) + if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ result += plannedstmt->planTree->total_cost; @@ -1419,7 +1420,7 @@ CachedPlanIsValid(CachedPlanSource *plansource) List * CachedPlanGetTargetList(CachedPlanSource *plansource) { - Node *pstmt; + Query *pstmt; /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); @@ -1436,9 +1437,34 @@ CachedPlanGetTargetList(CachedPlanSource *plansource) RevalidateCachedQuery(plansource); /* Get the primary statement and find out what it returns */ - pstmt = PortalListGetPrimaryStmt(plansource->query_list); + pstmt = QueryListGetPrimaryStmt(plansource->query_list); - return FetchStatementTargetList(pstmt); + return FetchStatementTargetList((Node *) pstmt); +} + +/* + * QueryListGetPrimaryStmt + * Get the "primary" stmt within a list, ie, the one marked canSetTag. + * + * Returns NULL if no such stmt. If multiple queries within the list are + * marked canSetTag, returns the first one. Neither of these cases should + * occur in present usages of this function. + */ +static Query * +QueryListGetPrimaryStmt(List *stmts) +{ + ListCell *lc; + + foreach(lc, stmts) + { + Query *stmt = (Query *) lfirst(lc); + + Assert(IsA(stmt, Query)); + + if (stmt->canSetTag) + return stmt; + } + return NULL; } /* @@ -1456,8 +1482,9 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) int rt_index; ListCell *lc2; - Assert(!IsA(plannedstmt, Query)); - if (!IsA(plannedstmt, PlannedStmt)) + Assert(IsA(plannedstmt, PlannedStmt)); + + if (plannedstmt->commandType == CMD_UTILITY) { /* * Ignore utility statements, except those (such as EXPLAIN) that @@ -1466,7 +1493,7 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) * rule rewriting, because rewriting doesn't change the query * representation. */ - Query *query = UtilityContainsQuery((Node *) plannedstmt); + Query *query = UtilityContainsQuery(plannedstmt->utilityStmt); if (query) ScanQueryForLocks(query, acquire); @@ -1654,8 +1681,7 @@ PlanCacheComputeResultDesc(List *stmt_list) return ExecCleanTypeFromTL(query->targetList, false); case PORTAL_ONE_RETURNING: - query = (Query *) PortalListGetPrimaryStmt(stmt_list); - Assert(IsA(query, Query)); + query = QueryListGetPrimaryStmt(stmt_list); Assert(query->returningList); return ExecCleanTypeFromTL(query->returningList, false); @@ -1720,8 +1746,7 @@ PlanCacheRelCallback(Datum arg, Oid relid) { PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); - Assert(!IsA(plannedstmt, Query)); - if (!IsA(plannedstmt, PlannedStmt)) + if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL : list_member_oid(plannedstmt->relationOids, relid)) @@ -1795,8 +1820,7 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); ListCell *lc3; - Assert(!IsA(plannedstmt, Query)); - if (!IsA(plannedstmt, PlannedStmt)) + if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ foreach(lc3, plannedstmt->invalItems) { diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 5a15ed21d6..e8ebc4684c 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -139,45 +139,26 @@ GetPortalByName(const char *name) } /* - * PortalListGetPrimaryStmt + * PortalGetPrimaryStmt * Get the "primary" stmt within a portal, ie, the one marked canSetTag. * * Returns NULL if no such stmt. If multiple PlannedStmt structs within the * portal are marked canSetTag, returns the first one. Neither of these * cases should occur in present usages of this function. - * - * Copes if given a list of Querys --- can't happen in a portal, but this - * code also supports plancache.c, which needs both cases. - * - * Note: the reason this is just handed a List is so that plancache.c - * can share the code. For use with a portal, use PortalGetPrimaryStmt - * rather than calling this directly. */ -Node * -PortalListGetPrimaryStmt(List *stmts) +PlannedStmt * +PortalGetPrimaryStmt(Portal portal) { ListCell *lc; - foreach(lc, stmts) + foreach(lc, portal->stmts) { - Node *stmt = (Node *) lfirst(lc); + PlannedStmt *stmt = (PlannedStmt *) lfirst(lc); - if (IsA(stmt, PlannedStmt)) - { - if (((PlannedStmt *) stmt)->canSetTag) - return stmt; - } - else if (IsA(stmt, Query)) - { - if (((Query *) stmt)->canSetTag) - return stmt; - } - else - { - /* Utility stmts are assumed canSetTag if they're the only stmt */ - if (list_length(stmts) == 1) - return stmt; - } + Assert(IsA(stmt, PlannedStmt)); + + if (stmt->canSetTag) + return stmt; } return NULL; } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 95cfd53eec..29604b6f4e 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201612231 +#define CATALOG_VERSION_NO 201701141 #endif diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index 4785631cef..d63ca0f5e9 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -22,7 +22,8 @@ /* CopyStateData is private in commands/copy.c */ typedef struct CopyStateData *CopyState; -extern Oid DoCopy(ParseState *state, const CopyStmt *stmt, +extern void DoCopy(ParseState *state, const CopyStmt *stmt, + int stmt_location, int stmt_len, uint64 *processed); extern void ProcessCopyOptions(ParseState *pstate, CopyState cstate, bool is_from, List *options); diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 1820dd66bb..9191e186c1 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -49,6 +49,7 @@ typedef struct ExplainState /* Hook for plugins to get control in ExplainOneQuery() */ typedef void (*ExplainOneQuery_hook_type) (Query *query, + int cursorOptions, IntoClause *into, ExplainState *es, const char *queryString, diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index 0ae9b7213c..8f0e6c48f4 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -18,7 +18,7 @@ #include "utils/portal.h" -extern void PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params, +extern void PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params, const char *queryString, bool isTopLevel); extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 0a3a8addca..d8d22edbbc 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -35,7 +35,8 @@ typedef struct /* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */ -extern void PrepareQuery(PrepareStmt *stmt, const char *queryString); +extern void PrepareQuery(PrepareStmt *stmt, const char *queryString, + int stmt_location, int stmt_len); extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, const char *queryString, ParamListInfo params, DestReceiver *dest, char *completionTag); diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h index 087878ddc3..f07a389c7f 100644 --- a/src/include/commands/schemacmds.h +++ b/src/include/commands/schemacmds.h @@ -19,7 +19,8 @@ #include "nodes/parsenodes.h" extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree, - const char *queryString); + const char *queryString, + int stmt_location, int stmt_len); extern void RemoveSchemaById(Oid schemaOid); diff --git a/src/include/commands/view.h b/src/include/commands/view.h index 648665ebe5..39763913c8 100644 --- a/src/include/commands/view.h +++ b/src/include/commands/view.h @@ -19,7 +19,8 @@ extern void validateWithCheckOption(char *value); -extern ObjectAddress DefineView(ViewStmt *stmt, const char *queryString); +extern ObjectAddress DefineView(ViewStmt *stmt, const char *queryString, + int stmt_location, int stmt_len); extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace); diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index 2c221255dc..c99ea81815 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -34,8 +34,7 @@ typedef struct QueryDesc { /* These fields are provided by CreateQueryDesc */ CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */ - PlannedStmt *plannedstmt; /* planner's output, or null if utility */ - Node *utilitystmt; /* utility statement, or null */ + PlannedStmt *plannedstmt; /* planner's output (could be utility, too) */ const char *sourceText; /* source text of the query */ Snapshot snapshot; /* snapshot to use for query */ Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */ @@ -61,12 +60,6 @@ extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt, ParamListInfo params, int instrument_options); -extern QueryDesc *CreateUtilityQueryDesc(Node *utilitystmt, - const char *sourceText, - Snapshot snapshot, - DestReceiver *dest, - ParamListInfo params); - extern void FreeQueryDesc(QueryDesc *qdesc); #endif /* EXECDESC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index a1bb0ac5cb..4c4319bcab 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -301,6 +301,7 @@ typedef enum NodeTag /* * TAGS FOR STATEMENT NODES (mostly in parsenodes.h) */ + T_RawStmt, T_Query, T_PlannedStmt, T_InsertStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7ceaa22c33..edb5cd2152 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,9 @@ * This is a byte (not character) offset in the original source text, to be * used for positioning an error cursor when there is an error related to * the node. Access to the original source text is needed to make use of - * the location. + * the location. At the topmost (statement) level, we also provide a + * statement length, likewise measured in bytes, for convenience in + * identifying statement boundaries in multi-statement source strings. * * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group @@ -89,9 +91,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ * for further processing by the rewriter and planner. * * Utility statements (i.e. non-optimizable statements) have the - * utilityStmt field set, and the Query itself is mostly dummy. - * DECLARE CURSOR is a special case: it is represented like a SELECT, - * but the original DeclareCursorStmt is stored in utilityStmt. + * utilityStmt field set, and the rest of the Query is mostly dummy. * * Planning converts a Query tree into a Plan tree headed by a PlannedStmt * node --- the Query structure is not used by the executor. @@ -108,8 +108,7 @@ typedef struct Query bool canSetTag; /* do I set the command result tag? */ - Node *utilityStmt; /* non-null if this is DECLARE CURSOR or a - * non-optimizable statement */ + Node *utilityStmt; /* non-null if commandType == CMD_UTILITY */ int resultRelation; /* rtable index of target relation for * INSERT/UPDATE/DELETE; 0 for SELECT */ @@ -162,6 +161,15 @@ typedef struct Query * are only added during rewrite and * therefore are not written out as * part of Query. */ + + /* + * The following two fields identify the portion of the source text string + * containing this query. They are typically only populated in top-level + * Queries, not in sub-queries. When not set, they might both be zero, or + * both be -1 meaning "unknown". + */ + int stmt_location; /* start location, or -1 if unknown */ + int stmt_len; /* length in bytes; 0 means "rest of string" */ } Query; @@ -1307,6 +1315,30 @@ typedef struct TriggerTransition bool isTable; } TriggerTransition; +/***************************************************************************** + * Raw Grammar Output Statements + *****************************************************************************/ + +/* + * RawStmt --- container for any one statement's raw parse tree + * + * Parse analysis converts a raw parse tree headed by a RawStmt node into + * an analyzed statement headed by a Query node. For optimizable statements, + * the conversion is complex. For utility statements, the parser usually just + * transfers the raw parse tree (sans RawStmt) into the utilityStmt field of + * the Query node, and all the useful work happens at execution time. + * + * stmt_location/stmt_len identify the portion of the source text string + * containing this raw statement (useful for multi-statement strings). + */ +typedef struct RawStmt +{ + NodeTag type; + Node *stmt; /* raw parse tree */ + int stmt_location; /* start location, or -1 if unknown */ + int stmt_len; /* length in bytes; 0 means "rest of string" */ +} RawStmt; + /***************************************************************************** * Optimizable Statements *****************************************************************************/ @@ -1474,6 +1506,9 @@ typedef struct SetOperationStmt * statements do need attention from parse analysis, and this is * done by routines in parser/parse_utilcmd.c after ProcessUtility * receives the command for execution. + * DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are special cases: + * they contain optimizable statements, which get processed normally + * by parser/analyze.c. *****************************************************************************/ /* @@ -1794,7 +1829,7 @@ typedef struct CopyStmt NodeTag type; RangeVar *relation; /* the relation to copy */ Node *query; /* the query (SELECT or DML statement with - * RETURNING) to copy */ + * RETURNING) to copy, as a raw parse tree */ List *attlist; /* List of column names (as Strings), or NIL * for all columns */ bool is_from; /* TO or FROM */ @@ -2472,9 +2507,9 @@ typedef struct SecLabelStmt /* ---------------------- * Declare Cursor Statement * - * Note: the "query" field of DeclareCursorStmt is only used in the raw grammar - * output. After parse analysis it's set to null, and the Query points to the - * DeclareCursorStmt, not vice versa. + * The "query" field is initially a raw parse tree, and is converted to a + * Query node during parse analysis. Note that rewriting and planning + * of the query are always postponed until execution. * ---------------------- */ #define CURSOR_OPT_BINARY 0x0001 /* BINARY */ @@ -2493,7 +2528,7 @@ typedef struct DeclareCursorStmt NodeTag type; char *portalname; /* name of the portal (cursor) */ int options; /* bitmask of options (see above) */ - Node *query; /* the raw SELECT query */ + Node *query; /* the query (see comments above) */ } DeclareCursorStmt; /* ---------------------- @@ -2841,7 +2876,7 @@ typedef struct ViewStmt NodeTag type; RangeVar *view; /* the view to be created */ List *aliases; /* target column names */ - Node *query; /* the SELECT query */ + Node *query; /* the SELECT query (as a raw parse tree) */ bool replace; /* replace an existing view? */ List *options; /* options from WITH clause */ ViewCheckOption withCheckOption; /* WITH CHECK OPTION */ @@ -2950,9 +2985,9 @@ typedef struct VacuumStmt /* ---------------------- * Explain Statement * - * The "query" field is either a raw parse tree (SelectStmt, InsertStmt, etc) - * or a Query node if parse analysis has been done. Note that rewriting and - * planning of the query are always postponed until execution of EXPLAIN. + * The "query" field is initially a raw parse tree, and is converted to a + * Query node during parse analysis. Note that rewriting and planning + * of the query are always postponed until execution. * ---------------------- */ typedef struct ExplainStmt diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 692a62698e..6810f8c099 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -31,13 +31,18 @@ * * The output of the planner is a Plan tree headed by a PlannedStmt node. * PlannedStmt holds the "one time" information needed by the executor. + * + * For simplicity in APIs, we also wrap utility statements in PlannedStmt + * nodes; in such cases, commandType == CMD_UTILITY, the statement itself + * is in the utilityStmt field, and the rest of the struct is mostly dummy. + * (We do use canSetTag, stmt_location, stmt_len, and possibly queryId.) * ---------------- */ typedef struct PlannedStmt { NodeTag type; - CmdType commandType; /* select|insert|update|delete */ + CmdType commandType; /* select|insert|update|delete|utility */ uint32 queryId; /* query identifier (copied from Query) */ @@ -60,8 +65,6 @@ typedef struct PlannedStmt /* rtable indexes of target relations for INSERT/UPDATE/DELETE */ List *resultRelations; /* integer list of RT indexes, or NIL */ - Node *utilityStmt; /* non-null if this is DECLARE CURSOR */ - List *subplans; /* Plan trees for SubPlan expressions */ Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */ @@ -73,6 +76,12 @@ typedef struct PlannedStmt List *invalItems; /* other dependencies, as PlanInvalItems */ int nParamExec; /* number of PARAM_EXEC Params used */ + + Node *utilityStmt; /* non-null if this is utility stmt */ + + /* statement location in source string (copied from Query) */ + int stmt_location; /* start location, or -1 if unknown */ + int stmt_len; /* length in bytes; 0 means "rest of string" */ } PlannedStmt; /* macro for fetching the Plan associated with a SubPlan node */ diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 3bf4edd0bf..a7e5c55ab4 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -22,19 +22,19 @@ typedef void (*post_parse_analyze_hook_type) (ParseState *pstate, extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook; -extern Query *parse_analyze(Node *parseTree, const char *sourceText, +extern Query *parse_analyze(RawStmt *parseTree, const char *sourceText, Oid *paramTypes, int numParams); -extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText, +extern Query *parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, Oid **paramTypes, int *numParams); extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState, CommonTableExpr *parentCTE, bool locked_from_parent); -extern Query *transformTopLevelStmt(ParseState *pstate, Node *parseTree); +extern Query *transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree); extern Query *transformStmt(ParseState *pstate, Node *parseTree); -extern bool analyze_requires_snapshot(Node *parseTree); +extern bool analyze_requires_snapshot(RawStmt *parseTree); extern const char *LCS_asString(LockClauseStrength strength); extern void CheckSelectLocking(Query *qry, LockClauseStrength strength); diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index 54da6b65ef..1958be85b7 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -47,9 +47,10 @@ typedef enum extern int log_statement; extern List *pg_parse_query(const char *query_string); -extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string, +extern List *pg_analyze_and_rewrite(RawStmt *parsetree, + const char *query_string, Oid *paramTypes, int numParams); -extern List *pg_analyze_and_rewrite_params(Node *parsetree, +extern List *pg_analyze_and_rewrite_params(RawStmt *parsetree, const char *query_string, ParserSetupHook parserSetup, void *parserSetupArg); diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index 42c52a1c75..4f8d353900 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -24,16 +24,16 @@ typedef enum } ProcessUtilityContext; /* Hook for plugins to get control in ProcessUtility() */ -typedef void (*ProcessUtility_hook_type) (Node *parsetree, +typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag); extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook; -extern void ProcessUtility(Node *parsetree, const char *queryString, +extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag); -extern void standard_ProcessUtility(Node *parsetree, const char *queryString, +extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, DestReceiver *dest, char *completionTag); @@ -47,6 +47,6 @@ extern const char *CreateCommandTag(Node *parsetree); extern LogStmtLevel GetCommandLogLevel(Node *parsetree); -extern bool CommandIsReadOnly(Node *parsetree); +extern bool CommandIsReadOnly(PlannedStmt *pstmt); #endif /* UTILITY_H */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 9ee0bc6e9f..84952d56e7 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -18,6 +18,9 @@ #include "access/tupdesc.h" #include "nodes/params.h" +/* Forward declaration, to avoid including parsenodes.h here */ +struct RawStmt; + #define CACHEDPLANSOURCE_MAGIC 195726186 #define CACHEDPLAN_MAGIC 953717834 @@ -76,7 +79,7 @@ typedef struct CachedPlanSource { int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ - Node *raw_parse_tree; /* output of raw_parser(), or NULL */ + struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */ const char *query_string; /* source text of query */ const char *commandTag; /* command tag (a constant!), or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */ @@ -126,8 +129,7 @@ typedef struct CachedPlanSource typedef struct CachedPlan { int magic; /* should equal CACHEDPLAN_MAGIC */ - List *stmt_list; /* list of statement nodes (PlannedStmts and - * bare utility statements) */ + List *stmt_list; /* list of PlannedStmts */ bool is_oneshot; /* is it a "oneshot" plan? */ bool is_saved; /* is CachedPlan in a long-lived context? */ bool is_valid; /* is the stmt_list currently valid? */ @@ -144,10 +146,10 @@ typedef struct CachedPlan extern void InitPlanCache(void); extern void ResetPlanCache(void); -extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, +extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree, const char *query_string, const char *commandTag); -extern CachedPlanSource *CreateOneShotCachedPlan(Node *raw_parse_tree, +extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree, const char *query_string, const char *commandTag); extern void CompleteCachedPlan(CachedPlanSource *plansource, diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index 8359416b15..dc76acd0a4 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -133,7 +133,7 @@ typedef struct PortalData /* The query or queries the portal will execute */ const char *sourceText; /* text of query (as of 8.4, never NULL) */ const char *commandTag; /* command tag for original query */ - List *stmts; /* PlannedStmts and/or utility statements */ + List *stmts; /* list of PlannedStmts */ CachedPlan *cplan; /* CachedPlan, if stmts are from one */ ParamListInfo portalParams; /* params to pass to query */ @@ -201,7 +201,6 @@ typedef struct PortalData */ #define PortalGetQueryDesc(portal) ((portal)->queryDesc) #define PortalGetHeapMemory(portal) ((portal)->heap) -#define PortalGetPrimaryStmt(portal) PortalListGetPrimaryStmt((portal)->stmts) /* Prototypes for functions in utils/mmgr/portalmem.c */ @@ -232,7 +231,7 @@ extern void PortalDefineQuery(Portal portal, const char *commandTag, List *stmts, CachedPlan *cplan); -extern Node *PortalListGetPrimaryStmt(List *stmts); +extern PlannedStmt *PortalGetPrimaryStmt(Portal portal); extern void PortalCreateHoldStore(Portal portal); extern void PortalHashTableDeleteAll(void); extern bool ThereAreNoReadyPortals(void); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 192cbcf983..bc7b00199e 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -6827,12 +6827,11 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan) if (list_length(cplan->stmt_list) != 1) return; stmt = (PlannedStmt *) linitial(cplan->stmt_list); + Assert(IsA(stmt, PlannedStmt)); /* * 2. It must be a RESULT plan --> no scan's required */ - if (!IsA(stmt, PlannedStmt)) - return; if (stmt->commandType != CMD_SELECT) return; plan = stmt->planTree;