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 <[email protected]>
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 */