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

Commit510e1b8

Browse files
committed
Give a hint, when [] is incorrectly used for a composite type in array.
That used to be accepted, so let's try to give a hint to users on whytheir PL/python functions no longer work.Reviewed by Pavel Stehule.Discussion: <CAH38_tmbqwaUyKs9yagyRra=SMaT45FPBxk1pmTYcM0TyXGG7Q@mail.gmail.com>
1 parent94aceed commit510e1b8

File tree

7 files changed

+129
-39
lines changed

7 files changed

+129
-39
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,16 @@ SELECT * FROM composite_type_as_list();
579579
{{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}}
580580
(1 row)
581581

582+
-- Starting with PostgreSQL 10, a composite type in an array cannot be
583+
-- represented as a Python list, because it's ambiguous with multi-dimensional
584+
-- arrays. So this throws an error now. The error should contain a useful hint
585+
-- on the issue.
586+
CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
587+
return [['first', 1]];
588+
$$ LANGUAGE plpythonu;
589+
SELECT * FROM composite_type_as_list_broken();
590+
ERROR: malformed record literal: "first"
591+
DETAIL: Missing left parenthesis.
592+
HINT: To return a composite type in an array, return the composite type as a Python tuple, e.g. "[('foo')]"
593+
CONTEXT: while creating return value
594+
PL/Python function "composite_type_as_list_broken"

‎src/pl/plpython/plpy_cursorobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
240240
plan->values[j]=
241241
plan->args[j].out.d.func(&(plan->args[j].out.d),
242242
-1,
243-
elem);
243+
elem,
244+
false);
244245
}
245246
PG_CATCH();
246247
{

‎src/pl/plpython/plpy_exec.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,15 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
245245
desc=lookup_rowtype_tupdesc(proc->result.out.d.typoid,
246246
proc->result.out.d.typmod);
247247

248-
rv=PLyObject_ToCompositeDatum(&proc->result,desc,plrv);
248+
rv=PLyObject_ToCompositeDatum(&proc->result,desc,plrv, false);
249249
fcinfo->isnull= (rv== (Datum)NULL);
250250

251251
ReleaseTupleDesc(desc);
252252
}
253253
else
254254
{
255255
fcinfo->isnull= false;
256-
rv= (proc->result.out.d.func) (&proc->result.out.d,-1,plrv);
256+
rv= (proc->result.out.d.func) (&proc->result.out.d,-1,plrv, false);
257257
}
258258
}
259259
PG_CATCH();
@@ -984,7 +984,8 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
984984

985985
modvalues[i]= (att->func) (att,
986986
tupdesc->attrs[atti]->atttypmod,
987-
plval);
987+
plval,
988+
false);
988989
modnulls[i]=' ';
989990
}
990991
else

‎src/pl/plpython/plpy_spi.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
264264
plan->values[j]=
265265
plan->args[j].out.d.func(&(plan->args[j].out.d),
266266
-1,
267-
elem);
267+
elem,
268+
false);
268269
}
269270
PG_CATCH();
270271
{

‎src/pl/plpython/plpy_typeio.c

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include"parser/parse_type.h"
1515
#include"utils/array.h"
1616
#include"utils/builtins.h"
17+
#include"utils/fmgroids.h"
1718
#include"utils/lsyscache.h"
1819
#include"utils/memutils.h"
1920
#include"utils/numeric.h"
@@ -49,21 +50,21 @@ static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndi
4950
char**dataptr_p,bits8**bitmap_p,int*bitmask_p);
5051

