From 68a1f8600e0b43c8c1960362e062165efe1379cc Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 31 Oct 2026 11:26:56 -0400
Subject: [PATCH v2 1/2] Fix failure in function cache entry hashing during
 validation mode.

When cached_function_compile() is called with forValidator = true,
the passed FunctionCallInfo is one that the caller set up specially,
and it's typically pretty incomplete.  compute_function_hashkey()
was careless about this and would call get_call_result_type()
if asked to, which would fail in any but the simplest cases.
We'd managed not to notice in the pre-existing use-cases because
plpgsql and functions.c don't pass includeResultType and forValidator
both true at the same time; but plpython does, so it would reach this
failure in cases such as polymorphic argument types.  plpython does
not yet support polymorphic arguments, and has no test cases about
them, so we did not see that the intended error message had degraded
to an internal failure in get_call_result_type().

To fix, just skip trying to fill the result type tupdesc when in
validation mode.  This leads to building a hashtable entry that will
never match any real calls, but that's true in a lot of other cases
as well.

The root cause here is lack of clarity about what we expect the
passed FunctionCallInfo to contain in validation mode, so add
comments specifying that.  Also add some plpython test cases showing
the expected error reports.

Backpatch to v18 where funccache.c was introduced.  This problem's
not reachable in the core code in v18, but extensions could perhaps
be using funccache.c as of v18, and they might hit it.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/2865288.1793403201@sss.pgh.pa.us
Backpatch-through: 18
---
 src/backend/utils/cache/funccache.c         | 15 ++++++++++++---
 src/pl/plpython/expected/plpython_error.out | 10 ++++++++++
 src/pl/plpython/sql/plpython_error.sql      | 10 ++++++++++
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c
index b3093ae5940..7b3422a07bf 100644
--- a/src/backend/utils/cache/funccache.c
+++ b/src/backend/utils/cache/funccache.c
@@ -242,6 +242,9 @@ cfunc_hashtable_delete(CachedFunction *function)
  * The hashkey is returned into the caller-provided storage at *hashkey.
  * Note however that if a callResultType is incorporated, we've not done
  * anything about copying that.
+ *
+ * See comments for cached_function_compile() about how much of *fcinfo
+ * we're allowed to consider when forValidator is true.
  */
 static void
 compute_function_hashkey(FunctionCallInfo fcinfo,
@@ -326,9 +329,12 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
 	 * a function returning RECORD may depend on a column definition list to
 	 * determine its output rowtype.  If the caller needs the exact result
 	 * type to be part of the hash lookup key, we must run
-	 * get_call_result_type() to find that out.
+	 * get_call_result_type() to find that out.  However, in validation mode
+	 * the supplied fcinfo will likely not be complete enough for that to
+	 * work, so leave callResultType unfilled (implying that the resulting
+	 * hash entry will never match any actual calls for such functions).
 	 */
-	if (includeResultType)
+	if (includeResultType && !forValidator)
 	{
 		Oid			resultTypeId;
 		TupleDesc	tupdesc;
@@ -482,7 +488,10 @@ delete_function(CachedFunction *func)
  * include the actual result descriptor in the cache lookup key.
  *
  * If forValidator is true, we're only compiling for validation purposes,
- * and so some checks are skipped.
+ * and so some checks are skipped.  In this case the passed fcinfo need
+ * only provide the function OID and indications of whether the function
+ * is a trigger or event trigger (as tested via CALLED_AS_TRIGGER or
+ * CALLED_AS_EVENT_TRIGGER).
  *
  * Note: it's important for this to fall through quickly if the function
  * has already been compiled.
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 96bbdfb9586..42003ffd69b 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -458,3 +458,13 @@ ERROR:  error fetching next item from iterator
 DETAIL:  spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD"
 CONTEXT:  Traceback (most recent call last):
 PL/Python function "python_error_detail"
+/* test the presently-unsupported case of polymorphic functions
+ */
+CREATE FUNCTION python_poly_ret(x anyelement) RETURNS anyelement AS $$
+  return x
+$$ LANGUAGE plpython3u;
+ERROR:  PL/Python functions cannot return type anyelement
+CREATE FUNCTION python_poly_arg(x anyelement) RETURNS int AS $$
+  return x
+$$ LANGUAGE plpython3u;
+ERROR:  PL/Python functions cannot accept type anyelement
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 9bb1c0b085a..35dd16440c4 100644
--- a/src/pl/plpython/sql/plpython_error.sql
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -355,3 +355,13 @@ CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
 $$ LANGUAGE plpython3u;
 
 SELECT python_error_detail();
+
+/* test the presently-unsupported case of polymorphic functions
+ */
+CREATE FUNCTION python_poly_ret(x anyelement) RETURNS anyelement AS $$
+  return x
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION python_poly_arg(x anyelement) RETURNS int AS $$
+  return x
+$$ LANGUAGE plpython3u;
-- 
2.52.0

