From 77339915477e54b024eb50874cdbf4e7b87b291c Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Tue, 20 Oct 2026 17:34:38 +0900 Subject: [PATCH v54 1/9] Row pattern recognition patch for raw parser v54. The series of patches are to implement the row pattern recognition (SQL/RPR) feature. Currently the implementation is a subset of SQL/RPR (ISO/IEC 19075-2:2016). Namely, implementation of some features of R020 (WINDOW clause). R010 (MATCH_RECOGNIZE) is out of the scope of the patches. Currently following features are implemented in the patches. - PATTERN - PATTERN regular expressions (+, *, ?) alternation (|), grouping () , {n}, {n,}, {n,m}, {,m} reluctant quantifiers (*? etc.), Empty pattern ("PATTERN ()") are not permitted by the standard Anchors (^, $) are not permitted in R020 by the standard - DEFINE - INITIAL - AFTER MATCH SKIP TO PAST LAST ROW - AFTER MATCH SKIP TO NEXT ROW - Row pattern navigation (FIRST, LAST, PREV, NEXT and their compound forms) Currently following features are not implemented in the patches and will not be supported in the initial commit of this patch. - MEASURES - Pattern variable name qualified column reference (e.g. A.price) - SUBSET - SEEK - AFTER MATCH SKIP TO varname - AFTER MATCH SKIP TO FIRST - AFTER MATCH SKIP TO LAST - PATTERN regular expression "{-" and "-}" - PERMUTE - CLASSIFIER - aggregates/subqueries in DEFINE - RUNNING with row pattern navigation in DEFINE clause Author: Tatsuo Ishii Author: Henson Choi Author: jian he Reviewed-by: Vik Fearing Reviewed-by: Jacob Champion Reviewed-by: Peter Eisentraut Reviewed-by: NINGWEI CHEN Reviewed-by: "David G. Johnston" Reviewed-by: Chao Li Reviewed-by: SungJun Jang Reviewed-by: Zsolt Parragi Reviewed-by: SeongJun Shin < Discussion: https://postgr.es/m/20230625.210509.1276733411677577841.t-ishii%40sranhm.sra.co.jp --- src/backend/parser/gram.y | 695 +++++++++++++++++++++++++++++++- src/backend/parser/scan.l | 4 +- src/include/nodes/parsenodes.h | 88 ++++ src/include/parser/kwlist.h | 6 + src/include/parser/parse_node.h | 15 + 5 files changed, 789 insertions(+), 19 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0563453fe24..cf30f443aad 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -209,6 +209,12 @@ static void preprocess_pub_all_objtype_list(List *all_objects_list, static void preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner); static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); +static RPRPatternNode *makeRPRSeqOrSingle(List *children, int location); +static RPRPatternNode *splitRPRTrailingAlt(RPRPatternNode *node, core_yyscan_t yyscanner); +static RPRPatternNode *makeRPRQuantifier(int32 min, int32 max, bool reluctant, + int location); +static const char *rpr_invalid_quantifier_token(const char *tok); +static bool rpr_is_quantifier_token(const char *tok); %} @@ -692,6 +698,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_constructor_null_clause_opt json_array_constructor_null_clause_opt +%type row_pattern_definition +%type opt_row_pattern_common_syntax + row_pattern row_pattern_alt row_pattern_seq + row_pattern_term row_pattern_primary + row_pattern_quantifier_opt +%type row_pattern_definition_list row_pattern_permute_list +%type opt_row_pattern_skip_to + /* * Non-keyword token types. These are hard-wired into the "flex" lexer. * They must be listed first so that their numeric codes do not depend on @@ -734,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS - DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC + DEFERRABLE DEFERRED DEFINE DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP @@ -750,7 +764,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P IDENTITY_P IF_P IGNORE_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE - INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P + INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIAL_P INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -775,8 +789,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER - PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH - PERIOD PLACING PLAN PLANS POLICY + PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PAST PATH + PATTERN_P PERIOD PERMUTE PLACING PLAN PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION @@ -787,7 +801,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SEEK SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P @@ -870,8 +884,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * 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). + * We need to do this for PARTITION, RANGE, ROWS, GROUPS, AFTER, INITIAL, + * SEEK, PATTERN to support opt_existing_window_name (see comment there). * * The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING * are even messier: since UNBOUNDED is an unreserved keyword (per spec!), @@ -897,11 +911,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * * Like the UNBOUNDED PRECEDING/FOLLOWING case, NESTED is assigned a lower * precedence than PATH to fix ambiguity in the json_table production. + * + * PERMUTE gets the same treatment as CUBE and ROLLUP, so that PERMUTE '(' + * shifts rather than reducing PERMUTE to a pattern variable. */ %nonassoc UNBOUNDED NESTED /* 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 PATH -%left Op OPERATOR /* multi-character ops and user-defined operators */ + AFTER INITIAL_P SEEK PATTERN_P PERMUTE +%left Op OPERATOR '|' /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' @@ -15364,6 +15382,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); } @@ -15844,6 +15866,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 @@ -16795,6 +16821,8 @@ over_clause: OVER window_specification n->startOffset = NULL; n->endOffset = NULL; n->location = @2; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } | /*EMPTY*/ @@ -16802,7 +16830,8 @@ over_clause: OVER window_specification ; window_specification: '(' opt_existing_window_name opt_partition_clause - opt_sort_clause opt_frame_clause ')' + opt_sort_clause opt_frame_clause + opt_row_pattern_common_syntax ')' { WindowDef *n = makeNode(WindowDef); @@ -16814,20 +16843,23 @@ window_specification: '(' opt_existing_window_name opt_partition_clause n->frameOptions = $5->frameOptions; n->startOffset = $5->startOffset; n->endOffset = $5->endOffset; + n->frameLocation = $5->frameLocation; + n->excludeLocation = $5->excludeLocation; + n->rpCommonSyntax = (RPCommonSyntax *)$6; n->location = @1; $$ = n; } ; /* - * If we see PARTITION, RANGE, ROWS or GROUPS as the first token after the '(' - * of a window_specification, we want the assumption to be that there is - * no existing_window_name; but those keywords are unreserved and so could - * be ColIds. We fix this by making them have the same precedence as IDENT - * and giving the empty production here a slightly higher precedence, so - * that the shift/reduce conflict is resolved in favor of reducing the rule. - * These keywords are thus precluded from being an existing_window_name but - * are not reserved for any other purpose. + * If we see PARTITION, RANGE, ROWS, GROUPS, AFTER, INITIAL, SEEK or PATTERN + * as the first token after the '(' of a window_specification, we want the + * assumption to be that there is no existing_window_name; but those keywords + * are unreserved and so could be ColIds. We fix this by making them have the + * same precedence as IDENT and giving the empty production here a slightly + * higher precedence, so that the shift/reduce conflict is resolved in favor + * of reducing the rule. These keywords are thus precluded from being an + * existing_window_name but are not reserved for any other purpose. */ opt_existing_window_name: ColId { $$ = $1; } | /*EMPTY*/ %prec Op { $$ = NULL; } @@ -16848,6 +16880,9 @@ opt_frame_clause: n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE; n->frameOptions |= $3; + n->frameLocation = @1; + /* -1 when no EXCLUDE clause (opt_window_exclusion_clause returns 0) */ + n->excludeLocation = ($3 != 0) ? @3 : -1; $$ = n; } | ROWS frame_extent opt_window_exclusion_clause @@ -16856,6 +16891,9 @@ opt_frame_clause: n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS; n->frameOptions |= $3; + n->frameLocation = @1; + /* -1 when no EXCLUDE clause (opt_window_exclusion_clause returns 0) */ + n->excludeLocation = ($3 != 0) ? @3 : -1; $$ = n; } | GROUPS frame_extent opt_window_exclusion_clause @@ -16864,6 +16902,9 @@ opt_frame_clause: n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_GROUPS; n->frameOptions |= $3; + n->frameLocation = @1; + /* -1 when no EXCLUDE clause (opt_window_exclusion_clause returns 0) */ + n->excludeLocation = ($3 != 0) ? @3 : -1; $$ = n; } | /*EMPTY*/ @@ -16873,6 +16914,8 @@ opt_frame_clause: n->frameOptions = FRAMEOPTION_DEFAULTS; n->startOffset = NULL; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } ; @@ -16948,6 +16991,8 @@ frame_bound: n->frameOptions = FRAMEOPTION_START_UNBOUNDED_PRECEDING; n->startOffset = NULL; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } | UNBOUNDED FOLLOWING @@ -16957,6 +17002,8 @@ frame_bound: n->frameOptions = FRAMEOPTION_START_UNBOUNDED_FOLLOWING; n->startOffset = NULL; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } | CURRENT_P ROW @@ -16966,6 +17013,8 @@ frame_bound: n->frameOptions = FRAMEOPTION_START_CURRENT_ROW; n->startOffset = NULL; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } | a_expr PRECEDING @@ -16975,6 +17024,8 @@ frame_bound: n->frameOptions = FRAMEOPTION_START_OFFSET_PRECEDING; n->startOffset = $1; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } | a_expr FOLLOWING @@ -16984,6 +17035,8 @@ frame_bound: n->frameOptions = FRAMEOPTION_START_OFFSET_FOLLOWING; n->startOffset = $1; n->endOffset = NULL; + n->frameLocation = -1; + n->excludeLocation = -1; $$ = n; } ; @@ -16996,6 +17049,457 @@ opt_window_exclusion_clause: | /*EMPTY*/ { $$ = 0; } ; +opt_row_pattern_common_syntax: +opt_row_pattern_skip_to opt_row_pattern_initial_or_seek + PATTERN_P '(' row_pattern ')' + DEFINE row_pattern_definition_list + { + RPCommonSyntax *n = makeNode(RPCommonSyntax); + n->rpSkipTo = $1; + n->rpPattern = (RPRPatternNode *) $5; + n->rpDefs = $8; + n->location = @3; + $$ = (Node *) n; + } + | /*EMPTY*/ { $$ = NULL; } + ; + +opt_row_pattern_skip_to: + AFTER MATCH SKIP TO NEXT ROW + { + $$ = ST_NEXT_ROW; + } + | AFTER MATCH SKIP PAST LAST_P ROW + { + $$ = ST_PAST_LAST_ROW; + } + | /*EMPTY*/ + { + $$ = ST_PAST_LAST_ROW; + } + ; + +opt_row_pattern_initial_or_seek: + INITIAL_P + | SEEK + { + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SEEK is not supported"), + errhint("Use INITIAL instead."), + parser_errposition(@1)); + } + | /*EMPTY*/ + ; + +row_pattern: + row_pattern_alt { $$ = $1; } + ; + +row_pattern_alt: + row_pattern_seq + { + $$ = (Node *) splitRPRTrailingAlt((RPRPatternNode *) $1, + yyscanner); + } + | row_pattern_alt '|' row_pattern_seq + { + RPRPatternNode *lhs = castNode(RPRPatternNode, $1); + RPRPatternNode *rhs = splitRPRTrailingAlt(castNode(RPRPatternNode, $3), + yyscanner); + + /* If left side is already ALT, append to it */ + if (lhs->nodeType == RPR_PATTERN_ALT) + { + lhs->children = lappend(lhs->children, rhs); + $$ = (Node *) lhs; + } + else + { + RPRPatternNode *n = makeNode(RPRPatternNode); + + n->nodeType = RPR_PATTERN_ALT; + n->children = list_make2(lhs, rhs); + n->min = 1; + n->max = 1; + n->reluctant = false; + n->location = @1; + $$ = (Node *) n; + } + } + ; + +row_pattern_seq: + row_pattern_term { $$ = $1; } + | row_pattern_seq row_pattern_term + { + RPRPatternNode *seq = castNode(RPRPatternNode, $1); + + /* + * If left side is already SEQ, append to it. A glued + * quantifier's trailing_alt stays on the child term; + * row_pattern_alt splits on it once the seq is complete. + */ + if (seq->nodeType == RPR_PATTERN_SEQ) + { + seq->children = lappend(seq->children, $2); + $$ = (Node *) seq; + } + else + { + RPRPatternNode *n = makeNode(RPRPatternNode); + + n->nodeType = RPR_PATTERN_SEQ; + n->children = list_make2(seq, $2); + n->min = 1; + n->max = 1; + n->reluctant = false; + n->location = @1; + $$ = (Node *) n; + } + } + ; + +row_pattern_term: + row_pattern_primary row_pattern_quantifier_opt + { + RPRPatternNode *n = (RPRPatternNode *) $1; + RPRPatternNode *q = (RPRPatternNode *) $2; + + n->min = q->min; + n->max = q->max; + n->reluctant = q->reluctant; + n->trailing_alt = q->trailing_alt; + $$ = (Node *) n; + } + ; + +row_pattern_primary: + ColId + { + RPRPatternNode *n = makeNode(RPRPatternNode); + n->nodeType = RPR_PATTERN_VAR; + n->varName = $1; + n->min = 1; + n->max = 1; + n->reluctant = false; + n->children = NIL; + n->location = @1; + $$ = (Node *) n; + } + | '(' row_pattern ')' + { + RPRPatternNode *inner = (RPRPatternNode *) $2; + RPRPatternNode *n = makeNode(RPRPatternNode); + n->nodeType = RPR_PATTERN_GROUP; + n->children = list_make1(inner); + n->min = 1; + n->max = 1; + n->reluctant = false; + n->location = @1; + $$ = (Node *) n; + } + | PERMUTE '(' row_pattern_permute_list ')' + { + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PERMUTE is not supported"), + errhint("Write the alternations out instead, or write \"permute\" to use it as a pattern variable."), + parser_errposition(@1)); + $$ = NULL; /* keep compiler quiet */ + } + ; + +row_pattern_permute_list: + row_pattern { $$ = list_make1($1); } + | row_pattern_permute_list ',' row_pattern + { $$ = lappend($1, $3); } + ; + +row_pattern_quantifier_opt: + /*EMPTY*/ + { + /* + * No quantifier means exactly once; @$ is unused since + * min=max=1 never produces an error. + */ + $$ = (Node *) makeRPRQuantifier(1, 1, false, @$); + } + | '*' + { + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, false, @1); + } + | '+' + { + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, false, @1); + } + | Op + { + /* Handle single Op: ? or reluctant quantifiers *?, +?, ?? */ + if (strcmp($1, "?") == 0) + $$ = (Node *) makeRPRQuantifier(0, 1, false, @1); + else if (strcmp($1, "*?") == 0) + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, true, @1); + else if (strcmp($1, "+?") == 0) + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, true, @1); + else if (strcmp($1, "??") == 0) + $$ = (Node *) makeRPRQuantifier(0, 1, true, @1); + else if (strcmp($1, "*|") == 0) + { + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, false, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else if (strcmp($1, "+|") == 0) + { + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, false, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else if (strcmp($1, "?|") == 0) + { + $$ = (Node *) makeRPRQuantifier(0, 1, false, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else if (strcmp($1, "*?|") == 0) + { + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else if (strcmp($1, "+?|") == 0) + { + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else if (strcmp($1, "??|") == 0) + { + $$ = (Node *) makeRPRQuantifier(0, 1, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid quantifier \"%s\"", rpr_invalid_quantifier_token($1)), + errhint("Valid quantifiers are: *, +, ?, {n}, {n,}, {,m}, {n,m}, each optionally followed by \"?\" for the reluctant version."), + parser_errposition(@1)); + } + /* RELUCTANT quantifiers (when lexer separates tokens) */ + | '*' Op + { + if (strcmp($2, "?") == 0) + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, true, @1); + else if (strcmp($2, "?|") == 0) + { + /* "A* ?|B" = reluctant "A*?" plus alternation */ + $$ = (Node *) makeRPRQuantifier(0, RPR_QUANTITY_INF, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after \"*\" quantifier", rpr_invalid_quantifier_token($2)), + errhint("Did you mean \"*?\" for reluctant quantifier?"), + parser_errposition(@2)); + } + | '+' Op + { + if (strcmp($2, "?") == 0) + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, true, @1); + else if (strcmp($2, "?|") == 0) + { + /* "A+ ?|B" = reluctant "A+?" plus alternation */ + $$ = (Node *) makeRPRQuantifier(1, RPR_QUANTITY_INF, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after \"+\" quantifier", rpr_invalid_quantifier_token($2)), + errhint("Did you mean \"+?\" for reluctant quantifier?"), + parser_errposition(@2)); + } + | Op Op + { + if (!rpr_is_quantifier_token($1)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid quantifier \"%s\"", rpr_invalid_quantifier_token($1)), + errhint("Valid quantifiers are: *, +, ?, {n}, {n,}, {,m}, {n,m}, each optionally followed by \"?\" for the reluctant version."), + parser_errposition(@1)); + /* + * A first token ending in "|" carries the alternation + * operator, so what comes after it has to be a pattern. + * An Op never is one, and naming the pair would describe + * a quantifier the token only half spells. + */ + if (strchr($1, '|') != NULL) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("alternation operator \"|\" requires a pattern on both sides"), + parser_errposition(@2)); + if (strcmp($1, "?") != 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after \"%s\" quantifier", + rpr_invalid_quantifier_token($2), $1), + errhint("Valid quantifiers are: *, +, ?, {n}, {n,}, {,m}, {n,m}, each optionally followed by \"?\" for the reluctant version."), + parser_errposition(@2)); + if (strcmp($2, "?") == 0) + $$ = (Node *) makeRPRQuantifier(0, 1, true, @1); + else if (strcmp($2, "?|") == 0) + { + /* "A? ?|B" = reluctant "A??" plus alternation */ + $$ = (Node *) makeRPRQuantifier(0, 1, true, @1); + ((RPRPatternNode *) $$)->trailing_alt = true; + } + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after \"?\" quantifier", rpr_invalid_quantifier_token($2)), + errhint("Valid quantifiers are: *, +, ?, {n}, {n,}, {,m}, {n,m}, each optionally followed by \"?\" for the reluctant version."), + parser_errposition(@2)); + } + /* {n}, {n,}, {,m}, {n,m} quantifiers */ + | '{' Iconst '}' + { + if ($2 <= 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, $2, false, @1); + } + | '{' Iconst ',' '}' + { + if ($2 < 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 0 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, RPR_QUANTITY_INF, false, @1); + } + | '{' ',' Iconst '}' + { + if ($3 <= 0 || $3 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@3)); + $$ = (Node *) makeRPRQuantifier(0, $3, false, @1); + } + | '{' Iconst ',' Iconst '}' + { + if ($2 < 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 0 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + if ($4 <= 0 || $4 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@4)); + if ($2 > $4) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier minimum bound must not exceed maximum"), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, $4, false, @1); + } + /* Reluctant versions: {n}?, {n,}?, {,m}?, {n,m}? */ + | '{' Iconst '}' Op + { + if (strcmp($4, "?") != 0 && strcmp($4, "?|") != 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after range quantifier", rpr_invalid_quantifier_token($4)), + errhint("Only \"?\" is allowed after {n} to make it reluctant."), + parser_errposition(@4)); + if ($2 <= 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, $2, true, @1); + if (strcmp($4, "?|") == 0) + ((RPRPatternNode *) $$)->trailing_alt = true; + } + | '{' Iconst ',' '}' Op + { + if (strcmp($5, "?") != 0 && strcmp($5, "?|") != 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after range quantifier", rpr_invalid_quantifier_token($5)), + errhint("Only \"?\" is allowed after {n,} or {,m} to make it reluctant."), + parser_errposition(@5)); + if ($2 < 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 0 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, RPR_QUANTITY_INF, true, @1); + if (strcmp($5, "?|") == 0) + ((RPRPatternNode *) $$)->trailing_alt = true; + } + | '{' ',' Iconst '}' Op + { + if (strcmp($5, "?") != 0 && strcmp($5, "?|") != 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after range quantifier", rpr_invalid_quantifier_token($5)), + errhint("Only \"?\" is allowed after {n,} or {,m} to make it reluctant."), + parser_errposition(@5)); + if ($3 <= 0 || $3 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@3)); + $$ = (Node *) makeRPRQuantifier(0, $3, true, @1); + if (strcmp($5, "?|") == 0) + ((RPRPatternNode *) $$)->trailing_alt = true; + } + | '{' Iconst ',' Iconst '}' Op + { + if (strcmp($6, "?") != 0 && strcmp($6, "?|") != 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid token \"%s\" after range quantifier", rpr_invalid_quantifier_token($6)), + errhint("Only \"?\" is allowed after {n,m} to make it reluctant."), + parser_errposition(@6)); + if ($2 < 0 || $2 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 0 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@2)); + if ($4 <= 0 || $4 >= RPR_QUANTITY_INF) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier bound must be between 1 and %d", RPR_QUANTITY_INF - 1), + parser_errposition(@4)); + if ($2 > $4) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("quantifier minimum bound must not exceed maximum"), + parser_errposition(@2)); + $$ = (Node *) makeRPRQuantifier($2, $4, true, @1); + if (strcmp($6, "?|") == 0) + ((RPRPatternNode *) $$)->trailing_alt = true; + } + ; + +row_pattern_definition_list: + row_pattern_definition { $$ = list_make1($1); } + | row_pattern_definition_list ',' row_pattern_definition { $$ = lappend($1, $3); } + ; + +row_pattern_definition: + ColId AS a_expr + { + $$ = makeNode(ResTarget); + $$->name = $1; + $$->indirection = NIL; + $$->val = (Node *) $3; + $$->location = @1; + } + ; /* * Supporting nonterminals for expressions. @@ -17040,6 +17544,7 @@ MathOp: '+' { $$ = "+"; } | LESS_EQUALS { $$ = "<="; } | GREATER_EQUALS { $$ = ">="; } | NOT_EQUALS { $$ = "<>"; } + | '|' { $$ = "|"; } ; qual_Op: Op @@ -18134,6 +18639,7 @@ unreserved_keyword: | DECLARE | DEFAULTS | DEFERRED + | DEFINE | DEFINER | DELETE_P | DELIMITER @@ -18199,6 +18705,7 @@ unreserved_keyword: | INDEXES | INHERIT | INHERITS + | INITIAL_P | INLINE_P | INPUT_P | INSENSITIVE @@ -18273,8 +18780,11 @@ unreserved_keyword: | PARTITION | PASSING | PASSWORD + | PAST | PATH + | PATTERN_P | PERIOD + | PERMUTE | PLAN | PLANS | POLICY @@ -18328,6 +18838,7 @@ unreserved_keyword: | SEARCH | SECOND_P | SECURITY + | SEEK | SEQUENCE | SEQUENCES | SERIALIZABLE @@ -18714,6 +19225,7 @@ bare_label_keyword: | DEFAULTS | DEFERRABLE | DEFERRED + | DEFINE | DEFINER | DELETE_P | DELIMITER @@ -18792,6 +19304,7 @@ bare_label_keyword: | INDEXES | INHERIT | INHERITS + | INITIAL_P | INITIALLY | INLINE_P | INNER_P @@ -18904,8 +19417,11 @@ bare_label_keyword: | PARTITION | PASSING | PASSWORD + | PAST | PATH + | PATTERN_P | PERIOD + | PERMUTE | PLACING | PLAN | PLANS @@ -18964,6 +19480,7 @@ bare_label_keyword: | SCROLL | SEARCH | SECURITY + | SEEK | SELECT | SEQUENCE | SEQUENCES @@ -20156,6 +20673,150 @@ makeRecursiveViewSelect(char *relname, List *aliases, Node *query) return (Node *) s; } +/* + * makeRPRQuantifier + * Create an RPRPatternNode with specified quantifier bounds. + */ +static RPRPatternNode * +makeRPRQuantifier(int32 min, int32 max, bool reluctant, int location) +{ + RPRPatternNode *n = makeNode(RPRPatternNode); + + n->min = min; + n->max = max; + n->reluctant = reluctant; + n->location = location; + + /* Other fields are irrelevant for a quantifier node */ + return n; +} + +/* + * Build a SEQ node from children, or return the lone child unchanged. + */ +static RPRPatternNode * +makeRPRSeqOrSingle(List *children, int location) +{ + RPRPatternNode *n; + + if (list_length(children) == 1) + return (RPRPatternNode *) linitial(children); + + n = makeNode(RPRPatternNode); + n->nodeType = RPR_PATTERN_SEQ; + n->children = children; + n->min = 1; + n->max = 1; + n->reluctant = false; + n->location = location; + return n; +} + +/* + * A glued quantifier such as "A*|" leaves trailing_alt set on its term while + * the enclosing sequence is built. Once the sequence is complete, split it at + * the flagged term into alt(left, right), where the right operand is the whole + * remaining sequence -- this keeps "|" as the lowest-precedence operator, so + * "A*|B C" parses as "A* | (B C)", identical to the spaced form. A flag with + * nothing to its right is a dangling "|" and is rejected. + */ +static RPRPatternNode * +splitRPRTrailingAlt(RPRPatternNode *node, core_yyscan_t yyscanner) +{ + if (node->nodeType != RPR_PATTERN_SEQ) + { + if (node->trailing_alt) + { + node->trailing_alt = false; + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("alternation operator \"|\" requires a pattern on both sides"), + parser_errposition(node->location)); + } + return node; + } + + foreach_node(RPRPatternNode, child, node->children) + { + if (child->trailing_alt) + { + int splitIdx = foreach_current_index(child); + List *lefthalf = list_copy_head(node->children, splitIdx + 1); + List *righthalf = list_copy_tail(node->children, splitIdx + 1); + RPRPatternNode *altn; + RPRPatternNode *rightnode; + + child->trailing_alt = false; + if (righthalf == NIL) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("alternation operator \"|\" requires a pattern on both sides"), + parser_errposition(child->location)); + + /* the right branch starts at its own first element, not the seq start */ + rightnode = splitRPRTrailingAlt(makeRPRSeqOrSingle(righthalf, + ((RPRPatternNode *) linitial(righthalf))->location), + yyscanner); + altn = makeNode(RPRPatternNode); + altn->nodeType = RPR_PATTERN_ALT; + altn->children = list_make2(makeRPRSeqOrSingle(lefthalf, node->location), + rightnode); + altn->min = 1; + altn->max = 1; + altn->reluctant = false; + altn->location = node->location; + return altn; + } + } + return node; +} + +/* + * rpr_invalid_quantifier_token + * Return the offending part of an invalid token in a quantifier position. + * + * The lexer glues a quantifier and a trailing alternation operator into a + * single token (for example "*|"). Drop that trailing '|': it is the + * alternation operator, not part of the offending quantifier, so "*|" reports + * '*' and "*?|" reports "*?", exactly as the spaced spellings "* |" and "*? |" + * do. Only a single trailing operator is dropped: with another '|' left over + * there is no quantifier to uncover, so "||" and "*||" are reported whole, as + * are tokens with no trailing '|' such as "??" or "?+". + */ +static const char * +rpr_invalid_quantifier_token(const char *tok) +{ + size_t len = strlen(tok); + + if (len > 1 && tok[len - 1] == '|' && memchr(tok, '|', len - 1) == NULL) + return pnstrdup(tok, len - 1); + return tok; +} + +/* + * rpr_is_quantifier_token + * Does this Op token spell a quantifier? + * + * These are exactly the tokens the single-Op arm of row_pattern_quantifier_opt + * accepts, so the two must be kept in step. A token outside the set is not a + * quantifier at all and has to be reported as an unsupported one, the way that + * arm reports it, rather than as something a quantifier was followed by. + */ +static bool +rpr_is_quantifier_token(const char *tok) +{ + static const char *const quantifiers[] = { + "?", "*?", "+?", "??", "*|", "+|", "?|", "*?|", "+?|", "??|" + }; + + for (int i = 0; i < lengthof(quantifiers); i++) + { + if (strcmp(tok, quantifiers[i]) == 0) + return true; + } + return false; +} + /* parser_init() * Initialize to parse one query string */ 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 diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0debcd193ab..5f06e054f32 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -579,6 +579,73 @@ typedef struct SortBy ParseLoc location; /* operator location, or -1 if none/unknown */ } SortBy; +/* + * AFTER MATCH row pattern skip to types in row pattern common syntax + */ +typedef enum RPSkipTo +{ + ST_NONE, /* not a row pattern window; an omitted AFTER + * MATCH gives ST_PAST_LAST_ROW */ + ST_NEXT_ROW, /* SKIP TO NEXT ROW */ + ST_PAST_LAST_ROW, /* SKIP TO PAST LAST ROW */ +} RPSkipTo; + +/* + * RPRPatternNodeType - Row Pattern Recognition pattern node types + */ +typedef enum RPRPatternNodeType +{ + RPR_PATTERN_VAR, /* variable reference */ + RPR_PATTERN_SEQ, /* sequence (concatenation) */ + RPR_PATTERN_ALT, /* alternation (|) */ + RPR_PATTERN_GROUP, /* group (parentheses) */ +} RPRPatternNodeType; + +/* + * RPR_QUANTITY_INF is the sentinel stored in RPRPatternNode.max for an + * unbounded quantifier (*, +, or {n,}); later stages treat this max as + * "no upper bound". It lives here, next to the node, so the parser, the + * planner (optimizer/rpr.h), and the executor all share one definition. + */ +#define RPR_QUANTITY_INF PG_INT32_MAX /* unbounded quantifier */ + +/* + * RPRPatternNode - Row Pattern Recognition pattern parse tree node + */ +typedef struct RPRPatternNode +{ + NodeTag type; /* T_RPRPatternNode */ + RPRPatternNodeType nodeType; /* VAR, SEQ, ALT, GROUP */ + int32 min; /* minimum repetitions (0 for *, ?) */ + int32 max; /* maximum repetitions (RPR_QUANTITY_INF for + * *, +) */ + bool reluctant; /* true for reluctant (non-greedy) */ + ParseLoc location; /* token location, or -1 if unknown */ + char *varName; /* VAR: variable name */ + List *children; /* SEQ, ALT, GROUP: child nodes */ + + /* + * Transient parse flag, cleared by splitRPRTrailingAlt before the tree is + * finalized: a glued quantifier such as "*|" is immediately followed by + * the alternation operator '|'. It is always false in a finalized tree, + * so it has no effect on the pg_stat_statements queryid. + */ + bool trailing_alt; +} RPRPatternNode; + +/* + * RPCommonSyntax - raw representation of row pattern common syntax + */ +typedef struct RPCommonSyntax +{ + NodeTag type; + RPSkipTo rpSkipTo; /* Row Pattern AFTER MATCH SKIP type */ + RPRPatternNode *rpPattern; /* PATTERN parse tree */ + List *rpDefs; /* row pattern definitions clause (list of + * ResTarget) */ + ParseLoc location; /* PATTERN keyword location, or -1 if unknown */ +} RPCommonSyntax; + /* * WindowDef - raw representation of WINDOW and OVER clauses * @@ -597,7 +664,10 @@ typedef struct WindowDef int frameOptions; /* frame_clause options, see below */ Node *startOffset; /* expression for starting bound, if any */ Node *endOffset; /* expression for ending bound, if any */ + RPCommonSyntax *rpCommonSyntax; /* row pattern common syntax */ ParseLoc location; /* parse location, or -1 if none/unknown */ + ParseLoc frameLocation; /* ROWS/RANGE/GROUPS location, or -1 */ + ParseLoc excludeLocation; /* EXCLUDE location, or -1 */ } WindowDef; /* @@ -1562,6 +1632,18 @@ typedef struct GroupingSet * When refname isn't null, the partitionClause is always copied from there; * the orderClause might or might not be copied (see copiedOrder); the framing * options are never copied, per spec. + * "defineClause" is Row Pattern Recognition DEFINE clause (list of + * TargetEntry). TargetEntry.resname represents row pattern definition + * variable name. "rpPattern" represents the PATTERN clause as a parse tree + * (RPRPatternNode). + * Parse analysis sets rpSkipTo, defineClause and rpPattern from one grammar + * production, or none of them. The planner does not keep them so: + * grouping_planner() empties defineClause alone on a window clause that will + * not run, one not in activeWindows. Test rpPattern, never defineClause, for + * "is this a row pattern window". An empty defineClause beside a non-null + * rpPattern means the clause will not run; a non-empty one does not mean it + * will. + * */ typedef struct WindowClause { @@ -1589,6 +1671,12 @@ typedef struct WindowClause Index winref; /* ID referenced by window functions */ /* did we copy orderClause from refname? */ bool copiedOrder pg_node_attr(query_jumble_ignore); + /* AFTER MATCH SKIP type; ST_NONE if this is not a row pattern window */ + RPSkipTo rpSkipTo; + /* Row Pattern DEFINE clause (list of TargetEntry) */ + List *defineClause pg_node_attr(custom_query_jumble); + /* Row Pattern PATTERN parse tree */ + RPRPatternNode *rpPattern; } WindowClause; /* diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 53ae96c0399..b601d697c06 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -129,6 +129,7 @@ PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("define", DEFINE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD, BARE_LABEL) @@ -217,6 +218,7 @@ PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("initial", INITIAL_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) @@ -341,8 +343,11 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("past", PAST, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("pattern", PATTERN_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("permute", PERMUTE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL) @@ -405,6 +410,7 @@ PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("seek", SEEK, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("select", SELECT, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index f23e21f318b..d898d117e95 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -51,6 +51,7 @@ typedef enum ParseExprKind EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */ EXPR_KIND_WINDOW_FRAME_ROWS, /* window frame clause with ROWS */ EXPR_KIND_WINDOW_FRAME_GROUPS, /* window frame clause with GROUPS */ + EXPR_KIND_RPR_DEFINE, /* DEFINE */ EXPR_KIND_SELECT_TARGET, /* SELECT target list item */ EXPR_KIND_INSERT_TARGET, /* INSERT target list item */ EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */ @@ -162,6 +163,18 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param, * p_expr_kind: kind of expression we're currently parsing, as per enum above; * EXPR_KIND_NONE when not in an expression. * + * p_rpr_define: true while we are anywhere inside a row pattern DEFINE + * expression of this query level. p_expr_kind names the innermost clause, so + * it stops saying EXPR_KIND_RPR_DEFINE as soon as a construct nested in the + * condition sets a kind of its own -- FILTER and an aggregate's ORDER BY both + * do. The DEFINE restrictions apply to the whole condition, so they test this + * instead. It is not inherited by a sub-select's ParseState, which is right: + * the restrictions stop at the query boundary. + * + * p_rpr_pattern_vars: names of the row pattern variables of the PATTERN that + * the DEFINE expression being parsed belongs to; NIL when p_rpr_define is + * false. + * * p_next_resno: next TargetEntry.resno to assign, starting from 1. * * p_multiassign_exprs: partially-processed MultiAssignRef source expressions. @@ -207,6 +220,8 @@ struct ParseState ParseNamespaceItem *p_grouping_nsitem; /* NSItem for grouping, or NULL */ List *p_windowdefs; /* raw representations of window clauses */ ParseExprKind p_expr_kind; /* what kind of expression we're parsing */ + bool p_rpr_define; /* inside a row pattern DEFINE expression? */ + List *p_rpr_pattern_vars; /* Row pattern variable names */ int p_next_resno; /* next targetlist resno to assign */ List *p_multiassign_exprs; /* junk tlist entries for multiassign */ List *p_locking_clause; /* raw FOR UPDATE/FOR SHARE info */ -- 2.43.0