Change representation of statement lists, and add statement location info.

This patch makes several changes that improve the consistency of
representation of lists of statements.  It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list.  This patch brings
similar consistency to the outputs of raw parsing and planning steps:

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

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

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

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

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

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

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

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

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

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

Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
This commit is contained in:
Tom Lane 2017-01-14 16:02:35 -05:00
parent 75abb955df
commit ab1f0c8225
53 changed files with 788 additions and 547 deletions

View File

@ -292,7 +292,7 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
uint64 count);
static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(Node *parsetree, const char *queryString,
static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
ProcessUtilityContext context, ParamListInfo params,
DestReceiver *dest, char *completionTag);
static uint32 pgss_hash_fn(const void *key, Size keysize);
@ -942,10 +942,12 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
* ProcessUtility hook
*/
static void
pgss_ProcessUtility(Node *parsetree, const char *queryString,
pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
ProcessUtilityContext context, ParamListInfo params,
DestReceiver *dest, char *completionTag)
{
Node *parsetree = pstmt->utilityStmt;
/*
* If it's an EXECUTE statement, we don't track it and don't increment the
* nesting level. This allows the cycles to be charged to the underlying
@ -979,11 +981,11 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
PG_TRY();
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString,
prev_ProcessUtility(pstmt, queryString,
context, params,
dest, completionTag);
else
standard_ProcessUtility(parsetree, queryString,
standard_ProcessUtility(pstmt, queryString,
context, params,
dest, completionTag);
nested_level--;
@ -1044,11 +1046,11 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
else
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString,
prev_ProcessUtility(pstmt, queryString,
context, params,
dest, completionTag);
else
standard_ProcessUtility(parsetree, queryString,
standard_ProcessUtility(pstmt, queryString,
context, params,
dest, completionTag);
}

View File

@ -297,13 +297,14 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
* break whole of the things if nefarious user would use.
*/
static void
sepgsql_utility_command(Node *parsetree,
sepgsql_utility_command(PlannedStmt *pstmt,
const char *queryString,
ProcessUtilityContext context,
ParamListInfo params,
DestReceiver *dest,
char *completionTag)
{
Node *parsetree = pstmt->utilityStmt;
sepgsql_context_info_t saved_context_info = sepgsql_context_info;
ListCell *cell;
@ -362,11 +363,11 @@ sepgsql_utility_command(Node *parsetree,
}
if (next_ProcessUtility_hook)
(*next_ProcessUtility_hook) (parsetree, queryString,
(*next_ProcessUtility_hook) (pstmt, queryString,
context, params,
dest, completionTag);
else
standard_ProcessUtility(parsetree, queryString,
standard_ProcessUtility(pstmt, queryString,
context, params,
dest, completionTag);
}

View File

@ -934,7 +934,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
querytree_list = NIL;
foreach(lc, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(lc);
RawStmt *parsetree = (RawStmt *) lfirst(lc);
List *querytree_sublist;
querytree_sublist = pg_analyze_and_rewrite_params(parsetree,

View File

@ -287,13 +287,13 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* non-export function prototypes */
static CopyState BeginCopy(ParseState *pstate, bool is_from, Relation rel, Node *raw_query,
const Oid queryRelId, List *attnamelist,
static CopyState BeginCopy(ParseState *pstate, bool is_from, Relation rel,
RawStmt *raw_query, Oid queryRelId, List *attnamelist,
List *options);
static void EndCopy(CopyState cstate);
static void ClosePipeToProgram(CopyState cstate);
static CopyState BeginCopyTo(ParseState *pstate, Relation rel, Node *query,
const Oid queryRelId, const char *filename, bool is_program,
static CopyState BeginCopyTo(ParseState *pstate, Relation rel, RawStmt *query,
Oid queryRelId, const char *filename, bool is_program,
List *attnamelist, List *options);
static void EndCopyTo(CopyState cstate);
static uint64 DoCopyTo(CopyState cstate);
@ -770,15 +770,17 @@ CopyLoadRawBuf(CopyState cstate)
* Do not allow the copy if user doesn't have proper permission to access
* the table or the specifically requested columns.
*/
Oid
DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
void
DoCopy(ParseState *pstate, const CopyStmt *stmt,
int stmt_location, int stmt_len,
uint64 *processed)
{
CopyState cstate;
bool is_from = stmt->is_from;
bool pipe = (stmt->filename == NULL);
Relation rel;
Oid relid;
Node *query = NULL;
RawStmt *query = NULL;
List *range_table = NIL;
/* Disallow COPY to/from file or program except to superusers. */
@ -929,7 +931,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
select->targetList = targetList;
select->fromClause = list_make1(from);
query = (Node *) select;
query = makeNode(RawStmt);
query->stmt = (Node *) select;
query->stmt_location = stmt_location;
query->stmt_len = stmt_len;
/*
* Close the relation for now, but keep the lock on it to prevent
@ -945,7 +950,11 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
{
Assert(stmt->query);
query = stmt->query;
query = makeNode(RawStmt);
query->stmt = stmt->query;
query->stmt_location = stmt_location;
query->stmt_len = stmt_len;
relid = InvalidOid;
rel = NULL;
}
@ -981,8 +990,6 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, uint64 *processed)
*/
if (rel != NULL)
heap_close(rel, (is_from ? NoLock : AccessShareLock));
return relid;
}
/*
@ -1364,8 +1371,8 @@ static CopyState
BeginCopy(ParseState *pstate,
bool is_from,
Relation rel,
Node *raw_query,
const Oid queryRelId,
RawStmt *raw_query,
Oid queryRelId,
List *attnamelist,
List *options)
{
@ -1456,7 +1463,7 @@ BeginCopy(ParseState *pstate,
* function and is executed repeatedly. (See also the same hack in
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
rewritten = pg_analyze_and_rewrite((RawStmt *) copyObject(raw_query),
pstate->p_sourcetext, NULL, 0);
/* check that we got back something we can work with */
@ -1747,8 +1754,8 @@ EndCopy(CopyState cstate)
static CopyState
BeginCopyTo(ParseState *pstate,
Relation rel,
Node *query,
const Oid queryRelId,
RawStmt *query,
Oid queryRelId,
const char *filename,
bool is_program,
List *attnamelist,

View File

@ -326,7 +326,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
/* plan the query */
/* plan the query --- note we disallow parallelism */
plan = pg_plan_query(query, 0, params);
/*

View File

@ -53,7 +53,8 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
#define X_CLOSE_IMMEDIATE 2
#define X_NOWHITESPACE 4
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
static void ExplainOneQuery(Query *query, int cursorOptions,
IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
@ -245,7 +246,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
/* Explain every plan */
foreach(l, rewritten)
{
ExplainOneQuery((Query *) lfirst(l), NULL, es,
ExplainOneQuery((Query *) lfirst(l),
CURSOR_OPT_PARALLEL_OK, NULL, es,
queryString, params);
/* Separate plans with an appropriate separator */
@ -329,7 +331,8 @@ ExplainResultDesc(ExplainStmt *stmt)
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
*/
static void
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
ExplainOneQuery(Query *query, int cursorOptions,
IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
/* planner will not cope with utility statements */
@ -341,7 +344,8 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
/* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook)
(*ExplainOneQuery_hook) (query, into, es, queryString, params);
(*ExplainOneQuery_hook) (query, cursorOptions, into, es,
queryString, params);
else
{
PlannedStmt *plan;
@ -351,7 +355,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
INSTR_TIME_SET_CURRENT(planstart);
/* plan the query */
plan = pg_plan_query(query, into ? 0 : CURSOR_OPT_PARALLEL_OK, params);
plan = pg_plan_query(query, cursorOptions, params);
INSTR_TIME_SET_CURRENT(planduration);
INSTR_TIME_SUBTRACT(planduration, planstart);
@ -385,6 +389,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
* We have to rewrite the contained SELECT and then pass it back to
* ExplainOneQuery. It's probably not really necessary to copy the
* contained parsetree another time, but let's be safe.
*
* Like ExecCreateTableAs, disallow parallelism in the plan.
*/
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
List *rewritten;
@ -392,7 +398,28 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
Assert(IsA(ctas->query, Query));
rewritten = QueryRewrite((Query *) copyObject(ctas->query));
Assert(list_length(rewritten) == 1);
ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
ExplainOneQuery((Query *) linitial(rewritten),
0, ctas->into, es,
queryString, params);
}
else if (IsA(utilityStmt, DeclareCursorStmt))
{
/*
* Likewise for DECLARE CURSOR.
*
* Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
* actually run the query. This is different from pre-8.3 behavior
* but seems more useful than not running the query. No cursor will
* be created, however.
*/
DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
List *rewritten;
Assert(IsA(dcs->query, Query));
rewritten = QueryRewrite((Query *) copyObject(dcs->query));
Assert(list_length(rewritten) == 1);
ExplainOneQuery((Query *) linitial(rewritten),
dcs->options, NULL, es,
queryString, params);
}
else if (IsA(utilityStmt, ExecuteStmt))
@ -423,11 +450,6 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
* in which case executing the query should result in creating that table.
*
* Since we ignore any DeclareCursorStmt that might be attached to the query,
* if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
* query. This is different from pre-8.3 behavior but seems more useful than
* not running the query. No cursor will be created, however.
*
* This is exported because it's called back from prepare.c in the
* EXPLAIN EXECUTE case, and because an index advisor plugin would need
* to call it.
@ -444,6 +466,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
int eflags;
int instrument_option = 0;
Assert(plannedstmt->commandType != CMD_UTILITY);
if (es->analyze && es->timing)
instrument_option |= INSTRUMENT_TIMER;
else if (es->analyze)

View File

@ -712,7 +712,7 @@ execute_sql_string(const char *sql, const char *filename)
*/
foreach(lc1, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(lc1);
RawStmt *parsetree = (RawStmt *) lfirst(lc1);
List *stmt_list;
ListCell *lc2;
@ -724,23 +724,17 @@ execute_sql_string(const char *sql, const char *filename)
foreach(lc2, stmt_list)
{
Node *stmt = (Node *) lfirst(lc2);
if (IsA(stmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("transaction control statements are not allowed within an extension script")));
PlannedStmt *stmt = (PlannedStmt *) lfirst(lc2);
CommandCounterIncrement();
PushActiveSnapshot(GetTransactionSnapshot());
if (IsA(stmt, PlannedStmt) &&
((PlannedStmt *) stmt)->utilityStmt == NULL)
if (stmt->utilityStmt == NULL)
{
QueryDesc *qdesc;
qdesc = CreateQueryDesc((PlannedStmt *) stmt,
qdesc = CreateQueryDesc(stmt,
sql,
GetActiveSnapshot(), NULL,
dest, NULL, 0);
@ -754,6 +748,11 @@ execute_sql_string(const char *sql, const char *filename)
}
else
{
if (IsA(stmt->utilityStmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("transaction control statements are not allowed within an extension script")));
ProcessUtility(stmt,
sql,
PROCESS_UTILITY_QUERY,
@ -1434,7 +1433,8 @@ CreateExtensionInternal(char *extensionName,
csstmt->authrole = NULL; /* will be created by current user */
csstmt->schemaElts = NIL;
csstmt->if_not_exists = false;
CreateSchemaCommand(csstmt, NULL);
CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-1, -1);
/*
* CreateSchemaCommand includes CommandCounterIncrement, so new

View File

@ -1572,7 +1572,9 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
*/
foreach(lc2, raw_parsetree_list)
{
CreateForeignTableStmt *cstmt = lfirst(lc2);
RawStmt *rs = (RawStmt *) lfirst(lc2);
CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) rs->stmt;
PlannedStmt *pstmt;
/*
* Because we only allow CreateForeignTableStmt, we can skip parse
@ -1593,8 +1595,16 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
/* Ensure creation schema is the one given in IMPORT statement */
cstmt->base.relation->schemaname = pstrdup(stmt->local_schema);
/* No planning needed, just make a wrapper PlannedStmt */
pstmt = makeNode(PlannedStmt);
pstmt->commandType = CMD_UTILITY;
pstmt->canSetTag = false;
pstmt->utilityStmt = (Node *) cstmt;
pstmt->stmt_location = rs->stmt_location;
pstmt->stmt_len = rs->stmt_len;
/* Execute statement */
ProcessUtility((Node *) cstmt,
ProcessUtility(pstmt,
cmd,
PROCESS_UTILITY_SUBCOMMAND, NULL,
None_Receiver, NULL);

View File

@ -27,7 +27,9 @@
#include "commands/portalcmds.h"
#include "executor/executor.h"
#include "executor/tstoreReceiver.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@ -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;

View File

@ -52,8 +52,10 @@ static Datum build_regtype_array(Oid *param_types, int num_params);
* Implements the 'PREPARE' utility statement.
*/
void
PrepareQuery(PrepareStmt *stmt, const char *queryString)
PrepareQuery(PrepareStmt *stmt, const char *queryString,
int stmt_location, int stmt_len)
{
RawStmt *rawstmt;
CachedPlanSource *plansource;
Oid *argtypes = NULL;
int nargs;
@ -70,11 +72,23 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
errmsg("invalid statement name: must not be empty")));
/*
* Need to wrap the contained statement in a RawStmt node to pass it to
* parse analysis.
*
* Because parse analysis scribbles on the raw querytree, we must make a
* copy to ensure we don't modify the passed-in tree. FIXME someday.
*/
rawstmt = makeNode(RawStmt);
rawstmt->stmt = (Node *) copyObject(stmt->query);
rawstmt->stmt_location = stmt_location;
rawstmt->stmt_len = stmt_len;
/*
* Create the CachedPlanSource before we do parse analysis, since it needs
* to see the unmodified raw parse tree.
*/
plansource = CreateCachedPlan(stmt->query, queryString,
plansource = CreateCachedPlan(rawstmt, queryString,
CreateCommandTag(stmt->query));
/* Transform list of TypeNames to array of type OIDs */
@ -108,12 +122,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* Analyze the statement using these parameter types (any parameters
* passed in from above us will not be visible to it), allowing
* information about unknown parameters to be deduced from context.
*
* Because parse analysis scribbles on the raw querytree, we must make a
* copy to ensure we don't modify the passed-in tree. FIXME someday.
*/
query = parse_analyze_varparams((Node *) copyObject(stmt->query),
queryString,
query = parse_analyze_varparams(rawstmt, queryString,
&argtypes, &nargs);
/*
@ -256,9 +266,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
pstmt = (PlannedStmt *) linitial(plan_list);
if (!IsA(pstmt, PlannedStmt) ||
pstmt->commandType != CMD_SELECT ||
pstmt->utilityStmt != NULL)
Assert(IsA(pstmt, PlannedStmt));
if (pstmt->commandType != CMD_SELECT)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
@ -664,10 +673,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
{
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt))
Assert(IsA(pstmt, PlannedStmt));
if (pstmt->commandType != CMD_UTILITY)
ExplainOnePlan(pstmt, into, es, query_string, paramLI, NULL);
else
ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI);
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */

View File

@ -40,9 +40,16 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
/*
* CREATE SCHEMA
*
* Note: caller should pass in location information for the whole
* CREATE SCHEMA statement, which in turn we pass down as the location
* of the component commands. This comports with our general plan of
* reporting location/len for the whole command even when executing
* a subquery.
*/
Oid
CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
int stmt_location, int stmt_len)
{
const char *schemaName = stmt->schemaname;
Oid namespaceId;
@ -172,14 +179,24 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
foreach(parsetree_item, parsetree_list)
{
Node *stmt = (Node *) lfirst(parsetree_item);
PlannedStmt *wrapper;
/* need to make a wrapper PlannedStmt */
wrapper = makeNode(PlannedStmt);
wrapper->commandType = CMD_UTILITY;
wrapper->canSetTag = false;
wrapper->utilityStmt = stmt;
wrapper->stmt_location = stmt_location;
wrapper->stmt_len = stmt_len;
/* do this step */
ProcessUtility(stmt,
ProcessUtility(wrapper,
queryString,
PROCESS_UTILITY_SUBCOMMAND,
NULL,
None_Receiver,
NULL);
/* make sure later steps can see the object created here */
CommandCounterIncrement();
}

View File

@ -9285,7 +9285,8 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = NIL;
foreach(list_item, raw_parsetree_list)
{
Node *stmt = (Node *) lfirst(list_item);
RawStmt *rs = (RawStmt *) lfirst(list_item);
Node *stmt = rs->stmt;
if (IsA(stmt, IndexStmt))
querytree_list = lappend(querytree_list,

View File

@ -1078,6 +1078,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
AlterTableStmt *atstmt = makeNode(AlterTableStmt);
AlterTableCmd *atcmd = makeNode(AlterTableCmd);
Constraint *fkcon = makeNode(Constraint);
PlannedStmt *wrapper = makeNode(PlannedStmt);
ereport(NOTICE,
(errmsg("converting trigger group into constraint \"%s\" %s",
@ -1167,8 +1168,15 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
fkcon->skip_validation = false;
fkcon->initially_valid = true;
/* finally, wrap it in a dummy PlannedStmt */
wrapper->commandType = CMD_UTILITY;
wrapper->canSetTag = false;
wrapper->utilityStmt = (Node *) atstmt;
wrapper->stmt_location = -1;
wrapper->stmt_len = -1;
/* ... and execute it */
ProcessUtility((Node *) atstmt,
ProcessUtility(wrapper,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
PROCESS_UTILITY_SUBCOMMAND, NULL,
None_Receiver, NULL);

View File

@ -414,8 +414,10 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
* Execute a CREATE VIEW command.
*/
ObjectAddress
DefineView(ViewStmt *stmt, const char *queryString)
DefineView(ViewStmt *stmt, const char *queryString,
int stmt_location, int stmt_len)
{
RawStmt *rawstmt;
Query *viewParse;
RangeVar *view;
ListCell *cell;
@ -429,8 +431,12 @@ DefineView(ViewStmt *stmt, const char *queryString)
* Since parse analysis scribbles on its input, copy the raw parse tree;
* this ensures we don't corrupt a prepared statement, for example.
*/
viewParse = parse_analyze((Node *) copyObject(stmt->query),
queryString, NULL, 0);
rawstmt = makeNode(RawStmt);
rawstmt->stmt = (Node *) copyObject(stmt->query);
rawstmt->stmt_location = stmt_location;
rawstmt->stmt_len = stmt_len;
viewParse = parse_analyze(rawstmt, queryString, NULL, 0);
/*
* The grammar should ensure that the result is a single SELECT Query.
@ -443,8 +449,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("views must not contain SELECT INTO")));
if (viewParse->commandType != CMD_SELECT ||
viewParse->utilityStmt != NULL)
if (viewParse->commandType != CMD_SELECT)
elog(ERROR, "unexpected parse analysis result");
/*

View File

@ -156,13 +156,15 @@ ExecSerializePlan(Plan *plan, EState *estate)
pstmt->planTree = plan;
pstmt->rtable = estate->es_range_table;
pstmt->resultRelations = NIL;
pstmt->utilityStmt = NULL;
pstmt->subplans = NIL;
pstmt->rewindPlanIDs = NULL;
pstmt->rowMarks = NIL;
pstmt->relationOids = NIL;
pstmt->invalItems = NIL; /* workers can't replan anyway... */
pstmt->nParamExec = estate->es_plannedstmt->nParamExec;
pstmt->utilityStmt = NULL;
pstmt->stmt_location = -1;
pstmt->stmt_len = -1;
/* Return serialized copy of our dummy PlannedStmt. */
return nodeToString(pstmt);

View File

@ -66,7 +66,7 @@ typedef struct execution_state
ExecStatus status;
bool setsResult; /* true if this query produces func's result */
bool lazyEval; /* true if should fetch one row at a time */
Node *stmt; /* PlannedStmt or utility statement */
PlannedStmt *stmt; /* plan for this query */
QueryDesc *qd; /* null unless status == RUN */
} execution_state;
@ -487,45 +487,56 @@ init_execution_state(List *queryTree_list,
foreach(lc2, qtlist)
{
Query *queryTree = (Query *) lfirst(lc2);
Node *stmt;
PlannedStmt *stmt;
execution_state *newes;
Assert(IsA(queryTree, Query));
/* Plan the query if needed */
if (queryTree->commandType == CMD_UTILITY)
stmt = queryTree->utilityStmt;
{
/* Utility commands require no planning. */
stmt = makeNode(PlannedStmt);
stmt->commandType = CMD_UTILITY;
stmt->canSetTag = queryTree->canSetTag;
stmt->utilityStmt = queryTree->utilityStmt;
stmt->stmt_location = queryTree->stmt_location;
stmt->stmt_len = queryTree->stmt_len;
}
else
stmt = (Node *) pg_plan_query(queryTree,
stmt = pg_plan_query(queryTree,
fcache->readonly_func ? CURSOR_OPT_PARALLEL_OK : 0,
NULL);
NULL);
/*
* Precheck all commands for validity in a function. This should
* generally match the restrictions spi.c applies.
*/
if (IsA(stmt, CopyStmt) &&
((CopyStmt *) stmt)->filename == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
if (stmt->commandType == CMD_UTILITY)
{
if (IsA(stmt->utilityStmt, CopyStmt) &&
((CopyStmt *) stmt->utilityStmt)->filename == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot COPY to/from client in a SQL function")));
if (IsA(stmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a SQL function",
CreateCommandTag(stmt))));
if (IsA(stmt->utilityStmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a SQL function",
CreateCommandTag(stmt->utilityStmt))));
}
if (fcache->readonly_func && !CommandIsReadOnly(stmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateCommandTag(stmt))));
CreateCommandTag((Node *) stmt))));
if (IsInParallelMode() && !CommandIsReadOnly(stmt))
PreventCommandIfParallelMode(CreateCommandTag(stmt));
PreventCommandIfParallelMode(CreateCommandTag((Node *) stmt));
/* OK, build the execution_state for this query */
newes = (execution_state *) palloc(sizeof(execution_state));
@ -569,15 +580,9 @@ init_execution_state(List *queryTree_list,
{
lasttages->setsResult = true;
if (lazyEvalOK &&
IsA(lasttages->stmt, PlannedStmt))
{
PlannedStmt *ps = (PlannedStmt *) lasttages->stmt;
if (ps->commandType == CMD_SELECT &&
ps->utilityStmt == NULL &&
!ps->hasModifyingCTE)
fcache->lazyEval = lasttages->lazyEval = true;
}
lasttages->stmt->commandType == CMD_SELECT &&
!lasttages->stmt->hasModifyingCTE)
fcache->lazyEval = lasttages->lazyEval = true;
}
return eslist;
@ -704,7 +709,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
flat_query_list = NIL;
foreach(lc, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(lc);
RawStmt *parsetree = (RawStmt *) lfirst(lc);
List *queryTree_sublist;
queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
@ -801,22 +806,15 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
else
dest = None_Receiver;
if (IsA(es->stmt, PlannedStmt))
es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
fcache->src,
GetActiveSnapshot(),
InvalidSnapshot,
dest,
fcache->paramLI, 0);
else
es->qd = CreateUtilityQueryDesc(es->stmt,
fcache->src,
GetActiveSnapshot(),
dest,
fcache->paramLI);
es->qd = CreateQueryDesc(es->stmt,
fcache->src,
GetActiveSnapshot(),
InvalidSnapshot,
dest,
fcache->paramLI, 0);
/* Utility commands don't need Executor. */
if (es->qd->utilitystmt == NULL)
if (es->qd->operation != CMD_UTILITY)
{
/*
* In lazyEval mode, do not let the executor set up an AfterTrigger
@ -844,12 +842,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{
bool result;
if (es->qd->utilitystmt)
if (es->qd->operation == CMD_UTILITY)
{
/* ProcessUtility needs the PlannedStmt for DECLARE CURSOR */
ProcessUtility((es->qd->plannedstmt ?
(Node *) es->qd->plannedstmt :
es->qd->utilitystmt),
ProcessUtility(es->qd->plannedstmt,
fcache->src,
PROCESS_UTILITY_QUERY,
es->qd->params,
@ -882,7 +877,7 @@ postquel_end(execution_state *es)
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
if (es->qd->utilitystmt == NULL)
if (es->qd->operation != CMD_UTILITY)
{
ExecutorFinish(es->qd);
ExecutorEnd(es->qd);
@ -1576,8 +1571,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
* entities.
*/
if (parse &&
parse->commandType == CMD_SELECT &&
parse->utilityStmt == NULL)
parse->commandType == CMD_SELECT)
{
tlist_ptr = &parse->targetList;
tlist = parse->targetList;

View File

@ -1232,7 +1232,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
{
if (list_length(stmt_list) == 1 &&
IsA((Node *) linitial(stmt_list), PlannedStmt) &&
((PlannedStmt *) linitial(stmt_list))->commandType != CMD_UTILITY &&
((PlannedStmt *) linitial(stmt_list))->rowMarks == NIL &&
ExecSupportsBackwardScan(((PlannedStmt *) linitial(stmt_list))->planTree))
portal->cursorOptions |= CURSOR_OPT_SCROLL;
@ -1248,7 +1248,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
{
if (list_length(stmt_list) == 1 &&
IsA((Node *) linitial(stmt_list), PlannedStmt) &&
((PlannedStmt *) linitial(stmt_list))->commandType != CMD_UTILITY &&
((PlannedStmt *) linitial(stmt_list))->rowMarks != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -1270,7 +1270,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
foreach(lc, stmt_list)
{
Node *pstmt = (Node *) lfirst(lc);
PlannedStmt *pstmt = (PlannedStmt *) lfirst(lc);
if (!CommandIsReadOnly(pstmt))
{
@ -1279,9 +1279,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateCommandTag(pstmt))));
CreateCommandTag((Node *) pstmt))));
else
PreventCommandIfParallelMode(CreateCommandTag(pstmt));
PreventCommandIfParallelMode(CreateCommandTag((Node *) pstmt));
}
}
}
@ -1757,7 +1757,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
foreach(list_item, raw_parsetree_list)
{
Node *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 */

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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? */

View File

@ -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;
/*

View File

@ -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;

View File

@ -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)

View File

@ -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");
/*

View File

@ -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;

View File

@ -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 ||

View File

@ -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)

View File

@ -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")));

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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;
}

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201612231
#define CATALOG_VERSION_NO 201701141
#endif

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 */

View File

@ -301,6 +301,7 @@ typedef enum NodeTag
/*
* TAGS FOR STATEMENT NODES (mostly in parsenodes.h)
*/
T_RawStmt,
T_Query,
T_PlannedStmt,
T_InsertStmt,

View File

@ -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

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -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 */

View File

@ -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,

View File

@ -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);

View File

@ -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;