Commit 8b6010b8 authored by Tom Lane's avatar Tom Lane

Improve support for composite types in PL/Python.

Allow PL/Python functions to return arrays of composite types.
Also, fix the restriction that plpy.prepare/plpy.execute couldn't
handle query parameters or result columns of composite types.

In passing, adopt a saner arrangement for where to release the
tupledesc reference counts acquired via lookup_rowtype_tupdesc.
The callers of PLyObject_ToCompositeDatum were doing the lookups,
but then the releases happened somewhere down inside subroutines
of PLyObject_ToCompositeDatum, which is bizarre and bug-prone.
Instead release in the same function that acquires the refcount.

Ed Behn and Ronan Dunklau, reviewed by Abhijit Menon-Sen
parent f545d233
...@@ -1026,13 +1026,6 @@ rv = plpy.execute(plan, ["name"], 5) ...@@ -1026,13 +1026,6 @@ rv = plpy.execute(plan, ["name"], 5)
<para> <para>
Query parameters and result row fields are converted between PostgreSQL Query parameters and result row fields are converted between PostgreSQL
and Python data types as described in <xref linkend="plpython-data">. and Python data types as described in <xref linkend="plpython-data">.
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.
</para> </para>
<para> <para>
......
...@@ -257,7 +257,7 @@ SELECT * FROM changing_test(); ...@@ -257,7 +257,7 @@ SELECT * FROM changing_test();
1 | (3,4) 1 | (3,4)
(2 rows) (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 $$ CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
yield {'tab': [['first', 1], ['second', 2]], yield {'tab': [['first', 1], ['second', 2]],
'typ': [{'first': 'third', 'second': 3}, 'typ': [{'first': 'third', 'second': 3},
...@@ -270,9 +270,13 @@ yield {'tab': [['first', 1], ['second', 2]], ...@@ -270,9 +270,13 @@ yield {'tab': [['first', 1], ['second', 2]],
{'first': 'fourth', 'second': 4}]} {'first': 'fourth', 'second': 4}]}
$$ LANGUAGE plpythonu; $$ LANGUAGE plpythonu;
SELECT * FROM composite_types_table(); SELECT * FROM composite_types_table();
ERROR: PL/Python functions cannot return type table_record[] tab | typ
DETAIL: PL/Python does not support conversion to arrays of row types. ----------------------------+----------------------------
CONTEXT: PL/Python function "composite_types_table" {"(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 -- check what happens if the output record descriptor changes
CREATE FUNCTION return_record(t text) RETURNS record AS $$ CREATE FUNCTION return_record(t text) RETURNS record AS $$
return {'t': t, 'val': 10} return {'t': t, 'val': 10}
......
...@@ -376,6 +376,15 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", ...@@ -376,6 +376,15 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
["text"]) ["text"])
c = plpy.cursor(plan, ["a", "b"]) c = plpy.cursor(plan, ["a", "b"])
$$ LANGUAGE plpythonu; $$ 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 simple_cursor_test();
simple_cursor_test simple_cursor_test
-------------------- --------------------
...@@ -432,3 +441,9 @@ CONTEXT: Traceback (most recent call last): ...@@ -432,3 +441,9 @@ CONTEXT: Traceback (most recent call last):
PL/Python function "cursor_plan_wrong_args", line 4, in <module> PL/Python function "cursor_plan_wrong_args", line 4, in <module>
c = plpy.cursor(plan, ["a", "b"]) c = plpy.cursor(plan, ["a", "b"])
PL/Python function "cursor_plan_wrong_args" PL/Python function "cursor_plan_wrong_args"
SELECT plan_composite_args();
plan_composite_args
---------------------
(3,label)
(1 row)
...@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc" ...@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc"
CONTEXT: while creating return value CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_mixed2" PL/Python function "test_type_conversion_array_mixed2"
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
return [None] return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpythonu; $$ 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(); SELECT * FROM test_type_conversion_array_record();
ERROR: function test_type_conversion_array_record() does not exist test_type_conversion_array_record
LINE 1: SELECT * FROM test_type_conversion_array_record(); -----------------------------------
^ {"(one,42)","(two,11)"}
HINT: No function matches the given name and argument types. You might need to add explicit type casts. (1 row)
CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
return 'abc' return 'abc'
$$ LANGUAGE plpythonu; $$ LANGUAGE plpythonu;
......
...@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc" ...@@ -630,15 +630,14 @@ ERROR: invalid input syntax for integer: "abc"
CONTEXT: while creating return value CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_mixed2" PL/Python function "test_type_conversion_array_mixed2"
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
return [None] return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpython3u; $$ 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(); SELECT * FROM test_type_conversion_array_record();
ERROR: function test_type_conversion_array_record() does not exist test_type_conversion_array_record
LINE 1: SELECT * FROM test_type_conversion_array_record(); -----------------------------------
^ {"(one,42)","(two,11)"}
HINT: No function matches the given name and argument types. You might need to add explicit type casts. (1 row)
CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
return 'abc' return 'abc'
$$ LANGUAGE plpython3u; $$ LANGUAGE plpython3u;
......
...@@ -194,6 +194,8 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -194,6 +194,8 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv); rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv);
fcinfo->isnull = (rv == (Datum) NULL); fcinfo->isnull = (rv == (Datum) NULL);
ReleaseTupleDesc(desc);
} }
else else
{ {
......
...@@ -130,12 +130,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -130,12 +130,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
plan->types[i] = typeId; plan->types[i] = typeId;
typeStruct = (Form_pg_type) GETSTRUCT(typeTup); typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
if (typeStruct->typtype != TYPTYPE_COMPOSITE) PLy_output_datum_func(&plan->args[i], typeTup);
PLy_output_datum_func(&plan->args[i], typeTup);
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("plpy.prepare does not support composite types")));
ReleaseSysCache(typeTup); ReleaseSysCache(typeTup);
} }
......
...@@ -404,11 +404,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup) ...@@ -404,11 +404,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
Oid funcid; Oid funcid;
if (type_is_rowtype(element_type)) if (type_is_rowtype(element_type))
ereport(ERROR, arg->func = PLyObject_ToComposite;
(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->elm = PLy_malloc0(sizeof(*arg->elm)); arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func; arg->elm->func = arg->func;
...@@ -742,6 +738,8 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv) ...@@ -742,6 +738,8 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
*/ */
rv = PLyObject_ToCompositeDatum(&info, desc, plrv); rv = PLyObject_ToCompositeDatum(&info, desc, plrv);
ReleaseTupleDesc(desc);
PLy_typeinfo_dealloc(&info); PLy_typeinfo_dealloc(&info);
return rv; return rv;
...@@ -835,11 +833,6 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) ...@@ -835,11 +833,6 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
else else
{ {
nulls[i] = false; 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); elems[i] = arg->elm->func(arg->elm, -1, obj);
} }
Py_XDECREF(obj); Py_XDECREF(obj);
...@@ -872,7 +865,6 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string) ...@@ -872,7 +865,6 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
PLy_output_datum_func2(&info->out.d, typeTup); PLy_output_datum_func2(&info->out.d, typeTup);
ReleaseSysCache(typeTup); ReleaseSysCache(typeTup);
ReleaseTupleDesc(desc);
return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string); return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string);
} }
...@@ -881,6 +873,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string) ...@@ -881,6 +873,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
static Datum static Datum
PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
{ {
Datum result;
HeapTuple tuple; HeapTuple tuple;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
...@@ -943,17 +936,20 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) ...@@ -943,17 +936,20 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
} }
tuple = heap_form_tuple(desc, values, nulls); tuple = heap_form_tuple(desc, values, nulls);
ReleaseTupleDesc(desc); result = heap_copy_tuple_as_datum(tuple, desc);
heap_freetuple(tuple);
pfree(values); pfree(values);
pfree(nulls); pfree(nulls);
return HeapTupleGetDatum(tuple); return result;
} }
static Datum static Datum
PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
{ {
Datum result;
HeapTuple tuple; HeapTuple tuple;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
...@@ -1029,17 +1025,20 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) ...@@ -1029,17 +1025,20 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
} }
tuple = heap_form_tuple(desc, values, nulls); tuple = heap_form_tuple(desc, values, nulls);
ReleaseTupleDesc(desc); result = heap_copy_tuple_as_datum(tuple, desc);
heap_freetuple(tuple);
pfree(values); pfree(values);
pfree(nulls); pfree(nulls);
return HeapTupleGetDatum(tuple); return result;
} }
static Datum static Datum
PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object) PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
{ {
Datum result;
HeapTuple tuple; HeapTuple tuple;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
...@@ -1101,11 +1100,13 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object ...@@ -1101,11 +1100,13 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
} }
tuple = heap_form_tuple(desc, values, nulls); tuple = heap_form_tuple(desc, values, nulls);
ReleaseTupleDesc(desc); result = heap_copy_tuple_as_datum(tuple, desc);
heap_freetuple(tuple);
pfree(values); pfree(values);
pfree(nulls); pfree(nulls);
return HeapTupleGetDatum(tuple); return result;
} }
/* /*
......
...@@ -125,7 +125,7 @@ SELECT * FROM changing_test(); ...@@ -125,7 +125,7 @@ SELECT * FROM changing_test();
ALTER TABLE changing ADD COLUMN j integer; ALTER TABLE changing ADD COLUMN j integer;
SELECT * FROM changing_test(); 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 $$ CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
yield {'tab': [['first', 1], ['second', 2]], yield {'tab': [['first', 1], ['second', 2]],
......
...@@ -284,6 +284,17 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", ...@@ -284,6 +284,17 @@ plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
c = plpy.cursor(plan, ["a", "b"]) c = plpy.cursor(plan, ["a", "b"])
$$ LANGUAGE plpythonu; $$ 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 simple_cursor_test();
SELECT double_cursor_close(); SELECT double_cursor_close();
SELECT cursor_fetch(); SELECT cursor_fetch();
...@@ -293,3 +304,4 @@ SELECT next_after_close(); ...@@ -293,3 +304,4 @@ SELECT next_after_close();
SELECT cursor_fetch_next_empty(); SELECT cursor_fetch_next_empty();
SELECT cursor_plan(); SELECT cursor_plan();
SELECT cursor_plan_wrong_args(); SELECT cursor_plan_wrong_args();
SELECT plan_composite_args();
...@@ -269,7 +269,7 @@ SELECT * FROM test_type_conversion_array_mixed2(); ...@@ -269,7 +269,7 @@ SELECT * FROM test_type_conversion_array_mixed2();
CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
return [None] return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
$$ LANGUAGE plpythonu; $$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_record(); SELECT * FROM test_type_conversion_array_record();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment