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

Commitb7001c6

Browse files
committed
Tighten array dimensionality checks in Python -> SQL array conversion.
Like plperl beforef47004a, plpython wasn't being sufficientlycareful about checking that list-of-list structures representrectangular arrays, so that it would accept some cases in whichdifferent parts of the "array" are nested to different depths.This was exacerbated by Python's weak distinction betweensequences and lists, so that in some cases strings could gettreated as though they are lists (and burst into individualcharacters) even though a different ordering of the upper-levellist would give a different result.Some of this behavior was unreachable (without risking a crash)before81eaaf6. It seems like a good idea to clean it all upin the same releases, rather than shipping a non-crashing butnonetheless visibly buggy behavior in the name of minimal change.Hence, back-patch.Per bug #17912 and further testing by Alexander Lakhin.Discussion:https://postgr.es/m/17912-82ceed78731d9cdc@postgresql.org
1 parent23c7aa8 commitb7001c6

File tree

3 files changed

+212
-121
lines changed

3 files changed

+212
-121
lines changed

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

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -687,23 +687,74 @@ SELECT * FROM test_type_conversion_array_mixed2();
687687
ERROR: invalid input syntax for type integer: "abc"
688688
CONTEXT: while creating return value
689689
PL/Python function "test_type_conversion_array_mixed2"
690-
CREATE FUNCTION test_type_conversion_array_mixed3() RETURNS text[] AS $$
691-
return [[], 'a']
690+
-- check output of multi-dimensional arrays
691+
CREATE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
692+
return [['a'], ['b'], ['c']]
692693
$$ LANGUAGE plpython3u;
693-
SELECT * FROM test_type_conversion_array_mixed3();
694-
test_type_conversion_array_mixed3
694+
select test_type_conversion_md_array_out();
695+
test_type_conversion_md_array_out
695696
-----------------------------------
696-
{[],a}
697+
{{a},{b},{c}}
697698
(1 row)
698699

700+
CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
701+
return [[], []]
702+
$$ LANGUAGE plpython3u;
703+
select test_type_conversion_md_array_out();
704+
test_type_conversion_md_array_out
705+
-----------------------------------
706+
{}
707+
(1 row)
708+
709+
CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
710+
return [[], [1]]
711+
$$ LANGUAGE plpython3u;
712+
select test_type_conversion_md_array_out(); -- fail
713+
ERROR: multidimensional arrays must have array expressions with matching dimensions
714+
CONTEXT: while creating return value
715+
PL/Python function "test_type_conversion_md_array_out"
716+
CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
717+
return [[], 1]
718+
$$ LANGUAGE plpython3u;
719+
select test_type_conversion_md_array_out(); -- fail
720+
ERROR: multidimensional arrays must have array expressions with matching dimensions
721+
CONTEXT: while creating return value
722+
PL/Python function "test_type_conversion_md_array_out"
723+
CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
724+
return [1, []]
725+
$$ LANGUAGE plpython3u;
726+
select test_type_conversion_md_array_out(); -- fail
727+
ERROR: multidimensional arrays must have array expressions with matching dimensions
728+
CONTEXT: while creating return value
729+
PL/Python function "test_type_conversion_md_array_out"
730+
CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
731+
return [[1], [[]]]
732+
$$ LANGUAGE plpython3u;
733+
select test_type_conversion_md_array_out(); -- fail
734+
ERROR: multidimensional arrays must have array expressions with matching dimensions
735+
CONTEXT: while creating return value
736+
PL/Python function "test_type_conversion_md_array_out"
699737
CREATE FUNCTION test_type_conversion_mdarray_malformed() RETURNS int[] AS $$
700738
return [[1,2,3],[4,5]]
701739
$$ LANGUAGE plpython3u;
702740
SELECT * FROM test_type_conversion_mdarray_malformed();
703-
ERROR: wrong length of inner sequence: has length 2, but 3 was expected
704-
DETAIL: To construct a multidimensional array, the inner sequences must all have the same length.
741+
ERROR: multidimensional arrays must have array expressions with matching dimensions
705742
CONTEXT: while creating return value
706743
PL/Python function "test_type_conversion_mdarray_malformed"
744+
CREATE FUNCTION test_type_conversion_mdarray_malformed2() RETURNS text[] AS $$
745+
return [[1,2,3], "abc"]
746+
$$ LANGUAGE plpython3u;
747+
SELECT * FROM test_type_conversion_mdarray_malformed2();
748+
ERROR: multidimensional arrays must have array expressions with matching dimensions
749+
CONTEXT: while creating return value
750+
PL/Python function "test_type_conversion_mdarray_malformed2"
751+
CREATE FUNCTION test_type_conversion_mdarray_malformed3() RETURNS text[] AS $$
752+
return ["abc", [1,2,3]]
753+
$$ LANGUAGE plpython3u;
754+
SELECT * FROM test_type_conversion_mdarray_malformed3();
755+
ERROR: multidimensional arrays must have array expressions with matching dimensions
756+
CONTEXT: while creating return value
757+
PL/Python function "test_type_conversion_mdarray_malformed3"
707758
CREATE FUNCTION test_type_conversion_mdarray_toodeep() RETURNS int[] AS $$
708759
return [[[[[[[1]]]]]]]
709760
$$ LANGUAGE plpython3u;

