Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit4fa520f

Browse files
committed
PL/Python: Accept strings in functions returning composite types
Before 9.1, PL/Python functions returning composite types could returna string and it would be parsed using record_in. The 9.1 changes madePL/Python only expect dictionaries, tuples, or objects supportinggetattr as output of composite functions, resulting in a regressionand a confusing error message, as the strings were interpreted assequences and the code for transforming lists to database tuples wasused. Fix this by treating strings separately as before, beforechecking for the other types.The reason why it's important to support string to database tupleconversion is that trigger functions on tables with composite columnsget the composite row passed in as a string (from record_out).Without supporting converting this back using record_in, this makes itimpossible to implement pass-through behavior for these columns, asPL/Python no longer accepts strings for composite values.A better solution would be to fix the code that transforms compositeinputs into Python objects to produce dictionaries that would then becorrectly interpreted by the Python->PostgreSQL counterpart code. Butthat would be too invasive to backpatch to 9.1, and it is too late inthe 9.2 cycle to attempt it. It should be revisited in the future,though.Reported as bug #6559 by Kirill Simonov.Jan Urbański
1 parentdc7521d commit4fa520f

File tree

5 files changed

+149
-44
lines changed

5 files changed

+149
-44
lines changed

‎src/pl/plpython/expected/plpython_record.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ elif typ == 'obj':
3838
type_record.first = first
3939
type_record.second = second
4040
return type_record
41+
elif typ == 'str':
42+
return "('%s',%r)" % (first, second)
4143
$$ LANGUAGE plpythonu;
4244
CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$
4345
return first + '_in_to_out';
@@ -290,6 +292,12 @@ SELECT * FROM test_type_record_as('obj', null, null, true);
290292
|
291293
(1 row)
292294

295+
SELECT * FROM test_type_record_as('str', 'one', 1, false);
296+
first | second
297+
-------+--------
298+
'one' | 1
299+
(1 row)
300+
293301
SELECT * FROM test_in_out_params('test_in');
294302
second
295303
-------------------
@@ -355,3 +363,11 @@ ERROR: attribute "second" does not exist in Python object
355363
HINT: To return null in a column, let the returned object have an attribute named after column with value None.
356364
CONTEXT: while creating return value
357365
PL/Python function "test_type_record_error3"
366+
CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$
367+
return 'foo'
368+
$$ LANGUAGE plpythonu;
369+
SELECT * FROM test_type_record_error4();
370+
ERROR: malformed record literal: "foo"
371+
DETAIL: Missing left parenthesis.
372+
CONTEXT: while creating return value
373+
PL/Python function "test_type_record_error4"

‎src/pl/plpython/expected/plpython_trigger.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,40 @@ SELECT * FROM composite_trigger_test;
567567
(3,f) | (7,t)
568568
(1 row)
569569

570+
-- triggers with composite type columns (bug #6559)
571+
CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
572+
CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$
573+
return 'MODIFY'
574+
$$ LANGUAGE plpythonu;
575+
CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test
576+
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
577+
INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL);
578+
INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL);
579+
INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f'));
580+
SELECT * FROM composite_trigger_noop_test;
581+
f1 | f2
582+
-------+-------
583+
|
584+
(1,f) |
585+
(,t) | (1,f)
586+
(3 rows)
587+
588+
-- nested composite types
589+
CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer);
590+
CREATE TABLE composite_trigger_nested_test(c comp3);
591+
CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$
592+
return 'MODIFY'
593+
$$ LANGUAGE plpythonu;
594+
CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test
595+
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
596+
INSERT INTO composite_trigger_nested_test VALUES (NULL);
597+
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
598+
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
599+
SELECT * FROM composite_trigger_nested_test;
600+
c
601+
-------------------
602+
603+
("(1,f)",,3)
604+
("(,t)","(1,f)",)
605+
(3 rows)
606+

‎src/pl/plpython/plpython.c

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -410,10 +410,11 @@ static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *);
410410
staticDatumPLyObject_ToDatum(PLyObToDatum*,int32,PyObject*);
411411
staticDatumPLySequence_ToArray(PLyObToDatum*,int32,PyObject*);
412412

413-
staticHeapTuplePLyObject_ToTuple(PLyTypeInfo*,TupleDesc,PyObject*);
414-
staticHeapTuplePLyMapping_ToTuple(PLyTypeInfo*,TupleDesc,PyObject*);
415-
staticHeapTuplePLySequence_ToTuple(PLyTypeInfo*,TupleDesc,PyObject*);
416-
staticHeapTuplePLyGenericObject_ToTuple(PLyTypeInfo*,TupleDesc,PyObject*);
413+
staticDatumPLyObject_ToCompositeDatum(PLyTypeInfo*,TupleDesc,PyObject*);
414+
staticDatumPLyString_ToComposite(PLyTypeInfo*,TupleDesc,PyObject*);
415+
staticDatumPLyMapping_ToComposite(PLyTypeInfo*,TupleDesc,PyObject*);
416+
staticDatumPLySequence_ToComposite(PLyTypeInfo*,TupleDesc,PyObject*);
417+
staticDatumPLyGenericObject_ToComposite(PLyTypeInfo*,TupleDesc,PyObject*);
417418

418419
/*
419420
* Currently active plpython function
@@ -1213,7 +1214,6 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
12131214
elseif (proc->result.is_rowtype >=1)
12141215
{
12151216
TupleDescdesc;
1216-
HeapTupletuple=NULL;
12171217

12181218
/* make sure it's not an unnamed record */
12191219
Assert((proc->result.out.d.typoid==RECORDOID&&
@@ -1224,18 +1224,8 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
12241224
desc=lookup_rowtype_tupdesc(proc->result.out.d.typoid,
12251225
proc->result.out.d.typmod);
12261226

1227-
tuple=PLyObject_ToTuple(&proc->result,desc,plrv);
1228-
1229-
if (tuple!=NULL)
1230-
{
1231-
fcinfo->isnull= false;
1232-
rv=HeapTupleGetDatum(tuple);
1233-
}
1234-
else
1235-
{
1236-
fcinfo->isnull= true;
1237-
rv= (Datum)NULL;
1238-
}
1227+
rv=PLyObject_ToCompositeDatum(&proc->result,desc,plrv);
1228+
fcinfo->isnull= (rv== (Datum)NULL);
12391229
}
12401230
else
12411231
{
@@ -2419,26 +2409,28 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
24192409
}
24202410

24212411
/*
2422-
*Convert a Python object to aPostgreSQL tuple, using all supported
2423-
*conversion methods:tuple as asequence, as amapping orasan object that
2424-
*has __getattr__ support.
2412+
*Convert a Python object to acomposite Datum, using all supported
2413+
*conversion methods:composite as astring, as asequence,asa mapping or
2414+
* as an object thathas __getattr__ support.
24252415
*/
2426-
staticHeapTuple
2427-
PLyObject_ToTuple(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv)
2416+
staticDatum
2417+
PLyObject_ToCompositeDatum(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv)
24282418
{
2429-
HeapTupletuple;
2419+
Datumdatum;
24302420

2431-
if (PySequence_Check(plrv))
2421+
if (PyString_Check(plrv)||PyUnicode_Check(plrv))
2422+
datum=PLyString_ToComposite(info,desc,plrv);
2423+
elseif (PySequence_Check(plrv))
24322424
/* composite type as sequence (tuple, list etc) */
2433-
tuple=PLySequence_ToTuple(info,desc,plrv);
2425+
datum=PLySequence_ToComposite(info,desc,plrv);
24342426
elseif (PyMapping_Check(plrv))
24352427
/* composite type as mapping (currently only dict) */
2436-
tuple=PLyMapping_ToTuple(info,desc,plrv);
2428+
datum=PLyMapping_ToComposite(info,desc,plrv);
24372429
else
24382430
/* returned as smth, must provide method __getattr__(name) */
2439-
tuple=PLyGenericObject_ToTuple(info,desc,plrv);
2431+
datum=PLyGenericObject_ToComposite(info,desc,plrv);
24402432

2441-
returntuple;
2433+
returndatum;
24422434
}
24432435

24442436
/*
@@ -2513,7 +2505,6 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
25132505
staticDatum
25142506
PLyObject_ToComposite(PLyObToDatum*arg,int32typmod,PyObject*plrv)
25152507
{
2516-
HeapTupletuple=NULL;
25172508
Datumrv;
25182509
PLyTypeInfoinfo;
25192510
TupleDescdesc;
@@ -2535,15 +2526,10 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
25352526
* that info instead of looking it up every time a tuple is returned from
25362527
* the function.
25372528
*/
2538-
tuple=PLyObject_ToTuple(&info,desc,plrv);
2529+
rv=PLyObject_ToCompositeDatum(&info,desc,plrv);
25392530

25402531
PLy_typeinfo_dealloc(&info);
25412532

2542-
if (tuple!=NULL)
2543-
rv=HeapTupleGetDatum(tuple);
2544-
else
2545-
rv= (Datum)NULL;
2546-
25472533
returnrv;
25482534
}
25492535

@@ -2650,8 +2636,28 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
26502636
returnPointerGetDatum(array);
26512637
}
26522638

2653-
staticHeapTuple
2654-
PLyMapping_ToTuple(PLyTypeInfo*info,TupleDescdesc,PyObject*mapping)
2639+
2640+
staticDatum
2641+
PLyString_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*string)
2642+
{
2643+
HeapTupletypeTup;
2644+
2645+
typeTup=SearchSysCache1(TYPEOID,ObjectIdGetDatum(desc->tdtypeid));
2646+
if (!HeapTupleIsValid(typeTup))
2647+
elog(ERROR,"cache lookup failed for type %u",desc->tdtypeid);
2648+
2649+
PLy_output_datum_func2(&info->out.d,typeTup);
2650+
2651+
ReleaseSysCache(typeTup);
2652+
ReleaseTupleDesc(desc);
2653+
2654+
returnPLyObject_ToDatum(&info->out.d,info->out.d.typmod,string);
2655+
}
2656+
2657+
2658+
2659+
staticDatum
2660+
PLyMapping_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*mapping)
26552661
{
26562662
HeapTupletuple;
26572663
Datum*values;
@@ -2719,12 +2725,12 @@ PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
27192725
pfree(values);
27202726
pfree(nulls);
27212727

2722-
returntuple;
2728+
returnHeapTupleGetDatum(tuple);
27232729
}
27242730

