aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2024-05-09 13:16:21 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2024-05-09 13:16:21 -0400
commit6e29963edd55cfc038304322049d8fe697580f2c (patch)
tree26ab079d0a86854ec493d42ca1b2887a5d756c44
parent8c3f30e675e972e3c2750ea82781f2c4ba77a9f8 (diff)
downloadpostgresql-6e29963edd55cfc038304322049d8fe697580f2c.tar.gz
postgresql-6e29963edd55cfc038304322049d8fe697580f2c.zip
Fix recursive RECORD-returning plpython functions.
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
-rw-r--r--src/pl/plpython/expected/plpython_composite.out14
-rw-r--r--src/pl/plpython/plpy_exec.c33
-rw-r--r--src/pl/plpython/sql/plpython_composite.sql11
3 files changed, 42 insertions, 16 deletions
diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out
index bb101e07d53..674af93ddcf 100644
--- a/src/pl/plpython/expected/plpython_composite.out
+++ b/src/pl/plpython/expected/plpython_composite.out
@@ -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 plpython3u;
+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)]];
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 9553c72e5ee..a7f7f6c553d 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -231,7 +231,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);
}
@@ -458,21 +474,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();
{
diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql
index 21757701cc1..1bb9b83b719 100644
--- a/src/pl/plpython/sql/plpython_composite.sql
+++ b/src/pl/plpython/sql/plpython_composite.sql
@@ -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 plpython3u;
+
+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)]];