‎src/pl/plpython/plpy_typeio.c

Lines changed: 107 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
5454
bool*isnull,boolinarray);
5555
staticDatumPLySequence_ToArray(PLyObToDatum*arg,PyObject*plrv,
5656
bool*isnull,boolinarray);
57-
staticvoidPLySequence_ToArray_recurse(PLyObToDatum*elm,PyObject*list,
58-
int*dims,intndim,intdim,
59-
Datum*elems,bool*nulls,int*currelem);
57+
staticvoidPLySequence_ToArray_recurse(PyObject*obj,
58+
ArrayBuildState**astatep,
59+
int*ndims,int*dims,intcur_depth,
60+
PLyObToDatum*elm,Oidelmbasetype);
6061

6162
/* conversion from Python objects to composite Datums */
6263
staticDatumPLyUnicode_ToComposite(PLyObToDatum*arg,PyObject*string,boolinarray);
@@ -1126,23 +1127,16 @@ PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
11261127

11271128

11281129
/*
1129-
* Convert Python sequence to SQL array.
1130+
* Convert Python sequence(or list of lists)to SQL array.
11301131
*/
11311132
staticDatum
11321133
PLySequence_ToArray(PLyObToDatum*arg,PyObject*plrv,
11331134
bool*isnull,boolinarray)
11341135
{
1135-
ArrayType*array;
1136-
inti;
1137-
Datum*elems;
1138-
bool*nulls;
1139-
intlen;
1140-
intndim;
1136+
ArrayBuildState*astate=NULL;
1137+
intndims=1;
11411138
intdims[MAXDIM];
11421139
intlbs[MAXDIM];
1143-
intcurrelem;
1144-
PyObject*pyptr=plrv;
1145-
PyObject*next;
11461140

11471141
if (plrv==Py_None)
11481142
{
@@ -1152,128 +1146,130 @@ PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
11521146
*isnull= false;
11531147

11541148
/*
1155-
* Determine the number of dimensions, and their sizes.
1149+
* For historical reasons, we allow any sequence (not only a list) at the
1150+
* top level when converting a Python object to a SQL array. However, a
1151+
* multi-dimensional array is recognized only when the object contains
1152+
* true lists.
11561153
*/
1157-
ndim=0;
1158-
1159-
Py_INCREF(plrv);
1160-
1161-
for (;;)
1162-
{
1163-
if (!PyList_Check(pyptr))
1164-
break;
1165-
1166-
if (ndim==MAXDIM)
1167-
ereport(ERROR,
1168-
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1169-
errmsg("number of array dimensions exceeds the maximum allowed (%d)",
1170-
MAXDIM)));
1171-
1172-
dims[ndim]=PySequence_Length(pyptr);
1173-
if (dims[ndim]<0)
1174-
PLy_elog(ERROR,"could not determine sequence length for function return value");
1175-
1176-
if (dims[ndim]==0)
1177-
{
1178-
/* empty sequence */
1179-
break;
1180-
}
1181-
1182-
ndim++;
1183-
1184-
next=PySequence_GetItem(pyptr,0);
1185-
Py_XDECREF(pyptr);
1186-
pyptr=next;
1187-
}
1188-
Py_XDECREF(pyptr);
1189-
1190-
/*
1191-
* Check for zero dimensions. This happens if the object is a tuple or a
1192-
* string, rather than a list, or is not a sequence at all. We don't map
1193-
* tuples or strings to arrays in general, but in the first level, be
1194-
* lenient, for historical reasons. So if the object is a sequence of any
1195-
* kind, treat it as a one-dimensional array.
1196-
*/
1197-
if (ndim==0)
1198-
{
1199-
if (!PySequence_Check(plrv))
1200-
ereport(ERROR,
1201-
(errcode(ERRCODE_DATATYPE_MISMATCH),
1202-
errmsg("return value of function with array return type is not a Python sequence")));
1203-
1204-
ndim=1;
1205-
dims[0]=PySequence_Length(plrv);
1206-
}
1154+
if (!PySequence_Check(plrv))
1155+
ereport(ERROR,
1156+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1157+
errmsg("return value of function with array return type is not a Python sequence")));
12071158

