diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/analyze.c | 88 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 58 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 7 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 7 | ||||
-rw-r--r-- | src/backend/parser/parse_type.c | 2 | ||||
-rw-r--r-- | src/backend/parser/parser.c | 3 |
6 files changed, 116 insertions, 49 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 5116cbb71a2..8278e742f8d 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -48,6 +48,7 @@ /* Hook for plugins to get control at end of parse analysis */ post_parse_analyze_hook_type post_parse_analyze_hook = NULL; +static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); static List *transformInsertRow(ParseState *pstate, List *exprlist, @@ -92,7 +93,7 @@ static bool test_raw_expression_coverage(Node *node, void *context); * a dummy CMD_UTILITY Query node. */ Query * -parse_analyze(Node *parseTree, const char *sourceText, +parse_analyze(RawStmt *parseTree, const char *sourceText, Oid *paramTypes, int numParams) { ParseState *pstate = make_parsestate(NULL); @@ -123,7 +124,7 @@ parse_analyze(Node *parseTree, const char *sourceText, * be modified or enlarged (via repalloc). */ Query * -parse_analyze_varparams(Node *parseTree, const char *sourceText, +parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, Oid **paramTypes, int *numParams) { ParseState *pstate = make_parsestate(NULL); @@ -174,14 +175,35 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState, * transformTopLevelStmt - * transform a Parse tree into a Query tree. * + * This function is just responsible for transferring statement location data + * from the RawStmt into the finished Query. + */ +Query * +transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree) +{ + Query *result; + + /* We're at top level, so allow SELECT INTO */ + result = transformOptionalSelectInto(pstate, parseTree->stmt); + + result->stmt_location = parseTree->stmt_location; + result->stmt_len = parseTree->stmt_len; + + return result; +} + +/* + * transformOptionalSelectInto - + * If SELECT has INTO, convert it to CREATE TABLE AS. + * * The only thing we do here that we don't do in transformStmt() is to * convert SELECT ... INTO into CREATE TABLE AS. Since utility statements * aren't allowed within larger statements, this is only allowed at the top * of the parse tree, and so we only try it before entering the recursive * transformStmt() processing. */ -Query * -transformTopLevelStmt(ParseState *pstate, Node *parseTree) +static Query * +transformOptionalSelectInto(ParseState *pstate, Node *parseTree) { if (IsA(parseTree, SelectStmt)) { @@ -318,11 +340,11 @@ transformStmt(ParseState *pstate, Node *parseTree) * Classification here should match transformStmt(). */ bool -analyze_requires_snapshot(Node *parseTree) +analyze_requires_snapshot(RawStmt *parseTree) { bool result; - switch (nodeTag(parseTree)) + switch (nodeTag(parseTree->stmt)) { /* * Optimizable statements @@ -338,10 +360,6 @@ analyze_requires_snapshot(Node *parseTree) * Special cases */ case T_DeclareCursorStmt: - /* yes, because it's analyzed just like SELECT */ - result = true; - break; - case T_ExplainStmt: case T_CreateTableAsStmt: /* yes, because we must analyze the contained statement */ @@ -563,8 +581,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* The grammar should have produced a SELECT */ if (!IsA(selectQuery, Query) || - selectQuery->commandType != CMD_SELECT || - selectQuery->utilityStmt != NULL) + selectQuery->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT"); /* @@ -2344,17 +2361,17 @@ transformReturningList(ParseState *pstate, List *returningList) * transformDeclareCursorStmt - * transform a DECLARE CURSOR Statement * - * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not - * significantly different from a SELECT) as far as parsing/rewriting/planning - * are concerned, but it's not passed to the executor and so in that sense is - * a utility statement. We transform it into a Query exactly as if it were - * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt - * field to carry the cursor name and options. + * DECLARE CURSOR is like other utility statements in that we emit it as a + * CMD_UTILITY Query node; however, we must first transform the contained + * query. We used to postpone that until execution, but it's really necessary + * to do it during the normal parse analysis phase to ensure that side effects + * of parser hooks happen at the expected time. */ static Query * transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) { Query *result; + Query *query; /* * Don't allow both SCROLL and NO SCROLL to be specified @@ -2365,12 +2382,13 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("cannot specify both SCROLL and NO SCROLL"))); - result = transformStmt(pstate, stmt->query); + /* Transform contained query, not allowing SELECT INTO */ + query = transformStmt(pstate, stmt->query); + stmt->query = (Node *) query; /* Grammar should not have allowed anything but SELECT */ - if (!IsA(result, Query) || - result->commandType != CMD_SELECT || - result->utilityStmt != NULL) + if (!IsA(query, Query) || + query->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR"); /* @@ -2378,47 +2396,47 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) * allowed, but the semantics of when the updates occur might be * surprising.) */ - if (result->hasModifyingCTE) + if (query->hasModifyingCTE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DECLARE CURSOR must not contain data-modifying statements in WITH"))); /* FOR UPDATE and WITH HOLD are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Holdable cursors must be READ ONLY."))); /* FOR UPDATE and SCROLL are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE SCROLL CURSOR ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Scrollable cursors must be READ ONLY."))); /* FOR UPDATE and INSENSITIVE are not compatible */ - if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE)) + if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /*------ translator: %s is a SQL row locking clause such as FOR UPDATE */ errmsg("DECLARE INSENSITIVE CURSOR ... %s is not supported", LCS_asString(((RowMarkClause *) - linitial(result->rowMarks))->strength)), + linitial(query->rowMarks))->strength)), errdetail("Insensitive cursors must be READ ONLY."))); - /* We won't need the raw querytree any more */ - stmt->query = NULL; - + /* represent the command as a utility Query */ + result = makeNode(Query); + result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) stmt; return result; @@ -2441,7 +2459,7 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) Query *result; /* transform contained query, allowing SELECT INTO */ - stmt->query = (Node *) transformTopLevelStmt(pstate, stmt->query); + stmt->query = (Node *) transformOptionalSelectInto(pstate, stmt->query); /* represent the command as a utility Query */ result = makeNode(Query); @@ -2457,7 +2475,7 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) * transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW * Statement * - * As with EXPLAIN, transform the contained statement now. + * As with DECLARE CURSOR and EXPLAIN, transform the contained statement now. */ static Query * transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) @@ -2465,7 +2483,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) Query *result; Query *query; - /* transform contained query */ + /* transform contained query, not allowing SELECT INTO */ query = transformStmt(pstate, stmt->query); stmt->query = (Node *) query; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9eef550d2ab..e61ba06efe5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -80,7 +80,8 @@ /* * The above macro assigns -1 (unknown) as the parse location of any - * nonterminal that was reduced from an empty rule. This is problematic + * nonterminal that was reduced from an empty rule, or whose leftmost + * component was reduced from an empty rule. This is problematic * for nonterminals defined like * OptFooList: / * EMPTY * / { ... } | OptFooList Foo { ... } ; * because we'll set -1 as the location during the first reduction and then @@ -91,6 +92,12 @@ * (Although we have many nonterminals that follow this pattern, we only * bother with fixing @$ like this when the nonterminal's parse location * is actually referenced in some rule.) + * + * A cleaner answer would be to make YYLLOC_DEFAULT scan all the Rhs + * locations until it's found one that's not -1. Then we'd get a correct + * location for any nonterminal that isn't entirely empty. But this way + * would add overhead to every rule reduction, and so far there's not been + * a compelling reason to pay that overhead. */ /* @@ -133,6 +140,8 @@ typedef struct ImportQual static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg); +static RawStmt *makeRawStmt(Node *stmt, int stmt_location); +static void updateRawStmtEnd(RawStmt *rs, int end_location); static Node *makeColumnRef(char *colname, List *indirection, int location, core_yyscan_t yyscanner); static Node *makeTypeCast(Node *arg, TypeName *typename, int location); @@ -758,18 +767,32 @@ stmtblock: stmtmulti } ; -/* the thrashing around here is to discard "empty" statements... */ +/* + * At top level, we wrap each stmt with a RawStmt node carrying start location + * and length of the stmt's text. Notice that the start loc/len are driven + * entirely from semicolon locations (@2). It would seem natural to use + * @1 or @3 to get the true start location of a stmt, but that doesn't work + * for statements that can start with empty nonterminals (opt_with_clause is + * the main offender here); as noted in the comments for YYLLOC_DEFAULT, + * we'd get -1 for the location in such cases. + * We also take care to discard empty statements entirely. + */ stmtmulti: stmtmulti ';' stmt { + if ($1 != NIL) + { + /* update length of previous stmt */ + updateRawStmtEnd((RawStmt *) llast($1), @2); + } if ($3 != NULL) - $$ = lappend($1, $3); + $$ = lappend($1, makeRawStmt($3, @2 + 1)); else $$ = $1; } | stmt { if ($1 != NULL) - $$ = list_make1($1); + $$ = list_make1(makeRawStmt($1, 0)); else $$ = NIL; } @@ -14474,6 +14497,33 @@ base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg) parser_yyerror(msg); } +static RawStmt * +makeRawStmt(Node *stmt, int stmt_location) +{ + RawStmt *rs = makeNode(RawStmt); + + rs->stmt = stmt; + rs->stmt_location = stmt_location; + rs->stmt_len = 0; /* might get changed later */ + return rs; +} + +/* Adjust a RawStmt to reflect that it doesn't run to the end of the string */ +static void +updateRawStmtEnd(RawStmt *rs, int end_location) +{ + /* + * If we already set the length, don't change it. This is for situations + * like "select foo ;; select bar" where the same statement will be last + * in the string for more than one semicolon. + */ + if (rs->stmt_len > 0) + return; + + /* OK, update length of RawStmt */ + rs->stmt_len = end_location - rs->stmt_location; +} + static Node * makeColumnRef(char *colname, List *indirection, int location, core_yyscan_t yyscanner) diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d788ffd7999..624ab41371f 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -478,12 +478,11 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) pstate->p_expr_kind = EXPR_KIND_NONE; /* - * Check that we got something reasonable. Many of these conditions are - * impossible given restrictions of the grammar, but check 'em anyway. + * Check that we got a SELECT. Anything else should be impossible given + * restrictions of the grammar, but check anyway. */ if (!IsA(query, Query) || - query->commandType != CMD_SELECT || - query->utilityStmt != NULL) + query->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in subquery in FROM"); /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f62e45f8ac8..73521b93fca 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1848,12 +1848,11 @@ transformSubLink(ParseState *pstate, SubLink *sublink) qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false); /* - * Check that we got something reasonable. Many of these conditions are - * impossible given restrictions of the grammar, but check 'em anyway. + * Check that we got a SELECT. Anything else should be impossible given + * restrictions of the grammar, but check anyway. */ if (!IsA(qtree, Query) || - qtree->commandType != CMD_SELECT || - qtree->utilityStmt != NULL) + qtree->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in SubLink"); sublink->subselect = (Node *) qtree; diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index e2c884caae0..6660929dec7 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -720,7 +720,7 @@ typeStringToTypeName(const char *str) */ if (list_length(raw_parsetree_list) != 1) goto fail; - stmt = (SelectStmt *) linitial(raw_parsetree_list); + stmt = (SelectStmt *) ((RawStmt *) linitial(raw_parsetree_list))->stmt; if (stmt == NULL || !IsA(stmt, SelectStmt) || stmt->distinctClause != NIL || diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index c870229817c..245b4cda3b9 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -29,7 +29,8 @@ * raw_parser * Given a query in string form, do lexical and grammatical analysis. * - * Returns a list of raw (un-analyzed) parse trees. + * Returns a list of raw (un-analyzed) parse trees. The immediate elements + * of the list are always RawStmt nodes. */ List * raw_parser(const char *str) |