5152
/* conversion from Python objects to Datums */
52-
staticDatumPLyObject_ToBool(PLyObToDatum*arg,int32typmod,PyObject*plrv);
53-
staticDatumPLyObject_ToBytea(PLyObToDatum*arg,int32typmod,PyObject*plrv);
54-
staticDatumPLyObject_ToComposite(PLyObToDatum*arg,int32typmod,PyObject*plrv);
55-
staticDatumPLyObject_ToDatum(PLyObToDatum*arg,int32typmod,PyObject*plrv);
56-
staticDatumPLyObject_ToTransform(PLyObToDatum*arg,int32typmod,PyObject*plrv);
57-
staticDatumPLySequence_ToArray(PLyObToDatum*arg,int32typmod,PyObject*plrv);
53+
staticDatumPLyObject_ToBool(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
54+
staticDatumPLyObject_ToBytea(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
55+
staticDatumPLyObject_ToComposite(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
56+
staticDatumPLyObject_ToDatum(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
57+
staticDatumPLyObject_ToTransform(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
58+
staticDatumPLySequence_ToArray(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray);
5859
staticvoidPLySequence_ToArray_recurse(PLyObToDatum*elm,PyObject*list,
5960
int*dims,intndim,intdim,
6061
Datum*elems,bool*nulls,int*currelem);
6162

6263
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
63-
staticDatumPLyString_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*string);
64+
staticDatumPLyString_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*string,boolinarray);
6465
staticDatumPLyMapping_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*mapping);
6566
staticDatumPLySequence_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*sequence);
66-
staticDatumPLyGenericObject_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*object);
67+
staticDatumPLyGenericObject_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*object,boolinarray);
6768

