mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
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:
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user