From: Tom Lane Date: Thu, 3 Jul 2014 20:10:50 +0000 (-0400) Subject: Improve support for composite types in PL/Python. X-Git-Tag: REL9_5_ALPHA1~1769 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=8b6010b8350a1756cd85595705971df81b5ffc07;p=postgresql 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 --- diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 3f0e6290bb..e209b2a2d2 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 61b3046790..ad454f3739 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 517579471c..e2861dfa72 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 b98318cb6c..61d800b57a 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 e10435653c..a3f9d4c50b 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 b6eb6f1f95..3724e67a07 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 060d514a80..6c3eefff68 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 566cf6c0fe..524534e613 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 cee98f288d..cb5fffeba9 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 09f0d7f2d7..a882738e0b 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 ee6b0e9e98..d9d0db66bc 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();