6869
void
6970
PLy_typeinfo_init(PLyTypeInfo*arg,MemoryContextmcxt)
@@ -341,12 +342,12 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
341342
*as an object that has __getattr__ support.
342343
*/
343344
Datum
344-
PLyObject_ToCompositeDatum(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv)
345+
PLyObject_ToCompositeDatum(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv,boolinarray)
345346
{
346347
Datumdatum;
347348

348349
if (PyString_Check(plrv)||PyUnicode_Check(plrv))
349-
datum=PLyString_ToComposite(info,desc,plrv);
350+
datum=PLyString_ToComposite(info,desc,plrv,inarray);
350351
elseif (PySequence_Check(plrv))
351352
/* composite type as sequence (tuple, list etc) */
352353
datum=PLySequence_ToComposite(info,desc,plrv);
@@ -355,7 +356,7 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
355356
datum=PLyMapping_ToComposite(info,desc,plrv);
356357
else
357358
/* returned as smth, must provide method __getattr__(name) */
358-
datum=PLyGenericObject_ToComposite(info,desc,plrv);
359+
datum=PLyGenericObject_ToComposite(info,desc,plrv,inarray);
359360

360361
returndatum;
361362
}
@@ -746,7 +747,7 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
746747
* type can parse.
747748
*/
748749
staticDatum
749-
PLyObject_ToBool(PLyObToDatum*arg,int32typmod,PyObject*plrv)
750+
PLyObject_ToBool(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
750751
{
751752
Datumrv;
752753

@@ -765,7 +766,7 @@ PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
765766
* with embedded nulls. And it's faster this way.
766767
*/
767768
staticDatum
768-
PLyObject_ToBytea(PLyObToDatum*arg,int32typmod,PyObject*plrv)
769+
PLyObject_ToBytea(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
769770
{
770771
PyObject*volatileplrv_so=NULL;
771772
Datumrv;
@@ -809,7 +810,7 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
809810
* for obtaining PostgreSQL tuples.
810811
*/
811812
staticDatum
812-
PLyObject_ToComposite(PLyObToDatum*arg,int32typmod,PyObject*plrv)
813+
PLyObject_ToComposite(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
813814
{
814815
Datumrv;
815816
PLyTypeInfoinfo;
@@ -836,7 +837,7 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
836837
* that info instead of looking it up every time a tuple is returned from
837838
* the function.
838839
*/
839-
rv=PLyObject_ToCompositeDatum(&info,desc,plrv);
840+
rv=PLyObject_ToCompositeDatum(&info,desc,plrv,inarray);
840841

841842
ReleaseTupleDesc(desc);
842843

@@ -908,26 +909,70 @@ PLyObject_AsString(PyObject *plrv)
908909
* cstring into PostgreSQL type.
909910
*/
910911
staticDatum
911-
PLyObject_ToDatum(PLyObToDatum*arg,int32typmod,PyObject*plrv)
912+
PLyObject_ToDatum(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
912913
{
914+
char*str;
915+
913916
Assert(plrv!=Py_None);
914917

918+
str=PLyObject_AsString(plrv);
919+
920+
/*
921+
* If we are parsing a composite type within an array, and the string
922+
* isn't a valid record literal, there's a high chance that the function
923+
* did something like:
924+
*
925+
* CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
926+
* LANGUAGE plpython;
927+
*
928+
* Before PostgreSQL 10, that was interpreted as a single-dimensional
929+
* array, containing record ('foo', 'bar'). PostgreSQL 10 added support
930+
* for multi-dimensional arrays, and it is now interpreted as a
931+
* two-dimensional array, containing two records, 'foo', and 'bar'.
932+
* record_in() will throw an error, because "foo" is not a valid record
933+
* literal.
934+
*
935+
* To make that less confusing to users who are upgrading from older
936+
* versions, try to give a hint in the typical instances of that. If we are
937+
* parsing an array of composite types, and we see a string literal that
938+
* is not a valid record literal, give a hint. We only want to give the
939+
* hint in the narrow case of a malformed string literal, not any error
940+
* from record_in(), so check for that case here specifically.
941+
*
942+
* This check better match the one in record_in(), so that we don't forbid
943+
* literals that are actually valid!
944+
*/
945+
if (inarray&&arg->typfunc.fn_oid==F_RECORD_IN)
946+
{
947+
char*ptr=str;
948+
949+
/* Allow leading whitespace */
950+
while (*ptr&&isspace((unsignedchar)*ptr))
951+
ptr++;
952+
if (*ptr++!='(')
953+
ereport(ERROR,
954+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
955+
errmsg("malformed record literal: \"%s\"",str),
956+
errdetail("Missing left parenthesis."),
957+
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\"")));
958+
}
959+
915960
returnInputFunctionCall(&arg->typfunc,
916-
PLyObject_AsString(plrv),
961+
str,
917962
arg->typioparam,
918963
typmod);
919964
}
920965

921966

922967
staticDatum
923-
PLyObject_ToTransform(PLyObToDatum*arg,int32typmod,PyObject*plrv)
968+
PLyObject_ToTransform(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
924969
{
925970
returnFunctionCall1(&arg->typtransform,PointerGetDatum(plrv));
926971
}
927972

928973

929974
staticDatum
930-
PLySequence_ToArray(PLyObToDatum*arg,int32typmod,PyObject*plrv)
975+
PLySequence_ToArray(PLyObToDatum*arg,int32typmod,PyObject*plrv,boolinarray)
931976
{
932977
ArrayType*array;
933978
inti;
@@ -1085,7 +1130,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
10851130
else
10861131
{
10871132
nulls[*currelem]= false;
1088-
elems[*currelem]=elm->func(elm,-1,obj);
1133+
elems[*currelem]=elm->func(elm,-1,obj, true);
10891134
}
10901135
Py_XDECREF(obj);
10911136
(*currelem)++;
@@ -1095,7 +1140,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
10951140

10961141

10971142
staticDatum
1098-
PLyString_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*string)
1143+
PLyString_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*string,boolinarray)
10991144
{
11001145
Datumresult;
11011146
HeapTupletypeTup;
@@ -1120,7 +1165,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
11201165

11211166
ReleaseSysCache(typeTup);
11221167

1123-
result=PLyObject_ToDatum(&locinfo.out.d,desc->tdtypmod,string);
1168+
result=PLyObject_ToDatum(&locinfo.out.d,desc->tdtypmod,string,inarray);
11241169

11251170
MemoryContextDelete(cxt);
11261171

@@ -1172,7 +1217,7 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
11721217
}
11731218
elseif (value)
11741219
{
1175-
values[i]= (att->func) (att,-1,value);
1220+
values[i]= (att->func) (att,-1,value, false);
11761221
nulls[i]= false;
11771222
}
11781223
else
@@ -1265,7 +1310,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
12651310
}
12661311
elseif (value)
12671312
{
1268-
values[i]= (att->func) (att,-1,value);
1313+
values[i]= (att->func) (att,-1,value, false);
12691314
nulls[i]= false;
12701315
}
12711316

@@ -1294,7 +1339,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
12941339

12951340

12961341
staticDatum
1297-
PLyGenericObject_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*object)
1342+
PLyGenericObject_ToComposite(PLyTypeInfo*info,TupleDescdesc,PyObject*object,boolinarray)
12981343
{
12991344
Datumresult;
13001345
HeapTupletuple;
@@ -1335,16 +1380,29 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
13351380
}
13361381
elseif (value)
13371382
{
1338-
values[i]= (att->func) (att,-1,value);
1383+
values[i]= (att->func) (att,-1,value, false);
13391384
nulls[i]= false;
13401385
}
13411386
else
1387+
{
1388+
/*
1389+
* No attribute for this column in the object.
1390+
*
1391+
* If we are parsing a composite type in an array, a likely
1392+
* cause is that the function contained something like "[[123,
1393+
* 'foo']]". Before PostgreSQL 10, that was interpreted as an
1394+
* array, with a composite type (123, 'foo') in it. But now
1395+
* it's interpreted as a two-dimensional array, and we try to
1396+
* interpret "123" as the composite type. See also similar
1397+
* heuristic in PLyObject_ToDatum().
1398+
*/
13421399
ereport(ERROR,
13431400
(errcode(ERRCODE_UNDEFINED_COLUMN),
13441401
errmsg("attribute \"%s\" does not exist in Python object",key),
1345-
errhint("To return null in a column, "
1346-
"let the returned object have an attribute named "
1347-
"after column with value None.")));
1402+
inarray ?
1403+
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\"") :
1404+
errhint("To return null in a column, let the returned object have an attribute named after column with value None.")));
1405+
}
13481406

