From 5ee60571a59763425c33be2491c33fbc96878088 Mon Sep 17 00:00:00 2001 From: Benjamin Poulain Date: Sun, 20 Feb 2011 02:34:54 +0100 Subject: [PATCH] Add a type converter to handle untyped empty arrays. Empty array can be returned untyped by postgres. To handle this case, a special handler is added for the type UNKNOWNOID. If the value return by the database is strictly equal to "{}", the value is converted. Otherwise, the conversion fallback on the default handler. --- psycopg/typecast.c | 26 ++++++++++++++++++++++---- psycopg/typecast_builtins.c | 2 ++ tests/types_basic.py | 10 ++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 484ce45e..ba3871e2 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -177,6 +177,28 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len, #endif #include "psycopg/typecast_array.c" + +static long int typecast_default_DEFAULT[] = {0}; +static typecastObject_initlist typecast_default = { + "DEFAULT", typecast_default_DEFAULT, typecast_STRING_cast}; + +static PyObject * +typecast_UNKNOWN_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + Dprintf("typecast_UNKNOWN_cast: str = '%s'," + " len = " FORMAT_CODE_PY_SSIZE_T, str, len); + + // PostgreSQL returns {} for empty array without explicit type. We convert + // that to list in order to handle empty lists. + if (len == 2 && str[0] == '{' && str[1] == '}') { + return PyList_New(0); + } + + Dprintf("typecast_UNKNOWN_cast: fallback to default cast"); + + return typecast_default.cast(str, len, curs); +} + #include "psycopg/typecast_builtins.c" #define typecast_PYDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast @@ -225,10 +247,6 @@ PyObject *psyco_default_cast; PyObject *psyco_binary_types; PyObject *psyco_default_binary_cast; -static long int typecast_default_DEFAULT[] = {0}; -static typecastObject_initlist typecast_default = { - "DEFAULT", typecast_default_DEFAULT, typecast_STRING_cast}; - /* typecast_init - initialize the dictionary and create default types */ diff --git a/psycopg/typecast_builtins.c b/psycopg/typecast_builtins.c index e8e5a1a2..a104b7c4 100644 --- a/psycopg/typecast_builtins.c +++ b/psycopg/typecast_builtins.c @@ -25,6 +25,7 @@ static long int typecast_DATEARRAY_types[] = {1182, 0}; static long int typecast_INTERVALARRAY_types[] = {1187, 0}; static long int typecast_BINARYARRAY_types[] = {1001, 0}; static long int typecast_ROWIDARRAY_types[] = {1028, 1013, 0}; +static long int typecast_UNKNOWN_types[] = {705, 0}; static typecastObject_initlist typecast_builtins[] = { @@ -55,6 +56,7 @@ static typecastObject_initlist typecast_builtins[] = { {"INTERVALARRAY", typecast_INTERVALARRAY_types, typecast_INTERVALARRAY_cast, "INTERVAL"}, {"BINARYARRAY", typecast_BINARYARRAY_types, typecast_BINARYARRAY_cast, "BINARY"}, {"ROWIDARRAY", typecast_ROWIDARRAY_types, typecast_ROWIDARRAY_cast, "ROWID"}, + {"UNKNOWN", typecast_UNKNOWN_types, typecast_UNKNOWN_cast, NULL}, {NULL, NULL, NULL, NULL} }; diff --git a/tests/types_basic.py b/tests/types_basic.py index 5321a0c8..56be342f 100755 --- a/tests/types_basic.py +++ b/tests/types_basic.py @@ -176,6 +176,16 @@ class TypesBasicTests(unittest.TestCase): curs.execute("select col from array_test where id = 2") self.assertEqual(curs.fetchone()[0], []) + def testEmptyArray(self): + s = self.execute("SELECT '{}' AS foo") + self.failUnlessEqual(s, []) + s = self.execute("SELECT '{}'::text[] AS foo") + self.failUnlessEqual(s, []) + s = self.execute("SELECT %s AS foo", ([],)) + self.failUnlessEqual(s, []) + s = self.execute("SELECT 1 != ALL(%s)", ([],)) + self.failUnlessEqual(s, True) + @testutils.skip_from_python(3) def testTypeRoundtripBuffer(self): o1 = buffer("".join(map(chr, range(256))))