diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 3f0e6290bb2..e209b2a2d23 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -1026,13 +1026,6 @@ rv = plpy.execute(plan, ["name"], 5)
Query parameters and result row fields are converted between PostgreSQL
and Python data types as described in .
- The exception is that composite types are currently not supported: They
- will be rejected as query parameters and are converted to strings when
- appearing in a query result. As a workaround for the latter problem, the
- query can sometimes be rewritten so that the composite type result
- appears as a result row rather than as a field of the result row.
- Alternatively, the resulting string could be parsed apart by hand, but
- this approach is not recommended because it is not future-proof.
diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out
index 61b3046790f..ad454f37394 100644
--- a/src/pl/plpython/expected/plpython_composite.out
+++ b/src/pl/plpython/expected/plpython_composite.out
@@ -257,7 +257,7 @@ SELECT * FROM changing_test();
1 | (3,4)
(2 rows)
--- tables of composite types (not yet implemented)
+-- tables of composite types
CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
yield {'tab': [['first', 1], ['second', 2]],
'typ': [{'first': 'third', 'second': 3},
@@ -270,9 +270,13 @@ yield {'tab': [['first', 1], ['second', 2]],
{'first': 'fourth', 'second': 4}]}
$$ LANGUAGE plpythonu;
SELECT * FROM composite_types_table();
-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"
+ tab | typ
+----------------------------+----------------------------
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+(3 rows)
+
-- check what happens if the output record descriptor changes
CREATE FUNCTION return_record(t text) RETURNS record AS $$
return {'t': t, 'val': 10}
diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
index 517579471c9..e2861dfa722 100644
--- a/src/pl/plpython/expected/plpython_spi.out
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -376,6 +376,15 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
["text"])
c = plpy.cursor(plan, ["a", "b"])
$$ LANGUAGE plpythonu;
+CREATE TYPE test_composite_type AS (
+ a1 int,
+ a2 varchar
+);
+CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$
+plan = plpy.prepare("select $1 as c1", ["test_composite_type"])
+res = plpy.execute(plan, [{"a1": 3, "a2": "label"}])
+return res[0]["c1"]
+$$ LANGUAGE plpythonu;
SELECT simple_cursor_test();
simple_cursor_test
--------------------
@@ -432,3 +441,9 @@ CONTEXT: Traceback (most recent call last):
PL/Python function "cursor_plan_wrong_args", line 4, in
c = plpy.cursor(plan, ["a", "b"])
PL/Python function "cursor_plan_wrong_args"
+SELECT plan_composite_args();
+ plan_composite_args
+---------------------
+ (3,label)
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index b98318cb6c9..61d800b57a2 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_mixed2"
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
-return [None]
+return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpythonu;
-ERROR: PL/Python functions cannot return type type_record[]
-DETAIL: PL/Python does not support conversion to arrays of row types.
SELECT * FROM test_type_conversion_array_record();
-ERROR: function test_type_conversion_array_record() does not exist
-LINE 1: SELECT * FROM test_type_conversion_array_record();
- ^
-HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+ test_type_conversion_array_record
+-----------------------------------
+ {"(one,42)","(two,11)"}
+(1 row)
+
CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
return 'abc'
$$ LANGUAGE plpythonu;
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index e10435653cd..a3f9d4c50b6 100644
--- a/src/pl/plpython/expected/plpython_types_3.out
+++ b/src/pl/plpython/expected/plpython_types_3.out
@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_mixed2"
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
-return [None]
+return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpython3u;
-ERROR: PL/Python functions cannot return type type_record[]
-DETAIL: PL/Python does not support conversion to arrays of row types.
SELECT * FROM test_type_conversion_array_record();
-ERROR: function test_type_conversion_array_record() does not exist
-LINE 1: SELECT * FROM test_type_conversion_array_record();
- ^
-HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+ test_type_conversion_array_record
+-----------------------------------
+ {"(one,42)","(two,11)"}
+(1 row)
+
CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
return 'abc'
$$ LANGUAGE plpython3u;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index b6eb6f1f951..3724e67a073 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -194,6 +194,8 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv);
fcinfo->isnull = (rv == (Datum) NULL);
+
+ ReleaseTupleDesc(desc);
}
else
{
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 060d514a80d..6c3eefff68b 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -130,12 +130,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
plan->types[i] = typeId;
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
- if (typeStruct->typtype != TYPTYPE_COMPOSITE)
- PLy_output_datum_func(&plan->args[i], typeTup);
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("plpy.prepare does not support composite types")));
+ PLy_output_datum_func(&plan->args[i], typeTup);
ReleaseSysCache(typeTup);
}
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 566cf6c0fec..524534e613d 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -404,11 +404,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
Oid funcid;
if (type_is_rowtype(element_type))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PL/Python functions cannot return type %s",
- format_type_be(arg->typoid)),
- errdetail("PL/Python does not support conversion to arrays of row types.")));
+ arg->func = PLyObject_ToComposite;
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
@@ -742,6 +738,8 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
*/
rv = PLyObject_ToCompositeDatum(&info, desc, plrv);
+ ReleaseTupleDesc(desc);
+
PLy_typeinfo_dealloc(&info);
return rv;
@@ -835,11 +833,6 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
else
{
nulls[i] = false;
-
- /*
- * We don't support arrays of row types yet, so the first argument
- * can be NULL.
- */
elems[i] = arg->elm->func(arg->elm, -1, obj);
}
Py_XDECREF(obj);
@@ -872,7 +865,6 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
PLy_output_datum_func2(&info->out.d, typeTup);
ReleaseSysCache(typeTup);
- ReleaseTupleDesc(desc);
return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string);
}
@@ -881,6 +873,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
static Datum
PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
{
+ Datum result;
HeapTuple tuple;
Datum *values;
bool *nulls;
@@ -943,17 +936,20 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
}
tuple = heap_form_tuple(desc, values, nulls);
- ReleaseTupleDesc(desc);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
pfree(values);
pfree(nulls);
- return HeapTupleGetDatum(tuple);
+ return result;
}
static Datum
PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
{
+ Datum result;
HeapTuple tuple;
Datum *values;
bool *nulls;
@@ -1029,17 +1025,20 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
}
tuple = heap_form_tuple(desc, values, nulls);
- ReleaseTupleDesc(desc);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
pfree(values);
pfree(nulls);
- return HeapTupleGetDatum(tuple);
+ return result;
}
static Datum
PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
{
+ Datum result;
HeapTuple tuple;
Datum *values;
bool *nulls;
@@ -1101,11 +1100,13 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
}
tuple = heap_form_tuple(desc, values, nulls);
- ReleaseTupleDesc(desc);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
pfree(values);
pfree(nulls);
- return HeapTupleGetDatum(tuple);
+ return result;
}
/*
diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql
index cee98f288d4..cb5fffeba9d 100644
--- a/src/pl/plpython/sql/plpython_composite.sql
+++ b/src/pl/plpython/sql/plpython_composite.sql
@@ -125,7 +125,7 @@ SELECT * FROM changing_test();
ALTER TABLE changing ADD COLUMN j integer;
SELECT * FROM changing_test();
--- tables of composite types (not yet implemented)
+-- tables of composite types
CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
yield {'tab': [['first', 1], ['second', 2]],
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
index 09f0d7f2d78..a882738e0bb 100644
--- a/src/pl/plpython/sql/plpython_spi.sql
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -284,6 +284,17 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
c = plpy.cursor(plan, ["a", "b"])
$$ LANGUAGE plpythonu;
+CREATE TYPE test_composite_type AS (
+ a1 int,
+ a2 varchar
+);
+
+CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$
+plan = plpy.prepare("select $1 as c1", ["test_composite_type"])
+res = plpy.execute(plan, [{"a1": 3, "a2": "label"}])
+return res[0]["c1"]
+$$ LANGUAGE plpythonu;
+
SELECT simple_cursor_test();
SELECT double_cursor_close();
SELECT cursor_fetch();
@@ -293,3 +304,4 @@ SELECT next_after_close();
SELECT cursor_fetch_next_empty();
SELECT cursor_plan();
SELECT cursor_plan_wrong_args();
+SELECT plan_composite_args();
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index ee6b0e9e981..d9d0db66bcc 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -269,7 +269,7 @@ SELECT * FROM test_type_conversion_array_mixed2();
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
-return [None]
+return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_record();