From d58451ec7cc04783e77ea6f4ceb9c96c2a33db10 Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 17 Jun 2026 13:08:28 +0800 Subject: [PATCH v48 1/1] Refactor ParseFuncOrColumn and ParseRPRNavCall Simplify ParseFuncOrColumn: It now routes to ParseRPRNavCall exclusively when ParseExprKind is EXPR_KIND_RPR_DEFINE and not column projection and list_length(funcname) == 1. Original behavior is preserved otherwise. Centralize error handling: Treat RPR navigation as FUNCDETAIL_NORMAL to reuse the common error handling in ParseFuncOrColumn, effectively stripping redundant error checks from ParseRPRNavCall. Other miscellaneous code cleanups and minor refactoring. --- src/backend/parser/parse_func.c | 225 +++++++++++-------------- src/test/regress/expected/rpr_base.out | 16 +- src/test/regress/sql/rpr_base.sql | 13 ++ 3 files changed, 125 insertions(+), 129 deletions(-) diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 0b9d676d64..50d7be58ae 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -124,6 +124,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, int fgc_flags; char aggkind = 0; ParseCallbackState pcbstate; + bool could_be_rpr_nav = false; /* * If there's an aggregate filter, transform it using transformWhereClause @@ -220,22 +221,19 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, Assert(first_arg != NULL); } - /* - * Inside an RPR DEFINE clause, an unqualified call to one of the row - * pattern navigation names PREV/NEXT/FIRST/LAST always denotes the - * navigation operation, regardless of what functions exist -- the names - * are recognized here, before any catalog lookup, with no fallback to - * function resolution. A schema-qualified call is the explicit way to - * reach an ordinary function of one of these names. - */ - if (!is_column && !proc_call && - pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE && - list_length(funcname) == 1) + if (pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE) { - retval = ParseRPRNavCall(pstate, funcname, fargs, argnames, fn, - location); - if (retval) - return retval; + if (!is_column && !proc_call && + list_length(funcname) == 1) + { + const char *name = strVal(linitial(funcname)); + + if (strcmp(name, "prev") == 0 || + (strcmp(name, "next") == 0) || + (strcmp(name, "first") == 0) || + (strcmp(name, "last") == 0)) + could_be_rpr_nav = true; + } } /* @@ -284,27 +282,6 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * ordinary function (as happens outside a DEFINE clause) is still open * for discussion. */ - if (is_column && pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE) - { - const char *navname = NULL; - - if (strcmp(strVal(llast(funcname)), "prev") == 0) - navname = "PREV"; - else if (strcmp(strVal(llast(funcname)), "next") == 0) - navname = "NEXT"; - else if (strcmp(strVal(llast(funcname)), "first") == 0) - navname = "FIRST"; - else if (strcmp(strVal(llast(funcname)), "last") == 0) - navname = "LAST"; - - if (navname != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cannot use row pattern navigation function %s in attribute notation", - navname), - errhint("To call an ordinary function of this name, schema-qualify it."), - parser_errposition(pstate, location))); - } /* * func_get_detail looks up the function in the catalogs, does @@ -321,17 +298,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * with default arguments. */ - setup_parser_errposition_callback(&pcbstate, pstate, location); + if (!could_be_rpr_nav) + { + setup_parser_errposition_callback(&pcbstate, pstate, location); - fdresult = func_get_detail(funcname, fargs, argnames, nargs, - actual_arg_types, - !func_variadic, true, proc_call, - &fgc_flags, - &funcid, &rettype, &retset, - &nvargs, &vatype, - &declared_arg_types, &argdefaults); + fdresult = func_get_detail(funcname, fargs, argnames, nargs, + actual_arg_types, + !func_variadic, true, proc_call, + &fgc_flags, + &funcid, &rettype, &retset, + &nvargs, &vatype, + &declared_arg_types, &argdefaults); - cancel_parser_errposition_callback(&pcbstate); + cancel_parser_errposition_callback(&pcbstate); + } + else + { + Assert(!proc_call); + + fdresult = FUNCDETAIL_NORMAL; + } /* * Check for various wrong-kind-of-routine cases. @@ -706,6 +692,22 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, parser_errposition(pstate, location))); } + if (could_be_rpr_nav) + { + /* + * Inside an RPR DEFINE clause, an unqualified call to one of the row + * pattern navigation names PREV/NEXT/FIRST/LAST always denotes the + * navigation operation, regardless of what functions exist -- the + * names are recognized here, before any catalog lookup, with no + * fallback to function resolution. A schema-qualified call is the + * explicit way to reach an ordinary function of one of these names. + */ + return ParseRPRNavCall(pstate, funcname, fargs, + argnames, + fn, + location); + } + /* * If there are default arguments, we have to include their types in * actual_arg_types for the purpose of checking generic type consistency. @@ -2098,8 +2100,6 @@ FuncNameAsType(List *funcname) * to unqualified names) is the documented way to reach such a function * instead. * - * Returns the RPRNavExpr, or NULL if funcname is not a navigation name, in - * which case the caller resolves it as an ordinary function. */ static Node * ParseRPRNavCall(ParseState *pstate, List *funcname, List *fargs, @@ -2134,7 +2134,10 @@ ParseRPRNavCall(ParseState *pstate, List *funcname, List *fargs, navname = "LAST"; } else + { + pg_unreachable(); return NULL; + } /* * Once the name matches we never fall back to function resolution, so any @@ -2143,77 +2146,43 @@ ParseRPRNavCall(ParseState *pstate, List *funcname, List *fargs, * existing messages so the user-visible text and translations are * unchanged. */ - if (fn->agg_star) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("%s(*) specified, but %s is not an aggregate function", - NameListToString(funcname), - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (fn->agg_distinct) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("DISTINCT specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (fn->agg_within_group) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("WITHIN GROUP specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (fn->agg_order != NIL) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("ORDER BY specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (fn->agg_filter) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("FILTER specified, but %s is not an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); - if (fn->over) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("OVER specified, but %s is not a window function nor an aggregate function", - NameListToString(funcname)), - parser_errposition(pstate, location))); if (fn->func_variadic) ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cannot use VARIADIC with row pattern navigation function %s", - navname), - parser_errposition(pstate, location))); + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use VARIADIC with row pattern navigation function %s", + navname), + parser_errposition(pstate, location)); + if (argnames != NIL) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("row pattern navigation operations cannot use named arguments"), - parser_errposition(pstate, location))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("row pattern navigation operations cannot use named arguments"), + parser_errposition(pstate, location)); + if (fn->ignore_nulls != NO_NULLTREATMENT) ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("row pattern navigation operations do not accept RESPECT/IGNORE NULLS"), - parser_errposition(pstate, location))); + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("row pattern navigation operations do not accept RESPECT/IGNORE NULLS"), + parser_errposition(pstate, location)); /* arity: a value expression and an optional offset */ if (nargs == 0) ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("too few arguments for row pattern navigation function %s", - navname), - errdetail("%s takes a value expression and an optional offset argument.", - navname), - parser_errposition(pstate, location))); + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too few arguments for row pattern navigation function %s", + navname), + errdetail("%s takes a value expression and an optional offset argument.", + navname), + parser_errposition(pstate, location)); + if (nargs > 2) ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("too many arguments for row pattern navigation function %s", - navname), - errdetail("%s takes a value expression and an optional offset argument.", - navname), - parser_errposition(pstate, location))); + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too many arguments for row pattern navigation function %s", + navname), + errdetail("%s takes a value expression and an optional offset argument.", + navname), + parser_errposition(pstate, location)); /* * Resolve a still-unknown first argument to text, the same way the @@ -2224,39 +2193,41 @@ ParseRPRNavCall(ParseState *pstate, List *funcname, List *fargs, */ arg = linitial(fargs); if (exprType(arg) == UNKNOWNOID) - arg = coerce_to_common_type(pstate, arg, TEXTOID, navname); + arg = coerce_to_target_type(pstate, arg, UNKNOWNOID, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); navexpr = makeNode(RPRNavExpr); navexpr->kind = kind; navexpr->arg = (Expr *) arg; + navexpr->offset_arg = NULL; - /* an explicit offset is coerced to int8, which the executor reads */ + /* compound_offset_arg is populated in define_walker */ + navexpr->compound_offset_arg = NULL; + + /* The offset argument must be coercible to int8 */ if (nargs == 2) { Node *offset = lsecond(fargs); - Oid offtype = exprType(offset); + Node *newoffset; - if (offtype != INT8OID) - { - Node *newoffset; + newoffset = coerce_to_target_type(pstate, offset, exprType(offset), + INT8OID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); - newoffset = coerce_to_target_type(pstate, offset, offtype, - INT8OID, -1, COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, -1); - if (newoffset == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("offset argument of %s must be type %s, not type %s", - navname, "bigint", format_type_be(offtype)), - parser_errposition(pstate, exprLocation(offset)))); - offset = newoffset; - } - navexpr->offset_arg = (Expr *) offset; + if (newoffset == NULL) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("%s offset argument of type %s cannot be coerced to the expected %s", + navname, format_type_be(exprType(offset)), format_type_be(INT8OID)), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(offset))); + + navexpr->offset_arg = (Expr *) newoffset; } - else - navexpr->offset_arg = NULL; - /* compound_offset_arg stays NULL; define_walker flattening fills it in */ navexpr->resulttype = exprType(arg); /* resultcollid will be set by parse_collate.c */ navexpr->location = location; diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out index ee74853a9f..5fbec93bab 100644 --- a/src/test/regress/expected/rpr_base.out +++ b/src/test/regress/expected/rpr_base.out @@ -1898,6 +1898,19 @@ SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id 5 | 150 | 0 | (5 rows) +-- (val).prev is interpreted as a call to the (not schema-qualified) function +-- prev with val as its argument +SELECT count(*) OVER w +FROM nt +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + PATTERN (START UP+) + DEFINE START AS TRUE, UP AS (val).prev IS NOT NULL); +ERROR: volatile functions are not allowed in DEFINE clause +LINE 6: DEFINE START AS TRUE, UP AS (val).prev IS NOT NULL); + ^ +-- rpr_navns.prev(val) is interpreted as a call to the schema-qualified function +-- rpr_navns.prev(), passing the val ((column of table nt) as its argument. SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id FROM nt WINDOW w AS (PARTITION BY g ORDER BY id @@ -2129,10 +2142,9 @@ SELECT count(*) OVER w FROM ct WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL PATTERN (A+) DEFINE A AS (p).prev > 0); -ERROR: cannot use row pattern navigation function PREV in attribute notation +ERROR: column "prev" not found in data type rpr_navns_pair LINE 4: PATTERN (A+) DEFINE A AS (p).prev > 0); ^ -HINT: To call an ordinary function of this name, schema-qualify it. -- Navigation offset must not contain a navigation operation SELECT id, val FROM nt diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql index 1d0cccf719..3be6de38ca 100644 --- a/src/test/regress/sql/rpr_base.sql +++ b/src/test/regress/sql/rpr_base.sql @@ -1352,6 +1352,18 @@ SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id PATTERN (START UP+) DEFINE START AS TRUE, UP AS val > PREV(val)) ORDER BY id; + +-- (val).prev is interpreted as a call to the (not schema-qualified) function +-- prev with val as its argument +SELECT count(*) OVER w +FROM nt +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + PATTERN (START UP+) + DEFINE START AS TRUE, UP AS (val).prev IS NOT NULL); + +-- rpr_navns.prev(val) is interpreted as a call to the schema-qualified function +-- rpr_navns.prev(), passing the val ((column of table nt) as its argument. SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id FROM nt WINDOW w AS (PARTITION BY g ORDER BY id @@ -1360,6 +1372,7 @@ SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id DEFINE A AS rpr_navns.prev(val) = -999) ORDER BY id; DROP FUNCTION prev(integer); + -- IMMUTABLE: unqualified is nav; qualified is the escape hatch and succeeds CREATE FUNCTION prev(integer) RETURNS integer AS 'SELECT -999' LANGUAGE sql IMMUTABLE; -- 2.34.1