27252731

2726-
staticHeapTuple
2727-
PLySequence_ToTuple(PLyTypeInfo*info,TupleDescdesc,PyObject*sequence)
2732+
staticDatum
2733+
PLySequence_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*sequence)
27282734
{
27292735
HeapTupletuple;
27302736
Datum*values;
@@ -2805,12 +2811,12 @@ PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
28052811
pfree(values);
28062812
pfree(nulls);
28072813

2808-
returntuple;
2814+
returnHeapTupleGetDatum(tuple);
28092815
}
28102816

28112817

2812-
staticHeapTuple
2813-
PLyGenericObject_ToTuple(PLyTypeInfo*info,TupleDescdesc,PyObject*object)
2818+
staticDatum
2819+
PLyGenericObject_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*object)
28142820
{
28152821
HeapTupletuple;
28162822
Datum*values;
@@ -2877,7 +2883,7 @@ PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
28772883
pfree(values);
28782884
pfree(nulls);
28792885

2880-
returntuple;
2886+
returnHeapTupleGetDatum(tuple);
28812887
}
28822888

28832889

‎src/pl/plpython/sql/plpython_record.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ elif typ == 'obj':
4343
type_record.first= first
4444
type_record.second= second
4545
return type_record
46+
elif typ=='str':
47+
return"('%s',%r)" % (first, second)
4648
$$ LANGUAGE plpythonu;
4749

4850
CREATEFUNCTIONtest_in_out_params(firstintext, second outtext)AS $$
@@ -108,6 +110,8 @@ SELECT * FROM test_type_record_as('obj', null, 2, false);
108110
SELECT*FROM test_type_record_as('obj','three',3, false);
109111
SELECT*FROM test_type_record_as('obj',null,null, true);
110112

113+
SELECT*FROM test_type_record_as('str','one',1, false);
114+
111115
SELECT*FROM test_in_out_params('test_in');
112116
SELECT*FROM test_in_out_params_multi('test_in');
113117
SELECT*FROM test_inout_params('test_in');
@@ -151,3 +155,9 @@ CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
151155
$$ LANGUAGE plpythonu;
152156

153157
SELECT*FROM test_type_record_error3();
158+
159+
CREATEFUNCTIONtest_type_record_error4() RETURNS type_recordAS $$
160+
return'foo'
161+
$$ LANGUAGE plpythonu;
162+
163+
SELECT*FROM test_type_record_error4();

‎src/pl/plpython/sql/plpython_trigger.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,39 @@ CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
346346

347347
INSERT INTO composite_trigger_testVALUES (NULL,NULL);
348348
SELECT*FROM composite_trigger_test;
349+
350+
351+
-- triggers with composite type columns (bug #6559)
352+
353+
CREATETABLEcomposite_trigger_noop_test (f1 comp1, f2 comp2);
354+
355+
CREATEFUNCTIONcomposite_trigger_noop_f() RETURNS triggerAS $$
356+
return'MODIFY'
357+
$$ LANGUAGE plpythonu;
358+
359+
CREATETRIGGERcomposite_trigger_noop BEFORE INSERTON composite_trigger_noop_test
360+
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
361+
362+
INSERT INTO composite_trigger_noop_testVALUES (NULL,NULL);
363+
INSERT INTO composite_trigger_noop_testVALUES (ROW(1,'f'),NULL);
364+
INSERT INTO composite_trigger_noop_testVALUES (ROW(NULL,'t'), ROW(1,'f'));
365+
SELECT*FROM composite_trigger_noop_test;
366+
367+
368+
-- nested composite types
369+
370+
CREATETYPEcomp3AS (c1 comp1, c2 comp2, minteger);
371+
372+
CREATETABLEcomposite_trigger_nested_test(c comp3);
373+
374+
CREATEFUNCTIONcomposite_trigger_nested_f() RETURNS triggerAS $$
375+
return'MODIFY'
376+
$$ LANGUAGE plpythonu;
377+
378+
CREATETRIGGERcomposite_trigger_nested BEFORE INSERTON composite_trigger_nested_test
379+
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
380+
381+
INSERT INTO composite_trigger_nested_testVALUES (NULL);
382+
INSERT INTO composite_trigger_nested_testVALUES (ROW(ROW(1,'f'),NULL,3));
383+
INSERT INTO composite_trigger_nested_testVALUES (ROW(ROW(NULL,'t'), ROW(1,'f'),NULL));
384+
SELECT*FROM composite_trigger_nested_test;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp