]> granicus.if.org Git - postgresql/commitdiff
Improve support for composite types in PL/Python.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 3 Jul 2014 20:10:50 +0000 (16:10 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 3 Jul 2014 20:10:50 +0000 (16:10 -0400)
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

doc/src/sgml/plpython.sgml
src/pl/plpython/expected/plpython_composite.out
src/pl/plpython/expected/plpython_spi.out
src/pl/plpython/expected/plpython_types.out
src/pl/plpython/expected/plpython_types_3.out
src/pl/plpython/plpy_exec.c
src/pl/plpython/plpy_spi.c
src/pl/plpython/plpy_typeio.c
src/pl/plpython/sql/plpython_composite.sql
src/pl/plpython/sql/plpython_spi.sql
src/pl/plpython/sql/plpython_types.sql

index 3f0e6290bb2325a3534f22cb84cf3aacda5efcf1..e209b2a2d23a68b091f0de86e51e9b1c54374bcd 100644 (file)
@@ -1026,13 +1026,6 @@ rv = plpy.execute(plan, ["name"], 5)
      <para>
       Query parameters and result row fields are converted between PostgreSQL
       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>
index 61b3046790f268409d8f4eed76657406c56f0cbf..ad454f37394bfbd2f6147333a310d66a905093e9 100644 (file)
@@ -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}
index 517579471c98def2a59f63dbbdd48b05fc47e0f3..e2861dfa722cb4cc28fa3873bd40053e399b0529 100644 (file)
@@ -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 <module>
     c = plpy.cursor(plan, ["a", "b"])
 PL/Python function "cursor_plan_wrong_args"
+SELECT plan_composite_args();
+ plan_composite_args 
+---------------------
+ (3,label)
+(1 row)
+
index b98318cb6c94b28cf696375c299a1650c713a926..61d800b57a2b3f20d817478caf61595070219a00 100644 (file)
@@ -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;
index e10435653cd9930111cf846f89de320502f7a036..a3f9d4c50b6baef4101e144c020c153e1a4c18d2 100644 (file)
@@ -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;
index b6eb6f1f9515cb4162c7fb168c6b6b63bd958a29..3724e67a0731830d3763c423abc4a2a161be15c3 100644 (file)
@@ -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
                {
index 060d514a80dccdcf6c4c8d4a345e862b0edbaa9c..6c3eefff68b5731d1118aeb5a0d424111c5085a9 100644 (file)
@@ -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);
                }
 
index 566cf6c0fec2d248e0687f8efaf7cba599981b60..524534e613ded0928fc33bac83c162bf82df0e76 100644 (file)
@@ -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;
 }
 
 /*
index cee98f288d4cf3cc33f77241905b9fae3ab40e4b..cb5fffeba9d8a1132ece9c22cf4ed6b2a3b910dd 100644 (file)
@@ -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]],
index 09f0d7f2d78bebe460d20f588f543148acc28919..a882738e0bb1fcde51cf720228ed99592c1f03e2 100644 (file)
@@ -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();
index ee6b0e9e981d4f8615f99a34198ed6d81bb6b9ff..d9d0db66bccf5ec4c2fc9ff698a8bfe1eaa6e6e8 100644 (file)
@@ -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();