diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out index 70a4571..1576588 100644 *** a/src/pl/plpython/expected/plpython_composite.out --- b/src/pl/plpython/expected/plpython_composite.out *************** SELECT * FROM composite_types_table(); *** 273,275 **** --- 273,309 ---- ERROR: PL/Python functions cannot return type table_record[] DETAIL: PL/Python does not support conversion to arrays of row types. CONTEXT: PL/Python function "composite_types_table" + -- check what happens if the output record descriptor changes + CREATE FUNCTION return_record(t text) RETURNS record AS $$ + return {'t': t, 'val': 10} + $$ LANGUAGE plpythonu; + SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val + -----+----- + abc | 10 + (1 row) + + SELECT * FROM return_record('abc') AS r(t text, val bigint); + t | val + -----+----- + abc | 10 + (1 row) + + SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val + -----+----- + abc | 10 + (1 row) + + SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); + t | val + -----+----- + abc | 10 + (1 row) + + SELECT * FROM return_record('abc') AS r(t varchar(100), val integer); + t | val + -----+----- + abc | 10 + (1 row) + diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index cc7a007..1f93c7b 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef int Py_ssize_t; *** 100,105 **** --- 100,106 ---- #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" + #include "parser/parse_coerce.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/lsyscache.h" *************** static void PLy_input_datum_func(PLyType *** 334,339 **** --- 335,341 ---- static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple); static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc); static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); + static void PLy_output_record_funcs(PLyTypeInfo *, TupleDesc); /* conversion functions */ static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d); *************** PLy_function_build_args(FunctionCallInfo *** 1262,1269 **** } /* Set up output conversion for functions returning RECORD */ ! if (proc->result.out.d.typoid == RECORDOID && ! proc->result.is_rowtype > 1) { TupleDesc desc; --- 1264,1270 ---- } /* Set up output conversion for functions returning RECORD */ ! if (proc->result.out.d.typoid == RECORDOID) { TupleDesc desc; *************** PLy_function_build_args(FunctionCallInfo *** 1275,1288 **** errmsg("function returning record called in context " "that cannot accept type record"))); } ! /* bless the record to make it known to the typcache lookup code */ ! BlessTupleDesc(desc); ! /* save the freshly generated typmod */ ! proc->result.out.d.typmod = desc->tdtypmod; ! /* proceed with normal I/O function caching */ ! PLy_output_tuple_funcs(&(proc->result), desc); ! /* it should change is_rowtype to 1, so we won't go through this again */ ! Assert(proc->result.is_rowtype == 1); } } PG_CATCH(); --- 1276,1284 ---- errmsg("function returning record called in context " "that cannot accept type record"))); } ! ! /* cache the output conversion functions */ ! PLy_output_record_funcs(&(proc->result), desc); } } PG_CATCH(); *************** PLy_input_tuple_funcs(PLyTypeInfo *arg, *** 1769,1774 **** --- 1765,1809 ---- } static void + PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc) + { + /* + * If the output record functions are already set, we just have to check + * if the record descriptor has not changed + */ + bool can_skip = false; + + if (arg->is_rowtype == 1) + { + int i; + + /* the functions are already set, check the attributes */ + Assert(arg->out.r.natts == desc->natts); + can_skip = true; + + for (i = 0; i < arg->out.r.natts; i++) + { + if (!IsBinaryCoercible(arg->out.r.atts[i].typoid, + desc->attrs[i]->atttypid)) + can_skip = false; + } + } + + if (can_skip) + return; + + /* bless the record to make it known to the typcache lookup code */ + BlessTupleDesc(desc); + /* save the freshly generated typmod */ + arg->out.d.typmod = desc->tdtypmod; + /* proceed with normal I/O function caching */ + PLy_output_tuple_funcs(arg, desc); + /* it should change is_rowtype to 1, so we won't go through this again + * unless the the output record description changes */ + Assert(arg->is_rowtype == 1); + } + + static void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) { int i; diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql index 1330e26..db4bd73 100644 *** a/src/pl/plpython/sql/plpython_composite.sql --- b/src/pl/plpython/sql/plpython_composite.sql *************** yield {'tab': [['first', 1], ['second', *** 140,142 **** --- 140,153 ---- $$ LANGUAGE plpythonu; SELECT * FROM composite_types_table(); + + -- check what happens if the output record descriptor changes + CREATE FUNCTION return_record(t text) RETURNS record AS $$ + return {'t': t, 'val': 10} + $$ LANGUAGE plpythonu; + + SELECT * FROM return_record('abc') AS r(t text, val integer); + SELECT * FROM return_record('abc') AS r(t text, val bigint); + SELECT * FROM return_record('abc') AS r(t text, val integer); + SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); + SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);