13491407
Py_XDECREF(value);
13501408
value=NULL;

‎src/pl/plpython/plpy_typeio.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
#include"fmgr.h"
1111
#include"storage/itemptr.h"
1212

13+
/*
14+
* Conversion from PostgreSQL Datum to a Python object.
15+
*/
1316
structPLyDatumToOb;
14-
typedefPyObject*(*PLyDatumToObFunc) (structPLyDatumToOb*,Datum);
17+
typedefPyObject*(*PLyDatumToObFunc) (structPLyDatumToOb*arg,Datumval);
1518

1619
typedefstructPLyDatumToOb
1720
{
@@ -39,11 +42,15 @@ typedef union PLyTypeInput
3942
PLyTupleToObr;
4043
}PLyTypeInput;
4144

42-
/* convert PyObject to a Postgresql Datum or tuple.
43-
* output from Python
45+
/*
46+
* Conversion from Python object to a Postgresql Datum.
47+
*
48+
* The 'inarray' argument to the conversion function is true, if the
49+
* converted value was in an array (Python list). It is used to give a
50+
* better error message in some cases.
4451
*/
4552
structPLyObToDatum;
46-
typedefDatum (*PLyObToDatumFunc) (structPLyObToDatum*,int32,PyObject*);
53+
typedefDatum (*PLyObToDatumFunc) (structPLyObToDatum*arg,int32typmod,PyObject*val,boolinarray);
4754

4855
typedefstructPLyObToDatum
4956
{
@@ -104,7 +111,7 @@ extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
104111
externvoidPLy_output_record_funcs(PLyTypeInfo*arg,TupleDescdesc);
105112

106113
/* conversion from Python objects to composite Datums */
107-
externDatumPLyObject_ToCompositeDatum(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv);
114+
externDatumPLyObject_ToCompositeDatum(PLyTypeInfo*info,TupleDescdesc,PyObject*plrv,boolisarray);
108115

109116
/* conversion from heap tuples to Python dictionaries */
110117
externPyObject*PLyDict_FromTuple(PLyTypeInfo*info,HeapTupletuple,TupleDescdesc);

‎src/pl/plpython/sql/plpython_composite.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,12 @@ CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$
213213
return [[('first',1), ('second',1)], [('first',2), ('second',2)], [('first',3), ('second',3)]];
214214
$$ LANGUAGE plpythonu;
215215
SELECT*FROM composite_type_as_list();
216+
217+
-- Starting with PostgreSQL 10, a composite type in an array cannot be
218+
-- represented as a Python list, because it's ambiguous with multi-dimensional
219+
-- arrays. So this throws an error now. The error should contain a useful hint
220+
-- on the issue.
221+
CREATEFUNCTIONcomposite_type_as_list_broken() RETURNS type_record[]AS $$
222+
return [['first',1]];
223+
$$ LANGUAGE plpythonu;
224+
SELECT*FROM composite_type_as_list_broken();

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp