diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 22e82ba..fda5f41 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -840,9 +840,16 @@ CreateRoleStmt: opt_with: WITH {} + | WITH_ORDINALITY {} + | WITH_TIME {} | /*EMPTY*/ {} ; +with: WITH {} + | WITH_ORDINALITY {} + | WITH_TIME {} + ; + /* * Options for CREATE ROLE and ALTER ROLE (also used by CREATE/ALTER USER * for backwards compatibility). Note: the only option required by SQL99 @@ -3127,7 +3134,16 @@ ExclusionConstraintList: { $$ = lappend($1, $3); } ; -ExclusionConstraintElem: index_elem WITH any_operator +ExclusionConstraintElem: + index_elem WITH any_operator + { + $$ = list_make2($1, $3); + } + | index_elem WITH_TIME any_operator + { + $$ = list_make2($1, $3); + } + | index_elem WITH_ORDINALITY any_operator { $$ = list_make2($1, $3); } @@ -6188,8 +6204,8 @@ opt_asc_desc: ASC { $$ = SORTBY_ASC; } | /*EMPTY*/ { $$ = SORTBY_DEFAULT; } ; -opt_nulls_order: NULLS_FIRST { $$ = SORTBY_NULLS_FIRST; } - | NULLS_LAST { $$ = SORTBY_NULLS_LAST; } +opt_nulls_order: NULLS_FIRST FIRST_P { $$ = SORTBY_NULLS_FIRST; } + | NULLS_LAST LAST_P { $$ = SORTBY_NULLS_LAST; } | /*EMPTY*/ { $$ = SORTBY_NULLS_DEFAULT; } ; @@ -8348,7 +8364,7 @@ AlterTSDictionaryStmt: ; AlterTSConfigurationStmt: - ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list WITH any_name_list + ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list with any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; @@ -8358,7 +8374,7 @@ AlterTSConfigurationStmt: n->replace = false; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list WITH any_name_list + | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list with any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; @@ -8368,7 +8384,7 @@ AlterTSConfigurationStmt: n->replace = false; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name WITH any_name + | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name with any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->cfgname = $5; @@ -8378,7 +8394,7 @@ AlterTSConfigurationStmt: n->replace = true; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name WITH any_name + | 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; @@ -9254,6 +9270,20 @@ with_clause: $$->recursive = false; $$->location = @1; } + | WITH_TIME cte_list + { + $$ = makeNode(WithClause); + $$->ctes = $2; + $$->recursive = false; + $$->location = @1; + } + | WITH_ORDINALITY cte_list + { + $$ = makeNode(WithClause); + $$->ctes = $2; + $$->recursive = false; + $$->location = @1; + } | WITH RECURSIVE cte_list { $$ = makeNode(WithClause); @@ -9593,14 +9623,14 @@ table_ref: relation_expr opt_alias_clause n->coldeflist = lsecond($2); $$ = (Node *) n; } - | func_table WITH_ORDINALITY func_alias_clause + | func_table WITH_ORDINALITY ORDINALITY func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = false; n->ordinality = true; n->funccallnode = $1; - n->alias = linitial($3); - n->coldeflist = lsecond($3); + n->alias = linitial($4); + n->coldeflist = lsecond($4); $$ = (Node *) n; } | LATERAL_P func_table func_alias_clause @@ -9613,14 +9643,14 @@ table_ref: relation_expr opt_alias_clause n->coldeflist = lsecond($3); $$ = (Node *) n; } - | LATERAL_P func_table WITH_ORDINALITY func_alias_clause + | LATERAL_P func_table WITH_ORDINALITY ORDINALITY func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = true; n->ordinality = true; n->funccallnode = $2; - n->alias = linitial($4); - n->coldeflist = lsecond($4); + n->alias = linitial($5); + n->coldeflist = lsecond($5); $$ = (Node *) n; } | select_with_parens opt_alias_clause @@ -10413,7 +10443,7 @@ ConstInterval: ; opt_timezone: - WITH_TIME ZONE { $$ = TRUE; } + WITH_TIME TIME ZONE { $$ = TRUE; } | WITHOUT TIME ZONE { $$ = FALSE; } | /*EMPTY*/ { $$ = FALSE; } ; diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 541d364..4802b7d 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -104,60 +104,53 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case NULLS_P: /* - * NULLS FIRST and NULLS LAST must be reduced to one token + * NULLS FIRST and NULLS LAST must each trigger a special token (but + * still output the regular tokens) to give the parser an extra + * token of lookahead */ 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; + if (next_token == FIRST_P) { + cur_token = NULLS_FIRST; + } else if (next_token == LAST_P) { + cur_token = NULLS_LAST; } + + /* 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; case WITH: /* - * WITH TIME and WITH ORDINALITY must each be reduced to one token + * WITH TIME and WITH ORDINALITY must each trigger the special + * "WITH_FOO" token (but still output the regular 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; + if (next_token == TIME) { + cur_token = WITH_TIME; + } else if (next_token == ORDINALITY) { + cur_token = WITH_ORDINALITY; } - break; + + /* 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; default: break; diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index f4b51d6..ed0674d 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -42,9 +42,10 @@ my %replace_token = ( # or in the block my %replace_string = ( - 'WITH_TIME' => 'with time', - 'NULLS_FIRST' => 'nulls first', - 'NULLS_LAST' => 'nulls last', + 'WITH_TIME' => 'with', + 'WITH_ORDINALITY' => 'with', + 'NULLS_FIRST' => 'nulls', + 'NULLS_LAST' => 'nulls', 'TYPECAST' => '::', 'DOT_DOT' => '..', 'COLON_EQUALS' => ':=',); diff --git a/src/test/regress/expected/special_tokens.out b/src/test/regress/expected/special_tokens.out new file mode 100644 index 0000000..fc3e908 --- /dev/null +++ b/src/test/regress/expected/special_tokens.out @@ -0,0 +1,41 @@ +-- ordinality and time should not require quoting here +WITH ordinality AS (SELECT 1) SELECT * FROM ordinality; + ?column? +---------- + 1 +(1 row) + +WITH time AS (SELECT 1) SELECT * FROM time; + ?column? +---------- + 1 +(1 row) + +-- These should produce the same error unless there's actually a dictionary +-- called "ordinality" present in which case it should just work. +ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf; +ERROR: text search dictionary "slkdf" does not exist +ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality; +ERROR: text search dictionary "ordinality" does not exist +-- first should not require quoting here +SELECT nulls first FROM (SELECT 1 AS nulls) AS x; + first +------- + 1 +(1 row) + +SELECT nulls last FROM (SELECT 1 AS nulls) AS x; + last +------ + 1 +(1 row) + +CREATE COLLATION nulls (locale='C'); +ALTER OPERATOR CLASS text_ops USING btree RENAME TO first; +CREATE TABLE nulls_first(t text); +-- Neither "nulls" nor "first" should require quoting here. This should +-- correctly find the "nulls" collation and the "first" operator class. +CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first); +ALTER OPERATOR CLASS first USING btree RENAME TO text_ops; +DROP TABLE nulls_first; +DROP COLLATION nulls; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index fd08e8d..8f89208 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -88,7 +88,7 @@ test: privileges security_label collate matview lock # ---------- # Another group of parallel tests # ---------- -test: alter_generic misc psql async +test: special_tokens alter_generic misc psql async # rules cannot run concurrently with any test that creates a view test: rules diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 1ed059b..063c470 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -97,6 +97,7 @@ test: security_label test: collate test: matview test: lock +test: special_tokens test: alter_generic test: misc test: psql diff --git a/src/test/regress/sql/special_tokens.sql b/src/test/regress/sql/special_tokens.sql new file mode 100644 index 0000000..0e6b6c8 --- /dev/null +++ b/src/test/regress/sql/special_tokens.sql @@ -0,0 +1,26 @@ +-- ordinality and time should not require quoting here +WITH ordinality AS (SELECT 1) SELECT * FROM ordinality; +WITH time AS (SELECT 1) SELECT * FROM time; + +-- These should produce the same error unless there's actually a dictionary +-- called "ordinality" present in which case it should just work. +ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf; +ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality; + + + +-- first should not require quoting here +SELECT nulls xirst FROM (SELECT 1 AS nulls) AS x; +SELECT nulls xast FROM (SELECT 1 AS nulls) AS x; + +CREATE COLLATION nulls (locale='C'); +ALTER OPERATOR CLASS text_ops USING btree RENAME TO xirst; +CREATE TABLE nulls_first(t text); + +-- Neither "nulls" nor "first" should require quoting here. This should +-- correctly find the "nulls" collation and the "first" operator class. +CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls xirst); + +ALTER OPERATOR CLASS xirst USING btree RENAME TO text_ops; +DROP TABLE nulls_first; +DROP COLLATION nulls;