Clean up usage of bison precedence for non-operator keywords.

Assigning a precedence to a keyword that isn't a kind of expression
operator is rather dangerous, because it might mask grammar
ambiguities that we'd rather know about.  It's much safer to attach
explicit precedences to individual rules, which will affect the
behavior of only that one rule.  Moreover, when we do have to give
a precedence to a non-operator keyword, we should try to give it the
same precedence as IDENT, thereby reducing the risk of surprising
side-effects.

Apply this hard-won knowledge to SET (which I misassigned ages ago
in commit 2647ad658) and some SQL/JSON-related productions
(from commits 6ee30209a, 71bfd1543).

Patch HEAD only, since there's no evidence of actual bugs here.

Discussion: https://postgr.es/m/CADT4RqBPdbsZW7HS1jJP319TMRHs1hzUiP=iRJYR6UqgHCrgNQ@mail.gmail.com
This commit is contained in:
Tom Lane 2023-11-28 13:32:15 -05:00
parent c82207a548
commit a916b47e23
1 changed files with 44 additions and 17 deletions

View File

@ -810,7 +810,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
%left UNION EXCEPT
%left INTERSECT
%left OR
@ -821,18 +820,23 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
%nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
/* SQL/JSON related keywords */
%nonassoc UNIQUE JSON
%nonassoc KEYS OBJECT_P SCALAR VALUE_P
%nonassoc WITH WITHOUT
/*
* To support target_el without AS, it used to be necessary to assign IDENT an
* explicit precedence just less than Op. While that's not really necessary
* since we removed postfix operators, it's still helpful to do so because
* there are some other unreserved keywords that need precedence assignments.
* If those keywords have the same precedence as IDENT then they clearly act
* the same as non-keywords, reducing the risk of unwanted precedence effects.
* Sometimes it is necessary to assign precedence to keywords that are not
* really part of the operator hierarchy, in order to resolve grammar
* ambiguities. It's best to avoid doing so whenever possible, because such
* assignments have global effect and may hide ambiguities besides the one
* you intended to solve. (Attaching a precedence to a single rule with
* %prec is far safer and should be preferred.) If you must give precedence
* to a new keyword, try very hard to give it the same precedence as IDENT.
* If the keyword has IDENT's precedence then it clearly acts the same as
* non-keywords and other similar keywords, thus reducing the risk of
* unexpected precedence effects.
*
* We used to need to assign IDENT an explicit precedence just less than Op,
* to support target_el without AS. While that's not really necessary since
* we removed postfix operators, we continue to do so because it provides a
* reference point for a precedence level that we can assign to other
* keywords that lack a natural precedence level.
*
* We need to do this for PARTITION, RANGE, ROWS, and GROUPS to support
* opt_existing_window_name (see comment there).
@ -850,9 +854,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* an explicit priority lower than '(', so that a rule with CUBE '(' will shift
* rather than reducing a conflicting rule that takes CUBE as a function name.
* Using the same precedence as IDENT seems right for the reasons given above.
*
* SET is likewise assigned the same precedence as IDENT, to support the
* relation_expr_opt_alias production (see comment there).
*
* KEYS, OBJECT_P, SCALAR, VALUE_P, WITH, and WITHOUT are similarly assigned
* the same precedence as IDENT. This allows resolving conflicts in the
* json_predicate_type_constraint and json_key_uniqueness_constraint_opt
* productions (see comments there).
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@ -16519,21 +16532,35 @@ json_returning_clause_opt:
| /* EMPTY */ { $$ = NULL; }
;
/*
* We must assign the only-JSON production a precedence less than IDENT in
* order to favor shifting over reduction when JSON is followed by VALUE_P,
* OBJECT_P, or SCALAR. (ARRAY doesn't need that treatment, because it's a
* fully reserved word.) Because json_predicate_type_constraint is always
* followed by json_key_uniqueness_constraint_opt, we also need the only-JSON
* production to have precedence less than WITH and WITHOUT. UNBOUNDED isn't
* really related to this syntax, but it's a convenient choice because it
* already has a precedence less than IDENT for other reasons.
*/
json_predicate_type_constraint:
JSON { $$ = JS_TYPE_ANY; }
JSON %prec UNBOUNDED { $$ = JS_TYPE_ANY; }
| JSON VALUE_P { $$ = JS_TYPE_ANY; }
| JSON ARRAY { $$ = JS_TYPE_ARRAY; }
| JSON OBJECT_P { $$ = JS_TYPE_OBJECT; }
| JSON SCALAR { $$ = JS_TYPE_SCALAR; }
;
/* KEYS is a noise word here */
/*
* KEYS is a noise word here. To avoid shift/reduce conflicts, assign the
* KEYS-less productions a precedence less than IDENT (i.e., less than KEYS).
* This prevents reducing them when the next token is KEYS.
*/
json_key_uniqueness_constraint_opt:
WITH UNIQUE KEYS { $$ = true; }
| WITH UNIQUE { $$ = true; }
| WITH UNIQUE %prec UNBOUNDED { $$ = true; }
| WITHOUT UNIQUE KEYS { $$ = false; }
| WITHOUT UNIQUE { $$ = false; }
| /* EMPTY */ %prec KEYS { $$ = false; }
| WITHOUT UNIQUE %prec UNBOUNDED { $$ = false; }
| /* EMPTY */ %prec UNBOUNDED { $$ = false; }
;
json_name_and_value_list: