diff --git a/NEWS b/NEWS index 3e8864f0..d2c85d3b 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Current release What's new in psycopg 2.6.1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Lists consisting of only `None` are escaped correctly (:ticket:`#285`). - Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`). - Correctly unlock the connection after error in flush (:ticket:`#294`). - Fixed ``MinTimeLoggingCursor.callproc()`` (:ticket:`#309`). diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index e68b1978..dec17b4c 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -39,6 +39,14 @@ list_quote(listObject *self) /* adapt the list by calling adapt() recursively and then wrapping everything into "ARRAY[]" */ PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL; + + /* 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 + * some element is a list of only null still fails: for that we should use + * the '{...}' syntax uniformly but we cannot do it in the current + * infrastructure. TODO in psycopg3 */ + int all_nulls = 1; + Py_ssize_t i, len; len = PyList_GET_SIZE(self->wrapped); @@ -60,6 +68,7 @@ list_quote(listObject *self) quoted = microprotocol_getquoted(wrapped, (connectionObject*)self->connection); if (quoted == NULL) goto error; + all_nulls = 0; } /* here we don't loose a refcnt: SET_ITEM does not change the @@ -74,7 +83,12 @@ list_quote(listObject *self) joined = PyObject_CallMethod(str, "join", "(O)", tmp); if (joined == NULL) goto error; - res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined)); + /* PG doesn't like ARRAY[NULL..] */ + if (!all_nulls) { + res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined)); + } else { + res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined)); + } error: Py_XDECREF(tmp); diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index 6c4cc970..199dc1b6 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -192,6 +192,40 @@ class TypesBasicTests(ConnectingTestCase): self.assertRaises(psycopg2.DataError, psycopg2.extensions.STRINGARRAY, b(s), curs) + def testArrayOfNulls(self): + curs = self.conn.cursor() + curs.execute(""" + create table na ( + texta text[], + inta int[], + boola boolean[], + + textaa text[][], + intaa int[][], + boolaa boolean[][] + )""") + + curs.execute("insert into na (texta) values (%s)", ([None],)) + curs.execute("insert into na (texta) values (%s)", (['a', None],)) + curs.execute("insert into na (texta) values (%s)", ([None, None],)) + curs.execute("insert into na (inta) values (%s)", ([None],)) + curs.execute("insert into na (inta) values (%s)", ([42, None],)) + curs.execute("insert into na (inta) values (%s)", ([None, None],)) + curs.execute("insert into na (boola) values (%s)", ([None],)) + curs.execute("insert into na (boola) values (%s)", ([True, 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)", ([['a', 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)", ([[42, 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)", ([[True, None]],)) + # curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],)) + @testutils.skip_from_python(3) def testTypeRoundtripBuffer(self): o1 = buffer("".join(map(chr, range(256))))