diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out index 0bcc46c55d1181a512ed1a38d7532f75de851505..458330713a8c45e8db78e97f5e1a35640d3aa8a1 100644 --- a/src/pl/plpython/expected/plpython_record.out +++ b/src/pl/plpython/expected/plpython_record.out @@ -38,6 +38,8 @@ elif typ == 'obj': type_record.first = first type_record.second = second return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) $$ LANGUAGE plpythonu; CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ return first + '_in_to_out'; @@ -290,6 +292,12 @@ SELECT * FROM test_type_record_as('obj', null, null, true); | (1 row) +SELECT * FROM test_type_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + SELECT * FROM test_in_out_params('test_in'); second ------------------- @@ -355,3 +363,11 @@ ERROR: attribute "second" does not exist in Python object HINT: To return null in a column, let the returned object have an attribute named after column with value None. CONTEXT: while creating return value PL/Python function "test_type_record_error3" +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpythonu; +SELECT * FROM test_type_record_error4(); +ERROR: malformed record literal: "foo" +DETAIL: Missing left parenthesis. +CONTEXT: while creating return value +PL/Python function "test_type_record_error4" diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index 2ef66a8f06d4005e728d3944938345f63981a088..58aa24b62cb6ae675258dba604ddcef6dd2c266a 100644 --- a/src/pl/plpython/expected/plpython_trigger.out +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -567,3 +567,40 @@ SELECT * FROM composite_trigger_test; (3,f) | (7,t) (1 row) +-- triggers with composite type columns (bug #6559) +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpythonu; +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + f1 | f2 +-------+------- + | + (1,f) | + (,t) | (1,f) +(3 rows) + +-- nested composite types +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); +CREATE TABLE composite_trigger_nested_test(c comp3); +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpythonu; +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test; + c +------------------- + + ("(1,f)",,3) + ("(,t)","(1,f)",) +(3 rows) + diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c index 280d3ed1aca083cb623a642bc18099e102dbb623..ad30fc0065bf27672f1e056dab0ac8a9d5af3edf 100644 --- a/src/pl/plpython/plpy_exec.c +++ b/src/pl/plpython/plpy_exec.c @@ -180,8 +180,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) } else if (proc->result.is_rowtype >= 1) { - TupleDesc desc; - HeapTuple tuple = NULL; + TupleDesc desc; /* make sure it's not an unnamed record */ Assert((proc->result.out.d.typoid == RECORDOID && @@ -192,18 +191,8 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, proc->result.out.d.typmod); - tuple = PLyObject_ToTuple(&proc->result, desc, plrv); - - if (tuple != NULL) - { - fcinfo->isnull = false; - rv = HeapTupleGetDatum(tuple); - } - else - { - fcinfo->isnull = true; - rv = (Datum) NULL; - } + rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv); + fcinfo->isnull = (rv == (Datum) NULL); } else { diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index 9d6af053761a13e19895e26891b1ec30a0fd8e38..c503d2b2dd1576b909c62844ccdd090f880673d2 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -49,10 +49,11 @@ static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *pl static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv); static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv); -/* conversion from Python objects to heap tuples (used by triggers and SRFs) */ -static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping); -static HeapTuple PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence); -static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object); +/* conversion from Python objects to composite Datums (used by triggers and SRFs) */ +static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string); +static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping); +static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence); +static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object); /* make allocations in the TopMemoryContext */ static void perm_fmgr_info(Oid functionId, FmgrInfo *finfo); @@ -333,26 +334,28 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc) } /* - * 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. + * Convert a Python object to a composite Datum, using all supported + * conversion methods: composite as a string, as a sequence, as a mapping or + * as an object that has __getattr__ support. */ -HeapTuple -PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv) +Datum +PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv) { - HeapTuple tuple; + Datum datum; - if (PySequence_Check(plrv)) + if (PyString_Check(plrv) || PyUnicode_Check(plrv)) + datum = PLyString_ToComposite(info, desc, plrv); + else if (PySequence_Check(plrv)) /* composite type as sequence (tuple, list etc) */ - tuple = PLySequence_ToTuple(info, desc, plrv); + datum = PLySequence_ToComposite(info, desc, plrv); else if (PyMapping_Check(plrv)) /* composite type as mapping (currently only dict) */ - tuple = PLyMapping_ToTuple(info, desc, plrv); + datum = PLyMapping_ToComposite(info, desc, plrv); else /* returned as smth, must provide method __getattr__(name) */ - tuple = PLyGenericObject_ToTuple(info, desc, plrv); + datum = PLyGenericObject_ToComposite(info, desc, plrv); - return tuple; + return datum; } static void @@ -681,7 +684,6 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv) static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv) { - HeapTuple tuple = NULL; Datum rv; PLyTypeInfo info; TupleDesc desc; @@ -703,15 +705,10 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv) * that info instead of looking it up every time a tuple is returned from * the function. */ - tuple = PLyObject_ToTuple(&info, desc, plrv); + rv = PLyObject_ToCompositeDatum(&info, desc, plrv); PLy_typeinfo_dealloc(&info); - if (tuple != NULL) - rv = HeapTupleGetDatum(tuple); - else - rv = (Datum) NULL; - return rv; } @@ -818,8 +815,27 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) return PointerGetDatum(array); } -static HeapTuple -PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) + +static Datum +PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string) +{ + HeapTuple typeTup; + + typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid)); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid); + + PLy_output_datum_func2(&info->out.d, typeTup); + + ReleaseSysCache(typeTup); + ReleaseTupleDesc(desc); + + return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string); +} + + +static Datum +PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) { HeapTuple tuple; Datum *values; @@ -887,12 +903,12 @@ PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) pfree(values); pfree(nulls); - return tuple; + return HeapTupleGetDatum(tuple); } -static HeapTuple -PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) +static Datum +PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) { HeapTuple tuple; Datum *values; @@ -973,12 +989,12 @@ PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) pfree(values); pfree(nulls); - return tuple; + return HeapTupleGetDatum(tuple); } -static HeapTuple -PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object) +static Datum +PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object) { HeapTuple tuple; Datum *values; @@ -1045,7 +1061,7 @@ PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object) pfree(values); pfree(nulls); - return tuple; + return HeapTupleGetDatum(tuple); } /* diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h index e52c5d50479a63acbfa82684148febfb40a0d743..11532b8c2012a29fceb655160119ac7d071f7d80 100644 --- a/src/pl/plpython/plpy_typeio.h +++ b/src/pl/plpython/plpy_typeio.h @@ -98,8 +98,8 @@ extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc); extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc); -/* conversion from Python objects to heap tuples */ -extern HeapTuple PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv); +/* conversion from Python objects to composite Datums */ +extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv); /* conversion from heap tuples to Python dictionaries */ extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc); diff --git a/src/pl/plpython/sql/plpython_record.sql b/src/pl/plpython/sql/plpython_record.sql index 8df65fbfe1f3af380a1f4fee97e99fad04436ab2..9bab4c9e82d96fa3b353805baa4343f74d3c6752 100644 --- a/src/pl/plpython/sql/plpython_record.sql +++ b/src/pl/plpython/sql/plpython_record.sql @@ -43,6 +43,8 @@ elif typ == 'obj': type_record.first = first type_record.second = second return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) $$ LANGUAGE plpythonu; CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ @@ -108,6 +110,8 @@ SELECT * FROM test_type_record_as('obj', null, 2, false); SELECT * FROM test_type_record_as('obj', 'three', 3, false); SELECT * FROM test_type_record_as('obj', null, null, true); +SELECT * FROM test_type_record_as('str', 'one', 1, false); + SELECT * FROM test_in_out_params('test_in'); SELECT * FROM test_in_out_params_multi('test_in'); SELECT * FROM test_inout_params('test_in'); @@ -151,3 +155,9 @@ CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$ $$ LANGUAGE plpythonu; SELECT * FROM test_type_record_error3(); + +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpythonu; + +SELECT * FROM test_type_record_error4(); diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql index 2afdf511275541e74b971ec3d78f3d0c74ae6a81..f60565cde6bdaaf72b7fb74dea92acec9807e332 100644 --- a/src/pl/plpython/sql/plpython_trigger.sql +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -346,3 +346,39 @@ CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test INSERT INTO composite_trigger_test VALUES (NULL, NULL); SELECT * FROM composite_trigger_test; + + +-- triggers with composite type columns (bug #6559) + +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); + +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpythonu; + +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); + +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + + +-- nested composite types + +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); + +CREATE TABLE composite_trigger_nested_test(c comp3); + +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpythonu; + +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); + +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test;