From 04abbe1adfaeb2d3c257d2ad4fd8e052d7925c46 Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Tue, 8 Sep 2026 11:22:42 +0900 Subject: [PATCH] Return "|" as a single-character token Row pattern recognition spells pattern alternation with "|", and row_pattern_alt matches it as the character token '|'. The lexer, however, lists "|" in op_chars but not in self, so a "|" standing on its own arrives as an Op and that production never fires. PATTERN (A | B) then fails with "unsupported quantifier". Add "|" to the self character class, and to the one-character fallback in the rule for "operator", so that a lone "|" is returned as a character token. That stops it from being lexed as an operator, so restore its use as one: prefix and infix productions in a_expr and b_expr, an entry in MathOp, and "|" in the precedence declaration for Op. This lexer behavior was in the tree as part of SQL/PGQ, which used "|" for label disjunction, and row pattern alternation has been relying on it. SQL/PGQ was reverted in b1f106c80cb, so the change has to be carried here. --- src/backend/parser/gram.y | 11 ++++++++++- src/backend/parser/scan.l | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 41f46d242fa..3a5fcfc9c81 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -924,7 +924,7 @@ static bool rpr_is_quantifier_token(const char *tok); %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP SET KEYS OBJECT_P SCALAR TO USING VALUE_P WITH WITHOUT PATH AFTER INITIAL_P SEEK PATTERN_P PERMUTE -%left Op OPERATOR /* multi-character ops and user-defined operators */ +%left Op OPERATOR '|' /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' @@ -15482,6 +15482,10 @@ a_expr: c_expr { $$ = $1; } { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); } | a_expr NOT_EQUALS a_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); } + | '|' a_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", NULL, $2, @1); } + | a_expr '|' a_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); } | a_expr qual_Op a_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } @@ -15962,6 +15966,10 @@ b_expr: c_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); } | b_expr NOT_EQUALS b_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); } + | '|' b_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", NULL, $2, @1); } + | b_expr '|' b_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); } | b_expr qual_Op b_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } | qual_Op b_expr %prec Op @@ -17627,6 +17635,7 @@ MathOp: '+' { $$ = "+"; } | LESS_EQUALS { $$ = "<="; } | GREATER_EQUALS { $$ = ">="; } | NOT_EQUALS { $$ = "<>"; } + | '|' { $$ = "|"; } ; qual_Op: Op diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index 6c162f48342..78654b45ad0 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -359,7 +359,7 @@ not_equals "!=" * If you change either set, adjust the character lists appearing in the * rule for "operator"! */ -self [,()\[\].;\:\+\-\*\/\%\^\<\>\=] +self [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=] op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=] operator {op_chars}+ @@ -930,7 +930,7 @@ other . * that the "self" rule would have. */ if (nchars == 1 && - strchr(",()[].;:+-*/%^<>=", yytext[0])) + strchr(",()[].;:|+-*/%^<>=", yytext[0])) return yytext[0]; /* * Likewise, if what we have left is two chars, and