Author: cito
Date: Sun Jan 31 17:57:22 2016
New Revision: 804

Log:
Make the automatic conversion to arrays configurable

The automatic conversion of arrays to lists can now be
disabled with the set_array() method.

Modified:
   trunk/docs/contents/changelog.rst
   trunk/docs/contents/pg/module.rst
   trunk/pgmodule.c
   trunk/tests/test_classic_dbwrapper.py

Modified: trunk/docs/contents/changelog.rst
==============================================================================
--- trunk/docs/contents/changelog.rst   Sun Jan 31 16:39:40 2016        (r803)
+++ trunk/docs/contents/changelog.rst   Sun Jan 31 17:57:22 2016        (r804)
@@ -51,7 +51,8 @@
     - Conversely, when the query method returns a PostgreSQL array, it is 
passed
       to Python as a list. PostgreSQL records are converted to named tuples as
       well, but only if you use one of the get/insert/update/delete() methods.
-      PyGreSQL uses a new fast built-in parser to achieve this.
+      PyGreSQL uses a new fast built-in parser to achieve this.  The automatic
+      conversion of arrays to lists can be disabled with set_array(False).
     - The pkey() method of the classic interface now returns tuples instead
       of frozenset. The order of the tuples is like in the primary key index.
     - Like the DB-API 2 module, the classic module now also returns bytea

Modified: trunk/docs/contents/pg/module.rst
==============================================================================
--- trunk/docs/contents/pg/module.rst   Sun Jan 31 16:39:40 2016        (r803)
+++ trunk/docs/contents/pg/module.rst   Sun Jan 31 17:57:22 2016        (r804)
@@ -428,6 +428,39 @@
 
 .. versionadded:: 4.2
 
+get/set_array -- whether arrays are returned as list objects
+-------------------------------------------------------------
+
+.. function:: get_array()
+
+    Check whether arrays are returned as list objects
+
+    :returns: whether or not list objects will be returned
+    :rtype: bool
+
+This function checks whether PyGreSQL returns PostgreSQL arrays converted
+to Python list objects, or simply as text in the internal special output
+syntax of PostgreSQL.  By default, conversion to list objects is activated,
+but you can disable this with the :func:`set_array` function.
+
+.. versionadded:: 5.0
+
+.. function:: set_array(on)
+
+    Set whether arrays are returned as list objects
+
+    :param on: whether or not list objects shall be returned
+
+This function can be used to specify whether PyGreSQL shall return PostgreSQL
+arrays converted to Python list objects, or simply as text in the internal
+special output syntax of PostgreSQL.  By default, conversion to list objects
+is activated, but you can disable this by calling ``set_array(False)``.
+
+.. versionadded:: 5.0
+
+.. versionchanged:: 5.0
+   Arrays had been returned as text strings only in earlier versions.
+
 get/set_bytea_escaped -- whether bytea data is returned escaped
 ---------------------------------------------------------------
 

Modified: trunk/pgmodule.c
==============================================================================
--- trunk/pgmodule.c    Sun Jan 31 16:39:40 2016        (r803)
+++ trunk/pgmodule.c    Sun Jan 31 17:57:22 2016        (r804)
@@ -96,7 +96,8 @@
                                *namedresult = NULL, /* function for getting 
named results */
                                *jsondecode = NULL; /* function for decoding 
json strings */
 static char decimal_point = '.'; /* decimal point used in money values */
-static int use_bool = 0; /* whether or not bool objects shall be returned */
+static int bool_as_text = 1; /* whether bool shall be returned as text */
+static int array_as_text = 0; /* whether arrays shall be returned as text */
 static int bytea_escaped = 0; /* whether bytea shall be returned escaped */
 
 static int pg_encoding_utf8 = 0;
@@ -314,38 +315,40 @@
                case CIDARRAYOID:
                case OIDARRAYOID:
                case XIDARRAYOID:
-                       t = PYGRES_INT | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_INT | 
PYGRES_ARRAY);
                        break;
 
                case INT8ARRAYOID:
-                       t = PYGRES_LONG | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_LONG | 
PYGRES_ARRAY);
                        break;
 
                case FLOAT4ARRAYOID:
                case FLOAT8ARRAYOID:
-                       t = PYGRES_FLOAT | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_FLOAT | 
PYGRES_ARRAY);
                        break;
 
                case NUMERICARRAYOID:
-                       t = PYGRES_DECIMAL | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_DECIMAL | 
PYGRES_ARRAY);
                        break;
 
                case CASHARRAYOID:
-                       t = (decimal_point ?
-                               PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : ((decimal_point ?
+                               PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY);
                        break;
 
                case BOOLARRAYOID:
-                       t = PYGRES_BOOL | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_BOOL | 
PYGRES_ARRAY);
                        break;
 
                case BYTEAARRAYOID:
-                       t = (bytea_escaped ? PYGRES_TEXT : PYGRES_BYTEA) | 
PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : ((bytea_escaped ?
+                           PYGRES_TEXT : PYGRES_BYTEA) | PYGRES_ARRAY);
                        break;
 
                case JSONARRAYOID:
                case JSONBARRAYOID:
-                       t = (jsondecode ? PYGRES_JSON : PYGRES_TEXT) | 
PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : ((jsondecode ?
+                           PYGRES_JSON : PYGRES_TEXT) | PYGRES_ARRAY);
                        break;
 
                case BPCHARARRAYOID:
@@ -360,7 +363,7 @@
                case TIMESTAMPARRAYOID:
                case TIMESTAMPTZARRAYOID:
                case REGTYPEARRAYOID:
-                       t = PYGRES_TEXT | PYGRES_ARRAY;
+                       t = array_as_text ? PYGRES_TEXT : (PYGRES_TEXT | 
PYGRES_ARRAY);
                        break;
 
                default:
@@ -537,15 +540,15 @@
                        break;
 
                case PYGRES_BOOL:
-                       /* convert to bool only if use_bool is set */
-                       if (use_bool)
+                       /* convert to bool only if bool_as_text is not set */
+                       if (bool_as_text)
                        {
-                               obj = *s == 't' ? Py_True : Py_False;
-                               Py_INCREF(obj);
+                               obj = PyStr_FromString(*s == 't' ? "t" : "f");
                        }
                        else
                        {
-                               obj = PyStr_FromString(*s == 't' ? "t" : "f");
+                               obj = *s == 't' ? Py_True : Py_False;
+                               Py_INCREF(obj);
                        }
                        break;
 
@@ -611,15 +614,15 @@
                        break;
 
                case PYGRES_BOOL:
-                       /* convert to bool only if use_bool is set */
-                       if (use_bool)
+                       /* convert to bool only if bool_as_text is not set */
+                       if (bool_as_text)
                        {
-                               obj = *s == 't' ? Py_True : Py_False;
-                               Py_INCREF(obj);
+                               obj = PyStr_FromString(*s == 't' ? "t" : "f");
                        }
                        else
                        {
-                               obj = PyStr_FromString(*s == 't' ? "t" : "f");
+                               obj = *s == 't' ? Py_True : Py_False;
+                               Py_INCREF(obj);
                        }
                        break;
 
@@ -5101,7 +5104,7 @@
 
        if (PyArg_ParseTuple(args, ""))
        {
-               ret = use_bool ? Py_True : Py_False;
+               ret = bool_as_text ? Py_False : Py_True;
                Py_INCREF(ret);
        }
        else
@@ -5124,7 +5127,7 @@
        /* gets arguments */
        if (PyArg_ParseTuple(args, "i", &i))
        {
-               use_bool = i ? 1 : 0;
+               bool_as_text = i ? 0 : 1;
                Py_INCREF(Py_None); ret = Py_None;
        }
        else
@@ -5134,6 +5137,50 @@
        return ret;
 }
 
+/* get conversion of arrays to lists */
+static char pgGetArray__doc__[] =
+"get_array() -- check whether arrays are converted as lists";
+
+static PyObject *
+pgGetArray(PyObject *self, PyObject * args)
+{
+       PyObject *ret = NULL;
+
+       if (PyArg_ParseTuple(args, ""))
+       {
+               ret = array_as_text ? Py_False : Py_True;
+               Py_INCREF(ret);
+       }
+       else
+               PyErr_SetString(PyExc_TypeError,
+                       "Function get_array() takes no arguments");
+
+       return ret;
+}
+
+/* set conversion of arrays to lists */
+static char pgSetArray__doc__[] =
+"set_array(on) -- set whether arrays should be converted to lists";
+
+static PyObject *
+pgSetArray(PyObject *self, PyObject * args)
+{
+       PyObject *ret = NULL;
+       int                     i;
+
+       /* gets arguments */
+       if (PyArg_ParseTuple(args, "i", &i))
+       {
+               array_as_text = i ? 0 : 1;
+               Py_INCREF(Py_None); ret = Py_None;
+       }
+       else
+               PyErr_SetString(PyExc_TypeError,
+                       "Function set_array() expects a boolean value as 
argument");
+
+       return ret;
+}
+
 /* check whether bytea values are unescaped */
 static char pgGetByteaEscaped__doc__[] =
 "get_bytea_escaped() -- check whether bytea will be returned escaped";
@@ -5722,6 +5769,8 @@
                        pgSetDecimal__doc__},
        {"get_bool", (PyCFunction) pgGetBool, METH_VARARGS, pgGetBool__doc__},
        {"set_bool", (PyCFunction) pgSetBool, METH_VARARGS, pgSetBool__doc__},
+       {"get_array", (PyCFunction) pgGetArray, METH_VARARGS, 
pgGetArray__doc__},
+       {"set_array", (PyCFunction) pgSetArray, METH_VARARGS, 
pgSetArray__doc__},
        {"get_bytea_escaped", (PyCFunction) pgGetByteaEscaped, METH_VARARGS,
                pgGetByteaEscaped__doc__},
        {"set_bytea_escaped", (PyCFunction) pgSetByteaEscaped, METH_VARARGS,

Modified: trunk/tests/test_classic_dbwrapper.py
==============================================================================
--- trunk/tests/test_classic_dbwrapper.py       Sun Jan 31 16:39:40 2016        
(r803)
+++ trunk/tests/test_classic_dbwrapper.py       Sun Jan 31 17:57:22 2016        
(r804)
@@ -3077,6 +3077,7 @@
         self.assertIsInstance(r['stock'], dict)
 
     def testArray(self):
+        returns_arrays = pg.get_array()
         self.createTable('arraytest',
             'id smallint, i2 smallint[], i4 integer[], i8 bigint[],'
             ' d numeric[], f4 real[], f8 double precision[], m money[],'
@@ -3118,33 +3119,56 @@
             t=['abc', 'Hello, World!', '"Hello, World!"', '', None])
         r = data.copy()
         self.db.insert('arraytest', r)
-        self.assertEqual(r, data)
+        if returns_arrays:
+            self.assertEqual(r, data)
+        else:
+            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
         self.db.insert('arraytest', r)
         r = self.db.get('arraytest', 42, 'id')
-        self.assertEqual(r, data)
+        if returns_arrays:
+            self.assertEqual(r, data)
+        else:
+            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
         r = self.db.query('select * from arraytest limit 1').dictresult()[0]
-        self.assertEqual(r, data)
+        if returns_arrays:
+            self.assertEqual(r, data)
+        else:
+            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
 
     def testArrayLiteral(self):
         insert = self.db.insert
+        returns_arrays = pg.get_array()
         self.createTable('arraytest', 'i int[], t text[]', oids=True)
         r = dict(i=[1, 2, 3], t=['a', 'b', 'c'])
         insert('arraytest', r)
-        self.assertEqual(r['i'], [1, 2, 3])
-        self.assertEqual(r['t'], ['a', 'b', 'c'])
+        if returns_arrays:
+            self.assertEqual(r['i'], [1, 2, 3])
+            self.assertEqual(r['t'], ['a', 'b', 'c'])
+        else:
+            self.assertEqual(r['i'], '{1,2,3}')
+            self.assertEqual(r['t'], '{a,b,c}')
         r = dict(i='{1,2,3}', t='{a,b,c}')
         self.db.insert('arraytest', r)
-        self.assertEqual(r['i'], [1, 2, 3])
-        self.assertEqual(r['t'], ['a', 'b', 'c'])
+        if returns_arrays:
+            self.assertEqual(r['i'], [1, 2, 3])
+            self.assertEqual(r['t'], ['a', 'b', 'c'])
+        else:
+            self.assertEqual(r['i'], '{1,2,3}')
+            self.assertEqual(r['t'], '{a,b,c}')
         L = pg.Literal
         r = dict(i=L("ARRAY[1, 2, 3]"), t=L("ARRAY['a', 'b', 'c']"))
         self.db.insert('arraytest', r)
-        self.assertEqual(r['i'], [1, 2, 3])
-        self.assertEqual(r['t'], ['a', 'b', 'c'])
+        if returns_arrays:
+            self.assertEqual(r['i'], [1, 2, 3])
+            self.assertEqual(r['t'], ['a', 'b', 'c'])
+        else:
+            self.assertEqual(r['i'], '{1,2,3}')
+            self.assertEqual(r['t'], '{a,b,c}')
         r = dict(i="1, 2, 3", t="'a', 'b', 'c'")
         self.assertRaises(pg.ProgrammingError, self.db.insert, 'arraytest', r)
 
     def testArrayOfIds(self):
+        array_on = pg.get_array()
         self.createTable('arraytest', 'c cid[], o oid[], x xid[]', oids=True)
         r = self.db.get_attnames('arraytest')
         if self.regtypes:
@@ -3158,13 +3182,20 @@
         self.db.insert('arraytest', r)
         qoid = 'oid(arraytest)'
         oid = r.pop(qoid)
-        self.assertEqual(r, data)
+        if array_on:
+            self.assertEqual(r, data)
+        else:
+            self.assertEqual(r['o'], '{21,22,23}')
         r = {qoid: oid}
         self.db.get('arraytest', r)
         self.assertEqual(oid, r.pop(qoid))
-        self.assertEqual(r, data)
+        if array_on:
+            self.assertEqual(r, data)
+        else:
+            self.assertEqual(r['o'], '{21,22,23}')
 
     def testArrayOfText(self):
+        array_on = pg.get_array()
         self.createTable('arraytest', 'data text[]', oids=True)
         r = self.db.get_attnames('arraytest')
         self.assertEqual(r['data'], 'text[]')
@@ -3173,17 +3204,22 @@
                 "It's all \\ kinds of\r nasty stuff!\n"]
         r = dict(data=data)
         self.db.insert('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'])
         self.assertEqual(r['data'], data)
         self.assertIsInstance(r['data'][1], str)
         self.assertIsNone(r['data'][2])
         r['data'] = None
         self.db.get('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'])
         self.assertEqual(r['data'], data)
         self.assertIsInstance(r['data'][1], str)
         self.assertIsNone(r['data'][2])
 
     def testArrayOfBytea(self):