1208-
/* Allocate space for work arrays, after detecting array size overflow */
1209-
len=ArrayGetNItems(ndim,dims);
1210-
elems=palloc(sizeof(Datum)*len);
1211-
nulls=palloc(sizeof(bool)*len);
1159+
/* Initialize dimensionality info with first-level dimension */
1160+
memset(dims,0,sizeof(dims));
1161+
dims[0]=PySequence_Length(plrv);
12121162

12131163
/*
12141164
* Traverse the Python lists, in depth-first order, and collect all the
1215-
* elements at the bottom level into'elems'/'nulls' arrays.
1165+
* elements at the bottom level intoan ArrayBuildState.
12161166
*/
1217-
currelem=0;
1218-
PLySequence_ToArray_recurse(arg->u.array.elm,plrv,
1219-
dims,ndim,0,
1220-
elems,nulls,&currelem);
1167+
PLySequence_ToArray_recurse(plrv,&astate,
1168+
&ndims,dims,1,
1169+
arg->u.array.elm,
1170+
arg->u.array.elmbasetype);
1171+
1172+
/* ensure we get zero-D array for no inputs, as per PG convention */
1173+
if (astate==NULL)
1174+
returnPointerGetDatum(construct_empty_array(arg->u.array.elmbasetype));
12211175

1222-
for (i=0;i<ndim;i++)
1176+
for (inti=0;i<ndims;i++)
12231177
lbs[i]=1;
12241178

1225-
array=construct_md_array(elems,
1226-
nulls,
1227-
ndim,
1228-
dims,
1229-
lbs,
1230-
arg->u.array.elmbasetype,
1231-
arg->u.array.elm->typlen,
1232-
arg->u.array.elm->typbyval,
1233-
arg->u.array.elm->typalign);
1234-
1235-
returnPointerGetDatum(array);
1179+
returnmakeMdArrayResult(astate,ndims,dims,lbs,
1180+
CurrentMemoryContext, true);
12361181
}
12371182

