diff --git a/doc/src/sgml/rowtypes.sgml b/doc/src/sgml/rowtypes.sgml index 3f24293175..2f924b1f85 100644 --- a/doc/src/sgml/rowtypes.sgml +++ b/doc/src/sgml/rowtypes.sgml @@ -441,9 +441,12 @@ SELECT c.somefunc FROM inventory_item c; Because of this behavior, it's unwise to give a function that takes a single composite-type argument the same name as any of the fields of that composite type. If there is ambiguity, the field-name - interpretation will be preferred, so that such a function could not be - called without tricks. One way to force the function interpretation is - to schema-qualify the function name, that is, write + interpretation will be chosen if field-name syntax is used, while the + function will be chosen if function-call syntax is used. However, + PostgreSQL versions before 11 always chose the + field-name interpretation, unless the syntax of the call required it to + be a function call. One way to force the function interpretation in + older versions is to schema-qualify the function name, that is, write schema.func(compositevalue). diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 21ddd5b7e0..abe1dbc521 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -49,15 +49,17 @@ static Node *ParseComplexProjection(ParseState *pstate, const char *funcname, * For historical reasons, Postgres tries to treat the notations tab.col * and col(tab) as equivalent: if a single-argument function call has an * argument of complex type and the (unqualified) function name matches - * any attribute of the type, we take it as a column projection. Conversely - * a function of a single complex-type argument can be written like a - * column reference, allowing functions to act like computed columns. + * any attribute of the type, we can interpret it as a column projection. + * Conversely a function of a single complex-type argument can be written + * like a column reference, allowing functions to act like computed columns. + * + * If both interpretations are possible, we prefer the one matching the + * syntactic form, but otherwise the form does not matter. * * Hence, both cases come through here. If fn is null, we're dealing with - * column syntax not function syntax, but in principle that should not - * affect the lookup behavior, only which error messages we deliver. - * The FuncCall struct is needed however to carry various decoration that - * applies to aggregate and window functions. + * column syntax not function syntax. In the function-syntax case, + * the FuncCall struct is needed to carry various decoration that applies + * to aggregate and window functions. * * Also, when fn is null, we return NULL on failure rather than * reporting a no-such-function error. @@ -84,6 +86,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, bool agg_distinct = (fn ? fn->agg_distinct : false); bool func_variadic = (fn ? fn->func_variadic : false); WindowDef *over = (fn ? fn->over : NULL); + bool could_be_projection; Oid rettype; Oid funcid; ListCell *l; @@ -202,36 +205,39 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, } /* - * Check for column projection: if function has one argument, and that - * argument is of complex type, and function name is not qualified, then - * the "function call" could be a projection. We also check that there - * wasn't any aggregate or variadic decoration, nor an argument name. + * Decide whether it's legitimate to consider the construct to be a column + * projection. For that, there has to be a single argument of complex + * type, the function name must not be qualified, and there cannot be any + * syntactic decoration that'd require it to be a function (such as + * aggregate or variadic decoration, or named arguments). */ - if (nargs == 1 && !proc_call && - agg_order == NIL && agg_filter == NULL && !agg_star && - !agg_distinct && over == NULL && !func_variadic && argnames == NIL && - list_length(funcname) == 1) + could_be_projection = (nargs == 1 && !proc_call && + agg_order == NIL && agg_filter == NULL && + !agg_star && !agg_distinct && over == NULL && + !func_variadic && argnames == NIL && + list_length(funcname) == 1 && + (actual_arg_types[0] == RECORDOID || + ISCOMPLEX(actual_arg_types[0]))); + + /* + * If it's column syntax, check for column projection case first. + */ + if (could_be_projection && is_column) { - Oid argtype = actual_arg_types[0]; + retval = ParseComplexProjection(pstate, + strVal(linitial(funcname)), + first_arg, + location); + if (retval) + return retval; - if (argtype == RECORDOID || ISCOMPLEX(argtype)) - { - retval = ParseComplexProjection(pstate, - strVal(linitial(funcname)), - first_arg, - location); - if (retval) - return retval; - - /* - * If ParseComplexProjection doesn't recognize it as a projection, - * just press on. - */ - } + /* + * If ParseComplexProjection doesn't recognize it as a projection, + * just press on. + */ } /* - * Okay, it's not a column projection, so it must really be a function. * func_get_detail looks up the function in the catalogs, does * disambiguation for polymorphic functions, handles inheritance, and * returns the funcid and type and set or singleton status of the @@ -334,7 +340,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, } /* - * So far so good, so do some routine-type-specific processing. + * So far so good, so do some fdresult-type-specific processing. */ if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE) { @@ -524,30 +530,55 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, actual_arg_types[0], rettype, -1, COERCION_EXPLICIT, COERCE_EXPLICIT_CALL, location); } + else if (fdresult == FUNCDETAIL_MULTIPLE) + { + /* + * We found multiple possible functional matches. If we are dealing + * with attribute notation, return failure, letting the caller report + * "no such column" (we already determined there wasn't one). If + * dealing with function notation, report "ambiguous function", + * regardless of whether there's also a column by this name. + */ + if (is_column) + return NULL; + + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("function %s is not unique", + func_signature_string(funcname, nargs, argnames, + actual_arg_types)), + errhint("Could not choose a best candidate function. " + "You might need to add explicit type casts."), + parser_errposition(pstate, location))); + } else { /* - * Oops. Time to die. - * - * If we are dealing with the attribute notation rel.function, let the - * caller handle failure. + * Not found as a function. If we are dealing with attribute + * notation, return failure, letting the caller report "no such + * column" (we already determined there wasn't one). */ if (is_column) return NULL; /* - * Else generate a detailed complaint for a function + * Check for column projection interpretation, since we didn't before. */ - if (fdresult == FUNCDETAIL_MULTIPLE) - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_FUNCTION), - errmsg("function %s is not unique", - func_signature_string(funcname, nargs, argnames, - actual_arg_types)), - errhint("Could not choose a best candidate function. " - "You might need to add explicit type casts."), - parser_errposition(pstate, location))); - else if (list_length(agg_order) > 1 && !agg_within_group) + if (could_be_projection) + { + retval = ParseComplexProjection(pstate, + strVal(linitial(funcname)), + first_arg, + location); + if (retval) + return retval; + } + + /* + * No function, and no column either. Since we're dealing with + * function notation, report "function does not exist". + */ + if (list_length(agg_order) > 1 && !agg_within_group) { /* It's agg(x, ORDER BY y,z) ... perhaps misplaced ORDER BY */ ereport(ERROR, diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 45cb6ff3da..30053d07df 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -797,6 +797,50 @@ select (row('Jim', 'Beam')).text; -- error ERROR: could not identify column "text" in record data type LINE 1: select (row('Jim', 'Beam')).text; ^ +-- +-- Check the equivalence of functional and column notation +-- +insert into fullname values ('Joe', 'Blow'); +select f.last from fullname f; + last +------ + Blow +(1 row) + +select last(f) from fullname f; + last +------ + Blow +(1 row) + +create function longname(fullname) returns text language sql +as $$select $1.first || ' ' || $1.last$$; +select f.longname from fullname f; + longname +---------- + Joe Blow +(1 row) + +select longname(f) from fullname f; + longname +---------- + Joe Blow +(1 row) + +-- Starting in v11, the notational form does matter if there's ambiguity +alter table fullname add column longname text; +select f.longname from fullname f; + longname +---------- + +(1 row) + +select longname(f) from fullname f; + longname +---------- + Joe Blow +(1 row) + -- -- Test that composite values are seen to have the correct column names -- (bug #11210 and other reports) diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 305639f05d..faf2e108d6 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -345,6 +345,26 @@ select (row('Jim', 'Beam'))::text; select text(row('Jim', 'Beam')); -- error select (row('Jim', 'Beam')).text; -- error +-- +-- Check the equivalence of functional and column notation +-- +insert into fullname values ('Joe', 'Blow'); + +select f.last from fullname f; +select last(f) from fullname f; + +create function longname(fullname) returns text language sql +as $$select $1.first || ' ' || $1.last$$; + +select f.longname from fullname f; +select longname(f) from fullname f; + +-- Starting in v11, the notational form does matter if there's ambiguity +alter table fullname add column longname text; + +select f.longname from fullname f; +select longname(f) from fullname f; + -- -- Test that composite values are seen to have the correct column names -- (bug #11210 and other reports)