@ -54,9 +54,10 @@ static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
bool * isnull , bool inarray ) ;
static Datum PLySequence_ToArray ( PLyObToDatum * arg , PyObject * plrv ,
bool * isnull , bool inarray ) ;
static void PLySequence_ToArray_recurse ( PLyObToDatum * elm , PyObject * list ,
int * dims , int ndim , int dim ,
Datum * elems , bool * nulls , int * currelem ) ;
static void PLySequence_ToArray_recurse ( PyObject * obj ,
ArrayBuildState * * astatep ,
int * ndims , int * dims , int cur_depth ,
PLyObToDatum * elm , Oid elmbasetype ) ;
/* conversion from Python objects to composite Datums */
static Datum PLyUnicode_ToComposite ( PLyObToDatum * arg , PyObject * string , bool inarray ) ;
@ -1126,23 +1127,16 @@ PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
/*
* Convert Python sequence to SQL array .
* Convert Python sequence ( or list of lists ) to SQL array .
*/
static Datum
PLySequence_ToArray ( PLyObToDatum * arg , PyObject * plrv ,
bool * isnull , bool inarray )
{
ArrayType * array ;
int i ;
Datum * elems ;
bool * nulls ;
int len ;
int ndim ;
ArrayBuildState * astate = NULL ;
int ndims = 1 ;
int dims [ MAXDIM ] ;
int lbs [ MAXDIM ] ;
int currelem ;
PyObject * pyptr = plrv ;
PyObject * next ;
if ( plrv = = Py_None )
{
@ -1152,128 +1146,130 @@ PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
* isnull = false ;
/*
* Determine the number of dimensions , and their sizes .
* For historical reasons , we allow any sequence ( not only a list ) at the
* top level when converting a Python object to a SQL array . However , a
* multi - dimensional array is recognized only when the object contains
* true lists .
*/
ndim = 0 ;
Py_INCREF ( plrv ) ;
for ( ; ; )
{
if ( ! PyList_Check ( pyptr ) )
break ;
if ( ndim = = MAXDIM )
ereport ( ERROR ,
( errcode ( ERRCODE_PROGRAM_LIMIT_EXCEEDED ) ,
errmsg ( " number of array dimensions exceeds the maximum allowed (%d) " ,
MAXDIM ) ) ) ;
dims [ ndim ] = PySequence_Length ( pyptr ) ;
if ( dims [ ndim ] < 0 )
PLy_elog ( ERROR , " could not determine sequence length for function return value " ) ;
if ( dims [ ndim ] = = 0 )
{
/* empty sequence */
break ;
}
ndim + + ;
next = PySequence_GetItem ( pyptr , 0 ) ;
Py_XDECREF ( pyptr ) ;
pyptr = next ;
}
Py_XDECREF ( pyptr ) ;
/*
* Check for zero dimensions . This happens if the object is a tuple or a
* string , rather than a list , or is not a sequence at all . We don ' t map
* tuples or strings to arrays in general , but in the first level , be
* lenient , for historical reasons . So if the object is a sequence of any
* kind , treat it as a one - dimensional array .
*/
if ( ndim = = 0 )
{
if ( ! PySequence_Check ( plrv ) )
ereport ( ERROR ,
( errcode ( ERRCODE_DATATYPE_MISMATCH ) ,
errmsg ( " return value of function with array return type is not a Python sequence " ) ) ) ;
ndim = 1 ;
dims [ 0 ] = PySequence_Length ( plrv ) ;
}
if ( ! PySequence_Check ( plrv ) )
ereport ( ERROR ,
( errcode ( ERRCODE_DATATYPE_MISMATCH ) ,
errmsg ( " return value of function with array return type is not a Python sequence " ) ) ) ;
/* Allocate space for work arrays, after detecting array size overflow */
len = ArrayGetNItems ( ndim , dims ) ;
elems = palloc ( sizeof ( Datum ) * len ) ;
nulls = palloc ( sizeof ( bool ) * len ) ;
/* Initialize dimensionality info with first-level dimension */
memset ( dims , 0 , sizeof ( dims ) ) ;
dims [ 0 ] = PySequence_Length ( plrv ) ;
/*
* Traverse the Python lists , in depth - first order , and collect all the
* elements at the bottom level into ' elems ' / ' nulls ' arrays .
* elements at the bottom level into an ArrayBuildState .
*/
currelem = 0 ;
PLySequence_ToArray_recurse ( arg - > u . array . elm , plrv ,
dims , ndim , 0 ,
elems , nulls , & currelem ) ;
PLySequence_ToArray_recurse ( plrv , & astate ,
& ndims , dims , 1 ,
arg - > u . array . elm ,
arg - > u . array . elmbasetype ) ;
/* ensure we get zero-D array for no inputs, as per PG convention */
if ( astate = = NULL )
return PointerGetDatum ( construct_empty_array ( arg - > u . array . elmbasetype ) ) ;
for ( i = 0 ; i < ndim ; i + + )
for ( int i = 0 ; i < ndims ; i + + )
lbs [ i ] = 1 ;
array = construct_md_array ( elems ,
nulls ,
ndim ,
dims ,
lbs ,
arg - > u . array . elmbasetype ,
arg - > u . array . elm - > typlen ,
arg - > u . array . elm - > typbyval ,
arg - > u . array . elm - > typalign ) ;
return PointerGetDatum ( array ) ;
return makeMdArrayResult ( astate , ndims , dims , lbs ,
CurrentMemoryContext , true ) ;
}
/*
* Helper function for PLySequence_ToArray . Traverse a Python list of lists in
* depth - first order , storing the elements in ' elems ' .
* depth - first order , storing the elements in * astatep .
*
* The ArrayBuildState is created only when we first find a scalar element ;
* if we didn ' t do it like that , we ' d need some other convention for knowing
* whether we ' d already found any scalars ( and thus the number of dimensions
* is frozen ) .
*/
static void
PLySequence_ToArray_recurse ( PLyObToDatum * elm , PyObject * list ,
int * dims , int ndim , int dim ,
Datum * elems , bool * nulls , int * currelem )
PLySequence_ToArray_recurse ( PyObject * obj , ArrayBuildState * * astatep ,
int * ndims , int * dims , int cur_depth ,
PLyObToDatum * elm , Oid elmbasetype )
{
int i ;
int len = PySequence_Length ( obj ) ;
if ( PySequence_Length ( list ) ! = dims [ dim ] )
ereport ( ERROR ,
( errcode ( ERRCODE_ARRAY_SUBSCRIPT_ERROR ) ,
errmsg ( " wrong length of inner sequence: has length %d, but %d was expected " ,
( int ) PySequence_Length ( list ) , dims [ dim ] ) ,
( errdetail ( " To construct a multidimensional array, the inner sequences must all have the same length. " ) ) ) ) ;
/* We should not get here with a non-sequence object */
if ( len < 0 )
PLy_elog ( ERROR , " could not determine sequence length for function return value " ) ;
i f ( d im < ndim - 1 )
for ( i = 0 ; i < len ; i + + )
{
for ( i = 0 ; i < dims [ dim ] ; i + + )
{
PyObject * sublist = PySequence_GetItem ( list , i ) ;
/* fetch the array element */
PyObject * subobj = PySequence_GetItem ( obj , i ) ;
PLySequence_ToArray_recurse ( elm , sublist , dims , ndim , dim + 1 ,
elems , nulls , currelem ) ;
Py_XDECREF ( sublist ) ;
/* need PG_TRY to ensure we release the subobj's refcount */
PG_TRY ( ) ;
{
/* multi-dimensional array? */
if ( PyList_Check ( subobj ) )
{
/* set size when at first element in this level, else compare */
if ( i = = 0 & & * ndims = = cur_depth )
{
/* array after some scalars at same level? */
if ( * astatep ! = NULL )
ereport ( ERROR ,
( errcode ( ERRCODE_INVALID_TEXT_REPRESENTATION ) ,
errmsg ( " multidimensional arrays must have array expressions with matching dimensions " ) ) ) ;
/* too many dimensions? */
if ( cur_depth > = MAXDIM )
ereport ( ERROR ,
( errcode ( ERRCODE_PROGRAM_LIMIT_EXCEEDED ) ,
errmsg ( " number of array dimensions exceeds the maximum allowed (%d) " ,
MAXDIM ) ) ) ;
/* OK, add a dimension */
dims [ * ndims ] = PySequence_Length ( subobj ) ;
( * ndims ) + + ;
}
else if ( cur_depth > = * ndims | |
PySequence_Length ( subobj ) ! = dims [ cur_depth ] )
ereport ( ERROR ,
( errcode ( ERRCODE_INVALID_TEXT_REPRESENTATION ) ,
errmsg ( " multidimensional arrays must have array expressions with matching dimensions " ) ) ) ;
/* recurse to fetch elements of this sub-array */
PLySequence_ToArray_recurse ( subobj , astatep ,
ndims , dims , cur_depth + 1 ,
elm , elmbasetype ) ;
}
else
{
Datum dat ;
bool isnull ;
/* scalar after some sub-arrays at same level? */
if ( * ndims ! = cur_depth )
ereport ( ERROR ,
( errcode ( ERRCODE_INVALID_TEXT_REPRESENTATION ) ,
errmsg ( " multidimensional arrays must have array expressions with matching dimensions " ) ) ) ;
/* convert non-list object to Datum */
dat = elm - > func ( elm , subobj , & isnull , true ) ;
/* create ArrayBuildState if we didn't already */
if ( * astatep = = NULL )
* astatep = initArrayResult ( elmbasetype ,
CurrentMemoryContext , true ) ;
/* ... and save the element value in it */
( void ) accumArrayResult ( * astatep , dat , isnull ,
elmbasetype , CurrentMemoryContext ) ;
}
}
}
else
{
for ( i = 0 ; i < dims [ dim ] ; i + + )
PG_FINALLY ( ) ;
{
PyObject * obj = PySequence_GetItem ( list , i ) ;
elems [ * currelem ] = elm - > func ( elm , obj , & nulls [ * currelem ] , true ) ;
Py_XDECREF ( obj ) ;
( * currelem ) + + ;
Py_XDECREF ( subobj ) ;
}
PG_END_TRY ( ) ;
}
}