mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-23 01:16:34 +03:00
Merge remote-tracking branch 'nested-array-nulls'
This commit is contained in:
commit
098c00d73e
1
NEWS
1
NEWS
|
@ -19,6 +19,7 @@ What's new in psycopg 2.7.5
|
||||||
|
|
||||||
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
|
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
|
||||||
:ticket':`#211`).
|
:ticket':`#211`).
|
||||||
|
- Fixed adaptation of arrays of arrays of nulls (:ticket:`#325`).
|
||||||
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
|
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
|
||||||
(:ticket:`#677`).
|
(:ticket:`#677`).
|
||||||
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
|
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
|
||||||
|
|
|
@ -38,13 +38,14 @@ list_quote(listObject *self)
|
||||||
{
|
{
|
||||||
/* adapt the list by calling adapt() recursively and then wrapping
|
/* adapt the list by calling adapt() recursively and then wrapping
|
||||||
everything into "ARRAY[]" */
|
everything into "ARRAY[]" */
|
||||||
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
|
PyObject *res = NULL;
|
||||||
|
PyObject **qs = NULL;
|
||||||
|
Py_ssize_t bufsize = 0;
|
||||||
|
char *buf = NULL, *ptr;
|
||||||
|
|
||||||
/* list consisting of only NULL don't work with the ARRAY[] construct
|
/* list consisting of only NULL don't work with the ARRAY[] construct
|
||||||
* so we use the {NULL,...} syntax. Note however that list of lists where
|
* so we use the {NULL,...} syntax. The same syntax is also necessary
|
||||||
* some element is a list of only null still fails: for that we should use
|
* to convert array of arrays containing only nulls. */
|
||||||
* the '{...}' syntax uniformly but we cannot do it in the current
|
|
||||||
* infrastructure. TODO in psycopg3 */
|
|
||||||
int all_nulls = 1;
|
int all_nulls = 1;
|
||||||
|
|
||||||
Py_ssize_t i, len;
|
Py_ssize_t i, len;
|
||||||
|
@ -53,47 +54,95 @@ list_quote(listObject *self)
|
||||||
|
|
||||||
/* empty arrays are converted to NULLs (still searching for a way to
|
/* empty arrays are converted to NULLs (still searching for a way to
|
||||||
insert an empty array in postgresql */
|
insert an empty array in postgresql */
|
||||||
if (len == 0) return Bytes_FromString("'{}'");
|
if (len == 0) {
|
||||||
|
res = Bytes_FromString("'{}'");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = PyTuple_New(len);
|
if (!(qs = PyMem_New(PyObject *, len))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
memset(qs, 0, len * sizeof(PyObject *));
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
PyObject *quoted;
|
|
||||||
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
|
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
|
||||||
if (wrapped == Py_None) {
|
if (wrapped == Py_None) {
|
||||||
Py_INCREF(psyco_null);
|
Py_INCREF(psyco_null);
|
||||||
quoted = psyco_null;
|
qs[i] = psyco_null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
quoted = microprotocol_getquoted(wrapped,
|
if (!(qs[i] = microprotocol_getquoted(
|
||||||
(connectionObject*)self->connection);
|
wrapped, (connectionObject*)self->connection))) {
|
||||||
if (quoted == NULL) goto error;
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists of arrays containing only nulls are also not supported
|
||||||
|
* by the ARRAY construct so we should do some special casing */
|
||||||
|
if (!PyList_Check(wrapped) || Bytes_AS_STRING(qs[i])[0] == 'A') {
|
||||||
all_nulls = 0;
|
all_nulls = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/* here we don't loose a refcnt: SET_ITEM does not change the
|
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
|
||||||
reference count and we are just transferring ownership of the tmp
|
|
||||||
object to the tuple */
|
|
||||||
PyTuple_SET_ITEM(tmp, i, quoted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now that we have a tuple of adapted objects we just need to join them
|
/* Create an array literal, usually ARRAY[...] but if the contents are
|
||||||
and put "ARRAY[] around the result */
|
* all NULL or array of NULL we must use the '{...}' syntax
|
||||||
str = Bytes_FromString(", ");
|
*/
|
||||||
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
|
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
|
||||||
if (joined == NULL) goto error;
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
/* PG doesn't like ARRAY[NULL..] */
|
|
||||||
if (!all_nulls) {
|
if (!all_nulls) {
|
||||||
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
|
strcpy(ptr, "ARRAY[");
|
||||||
} else {
|
ptr += 6;
|
||||||
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined));
|
for (i = 0; i < len; i++) {
|
||||||
|
Py_ssize_t sl;
|
||||||
|
sl = Bytes_GET_SIZE(qs[i]);
|
||||||
|
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
|
||||||
|
ptr += sl;
|
||||||
|
*ptr++ = ',';
|
||||||
|
}
|
||||||
|
*(ptr - 1) = ']';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*ptr++ = '\'';
|
||||||
|
*ptr++ = '{';
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
/* in case all the adapted things are nulls (or array of nulls),
|
||||||
|
* the quoted string is either NULL or an array of the form
|
||||||
|
* '{NULL,...}', in which case we have to strip the extra quotes */
|
||||||
|
char *s;
|
||||||
|
Py_ssize_t sl;
|
||||||
|
s = Bytes_AS_STRING(qs[i]);
|
||||||
|
sl = Bytes_GET_SIZE(qs[i]);
|
||||||
|
if (s[0] != '\'') {
|
||||||
|
memcpy(ptr, s, sl);
|
||||||
|
ptr += sl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(ptr, s + 1, sl - 2);
|
||||||
|
ptr += sl - 2;
|
||||||
|
}
|
||||||
|
*ptr++ = ',';
|
||||||
|
}
|
||||||
|
*(ptr - 1) = '}';
|
||||||
|
*ptr++ = '\'';
|
||||||
}
|
}
|
||||||
|
|
||||||
error:
|
res = Bytes_FromStringAndSize(buf, ptr - buf);
|
||||||
Py_XDECREF(tmp);
|
|
||||||
Py_XDECREF(str);
|
exit:
|
||||||
Py_XDECREF(joined);
|
if (qs) {
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
PyObject *q = qs[i];
|
||||||
|
Py_XDECREF(q);
|
||||||
|
}
|
||||||
|
PyMem_Free(qs);
|
||||||
|
}
|
||||||
|
PyMem_Free(buf);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,16 +224,31 @@ class TypesBasicTests(ConnectingTestCase):
|
||||||
curs.execute("insert into na (boola) values (%s)", ([True, None],))
|
curs.execute("insert into na (boola) values (%s)", ([True, None],))
|
||||||
curs.execute("insert into na (boola) values (%s)", ([None, None],))
|
curs.execute("insert into na (boola) values (%s)", ([None, None],))
|
||||||
|
|
||||||
# TODO: array of array of nulls are not supported yet
|
curs.execute("insert into na (textaa) values (%s)", ([[None]],))
|
||||||
# curs.execute("insert into na (textaa) values (%s)", ([[None]],))
|
|
||||||
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
|
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
|
||||||
# curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
|
curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
|
||||||
# curs.execute("insert into na (intaa) values (%s)", ([[None]],))
|
|
||||||
|
curs.execute("insert into na (intaa) values (%s)", ([[None]],))
|
||||||
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
|
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
|
||||||
# curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
|
curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
|
||||||
# curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
|
|
||||||
|
curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
|
||||||
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
|
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
|
||||||
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
|
curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
|
||||||
|
|
||||||
|
@testutils.skip_before_postgres(8, 2)
|
||||||
|
def testNestedArrays(self):
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
for a in [
|
||||||
|
[[1]],
|
||||||
|
[[None]],
|
||||||
|
[[None, None, None]],
|
||||||
|
[[None, None], [1, None]],
|
||||||
|
[[None, None], [None, None]],
|
||||||
|
[[[None, None], [None, None]]],
|
||||||
|
]:
|
||||||
|
curs.execute("select %s::int[]", (a,))
|
||||||
|
self.assertEqual(curs.fetchone()[0], a)
|
||||||
|
|
||||||
@testutils.skip_from_python(3)
|
@testutils.skip_from_python(3)
|
||||||
def testTypeRoundtripBuffer(self):
|
def testTypeRoundtripBuffer(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user