@@ -54,9 +54,10 @@ static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
54
54
bool * isnull , bool inarray );
55
55
static Datum PLySequence_ToArray (PLyObToDatum * arg , PyObject * plrv ,
56
56
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 );
60
61
61
62
/* conversion from Python objects to composite Datums */
62
63
static Datum PLyUnicode_ToComposite (PLyObToDatum * arg , PyObject * string , bool inarray );
@@ -1126,23 +1127,16 @@ PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
1126
1127
1127
1128
1128
1129
/*
1129
- * Convert Python sequence to SQL array.
1130
+ * Convert Python sequence (or list of lists) to SQL array.
1130
1131
*/
1131
1132
static Datum
1132
1133
PLySequence_ToArray (PLyObToDatum * arg , PyObject * plrv ,
1133
1134
bool * isnull , bool inarray )
1134
1135
{
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 ;
1141
1138
int dims [MAXDIM ];
1142
1139
int lbs [MAXDIM ];
1143
- int currelem ;
1144
- PyObject * pyptr = plrv ;
1145
- PyObject * next ;
1146
1140
1147
1141
if (plrv == Py_None )
1148
1142
{
@@ -1152,128 +1146,130 @@ PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
1152
1146
* isnull = false;
1153
1147
1154
1148
/*
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.
1156
1153
*/
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" )));
1207
1158
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 );
1212
1162
1213
1163
/*
1214
1164
* 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 .
1216
1166
*/
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 ));
1221
1175
1222
- for (i = 0 ; i < ndim ; i ++ )
1176
+ for (int i = 0 ; i < ndims ; i ++ )
1223
1177
lbs [i ] = 1 ;
1224
1178
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);
1236
1181
}
1237
1182
1238
1183
/*
1239
1184
* 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).
1241
1191
*/
1242
1192
static 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 )
1246
1196
{
1247
1197
int i ;
1198
+ int len = PySequence_Length (obj );
1248
1199
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" );
1255
1203
1256
- if ( dim < ndim - 1 )
1204
+ for ( i = 0 ; i < len ; i ++ )
1257
1205
{
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 );
1261
1208
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
+ }
1265
1267
}
1266
- }
1267
- else
1268
- {
1269
- for (i = 0 ; i < dims [dim ]; i ++ )
1268
+ PG_FINALLY ();
1270
1269
{
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 );
1276
1271
}
1272
+ PG_END_TRY ();
1277
1273
}
1278
1274
}
1279
1275
0 commit comments