This is an automated email from the ASF dual-hosted git repository. mtaha pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/master by this push: new 94770a69 Fix issue with CALL/YIELD for user defined and qualified functions. (#2217) 94770a69 is described below commit 94770a69751c8ecdbf21887ee83a98ed6ae979c8 Author: John Gemignani <jrgemign...@gmail.com> AuthorDate: Mon Sep 15 06:35:52 2025 -0700 Fix issue with CALL/YIELD for user defined and qualified functions. (#2217) Fixed 2 issues with CALL/YIELD - 1) If a user defined function was in search_path, the transform_FuncCall logic would only find it, if it were part of an extension. 2) If a function were qualified, the transform_cypher_call_subquery logic would mistakenly extract the schema name instead of the function name. NOTE: transform_FuncCall should be reviewed for possible refactor. Added regression tests. modified: src/backend/parser/cypher_clause.c modified: src/backend/parser/cypher_expr.c modified: regress/expected/cypher_call.out modified: regress/sql/cypher_call.sql --- regress/expected/cypher_call.out | 52 ++++++++++++++++++++++++++++++++++++++ regress/sql/cypher_call.sql | 25 ++++++++++++++++++ src/backend/parser/cypher_clause.c | 6 ++--- src/backend/parser/cypher_expr.c | 13 +++++----- 4 files changed, 86 insertions(+), 10 deletions(-) diff --git a/regress/expected/cypher_call.out b/regress/expected/cypher_call.out index 6980abe4..bb1185b9 100644 --- a/regress/expected/cypher_call.out +++ b/regress/expected/cypher_call.out @@ -240,6 +240,58 @@ SELECT * FROM cypher('cypher_call', $$ CALL sqrt(64) YIELD sqrt CALL agtype_sum( ERROR: duplicate variable "sqrt" LINE 1: ...LL sqrt(64) YIELD sqrt CALL agtype_sum(2,2) YIELD agtype_sum... ^ +-- Fix CALL/YIELD issues +CREATE OR REPLACE FUNCTION myfunc(i agtype) +RETURNS agtype +LANGUAGE plpgsql +AS $$ +DECLARE + result agtype; +BEGIN + RETURN ag_catalog.age_sqrt(i); +END; +$$; +-- should have no errors +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.age_sqrt(64) YIELD age_sqrt RETURN age_sqrt $$) as (sqrt agtype); + sqrt +------ + 8.0 +(1 row) + +SELECT * FROM cypher('cypher_call', $$ CALL myfunc(25) YIELD myfunc RETURN myfunc $$) as (result agtype); + result +-------- + 5.0 +(1 row) + +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunc(25) YIELD myfunc RETURN myfunc $$) as (result agtype); + result +-------- + 5.0 +(1 row) + +-- should error +SELECT * FROM cypher('cypher_call', $$ CALL myfunc() YIELD myfunc RETURN myfunc $$) as (result agtype); +ERROR: function myfunc does not exist +LINE 1: SELECT * FROM cypher('cypher_call', $$ CALL myfunc() YIELD m... + ^ +HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path. +SELECT * FROM cypher('cypher_call', $$ CALL myfunz(25) YIELD myfunc RETURN myfunc $$) as (result agtype); +ERROR: function myfunz does not exist +LINE 1: SELECT * FROM cypher('cypher_call', $$ CALL myfunz(25) YIELD... + ^ +HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path. +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunc() YIELD myfunc RETURN myfunc $$) as (result agtype); +ERROR: function ag_catalog.myfunc() does not exist +LINE 1: ...T * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunc() Y... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunz(25) YIELD myfunc RETURN myfunc $$) as (result agtype); +ERROR: function ag_catalog.myfunz(agtype) does not exist +LINE 1: ...OM cypher('cypher_call', $$ CALL ag_catalog.myfunz(25) YIELD... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +DROP FUNCTION myfunc; DROP SCHEMA call_stmt_test CASCADE; NOTICE: drop cascades to function call_stmt_test.add_agtype(agtype,agtype) SELECT drop_graph('cypher_call', true); diff --git a/regress/sql/cypher_call.sql b/regress/sql/cypher_call.sql index 91727680..ee146875 100644 --- a/regress/sql/cypher_call.sql +++ b/regress/sql/cypher_call.sql @@ -104,5 +104,30 @@ SELECT * FROM cypher('cypher_call', $$ CALL sqrt(64) YIELD sqrt AS sqrt1 CALL sq SELECT * FROM cypher('cypher_call', $$ CALL sqrt(64) YIELD sqrt AS sqrt1 CALL sqrt(81) YIELD sqrt AS sqrt1 RETURN sqrt1, sqrt1 $$) as (a agtype, b agtype); SELECT * FROM cypher('cypher_call', $$ CALL sqrt(64) YIELD sqrt CALL agtype_sum(2,2) YIELD agtype_sum AS sqrt RETURN sqrt, sqrt $$) as (a agtype, b agtype); +-- Fix CALL/YIELD issues +CREATE OR REPLACE FUNCTION myfunc(i agtype) +RETURNS agtype +LANGUAGE plpgsql +AS $$ +DECLARE + result agtype; +BEGIN + RETURN ag_catalog.age_sqrt(i); +END; +$$; + +-- should have no errors +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.age_sqrt(64) YIELD age_sqrt RETURN age_sqrt $$) as (sqrt agtype); +SELECT * FROM cypher('cypher_call', $$ CALL myfunc(25) YIELD myfunc RETURN myfunc $$) as (result agtype); +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunc(25) YIELD myfunc RETURN myfunc $$) as (result agtype); + +-- should error +SELECT * FROM cypher('cypher_call', $$ CALL myfunc() YIELD myfunc RETURN myfunc $$) as (result agtype); +SELECT * FROM cypher('cypher_call', $$ CALL myfunz(25) YIELD myfunc RETURN myfunc $$) as (result agtype); +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunc() YIELD myfunc RETURN myfunc $$) as (result agtype); +SELECT * FROM cypher('cypher_call', $$ CALL ag_catalog.myfunz(25) YIELD myfunc RETURN myfunc $$) as (result agtype); + +DROP FUNCTION myfunc; + DROP SCHEMA call_stmt_test CASCADE; SELECT drop_graph('cypher_call', true); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 172e6305..93e710ab 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1153,7 +1153,7 @@ static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, EXPR_KIND_FROM_FUNCTION)); /* retrieve the column name from funccall */ - colName = strVal(linitial(self->funccall->funcname)); + colName = strVal(llast(self->funccall->funcname)); /* make a targetentry from the funcexpr node */ tle = makeTargetEntry((Expr *) node, @@ -3957,7 +3957,7 @@ static List *transform_map_to_ind_recursive(cypher_parsestate *cpstate, * * Transforms the map to a list of equality irrespective of * value type. For example, - * + * * x.name = 'xyz' * x.map = {"city": "abc", "street": {"name": "pqr", "number": 123}} * x.list = [9, 8, 7] @@ -4011,7 +4011,7 @@ static List *transform_map_to_ind_top_level(cypher_parsestate *cpstate, qual = (Node *)make_op(pstate, op, lhs, rhs, last_srf, -1); quals = lappend(quals, qual); } - + return quals; } diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 390bfb39..993957c8 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -2036,7 +2036,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) targs = lcons(c, targs); } } - /* + /* * If it's not in age, check if it's a potential call to some function * in another installed extension. */ @@ -2055,14 +2055,13 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) procform, extension); return retval; } + /* + * Else we have a function that is in the search_path, and not + * qualified, but is not in an extension. Pass it through. + */ else { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", name), - errhint("If the function is from an external extension, " - "make sure the extension is installed and the " - "function is in the search path."))); + fname = fn->funcname; } } /* no function found */