-        unescape = pg.unescape_bytea if pg.get_bytea_escaped() else None
+        array_on = pg.get_array()
+        bytea_escaped = pg.get_bytea_escaped()
         self.createTable('arraytest', 'data bytea[]', oids=True)
         r = self.db.get_attnames('arraytest')
         self.assertEqual(r['data'], 'bytea[]')
@@ -3191,20 +3227,24 @@
                 b"It's all \\ kinds \x00 of\r nasty \xff stuff!\n"]
         r = dict(data=data)
         self.db.insert('arraytest', r)
-        if unescape:
+        if array_on:
+            self.assertIsInstance(r['data'], list)
+        if array_on and not bytea_escaped:
+            self.assertEqual(r['data'], data)
+            self.assertIsInstance(r['data'][1], bytes)
+            self.assertIsNone(r['data'][2])
+        else:
             self.assertNotEqual(r['data'], data)
-            r['data'] = [unescape(v) if v else v for v in r['data']]
-        self.assertEqual(r['data'], data)
-        self.assertIsInstance(r['data'][1], bytes)
-        self.assertIsNone(r['data'][2])
         r['data'] = None
         self.db.get('arraytest', r)
-        if unescape:
+        if array_on:
+            self.assertIsInstance(r['data'], list)
+        if array_on and not bytea_escaped:
+            self.assertEqual(r['data'], data)
+            self.assertIsInstance(r['data'][1], bytes)
+            self.assertIsNone(r['data'][2])
+        else:
             self.assertNotEqual(r['data'], data)
