On 27/01/11 00:41, Jan Urbański wrote:
> I'm also attaching an updated version that should apply on top of my
> github refactor branch (or incrementally over the new set of refactor
> patches that I will post shortly to the refactor thread).
Attached is a patch for master, as the refactorings have already been
merged.
Jan
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 16d78ae..167393e 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** REGRESS = \
*** 79,84 ****
--- 79,85 ----
plpython_types \
plpython_error \
plpython_unicode \
+ plpython_composite \
plpython_drop
# where to find psql for running the tests
PSQLDIR = $(bindir)
diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out
index ...1576588 .
*** a/src/pl/plpython/expected/plpython_composite.out
--- b/src/pl/plpython/expected/plpython_composite.out
***************
*** 0 ****
--- 1,309 ----
+ CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
+ return (1, 2)
+ $$ LANGUAGE plpythonu;
+ SELECT multiout_simple();
+ multiout_simple
+ -----------------
+ (1,2)
+ (1 row)
+
+ SELECT * FROM multiout_simple();
+ i | j
+ ---+---
+ 1 | 2
+ (1 row)
+
+ SELECT i, j + 2 FROM multiout_simple();
+ i | ?column?
+ ---+----------
+ 1 | 4
+ (1 row)
+
+ SELECT (multiout_simple()).j + 3;
+ ?column?
+ ----------
+ 5
+ (1 row)
+
+ CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$
+ return [(1, 2)] * n
+ $$ LANGUAGE plpythonu;
+ SELECT multiout_simple_setof();
+ multiout_simple_setof
+ -----------------------
+ (1,2)
+ (1 row)
+
+ SELECT * FROM multiout_simple_setof();
+ column1 | column2
+ ---------+---------
+ 1 | 2
+ (1 row)
+
+ SELECT * FROM multiout_simple_setof(3);
+ column1 | column2
+ ---------+---------
+ 1 | 2
+ 1 | 2
+ 1 | 2
+ (3 rows)
+
+ CREATE FUNCTION multiout_record_as(typ text,
+ first text, OUT first text,
+ second integer, OUT second integer,
+ retnull boolean) RETURNS record AS $$
+ if retnull:
+ return None
+ if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+ elif typ == 'tuple':
+ return ( first, second )
+ elif typ == 'list':
+ return [ first, second ]
+ elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+ $$ LANGUAGE plpythonu;
+ SELECT * from multiout_record_as('dict', 'foo', 1, 'f');
+ first | second
+ -------+--------
+ foo | 1
+ (1 row)
+
+ SELECT multiout_record_as('dict', 'foo', 1, 'f');
+ multiout_record_as
+ --------------------
+ (foo,1)
+ (1 row)
+
+ SELECT *, s IS NULL as snull from multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s);
+ f | s | snull
+ -----+---+-------
+ xxx | | t
+ (1 row)
+
+ SELECT *, f IS NULL as fnull, s IS NULL as snull from multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s);
+ f | s | fnull | snull
+ ---+---+-------+-------
+ | | t | t
+ (1 row)
+
+ SELECT * from multiout_record_as('obj', NULL, 10, 'f');
+ first | second
+ -------+--------
+ | 10
+ (1 row)
+
+ CREATE FUNCTION multiout_setof(n integer,
+ OUT power_of_2 integer,
+ OUT length integer) RETURNS SETOF record AS $$
+ for i in range(n):
+ power = 2 ** i
+ length = plpy.execute("select length('%d')" % power)[0]['length']
+ yield power, length
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM multiout_setof(3);
+ power_of_2 | length
+ ------------+--------
+ 1 | 1
+ 2 | 1
+ 4 | 1
+ (3 rows)
+
+ SELECT multiout_setof(5);
+ multiout_setof
+ ----------------
+ (1,1)
+ (2,1)
+ (4,1)
+ (8,1)
+ (16,2)
+ (5 rows)
+
+ CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$
+ return [{'x': 4, 'y' :'four'},
+ {'x': 7, 'y' :'seven'},
+ {'x': 0, 'y' :'zero'}]
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM multiout_return_table();
+ x | y
+ ---+-------
+ 4 | four
+ 7 | seven
+ 0 | zero
+ (3 rows)
+
+ CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$
+ yield [[1], 'a']
+ yield [[1,2], 'b']
+ yield [[1,2,3], None]
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM multiout_array();
+ column1 | column2
+ ---------+---------
+ {1} | a
+ {1,2} | b
+ {1,2,3} |
+ (3 rows)
+
+ CREATE FUNCTION singleout_composite(OUT type_record) AS $$
+ return {'first': 1, 'second': 2}
+ $$ LANGUAGE plpythonu;
+ CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$
+ return [{'first': 1, 'second': 2},
+ {'first': 3, 'second': 4 }]
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM singleout_composite();
+ first | second
+ -------+--------
+ 1 | 2
+ (1 row)
+
+ SELECT * FROM multiout_composite();
+ first | second
+ -------+--------
+ 1 | 2
+ 3 | 4
+ (2 rows)
+
+ -- composite OUT parameters in functions returning RECORD not supported yet
+ CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$
+ return (n, (n * 2, n * 3))
+ $$ LANGUAGE plpythonu;
+ CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$
+ if returnnull:
+ d = None
+ elif typ == 'dict':
+ d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'}
+ elif typ == 'tuple':
+ d = (n * 2, n * 3)
+ elif typ == 'obj':
+ class d: pass
+ d.first = n * 2
+ d.second = n * 3
+ for i in range(n):
+ yield (i, d)
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM multiout_composite(2);
+ n | column2
+ ---+---------
+ 2 | (4,6)
+ (1 row)
+
+ SELECT * from multiout_table_type_setof('dict', 'f', 3);
+ n | column2
+ ---+---------
+ 0 | (6,9)
+ 1 | (6,9)
+ 2 | (6,9)
+ (3 rows)
+
+ SELECT * from multiout_table_type_setof('tuple', 'f', 2);
+ n | column2
+ ---+---------
+ 0 | (4,6)
+ 1 | (4,6)
+ (2 rows)
+
+ SELECT * from multiout_table_type_setof('obj', 'f', 4);
+ n | column2
+ ---+---------
+ 0 | (8,12)
+ 1 | (8,12)
+ 2 | (8,12)
+ 3 | (8,12)
+ (4 rows)
+
+ SELECT * from multiout_table_type_setof('dict', 't', 3);
+ n | column2
+ ---+---------
+ 0 |
+ 1 |
+ 2 |
+ (3 rows)
+
+ -- check what happens if a type changes under us
+ CREATE TABLE changing (
+ i integer,
+ j integer
+ );
+ CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$
+ return [(1, {'i': 1, 'j': 2}),
+ (1, (3, 4))]
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM changing_test();
+ n | column2
+ ---+---------
+ 1 | (1,2)
+ 1 | (3,4)
+ (2 rows)
+
+ ALTER TABLE changing DROP COLUMN j;
+ SELECT * FROM changing_test();
+ ERROR: length of returned sequence did not match number of columns in row
+ CONTEXT: while creating return value
+ PL/Python function "changing_test"
+ SELECT * FROM changing_test();
+ ERROR: length of returned sequence did not match number of columns in row
+ CONTEXT: while creating return value
+ PL/Python function "changing_test"
+ ALTER TABLE changing ADD COLUMN j integer;
+ SELECT * FROM changing_test();
+ n | column2
+ ---+---------
+ 1 | (1,2)
+ 1 | (3,4)
+ (2 rows)
+
+ -- tables of composite types (not yet implemented)
+ 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},
+ {'first': 'fourth', 'second': 4}]}
+ yield {'tab': [['first', 1], ['second', 2]],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+ yield {'tab': [['first', 1], ['second', 2]],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'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"
+ -- check what happens if the output record descriptor changes
+ CREATE FUNCTION return_record(t text) RETURNS record AS $$
+ return {'t': t, 'val': 10}
+ $$ LANGUAGE plpythonu;
+ SELECT * FROM return_record('abc') AS r(t text, val integer);
+ t | val
+ -----+-----
+ abc | 10
+ (1 row)
+
+ SELECT * FROM return_record('abc') AS r(t text, val bigint);
+ t | val
+ -----+-----
+ abc | 10
+ (1 row)
+
+ SELECT * FROM return_record('abc') AS r(t text, val integer);
+ t | val
+ -----+-----
+ abc | 10
+ (1 row)
+
+ SELECT * FROM return_record('abc') AS r(t varchar(30), val integer);
+ t | val
+ -----+-----
+ abc | 10
+ (1 row)
+
+ SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);
+ t | val
+ -----+-----
+ abc | 10
+ (1 row)
+
diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out
index c8c4f9d..7c60089 100644
*** a/src/pl/plpython/expected/plpython_record.out
--- b/src/pl/plpython/expected/plpython_record.out
*************** $$ LANGUAGE plpythonu;
*** 42,51 ****
CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$
return first + '_in_to_out';
$$ LANGUAGE plpythonu;
- -- this doesn't work yet :-(
CREATE FUNCTION test_in_out_params_multi(first in text,
second out text, third out text) AS $$
! return first + '_record_in_to_out';
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_inout_params(first inout text) AS $$
return first + '_inout';
--- 42,50 ----
CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$
return first + '_in_to_out';
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_in_out_params_multi(first in text,
second out text, third out text) AS $$
! return (first + '_record_in_to_out_1', first + '_record_in_to_out_2');
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_inout_params(first inout text) AS $$
return first + '_inout';
*************** SELECT * FROM test_in_out_params('test_i
*** 297,305 ****
test_in_in_to_out
(1 row)
- -- this doesn't work yet :-(
SELECT * FROM test_in_out_params_multi('test_in');
! ERROR: PL/Python functions cannot return type record
SELECT * FROM test_inout_params('test_in');
first
---------------
--- 296,307 ----
test_in_in_to_out
(1 row)
SELECT * FROM test_in_out_params_multi('test_in');
! second | third
! ----------------------------+----------------------------
! test_in_record_in_to_out_1 | test_in_record_in_to_out_2
! (1 row)
!
SELECT * FROM test_inout_params('test_in');
first
---------------
diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out
index e04da22..2ef66a8 100644
*** a/src/pl/plpython/expected/plpython_trigger.out
--- b/src/pl/plpython/expected/plpython_trigger.out
*************** SELECT * FROM pb;
*** 549,551 ****
--- 549,569 ----
b | 2010-10-13 21:57:29
(1 row)
+ -- triggers for tables with composite types
+ CREATE TABLE comp1 (i integer, j boolean);
+ CREATE TYPE comp2 AS (k integer, l boolean);
+ CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
+ CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
+ TD['new']['f1'] = (3, False)
+ TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
+ return 'MODIFY'
+ $$ LANGUAGE plpythonu;
+ CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
+ INSERT INTO composite_trigger_test VALUES (NULL, NULL);
+ SELECT * FROM composite_trigger_test;
+ f1 | f2
+ -------+-------
+ (3,f) | (7,t)
+ (1 row)
+
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index aafe556..0a800c5 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef int Py_ssize_t;
*** 100,105 ****
--- 100,106 ----
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
+ #include "parser/parse_coerce.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
*************** typedef struct PLyDatumToOb
*** 130,135 ****
--- 131,137 ----
PLyDatumToObFunc func;
FmgrInfo typfunc; /* The type's output function */
Oid typoid; /* The OID of the type */
+ int32 typmod; /* The typmod of the type */
Oid typioparam;
bool typbyval;
int16 typlen;
*************** typedef struct PLyObToDatum
*** 162,167 ****
--- 164,170 ----
PLyObToDatumFunc func;
FmgrInfo typfunc; /* The type's input function */
Oid typoid; /* The OID of the type */
+ int32 typmod; /* The typmod of the type */
Oid typioparam;
bool typbyval;
int16 typlen;
*************** static void PLy_input_datum_func(PLyType
*** 339,344 ****
--- 342,348 ----
static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple);
static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
+ static void PLy_output_record_funcs(PLyTypeInfo *, TupleDesc);
/* conversion functions */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
*************** static PyObject *PLyDict_FromTuple(PLyTy
*** 356,367 ****
static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *);
static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *);
static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *);
static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *);
! static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
! static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
/*
* Currently active plpython function
--- 360,373 ----
static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *);
static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *);
+ static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *);
static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *);
static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *);
! static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
! static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
! static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
! static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
/*
* Currently active plpython function
*************** PLy_function_handler(FunctionCallInfo fc
*** 1120,1136 ****
}
else if (proc->result.is_rowtype >= 1)
{
HeapTuple tuple = NULL;
! if (PySequence_Check(plrv))
! /* composite type as sequence (tuple, list etc) */
! tuple = PLySequence_ToTuple(&proc->result, plrv);
! else if (PyMapping_Check(plrv))
! /* composite type as mapping (currently only dict) */
! tuple = PLyMapping_ToTuple(&proc->result, plrv);
! else
! /* returned as smth, must provide method __getattr__(name) */
! tuple = PLyObject_ToTuple(&proc->result, plrv);
if (tuple != NULL)
{
--- 1126,1144 ----
}
else if (proc->result.is_rowtype >= 1)
{
+ TupleDesc desc;
HeapTuple tuple = NULL;
! /* make sure it's not an unnamed record */
! Assert((proc->result.out.d.typoid == RECORDOID &&
! proc->result.out.d.typmod != -1) ||
! (proc->result.out.d.typoid != RECORDOID &&
! proc->result.out.d.typmod == -1));
!
! desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
! proc->result.out.d.typmod);
!
! tuple = PLyObject_ToTuple(&proc->result, desc, plrv);
if (tuple != NULL)
{
*************** PLy_function_build_args(FunctionCallInfo
*** 1257,1262 ****
--- 1265,1288 ----
PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments");
arg = NULL;
}
+
+ /* Set up output conversion for functions returning RECORD */
+ if (proc->result.out.d.typoid == RECORDOID)
+ {
+ TupleDesc desc;
+
+ if (get_call_result_type(
+ fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ }
+
+ /* cache the output conversion functions */
+ PLy_output_record_funcs(&(proc->result), desc);
+ }
}
PG_CATCH();
{
*************** PLy_procedure_create(HeapTuple procTup,
*** 1419,1450 ****
procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
! /* Disallow pseudotype result, except for void */
! if (rvTypeStruct->typtype == TYPTYPE_PSEUDO &&
! procStruct->prorettype != VOIDOID)
{
if (procStruct->prorettype == TRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
! else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("PL/Python functions cannot return type %s",
! format_type_be(procStruct->prorettype))));
}
! if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE)
{
/*
* Tuple: set up later, during first call to
* PLy_function_handler
*/
proc->result.out.d.typoid = procStruct->prorettype;
proc->result.is_rowtype = 2;
}
else
PLy_output_datum_func(&proc->result, rvTypeTup);
ReleaseSysCache(rvTypeTup);
}
--- 1445,1481 ----
procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
! /* Disallow pseudotype result, except for void or record */
! if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
{
if (procStruct->prorettype == TRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
! else if (procStruct->prorettype != VOIDOID &&
! procStruct->prorettype != RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("PL/Python functions cannot return type %s",
! format_type_be(procStruct->prorettype))));
}
! if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
! procStruct->prorettype == RECORDOID)
{
/*
* Tuple: set up later, during first call to
* PLy_function_handler
*/
proc->result.out.d.typoid = procStruct->prorettype;
+ proc->result.out.d.typmod = -1;
proc->result.is_rowtype = 2;
}
else
+ {
+ /* do the real work */
PLy_output_datum_func(&proc->result, rvTypeTup);
+ }
ReleaseSysCache(rvTypeTup);
}
*************** PLy_input_tuple_funcs(PLyTypeInfo *arg,
*** 1727,1732 ****
--- 1758,1802 ----
}
static void
+ PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
+ {
+ /*
+ * If the output record functions are already set, we just have to check
+ * if the record descriptor has not changed
+ */
+ bool can_skip = false;
+
+ if (arg->is_rowtype == 1)
+ {
+ int i;
+
+ /* the functions are already set, check the attributes */
+ Assert(arg->out.r.natts == desc->natts);
+ can_skip = true;
+
+ for (i = 0; i < arg->out.r.natts; i++)
+ {
+ if (!IsBinaryCoercible(arg->out.r.atts[i].typoid,
+ desc->attrs[i]->atttypid))
+ can_skip = false;
+ }
+ }
+
+ if (can_skip)
+ return;
+
+ /* bless the record to make it known to the typcache lookup code */
+ BlessTupleDesc(desc);
+ /* save the freshly generated typmod */
+ arg->out.d.typmod = desc->tdtypmod;
+ /* proceed with normal I/O function caching */
+ PLy_output_tuple_funcs(arg, desc);
+ /* it should change is_rowtype to 1, so we won't go through this again
+ * unless the the output record description changes */
+ Assert(arg->is_rowtype == 1);
+ }
+
+ static void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
*************** PLy_output_datum_func2(PLyObToDatum *arg
*** 1782,1787 ****
--- 1852,1858 ----
perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
arg->typoid = HeapTupleGetOid(typeTup);
+ arg->typmod = -1;
arg->typioparam = getTypeIOParam(typeTup);
arg->typbyval = typeStruct->typbyval;
*************** PLy_output_datum_func2(PLyObToDatum *arg
*** 1804,1809 ****
--- 1875,1886 ----
break;
}
+ /* Composite types need their own input routine, though */
+ if (typeStruct->typtype == TYPTYPE_COMPOSITE)
+ {
+ arg->func = PLyObject_ToComposite;
+ }
+
if (element_type)
{
char dummy_delim;
*************** PLy_output_datum_func2(PLyObToDatum *arg
*** 1821,1826 ****
--- 1898,1904 ----
arg->func = PLySequence_ToArray;
arg->elm->typoid = element_type;
+ arg->elm->typmod = -1;
get_type_io_data(element_type, IOFunc_input,
&arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
&arg->elm->typioparam, &funcid);
*************** PLy_input_datum_func2(PLyDatumToOb *arg,
*** 1846,1851 ****
--- 1924,1930 ----
/* Get the type's conversion information */
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
arg->typoid = HeapTupleGetOid(typeTup);
+ arg->typmod = -1;
arg->typioparam = getTypeIOParam(typeTup);
arg->typbyval = typeStruct->typbyval;
arg->typlen = typeStruct->typlen;
*************** PLy_input_datum_func2(PLyDatumToOb *arg,
*** 1892,1897 ****
--- 1971,1977 ----
arg->elm->func = arg->func;
arg->func = PLyList_FromArray;
arg->elm->typoid = element_type;
+ arg->elm->typmod = -1;
get_type_io_data(element_type, IOFunc_output,
&arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
&arg->elm->typioparam, &funcid);
*************** PLyDict_FromTuple(PLyTypeInfo *info, Hea
*** 2095,2100 ****
--- 2175,2203 ----
}
/*
+ * Convert a Python object to a PostgreSQL tuple, using all supported
+ * conversion methods: tuple as a sequence, as a mapping or as an object that
+ * has __getattr__ support.
+ */
+ static HeapTuple
+ PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
+ {
+ HeapTuple tuple;
+
+ if (PySequence_Check(plrv))
+ /* composite type as sequence (tuple, list etc) */
+ tuple = PLySequence_ToTuple(info, desc, plrv);
+ else if (PyMapping_Check(plrv))
+ /* composite type as mapping (currently only dict) */
+ tuple = PLyMapping_ToTuple(info, desc, plrv);
+ else
+ /* returned as smth, must provide method __getattr__(name) */
+ tuple = PLyGenericObject_ToTuple(info, desc, plrv);
+
+ return tuple;
+ }
+
+ /*
* Convert a Python object to a PostgreSQL bool datum. This can't go
* through the generic conversion function, because Python attaches a
* Boolean value to everything, more things than the PostgreSQL bool
*************** PLyObject_ToBytea(PLyObToDatum *arg, int
*** 2157,2162 ****
--- 2260,2309 ----
return rv;
}
+
+ /*
+ * Convert a Python object to a composite type. First look up the type's
+ * description, then route the Python object through the conversion function
+ * for obtaining PostgreSQL tuples.
+ */
+ static Datum
+ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+ {
+ HeapTuple tuple = NULL;
+ Datum rv;
+ PLyTypeInfo info;
+ TupleDesc desc;
+
+ if (typmod != -1)
+ elog(ERROR, "received unnamed record type as input");
+
+ /* Create a dummy PLyTypeInfo */
+ MemSet(&info, 0, sizeof(PLyTypeInfo));
+ PLy_typeinfo_init(&info);
+ /* Mark it as needing output routines lookup */
+ info.is_rowtype = 2;
+
+ desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
+
+ /*
+ * This will set up the dummy PLyTypeInfo's output conversion routines,
+ * since we left is_rowtype as 2. A future optimisation could be caching
+ * that info instead of looking it up every time a tuple is returned from
+ * the function.
+ */
+ tuple = PLyObject_ToTuple(&info, desc, plrv);
+
+ PLy_typeinfo_dealloc(&info);
+
+ if (tuple != NULL)
+ rv = HeapTupleGetDatum(tuple);
+ else
+ rv = (Datum) NULL;
+
+ return rv;
+ }
+
+
/*
* Generic conversion function: Convert PyObject to cstring and
* cstring into PostgreSQL type.
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 2260,2268 ****
}
static HeapTuple
! PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
{
- TupleDesc desc;
HeapTuple tuple;
Datum *values;
bool *nulls;
--- 2407,2414 ----
}
static HeapTuple
! PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
{
HeapTuple tuple;
Datum *values;
bool *nulls;
*************** PLyMapping_ToTuple(PLyTypeInfo *info, Py
*** 2270,2276 ****
Assert(PyMapping_Check(mapping));
- desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
--- 2416,2421 ----
*************** PLyMapping_ToTuple(PLyTypeInfo *info, Py
*** 2331,2339 ****
static HeapTuple
! PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
{
- TupleDesc desc;
HeapTuple tuple;
Datum *values;
bool *nulls;
--- 2476,2483 ----
static HeapTuple
! PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
{
HeapTuple tuple;
Datum *values;
bool *nulls;
*************** PLySequence_ToTuple(PLyTypeInfo *info, P
*** 2347,2353 ****
* can ignore exceeding items or assume missing ones as null but to avoid
* plpython developer's errors we are strict here
*/
- desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
idx = 0;
for (i = 0; i < desc->natts; i++)
{
--- 2491,2496 ----
*************** PLySequence_ToTuple(PLyTypeInfo *info, P
*** 2415,2429 ****
static HeapTuple
! PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
{
- TupleDesc desc;
HeapTuple tuple;
Datum *values;
bool *nulls;
volatile int i;
- desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
--- 2558,2570 ----
static HeapTuple
! PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
{
HeapTuple tuple;
Datum *values;
bool *nulls;
volatile int i;
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql
index ...db4bd73 .
*** a/src/pl/plpython/sql/plpython_composite.sql
--- b/src/pl/plpython/sql/plpython_composite.sql
***************
*** 0 ****
--- 1,153 ----
+ CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
+ return (1, 2)
+ $$ LANGUAGE plpythonu;
+
+ SELECT multiout_simple();
+ SELECT * FROM multiout_simple();
+ SELECT i, j + 2 FROM multiout_simple();
+ SELECT (multiout_simple()).j + 3;
+
+ CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$
+ return [(1, 2)] * n
+ $$ LANGUAGE plpythonu;
+
+ SELECT multiout_simple_setof();
+ SELECT * FROM multiout_simple_setof();
+ SELECT * FROM multiout_simple_setof(3);
+
+ CREATE FUNCTION multiout_record_as(typ text,
+ first text, OUT first text,
+ second integer, OUT second integer,
+ retnull boolean) RETURNS record AS $$
+ if retnull:
+ return None
+ if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+ elif typ == 'tuple':
+ return ( first, second )
+ elif typ == 'list':
+ return [ first, second ]
+ elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+ $$ LANGUAGE plpythonu;
+
+ SELECT * from multiout_record_as('dict', 'foo', 1, 'f');
+ SELECT multiout_record_as('dict', 'foo', 1, 'f');
+ SELECT *, s IS NULL as snull from multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s);
+ SELECT *, f IS NULL as fnull, s IS NULL as snull from multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s);
+ SELECT * from multiout_record_as('obj', NULL, 10, 'f');
+
+ CREATE FUNCTION multiout_setof(n integer,
+ OUT power_of_2 integer,
+ OUT length integer) RETURNS SETOF record AS $$
+ for i in range(n):
+ power = 2 ** i
+ length = plpy.execute("select length('%d')" % power)[0]['length']
+ yield power, length
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM multiout_setof(3);
+ SELECT multiout_setof(5);
+
+ CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$
+ return [{'x': 4, 'y' :'four'},
+ {'x': 7, 'y' :'seven'},
+ {'x': 0, 'y' :'zero'}]
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM multiout_return_table();
+
+ CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$
+ yield [[1], 'a']
+ yield [[1,2], 'b']
+ yield [[1,2,3], None]
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM multiout_array();
+
+ CREATE FUNCTION singleout_composite(OUT type_record) AS $$
+ return {'first': 1, 'second': 2}
+ $$ LANGUAGE plpythonu;
+
+ CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$
+ return [{'first': 1, 'second': 2},
+ {'first': 3, 'second': 4 }]
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM singleout_composite();
+ SELECT * FROM multiout_composite();
+
+ -- composite OUT parameters in functions returning RECORD not supported yet
+ CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$
+ return (n, (n * 2, n * 3))
+ $$ LANGUAGE plpythonu;
+
+ CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$
+ if returnnull:
+ d = None
+ elif typ == 'dict':
+ d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'}
+ elif typ == 'tuple':
+ d = (n * 2, n * 3)
+ elif typ == 'obj':
+ class d: pass
+ d.first = n * 2
+ d.second = n * 3
+ for i in range(n):
+ yield (i, d)
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM multiout_composite(2);
+ SELECT * from multiout_table_type_setof('dict', 'f', 3);
+ SELECT * from multiout_table_type_setof('tuple', 'f', 2);
+ SELECT * from multiout_table_type_setof('obj', 'f', 4);
+ SELECT * from multiout_table_type_setof('dict', 't', 3);
+
+ -- check what happens if a type changes under us
+
+ CREATE TABLE changing (
+ i integer,
+ j integer
+ );
+
+ CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$
+ return [(1, {'i': 1, 'j': 2}),
+ (1, (3, 4))]
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM changing_test();
+ ALTER TABLE changing DROP COLUMN j;
+ SELECT * FROM changing_test();
+ SELECT * FROM changing_test();
+ ALTER TABLE changing ADD COLUMN j integer;
+ SELECT * FROM changing_test();
+
+ -- tables of composite types (not yet implemented)
+
+ 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},
+ {'first': 'fourth', 'second': 4}]}
+ yield {'tab': [['first', 1], ['second', 2]],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+ yield {'tab': [['first', 1], ['second', 2]],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM composite_types_table();
+
+ -- check what happens if the output record descriptor changes
+ CREATE FUNCTION return_record(t text) RETURNS record AS $$
+ return {'t': t, 'val': 10}
+ $$ LANGUAGE plpythonu;
+
+ SELECT * FROM return_record('abc') AS r(t text, val integer);
+ SELECT * FROM return_record('abc') AS r(t text, val bigint);
+ SELECT * FROM return_record('abc') AS r(t text, val integer);
+ SELECT * FROM return_record('abc') AS r(t varchar(30), val integer);
+ SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);
diff --git a/src/pl/plpython/sql/plpython_record.sql b/src/pl/plpython/sql/plpython_record.sql
index 5a41565..d727e60 100644
*** a/src/pl/plpython/sql/plpython_record.sql
--- b/src/pl/plpython/sql/plpython_record.sql
*************** CREATE FUNCTION test_in_out_params(first
*** 49,58 ****
return first + '_in_to_out';
$$ LANGUAGE plpythonu;
- -- this doesn't work yet :-(
CREATE FUNCTION test_in_out_params_multi(first in text,
second out text, third out text) AS $$
! return first + '_record_in_to_out';
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_inout_params(first inout text) AS $$
--- 49,57 ----
return first + '_in_to_out';
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_in_out_params_multi(first in text,
second out text, third out text) AS $$
! return (first + '_record_in_to_out_1', first + '_record_in_to_out_2');
$$ LANGUAGE plpythonu;
CREATE FUNCTION test_inout_params(first inout text) AS $$
*************** SELECT * FROM test_type_record_as('obj',
*** 110,116 ****
SELECT * FROM test_type_record_as('obj', null, null, true);
SELECT * FROM test_in_out_params('test_in');
- -- this doesn't work yet :-(
SELECT * FROM test_in_out_params_multi('test_in');
SELECT * FROM test_inout_params('test_in');
--- 109,114 ----
diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql
index 4994d8f..2afdf51 100644
*** a/src/pl/plpython/sql/plpython_trigger.sql
--- b/src/pl/plpython/sql/plpython_trigger.sql
*************** INSERT INTO pb VALUES ('a', '2010-10-09
*** 326,328 ****
--- 326,348 ----
SELECT * FROM pb;
UPDATE pb SET a = 'b';
SELECT * FROM pb;
+
+
+ -- triggers for tables with composite types
+
+ CREATE TABLE comp1 (i integer, j boolean);
+ CREATE TYPE comp2 AS (k integer, l boolean);
+
+ CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
+
+ CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
+ TD['new']['f1'] = (3, False)
+ TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
+ return 'MODIFY'
+ $$ LANGUAGE plpythonu;
+
+ CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
+
+ INSERT INTO composite_trigger_test VALUES (NULL, NULL);
+ SELECT * FROM composite_trigger_test;
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers