mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03: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>
|
<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();
|
|||||||
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]],
|
|||||||
{'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 || '%'",
|
|||||||
["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):
|
|||||||
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"
|
|||||||
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"
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
*/
|
*/
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|||||||
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 || '%'",
|
|||||||
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();
|
|||||||
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();
|
|||||||
|
|
||||||
|
|
||||||
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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user