Fix recursive RECORD-returning plpython functions.
authorTom Lane <[email protected]>
Thu, 9 May 2024 17:16:21 +0000 (13:16 -0400)
committerTom Lane <[email protected]>
Thu, 9 May 2024 17:16:21 +0000 (13:16 -0400)
If we recursed to a new call of the same function, with a different
coldeflist (AS clause), it would fail because the inner call would
overwrite the outer call's idea of what to return.  This is vaguely
like 1d2fe56e4 and c5bec5426, but it's not due to any API decisions:
it's just that we computed the actual output rowtype at the start of
the call, and saved it in the per-procedure data structure.  We can
fix it at basically zero cost by doing the computation at the end
of each call instead of the start.

It's not clear that there's any real-world use-case for such a
function, but given that it doesn't cost anything to fix,
it'd be silly not to.

Per report from Andreas Karlsson.  Back-patch to all supported
branches.

Discussion: https://postgr.es/m/1651a46d-3c15-4028-a8c1-d74937b54e19@proxel.se

src/pl/plpython/expected/plpython_composite.out
src/pl/plpython/plpy_exec.c
src/pl/plpython/sql/plpython_composite.sql

index af801923343d6ef5746e09bcb8d734240eba588b..b9111210d54a75c4ef87ed8b1cf52a8c4cba9978 100644 (file)
@@ -569,6 +569,20 @@ SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int);
   1 |  2 |  3
 (1 row)
 
+-- recursion with a different inner result type didn't use to work
+CREATE FUNCTION return_record_3(t text) RETURNS record AS $$
+if t == "text":
+     plpy.execute("SELECT * FROM return_record_3('int') AS (a int)");
+     return { "a": "x" }
+elif t == "int":
+     return { "a": 1 }
+$$ LANGUAGE plpythonu;
+SELECT * FROM return_record_3('text') AS (a text);
+ a 
+---
+ x
+(1 row)
+
 -- multi-dimensional array of composite types.
 CREATE FUNCTION composite_type_as_list()  RETURNS type_record[] AS $$
   return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];
index 697726162a8b502e304915a025887a88386bfca5..f4b01b76a5abe53d7e7fe04cf6c837052e1d4a2b 100644 (file)
@@ -235,7 +235,23 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
        }
        else
        {
-           /* Normal conversion of result */
+           /*
+            * Normal conversion of result.  However, if the result is of type
+            * RECORD, we have to set up for that each time through, since it
+            * might be different from last time.
+            */
+           if (proc->result.typoid == RECORDOID)
+           {
+               TupleDesc   desc;
+
+               if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("function returning record called in context "
+                                   "that cannot accept type record")));
+               PLy_output_setup_record(&proc->result, desc, proc);
+           }
+
            rv = PLy_output_convert(&proc->result, plrv,
                                    &fcinfo->isnull);
        }
@@ -470,21 +486,6 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
                PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments");
            arg = NULL;
        }
-
-       /* Set up output conversion for functions returning RECORD */
-       if (proc->result.typoid == RECORDOID)
-       {
-           TupleDesc   desc;
-
-           if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE)
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("function returning record called in context "
-                               "that cannot accept type record")));
-
-           /* cache the output conversion functions */
-           PLy_output_setup_record(&proc->result, desc, proc);
-       }
    }
    PG_CATCH();
    {
index 0fd2f5d5e3bc40648f96b412017c086f0a27fac1..844b761bf11c85906eacff44a098bc336275b444 100644 (file)
@@ -208,6 +208,17 @@ SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int);
 SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int);
 SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int);
 
+-- recursion with a different inner result type didn't use to work
+CREATE FUNCTION return_record_3(t text) RETURNS record AS $$
+if t == "text":
+     plpy.execute("SELECT * FROM return_record_3('int') AS (a int)");
+     return { "a": "x" }
+elif t == "int":
+     return { "a": 1 }
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM return_record_3('text') AS (a text);
+
 -- multi-dimensional array of composite types.
 CREATE FUNCTION composite_type_as_list()  RETURNS type_record[] AS $$
   return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];