*** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 608,615 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * The grammar thinks these are keywords, but they are not in the kwlist.h * list and so can never be entered directly. The filter in parser.c * creates these tokens when required. */ ! %token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ --- 608,645 ---- * The grammar thinks these are keywords, but they are not in the kwlist.h * list and so can never be entered directly. The filter in parser.c * creates these tokens when required. + * + * The rules for referencing WITH and these special lookahead keywords are + * as follows: + * + * If WITH is followed by a fixed token, such as WITH OIDS, or a non-keyword + * token such as '(', then use WITH directly, except as indicated below. + * + * If WITH could be followed by an object name, then use the with_keyword + * production instead. Also, if there are alternative branches in which some + * have a fixed keyword following WITH and some have an object name, then + * use with_keyword for all of them, overriding the above rule. + * + * (Similar rules would apply for NULLS_P, but currently there are no + * instances in the grammar where this is used other than as a special + * case or as an identifier.) + * + * The productions associated with these special cases are listed under + * "Special-case keyword sequences" near the end of the grammar. It is + * intended that these be the ONLY places that the special lookahead + * keywords appear, in order to avoid complicating the main body of the + * grammar. + * + * To add a new special case: + * - add the special token names here in a %token decl + * - add or extend the productions under "Special-case keyword sequences" + * - add appropriate comparisons in: + * base_yylex in src/backend/parser/parser.c + * filtered_base_yylex in src/interfaces/ecpg/preproc/parser.c */ ! ! %token NULLS_BEFORE_FIRST NULLS_BEFORE_LAST ! %token WITH_BEFORE_ORDINALITY WITH_BEFORE_TIME /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ *************** *** 838,848 **** CreateRoleStmt: } ; - - opt_with: WITH {} - | /*EMPTY*/ {} - ; - /* * Options for CREATE ROLE and ALTER ROLE (also used by CREATE/ALTER USER * for backwards compatibility). Note: the only option required by SQL99 --- 868,873 ---- *************** *** 3127,3138 **** ExclusionConstraintList: { $$ = lappend($1, $3); } ; ! ExclusionConstraintElem: index_elem WITH any_operator { $$ = list_make2($1, $3); } /* allow OPERATOR() decoration for the benefit of ruleutils.c */ ! | index_elem WITH OPERATOR '(' any_operator ')' { $$ = list_make2($1, $5); } --- 3152,3163 ---- { $$ = lappend($1, $3); } ; ! ExclusionConstraintElem: index_elem with_keyword any_operator { $$ = list_make2($1, $3); } /* allow OPERATOR() decoration for the benefit of ruleutils.c */ ! | index_elem with_keyword OPERATOR '(' any_operator ')' { $$ = list_make2($1, $5); } *************** *** 6188,6195 **** opt_asc_desc: ASC { $$ = SORTBY_ASC; } | /*EMPTY*/ { $$ = SORTBY_DEFAULT; } ; ! opt_nulls_order: NULLS_FIRST { $$ = SORTBY_NULLS_FIRST; } ! | NULLS_LAST { $$ = SORTBY_NULLS_LAST; } | /*EMPTY*/ { $$ = SORTBY_NULLS_DEFAULT; } ; --- 6213,6220 ---- | /*EMPTY*/ { $$ = SORTBY_DEFAULT; } ; ! opt_nulls_order: nulls_first { $$ = SORTBY_NULLS_FIRST; } ! | nulls_last { $$ = SORTBY_NULLS_LAST; } | /*EMPTY*/ { $$ = SORTBY_NULLS_DEFAULT; } ; *************** *** 8348,8354 **** AlterTSDictionaryStmt: ; AlterTSConfigurationStmt: ! ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list WITH any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; --- 8373,8379 ---- ; AlterTSConfigurationStmt: ! ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list with_keyword any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; *************** *** 8358,8364 **** AlterTSConfigurationStmt: n->replace = false; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list WITH any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; --- 8383,8389 ---- n->replace = false; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list with_keyword any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; *************** *** 8368,8374 **** AlterTSConfigurationStmt: n->replace = false; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name WITH any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; --- 8393,8399 ---- n->replace = false; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name with_keyword any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; *************** *** 8378,8384 **** AlterTSConfigurationStmt: n->replace = true; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name WITH any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; --- 8403,8409 ---- n->replace = true; $$ = (Node*)n; } ! | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name with_keyword any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; *************** *** 9247,9260 **** simple_select: * We don't currently support the SEARCH or CYCLE clause. */ with_clause: ! WITH cte_list { $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; $$->location = @1; } ! | WITH RECURSIVE cte_list { $$ = makeNode(WithClause); $$->ctes = $3; --- 9272,9285 ---- * We don't currently support the SEARCH or CYCLE clause. */ with_clause: ! with_keyword cte_list { $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; $$->location = @1; } ! | with_keyword RECURSIVE cte_list { $$ = makeNode(WithClause); $$->ctes = $3; *************** *** 9593,9599 **** table_ref: relation_expr opt_alias_clause n->coldeflist = lsecond($2); $$ = (Node *) n; } ! | func_table WITH_ORDINALITY func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = false; --- 9618,9624 ---- n->coldeflist = lsecond($2); $$ = (Node *) n; } ! | func_table with_ordinality func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = false; *************** *** 9613,9619 **** table_ref: relation_expr opt_alias_clause n->coldeflist = lsecond($3); $$ = (Node *) n; } ! | LATERAL_P func_table WITH_ORDINALITY func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = true; --- 9638,9644 ---- n->coldeflist = lsecond($3); $$ = (Node *) n; } ! | LATERAL_P func_table with_ordinality func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = true; *************** *** 10413,10419 **** ConstInterval: ; opt_timezone: ! WITH_TIME ZONE { $$ = TRUE; } | WITHOUT TIME ZONE { $$ = FALSE; } | /*EMPTY*/ { $$ = FALSE; } ; --- 10438,10444 ---- ; opt_timezone: ! with_time ZONE { $$ = TRUE; } | WITHOUT TIME ZONE { $$ = FALSE; } | /*EMPTY*/ { $$ = FALSE; } ; *************** *** 12431,12436 **** ColLabel: IDENT { $$ = $1; } --- 12456,12499 ---- | reserved_keyword { $$ = pstrdup($1); } ; + /* + * Special-case keyword sequences. + * + * To disambiguate WITH TIME, WITH ORDINALITY, NULLS FIRST, NULLS + * LAST, which otherwise cause conflicts, the lexer looks ahead one + * extra token and may replace the WITH or NULLS keyword by + * WITH_BEFORE_* or NULLS_BEFORE_* (the following keyword is not + * touched). + * + * These productions collect the special cases in one place; see the + * token declarations at the top of the file for the rules used. + */ + + opt_with: with_keyword + | /*EMPTY*/ + ; + + with_keyword: WITH + | WITH_BEFORE_TIME + | WITH_BEFORE_ORDINALITY + ; + + with_ordinality: WITH_BEFORE_ORDINALITY ORDINALITY ; + + with_time: WITH_BEFORE_TIME TIME ; + + /* + * not needed since NULLS never occurs alone: + * + * nulls_keyword: NULLS_P + * | NULLS_BEFORE_FIRST + * | NULLS_BEFORE_LAST + * ; + */ + + nulls_first: NULLS_BEFORE_FIRST FIRST_P ; + + nulls_last: NULLS_BEFORE_LAST LAST_P ; /* * Keyword category lists. Generally, every keyword present in *** a/src/backend/parser/parser.c --- b/src/backend/parser/parser.c *************** *** 65,72 **** raw_parser(const char *str) * Intermediate filter between parser and core lexer (core_yylex in scan.l). * * The filter is needed because in some cases the standard SQL grammar ! * requires more than one token lookahead. We reduce these cases to one-token ! * lookahead by combining tokens here, in order to keep the grammar LALR(1). * * Using a filter is simpler than trying to recognize multiword tokens * directly in scan.l, because we'd have to allow for comments between the --- 65,80 ---- * Intermediate filter between parser and core lexer (core_yylex in scan.l). * * The filter is needed because in some cases the standard SQL grammar ! * requires more than one token lookahead. We reduce these cases to ! * one-token lookahead by adding one extra token of lookahead here, and ! * altering the keyword returned based on what follows, in order to keep ! * the grammar LALR(1). ! * ! * We used to combine keywords, but no longer do. Now, keywords that would ! * cause conflicts have special _BEFORE_ forms, which ! * flag up in advance that the next token is . This is more ! * flexible when handling some of the edge cases such as ! * WITH ordinality AS (...) * * Using a filter is simpler than trying to recognize multiword tokens * directly in scan.l, because we'd have to allow for comments between the *************** *** 98,167 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) else cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); ! /* Do we need to look ahead for a possible multiword token? */ ! switch (cur_token) ! { ! case NULLS_P: ! ! /* ! * NULLS FIRST and NULLS LAST must be reduced to one token ! */ ! cur_yylval = lvalp->core_yystype; ! cur_yylloc = *llocp; ! next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); ! switch (next_token) ! { ! case FIRST_P: ! cur_token = NULLS_FIRST; ! break; ! case LAST_P: ! cur_token = NULLS_LAST; ! break; ! default: ! /* save the lookahead token for next time */ ! yyextra->lookahead_token = next_token; ! yyextra->lookahead_yylval = lvalp->core_yystype; ! yyextra->lookahead_yylloc = *llocp; ! yyextra->have_lookahead = true; ! /* and back up the output info to cur_token */ ! lvalp->core_yystype = cur_yylval; ! *llocp = cur_yylloc; ! break; ! } ! break; ! ! case WITH: ! ! /* ! * WITH TIME and WITH ORDINALITY must each be reduced to one token ! */ ! cur_yylval = lvalp->core_yystype; ! cur_yylloc = *llocp; ! next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); ! switch (next_token) ! { ! case TIME: ! cur_token = WITH_TIME; ! break; ! case ORDINALITY: ! cur_token = WITH_ORDINALITY; ! break; ! default: ! /* save the lookahead token for next time */ ! yyextra->lookahead_token = next_token; ! yyextra->lookahead_yylval = lvalp->core_yystype; ! yyextra->lookahead_yylloc = *llocp; ! yyextra->have_lookahead = true; ! /* and back up the output info to cur_token */ ! lvalp->core_yystype = cur_yylval; ! *llocp = cur_yylloc; ! break; ! } ! break; ! ! default: ! break; ! } return cur_token; } --- 106,151 ---- else cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); ! /* ! * Do we need to look ahead for a possible multiword token? ! * If not, we're done here. ! */ ! ! if (cur_token != NULLS_P && cur_token != WITH) ! return cur_token; ! ! /* Fetch the lookahead token */ ! ! cur_yylval = lvalp->core_yystype; ! cur_yylloc = *llocp; ! next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); ! ! /* save the lookahead token for next time */ ! ! yyextra->lookahead_token = next_token; ! yyextra->lookahead_yylval = lvalp->core_yystype; ! yyextra->lookahead_yylloc = *llocp; ! yyextra->have_lookahead = true; ! ! /* and back up the output info to cur_token */ ! ! lvalp->core_yystype = cur_yylval; ! *llocp = cur_yylloc; ! ! /* ! * We don't merge the two tokens into one, but just modify the value of ! * the leading token to reflect what follows. ! */ ! ! if (cur_token == NULLS_P && next_token == FIRST_P) ! return NULLS_BEFORE_FIRST; ! else if (cur_token == NULLS_P && next_token == LAST_P) ! return NULLS_BEFORE_LAST; ! ! if (cur_token == WITH && next_token == TIME) ! return WITH_BEFORE_TIME; ! else if (cur_token == WITH && next_token == ORDINALITY) ! return WITH_BEFORE_ORDINALITY; return cur_token; } *** a/src/interfaces/ecpg/preproc/parser.c --- b/src/interfaces/ecpg/preproc/parser.c *************** *** 35,42 **** static YYLTYPE lookahead_yylloc; /* yylloc for lookahead token */ * Intermediate filter between parser and base lexer (base_yylex in scan.l). * * The filter is needed because in some cases the standard SQL grammar ! * requires more than one token lookahead. We reduce these cases to one-token ! * lookahead by combining tokens here, in order to keep the grammar LALR(1). * * Using a filter is simpler than trying to recognize multiword tokens * directly in scan.l, because we'd have to allow for comments between the --- 35,50 ---- * Intermediate filter between parser and base lexer (base_yylex in scan.l). * * The filter is needed because in some cases the standard SQL grammar ! * requires more than one token lookahead. We reduce these cases to ! * one-token lookahead by adding one extra token of lookahead here, and ! * altering the keyword returned based on what follows, in order to keep ! * the grammar LALR(1). ! * ! * We used to combine keywords, but no longer do. Now, keywords that would ! * cause conflicts have special _BEFORE_ forms, which ! * flag up in advance that the next token is . This is more ! * flexible when handling some of the edge cases such as ! * WITH ordinality AS (...) * * Using a filter is simpler than trying to recognize multiword tokens * directly in scan.l, because we'd have to allow for comments between the *************** *** 63,129 **** filtered_base_yylex(void) else cur_token = base_yylex(); ! /* Do we need to look ahead for a possible multiword token? */ ! switch (cur_token) ! { ! case NULLS_P: ! ! /* ! * NULLS FIRST and NULLS LAST must be reduced to one token ! */ ! cur_yylval = base_yylval; ! cur_yylloc = base_yylloc; ! next_token = base_yylex(); ! switch (next_token) ! { ! case FIRST_P: ! cur_token = NULLS_FIRST; ! break; ! case LAST_P: ! cur_token = NULLS_LAST; ! break; ! default: ! /* save the lookahead token for next time */ ! lookahead_token = next_token; ! lookahead_yylval = base_yylval; ! lookahead_yylloc = base_yylloc; ! have_lookahead = true; ! /* and back up the output info to cur_token */ ! base_yylval = cur_yylval; ! base_yylloc = cur_yylloc; ! break; ! } ! break; ! ! case WITH: ! ! /* ! * WITH TIME must be reduced to one token ! */ ! cur_yylval = base_yylval; ! cur_yylloc = base_yylloc; ! next_token = base_yylex(); ! switch (next_token) ! { ! case TIME: ! cur_token = WITH_TIME; ! break; ! default: ! /* save the lookahead token for next time */ ! lookahead_token = next_token; ! lookahead_yylval = base_yylval; ! lookahead_yylloc = base_yylloc; ! have_lookahead = true; ! /* and back up the output info to cur_token */ ! base_yylval = cur_yylval; ! base_yylloc = cur_yylloc; ! break; ! } ! break; ! ! default: ! break; ! } return cur_token; } --- 71,116 ---- else cur_token = base_yylex(); ! /* ! * Do we need to look ahead for a possible multiword token? ! * If not, we're done here. ! */ ! ! if (cur_token != NULLS_P && cur_token != WITH) ! return cur_token; ! ! /* fetch the lookahead token */ ! ! cur_yylval = base_yylval; ! cur_yylloc = base_yylloc; ! next_token = base_yylex(); ! ! /* save the lookahead token for next time */ ! ! lookahead_token = next_token; ! lookahead_yylval = base_yylval; ! lookahead_yylloc = base_yylloc; ! have_lookahead = true; ! ! /* and back up the output info to cur_token */ ! ! base_yylval = cur_yylval; ! base_yylloc = cur_yylloc; ! ! /* ! * We don't merge the two tokens into one, but just modify the value of ! * the leading token to reflect what follows. ! */ ! ! if (cur_token == NULLS_P && next_token == FIRST_P) ! return NULLS_BEFORE_FIRST; ! else if (cur_token == NULLS_P && next_token == LAST_P) ! return NULLS_BEFORE_LAST; ! ! if (cur_token == WITH && next_token == TIME) ! return WITH_BEFORE_TIME; ! else if (cur_token == WITH && next_token == ORDINALITY) ! return WITH_BEFORE_ORDINALITY; return cur_token; }