@@ -54,9 +54,10 @@ static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
5454 bool * isnull , bool inarray );
5555static Datum PLySequence_ToArray (PLyObToDatum * arg , PyObject * plrv ,
5656 bool * isnull , bool inarray );
57- static void PLySequence_ToArray_recurse (PLyObToDatum * elm , PyObject * list ,
58- int * dims , int ndim , int dim ,
59- Datum * elems , bool * nulls , int * currelem );
57+ static void PLySequence_ToArray_recurse (PyObject * obj ,
58+ ArrayBuildState * * astatep ,
59+ int * ndims , int * dims , int cur_depth ,
60+ PLyObToDatum * elm , Oid elmbasetype );
6061
6162/* conversion from Python objects to composite Datums */
6263static Datum PLyUnicode_ToComposite (PLyObToDatum * arg , PyObject * string , bool inarray );
@@ -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 */
11311132static Datum
11321133PLySequence_ToArray (PLyObToDatum * arg , PyObject * plrv ,
11331134 bool * isnull , bool inarray )
11341135{
1135- ArrayType * array ;
1136- int i ;
1137- Datum * elems ;
1138- bool * nulls ;
1139- int len ;
1140- int ndim ;
1136+ ArrayBuildState * astate = NULL ;
1137+ int ndims = 1 ;
11411138 int dims [MAXDIM ];
11421139 int lbs [MAXDIM ];
1143- int currelem ;
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 into an 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+ return PointerGetDatum (construct_empty_array (arg -> u .array .elmbasetype ));
12211175
1222- for (i = 0 ; i < ndim ; i ++ )
1176+ for (int i = 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- return PointerGetDatum (array );
1179+ return makeMdArrayResult (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 */
12421192static void
1243- PLySequence_ToArray_recurse (PLyObToDatum * elm , PyObject * list ,
1244- int * dims , int ndim , int dim ,
1245- Datum * elems , bool * nulls , int * currelem )
1193+ PLySequence_ToArray_recurse (PyObject * obj , ArrayBuildState * * astatep ,
1194+ int * ndims , int * dims , int cur_depth ,
1195+ PLyObToDatum * elm , Oid elmbasetype )
12461196{
12471197 int i ;
1198+ int len = 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+ else if (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+ Datum dat ;
1247+ bool isnull ;
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