12381183
/*
12391184
* Helper function for PLySequence_ToArray. Traverse a Python list of lists in
1240-
* depth-first order, storing the elements in 'elems'.
1185+
* depth-first order, storing the elements in *astatep.
1186+
*
1187+
* The ArrayBuildState is created only when we first find a scalar element;
1188+
* if we didn't do it like that, we'd need some other convention for knowing
1189+
* whether we'd already found any scalars (and thus the number of dimensions
1190+
* is frozen).
12411191
*/
12421192
staticvoid
1243-
PLySequence_ToArray_recurse(PLyObToDatum*elm,PyObject*list,
1244-
int*dims,intndim,intdim,
1245-
Datum*elems,bool*nulls,int*currelem)
1193+
PLySequence_ToArray_recurse(PyObject*obj,ArrayBuildState**astatep,
1194+
int*ndims,int*dims,intcur_depth,
1195+
PLyObToDatum*elm,Oidelmbasetype)
12461196
{
12471197
inti;
1198+
intlen=PySequence_Length(obj);
12481199

1249-
if (PySequence_Length(list)!=dims[dim])
1250-
ereport(ERROR,
1251-
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1252-
errmsg("wrong length of inner sequence: has length %d, but %d was expected",
1253-
(int)PySequence_Length(list),dims[dim]),
1254-
(errdetail("To construct a multidimensional array, the inner sequences must all have the same length."))));
1200+
/* We should not get here with a non-sequence object */
1201+
if (len<0)
1202+
PLy_elog(ERROR,"could not determine sequence length for function return value");
12551203

1256-
if (dim<ndim-1)
1204+
for (i=0;i<len;i++)
12571205
{
1258-
for (i=0;i<dims[dim];i++)
1259-
{
1260-
PyObject*sublist=PySequence_GetItem(list,i);
1206+
/* fetch the array element */
1207+
PyObject*subobj=PySequence_GetItem(obj,i);
12611208

1262-
PLySequence_ToArray_recurse(elm,sublist,dims,ndim,dim+1,
1263-
elems,nulls,currelem);
1264-
Py_XDECREF(sublist);
1209+
/* need PG_TRY to ensure we release the subobj's refcount */
1210+
PG_TRY();
1211+
{
1212+
/* multi-dimensional array? */
1213+
if (PyList_Check(subobj))
1214+
{
1215+
/* set size when at first element in this level, else compare */
1216+
if (i==0&&*ndims==cur_depth)
1217+
{
1218+
/* array after some scalars at same level? */
1219+
if (*astatep!=NULL)
1220+
ereport(ERROR,
1221+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1222+
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
1223+
/* too many dimensions? */
1224+
if (cur_depth >=MAXDIM)
1225+
ereport(ERROR,
1226+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1227+
errmsg("number of array dimensions exceeds the maximum allowed (%d)",
1228+
MAXDIM)));
1229+
/* OK, add a dimension */
1230+
dims[*ndims]=PySequence_Length(subobj);
1231+
(*ndims)++;
1232+
}
1233+
elseif (cur_depth >=*ndims||
1234+
PySequence_Length(subobj)!=dims[cur_depth])
1235+
ereport(ERROR,
1236+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1237+
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
1238+
1239+
/* recurse to fetch elements of this sub-array */
1240+
PLySequence_ToArray_recurse(subobj,astatep,
1241+
ndims,dims,cur_depth+1,
1242+
elm,elmbasetype);
1243+
}
1244+
else
1245+
{
1246+
Datumdat;
1247+
boolisnull;
1248+
1249+
/* scalar after some sub-arrays at same level? */
1250+
if (*ndims!=cur_depth)
1251+
ereport(ERROR,
1252+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1253+
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
1254+
1255+
/* convert non-list object to Datum */
1256+
dat=elm->func(elm,subobj,&isnull, true);
1257+
1258+
/* create ArrayBuildState if we didn't already */
1259+
if (*astatep==NULL)
1260+
*astatep=initArrayResult(elmbasetype,
1261+
CurrentMemoryContext, true);
1262+
1263+
/* ... and save the element value in it */
1264+
(void)accumArrayResult(*astatep,dat,isnull,
1265+
elmbasetype,CurrentMemoryContext);
1266+
}
12651267
}
1266-
}
1267-
else
1268-
{
1269-
for (i=0;i<dims[dim];i++)
1268+
PG_FINALLY();
12701269
{
1271-
PyObject*obj=PySequence_GetItem(list,i);
1272-
1273-
elems[*currelem]=elm->func(elm,obj,&nulls[*currelem], true);
1274-
Py_XDECREF(obj);
1275-
(*currelem)++;
1270+
Py_XDECREF(subobj);
12761271
}
1272+
PG_END_TRY();
12771273
}
12781274
}
12791275

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp