mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-04 00:02:52 -05:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									f545d233eb
								
							
						
					
					
						commit
						8b6010b835
					
				@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
@ -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")));
 | 
			
		||||
			ReleaseSysCache(typeTup);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
@ -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]],
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user