-            r['data'] = [unescape(v) if v else v for v in r['data']]
-        self.assertEqual(r['data'], data)
-        self.assertIsInstance(r['data'][1], bytes)
-        self.assertIsNone(r['data'][2])
 
     def testArrayOfJson(self):
         try:
@@ -3216,19 +3256,26 @@
         r = self.db.get_attnames('arraytest')
         self.assertEqual(r['data'], 'json[]')
         data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
+        array_on = pg.get_array()
         jsondecode = pg.get_jsondecode()
         r = dict(data=data)
         self.db.insert('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
         r['data'] = None
         self.db.get('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
         r = dict(data=[json.dumps(d) for d in data])
         self.db.insert('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
@@ -3238,10 +3285,13 @@
         r = dict(data=['', None])
         self.db.insert('arraytest', r)
         r = r['data']
-        self.assertIsInstance(r, list)
-        self.assertEqual(len(r), 2)
-        self.assertIsNone(r[0])
-        self.assertIsNone(r[1])
+        if array_on:
+            self.assertIsInstance(r, list)
+            self.assertEqual(len(r), 2)
+            self.assertIsNone(r[0])
+            self.assertIsNone(r[1])
+        else:
+            self.assertEqual(r, '{NULL,NULL}')
 
     def testArrayOfJsonb(self):
         try:
@@ -3253,19 +3303,26 @@
         r = self.db.get_attnames('arraytest')
         self.assertEqual(r['data'], 'jsonb[]' if self.regtypes else 'json[]')
         data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
+        array_on = pg.get_array()
         jsondecode = pg.get_jsondecode()
         r = dict(data=data)
         self.db.insert('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
         r['data'] = None
         self.db.get('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
         r = dict(data=[json.dumps(d) for d in data])
         self.db.insert('arraytest', r)
+        if not array_on:
+            r['data'] = pg.cast_array(r['data'], jsondecode)
         if jsondecode is None:
             r['data'] = [json.loads(d) for d in r['data']]
         self.assertEqual(r['data'], data)
@@ -3275,22 +3332,32 @@
         r = dict(data=['', None])
         self.db.insert('arraytest', r)
         r = r['data']
-        self.assertIsInstance(r, list)
-        self.assertEqual(len(r), 2)
-        self.assertIsNone(r[0])
-        self.assertIsNone(r[1])
+        if array_on:
+            self.assertIsInstance(r, list)
+            self.assertEqual(len(r), 2)
+            self.assertIsNone(r[0])
+            self.assertIsNone(r[1])
+        else:
+            self.assertEqual(r, '{NULL,NULL}')
 
     def testDeepArray(self):
+        array_on = pg.get_array()
         self.createTable('arraytest', 'data text[][][]', oids=True)
         r = self.db.get_attnames('arraytest')
         self.assertEqual(r['data'], 'text[]')
         data = [[['Hello, World!', '{a,b,c}', 'back\\slash']]]
         r = dict(data=data)
         self.db.insert('arraytest', r)
-        self.assertEqual(r['data'], data)
+        if array_on:
+            self.assertEqual(r['data'], data)
+        else:
+            self.assertTrue(r['data'].startswith('{{{"Hello,'))
         r['data'] = None
         self.db.get('arraytest', r)
-        self.assertEqual(r['data'], data)
+        if array_on:
+            self.assertEqual(r['data'], data)
+        else:
+            self.assertTrue(r['data'].startswith('{{{"Hello,'))
 
     def testInsertUpdateGetRecord(self):
         query = self.db.query
@@ -3635,6 +3702,8 @@
         cls.set_option('decimal', float)
         not_bool = not pg.get_bool()
         cls.set_option('bool', not_bool)
+        not_array = not pg.get_array()
+        cls.set_option('array', not_array)
         not_bytea_escaped = not pg.get_bytea_escaped()
         cls.set_option('bytea_escaped', not_bytea_escaped)
         cls.set_option('namedresult', None)
@@ -3648,6 +3717,7 @@
         cls.reset_option('jsondecode')
         cls.reset_option('namedresult')
         cls.reset_option('bool')
+        cls.reset_option('array')
         cls.reset_option('bytea_escaped')
         cls.reset_option('decimal')
 
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo.cgi/pygresql

Reply via email to