Changeset 804


Ignore:
Timestamp:
Jan 31, 2016, 5:57:22 PM (4 years ago)
Author:
cito
Message:

Make the automatic conversion to arrays configurable

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

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/docs/contents/changelog.rst

    r802 r804  
    5252      to Python as a list. PostgreSQL records are converted to named tuples as
    5353      well, but only if you use one of the get/insert/update/delete() methods.
    54       PyGreSQL uses a new fast built-in parser to achieve this.
     54      PyGreSQL uses a new fast built-in parser to achieve this.  The automatic
     55      conversion of arrays to lists can be disabled with set_array(False).
    5556    - The pkey() method of the classic interface now returns tuples instead
    5657      of frozenset. The order of the tuples is like in the primary key index.
  • trunk/docs/contents/pg/module.rst

    r803 r804  
    429429.. versionadded:: 4.2
    430430
     431get/set_array -- whether arrays are returned as list objects
     432-------------------------------------------------------------
     433
     434.. function:: get_array()
     435
     436    Check whether arrays are returned as list objects
     437
     438    :returns: whether or not list objects will be returned
     439    :rtype: bool
     440
     441This function checks whether PyGreSQL returns PostgreSQL arrays converted
     442to Python list objects, or simply as text in the internal special output
     443syntax of PostgreSQL.  By default, conversion to list objects is activated,
     444but you can disable this with the :func:`set_array` function.
     445
     446.. versionadded:: 5.0
     447
     448.. function:: set_array(on)
     449
     450    Set whether arrays are returned as list objects
     451
     452    :param on: whether or not list objects shall be returned
     453
     454This function can be used to specify whether PyGreSQL shall return PostgreSQL
     455arrays converted to Python list objects, or simply as text in the internal
     456special output syntax of PostgreSQL.  By default, conversion to list objects
     457is activated, but you can disable this by calling ``set_array(False)``.
     458
     459.. versionadded:: 5.0
     460
     461.. versionchanged:: 5.0
     462   Arrays had been returned as text strings only in earlier versions.
     463
    431464get/set_bytea_escaped -- whether bytea data is returned escaped
    432465---------------------------------------------------------------
  • trunk/pgmodule.c

    r802 r804  
    9797                                *jsondecode = NULL; /* function for decoding json strings */
    9898static char decimal_point = '.'; /* decimal point used in money values */
    99 static int use_bool = 0; /* whether or not bool objects shall be returned */
     99static int bool_as_text = 1; /* whether bool shall be returned as text */
     100static int array_as_text = 0; /* whether arrays shall be returned as text */
    100101static int bytea_escaped = 0; /* whether bytea shall be returned escaped */
    101102
     
    315316                case OIDARRAYOID:
    316317                case XIDARRAYOID:
    317                         t = PYGRES_INT | PYGRES_ARRAY;
     318                        t = array_as_text ? PYGRES_TEXT : (PYGRES_INT | PYGRES_ARRAY);
    318319                        break;
    319320
    320321                case INT8ARRAYOID:
    321                         t = PYGRES_LONG | PYGRES_ARRAY;
     322                        t = array_as_text ? PYGRES_TEXT : (PYGRES_LONG | PYGRES_ARRAY);
    322323                        break;
    323324
    324325                case FLOAT4ARRAYOID:
    325326                case FLOAT8ARRAYOID:
    326                         t = PYGRES_FLOAT | PYGRES_ARRAY;
     327                        t = array_as_text ? PYGRES_TEXT : (PYGRES_FLOAT | PYGRES_ARRAY);
    327328                        break;
    328329
    329330                case NUMERICARRAYOID:
    330                         t = PYGRES_DECIMAL | PYGRES_ARRAY;
     331                        t = array_as_text ? PYGRES_TEXT : (PYGRES_DECIMAL | PYGRES_ARRAY);
    331332                        break;
    332333
    333334                case CASHARRAYOID:
    334                         t = (decimal_point ?
    335                                 PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY;
     335                        t = array_as_text ? PYGRES_TEXT : ((decimal_point ?
     336                                PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY);
    336337                        break;
    337338
    338339                case BOOLARRAYOID:
    339                         t = PYGRES_BOOL | PYGRES_ARRAY;
     340                        t = array_as_text ? PYGRES_TEXT : (PYGRES_BOOL | PYGRES_ARRAY);
    340341                        break;
    341342
    342343                case BYTEAARRAYOID:
    343                         t = (bytea_escaped ? PYGRES_TEXT : PYGRES_BYTEA) | PYGRES_ARRAY;
     344                        t = array_as_text ? PYGRES_TEXT : ((bytea_escaped ?
     345                            PYGRES_TEXT : PYGRES_BYTEA) | PYGRES_ARRAY);
    344346                        break;
    345347
    346348                case JSONARRAYOID:
    347349                case JSONBARRAYOID:
    348                         t = (jsondecode ? PYGRES_JSON : PYGRES_TEXT) | PYGRES_ARRAY;
     350                        t = array_as_text ? PYGRES_TEXT : ((jsondecode ?
     351                            PYGRES_JSON : PYGRES_TEXT) | PYGRES_ARRAY);
    349352                        break;
    350353
     
    361364                case TIMESTAMPTZARRAYOID:
    362365                case REGTYPEARRAYOID:
    363                         t = PYGRES_TEXT | PYGRES_ARRAY;
     366                        t = array_as_text ? PYGRES_TEXT : (PYGRES_TEXT | PYGRES_ARRAY);
    364367                        break;
    365368
     
    538541
    539542                case PYGRES_BOOL:
    540                         /* convert to bool only if use_bool is set */
    541                         if (use_bool)
     543                        /* convert to bool only if bool_as_text is not set */
     544                        if (bool_as_text)
     545                        {
     546                                obj = PyStr_FromString(*s == 't' ? "t" : "f");
     547                        }
     548                        else
    542549                        {
    543550                                obj = *s == 't' ? Py_True : Py_False;
    544551                                Py_INCREF(obj);
    545                         }
    546                         else
    547                         {
    548                                 obj = PyStr_FromString(*s == 't' ? "t" : "f");
    549552                        }
    550553                        break;
     
    612615
    613616                case PYGRES_BOOL:
    614                         /* convert to bool only if use_bool is set */
    615                         if (use_bool)
     617                        /* convert to bool only if bool_as_text is not set */
     618                        if (bool_as_text)
     619                        {
     620                                obj = PyStr_FromString(*s == 't' ? "t" : "f");
     621                        }
     622                        else
    616623                        {
    617624                                obj = *s == 't' ? Py_True : Py_False;
    618625                                Py_INCREF(obj);
    619                         }
    620                         else
    621                         {
    622                                 obj = PyStr_FromString(*s == 't' ? "t" : "f");
    623626                        }
    624627                        break;
     
    51025105        if (PyArg_ParseTuple(args, ""))
    51035106        {
    5104                 ret = use_bool ? Py_True : Py_False;
     5107                ret = bool_as_text ? Py_False : Py_True;
    51055108                Py_INCREF(ret);
    51065109        }
     
    51255128        if (PyArg_ParseTuple(args, "i", &i))
    51265129        {
    5127                 use_bool = i ? 1 : 0;
     5130                bool_as_text = i ? 0 : 1;
    51285131                Py_INCREF(Py_None); ret = Py_None;
    51295132        }
     
    51315134                PyErr_SetString(PyExc_TypeError,
    51325135                        "Function set_bool() expects a boolean value as argument");
     5136
     5137        return ret;
     5138}
     5139
     5140/* get conversion of arrays to lists */
     5141static char pgGetArray__doc__[] =
     5142"get_array() -- check whether arrays are converted as lists";
     5143
     5144static PyObject *
     5145pgGetArray(PyObject *self, PyObject * args)
     5146{
     5147        PyObject *ret = NULL;
     5148
     5149        if (PyArg_ParseTuple(args, ""))
     5150        {
     5151                ret = array_as_text ? Py_False : Py_True;
     5152                Py_INCREF(ret);
     5153        }
     5154        else
     5155                PyErr_SetString(PyExc_TypeError,
     5156                        "Function get_array() takes no arguments");
     5157
     5158        return ret;
     5159}
     5160
     5161/* set conversion of arrays to lists */
     5162static char pgSetArray__doc__[] =
     5163"set_array(on) -- set whether arrays should be converted to lists";
     5164
     5165static PyObject *
     5166pgSetArray(PyObject *self, PyObject * args)
     5167{
     5168        PyObject *ret = NULL;
     5169        int                     i;
     5170
     5171        /* gets arguments */
     5172        if (PyArg_ParseTuple(args, "i", &i))
     5173        {
     5174                array_as_text = i ? 0 : 1;
     5175                Py_INCREF(Py_None); ret = Py_None;
     5176        }
     5177        else
     5178                PyErr_SetString(PyExc_TypeError,
     5179                        "Function set_array() expects a boolean value as argument");
    51335180
    51345181        return ret;
     
    57235770        {"get_bool", (PyCFunction) pgGetBool, METH_VARARGS, pgGetBool__doc__},
    57245771        {"set_bool", (PyCFunction) pgSetBool, METH_VARARGS, pgSetBool__doc__},
     5772        {"get_array", (PyCFunction) pgGetArray, METH_VARARGS, pgGetArray__doc__},
     5773        {"set_array", (PyCFunction) pgSetArray, METH_VARARGS, pgSetArray__doc__},
    57255774        {"get_bytea_escaped", (PyCFunction) pgGetByteaEscaped, METH_VARARGS,
    57265775                pgGetByteaEscaped__doc__},
  • trunk/tests/test_classic_dbwrapper.py

    r802 r804  
    30783078
    30793079    def testArray(self):
     3080        returns_arrays = pg.get_array()
    30803081        self.createTable('arraytest',
    30813082            'id smallint, i2 smallint[], i4 integer[], i8 bigint[],'
     
    31193120        r = data.copy()
    31203121        self.db.insert('arraytest', r)
    3121         self.assertEqual(r, data)
     3122        if returns_arrays:
     3123            self.assertEqual(r, data)
     3124        else:
     3125            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
    31223126        self.db.insert('arraytest', r)
    31233127        r = self.db.get('arraytest', 42, 'id')
    3124         self.assertEqual(r, data)
     3128        if returns_arrays:
     3129            self.assertEqual(r, data)
     3130        else:
     3131            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
    31253132        r = self.db.query('select * from arraytest limit 1').dictresult()[0]
    3126         self.assertEqual(r, data)
     3133        if returns_arrays:
     3134            self.assertEqual(r, data)
     3135        else:
     3136            self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
    31273137
    31283138    def testArrayLiteral(self):
    31293139        insert = self.db.insert
     3140        returns_arrays = pg.get_array()
    31303141        self.createTable('arraytest', 'i int[], t text[]', oids=True)
    31313142        r = dict(i=[1, 2, 3], t=['a', 'b', 'c'])
    31323143        insert('arraytest', r)
    3133         self.assertEqual(r['i'], [1, 2, 3])
    3134         self.assertEqual(r['t'], ['a', 'b', 'c'])
     3144        if returns_arrays:
     3145            self.assertEqual(r['i'], [1, 2, 3])
     3146            self.assertEqual(r['t'], ['a', 'b', 'c'])
     3147        else:
     3148            self.assertEqual(r['i'], '{1,2,3}')
     3149            self.assertEqual(r['t'], '{a,b,c}')
    31353150        r = dict(i='{1,2,3}', t='{a,b,c}')
    31363151        self.db.insert('arraytest', r)
    3137         self.assertEqual(r['i'], [1, 2, 3])
    3138         self.assertEqual(r['t'], ['a', 'b', 'c'])
     3152        if returns_arrays:
     3153            self.assertEqual(r['i'], [1, 2, 3])
     3154            self.assertEqual(r['t'], ['a', 'b', 'c'])
     3155        else:
     3156            self.assertEqual(r['i'], '{1,2,3}')
     3157            self.assertEqual(r['t'], '{a,b,c}')
    31393158        L = pg.Literal
    31403159        r = dict(i=L("ARRAY[1, 2, 3]"), t=L("ARRAY['a', 'b', 'c']"))
    31413160        self.db.insert('arraytest', r)
    3142         self.assertEqual(r['i'], [1, 2, 3])
    3143         self.assertEqual(r['t'], ['a', 'b', 'c'])
     3161        if returns_arrays:
     3162            self.assertEqual(r['i'], [1, 2, 3])
     3163            self.assertEqual(r['t'], ['a', 'b', 'c'])
     3164        else:
     3165            self.assertEqual(r['i'], '{1,2,3}')
     3166            self.assertEqual(r['t'], '{a,b,c}')
    31443167        r = dict(i="1, 2, 3", t="'a', 'b', 'c'")
    31453168        self.assertRaises(pg.ProgrammingError, self.db.insert, 'arraytest', r)
    31463169
    31473170    def testArrayOfIds(self):
     3171        array_on = pg.get_array()
    31483172        self.createTable('arraytest', 'c cid[], o oid[], x xid[]', oids=True)
    31493173        r = self.db.get_attnames('arraytest')
     
    31593183        qoid = 'oid(arraytest)'
    31603184        oid = r.pop(qoid)
    3161         self.assertEqual(r, data)
     3185        if array_on:
     3186            self.assertEqual(r, data)
     3187        else:
     3188            self.assertEqual(r['o'], '{21,22,23}')
    31623189        r = {qoid: oid}
    31633190        self.db.get('arraytest', r)
    31643191        self.assertEqual(oid, r.pop(qoid))
    3165         self.assertEqual(r, data)
     3192        if array_on:
     3193            self.assertEqual(r, data)
     3194        else:
     3195            self.assertEqual(r['o'], '{21,22,23}')
    31663196
    31673197    def testArrayOfText(self):
     3198        array_on = pg.get_array()
    31683199        self.createTable('arraytest', 'data text[]', oids=True)
    31693200        r = self.db.get_attnames('arraytest')
     
    31743205        r = dict(data=data)
    31753206        self.db.insert('arraytest', r)
     3207        if not array_on:
     3208            r['data'] = pg.cast_array(r['data'])
    31763209        self.assertEqual(r['data'], data)
    31773210        self.assertIsInstance(r['data'][1], str)
     
    31793212        r['data'] = None
    31803213        self.db.get('arraytest', r)
     3214        if not array_on:
     3215            r['data'] = pg.cast_array(r['data'])
    31813216        self.assertEqual(r['data'], data)
    31823217        self.assertIsInstance(r['data'][1], str)
     
    31843219
    31853220    def testArrayOfBytea(self):
    3186         unescape = pg.unescape_bytea if pg.get_bytea_escaped() else None
     3221        array_on = pg.get_array()
     3222        bytea_escaped = pg.get_bytea_escaped()
    31873223        self.createTable('arraytest', 'data bytea[]', oids=True)
    31883224        r = self.db.get_attnames('arraytest')
     
    31923228        r = dict(data=data)
    31933229        self.db.insert('arraytest', r)
    3194         if unescape:
     3230        if array_on:
     3231            self.assertIsInstance(r['data'], list)
     3232        if array_on and not bytea_escaped:
     3233            self.assertEqual(r['data'], data)
     3234            self.assertIsInstance(r['data'][1], bytes)
     3235            self.assertIsNone(r['data'][2])
     3236        else:
    31953237            self.assertNotEqual(r['data'], data)
    3196             r['data'] = [unescape(v) if v else v for v in r['data']]
    3197         self.assertEqual(r['data'], data)
    3198         self.assertIsInstance(r['data'][1], bytes)
    3199         self.assertIsNone(r['data'][2])
    32003238        r['data'] = None
    32013239        self.db.get('arraytest', r)
    3202         if unescape:
     3240        if array_on:
     3241            self.assertIsInstance(r['data'], list)
     3242        if array_on and not bytea_escaped:
     3243            self.assertEqual(r['data'], data)
     3244            self.assertIsInstance(r['data'][1], bytes)
     3245            self.assertIsNone(r['data'][2])
     3246        else:
    32033247            self.assertNotEqual(r['data'], data)
    3204             r['data'] = [unescape(v) if v else v for v in r['data']]
    3205         self.assertEqual(r['data'], data)
    3206         self.assertIsInstance(r['data'][1], bytes)
    3207         self.assertIsNone(r['data'][2])
    32083248
    32093249    def testArrayOfJson(self):
     
    32173257        self.assertEqual(r['data'], 'json[]')
    32183258        data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
     3259        array_on = pg.get_array()
    32193260        jsondecode = pg.get_jsondecode()
    32203261        r = dict(data=data)
    32213262        self.db.insert('arraytest', r)
     3263        if not array_on:
     3264            r['data'] = pg.cast_array(r['data'], jsondecode)
    32223265        if jsondecode is None:
    32233266            r['data'] = [json.loads(d) for d in r['data']]
     
    32253268        r['data'] = None
    32263269        self.db.get('arraytest', r)
     3270        if not array_on:
     3271            r['data'] = pg.cast_array(r['data'], jsondecode)
    32273272        if jsondecode is None:
    32283273            r['data'] = [json.loads(d) for d in r['data']]
     
    32303275        r = dict(data=[json.dumps(d) for d in data])
    32313276        self.db.insert('arraytest', r)
     3277        if not array_on:
     3278            r['data'] = pg.cast_array(r['data'], jsondecode)
    32323279        if jsondecode is None:
    32333280            r['data'] = [json.loads(d) for d in r['data']]
     
    32393286        self.db.insert('arraytest', r)
    32403287        r = r['data']
    3241         self.assertIsInstance(r, list)
    3242         self.assertEqual(len(r), 2)
    3243         self.assertIsNone(r[0])
    3244         self.assertIsNone(r[1])
     3288        if array_on:
     3289            self.assertIsInstance(r, list)
     3290            self.assertEqual(len(r), 2)
     3291            self.assertIsNone(r[0])
     3292            self.assertIsNone(r[1])
     3293        else:
     3294            self.assertEqual(r, '{NULL,NULL}')
    32453295
    32463296    def testArrayOfJsonb(self):
     
    32543304        self.assertEqual(r['data'], 'jsonb[]' if self.regtypes else 'json[]')
    32553305        data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
     3306        array_on = pg.get_array()
    32563307        jsondecode = pg.get_jsondecode()
    32573308        r = dict(data=data)
    32583309        self.db.insert('arraytest', r)
     3310        if not array_on:
     3311            r['data'] = pg.cast_array(r['data'], jsondecode)
    32593312        if jsondecode is None:
    32603313            r['data'] = [json.loads(d) for d in r['data']]
     
    32623315        r['data'] = None
    32633316        self.db.get('arraytest', r)
     3317        if not array_on:
     3318            r['data'] = pg.cast_array(r['data'], jsondecode)
    32643319        if jsondecode is None:
    32653320            r['data'] = [json.loads(d) for d in r['data']]
     
    32673322        r = dict(data=[json.dumps(d) for d in data])
    32683323        self.db.insert('arraytest', r)
     3324        if not array_on:
     3325            r['data'] = pg.cast_array(r['data'], jsondecode)
    32693326        if jsondecode is None:
    32703327            r['data'] = [json.loads(d) for d in r['data']]
     
    32763333        self.db.insert('arraytest', r)
    32773334        r = r['data']
    3278         self.assertIsInstance(r, list)
    3279         self.assertEqual(len(r), 2)
    3280         self.assertIsNone(r[0])
    3281         self.assertIsNone(r[1])
     3335        if array_on:
     3336            self.assertIsInstance(r, list)
     3337            self.assertEqual(len(r), 2)
     3338            self.assertIsNone(r[0])
     3339            self.assertIsNone(r[1])
     3340        else:
     3341            self.assertEqual(r, '{NULL,NULL}')
    32823342
    32833343    def testDeepArray(self):
     3344        array_on = pg.get_array()
    32843345        self.createTable('arraytest', 'data text[][][]', oids=True)
    32853346        r = self.db.get_attnames('arraytest')
     
    32883349        r = dict(data=data)
    32893350        self.db.insert('arraytest', r)
    3290         self.assertEqual(r['data'], data)
     3351        if array_on:
     3352            self.assertEqual(r['data'], data)
     3353        else:
     3354            self.assertTrue(r['data'].startswith('{{{"Hello,'))
    32913355        r['data'] = None
    32923356        self.db.get('arraytest', r)
    3293         self.assertEqual(r['data'], data)
     3357        if array_on:
     3358            self.assertEqual(r['data'], data)
     3359        else:
     3360            self.assertTrue(r['data'].startswith('{{{"Hello,'))
    32943361
    32953362    def testInsertUpdateGetRecord(self):
     
    36363703        not_bool = not pg.get_bool()
    36373704        cls.set_option('bool', not_bool)
     3705        not_array = not pg.get_array()
     3706        cls.set_option('array', not_array)
    36383707        not_bytea_escaped = not pg.get_bytea_escaped()
    36393708        cls.set_option('bytea_escaped', not_bytea_escaped)
     
    36493718        cls.reset_option('namedresult')
    36503719        cls.reset_option('bool')
     3720        cls.reset_option('array')
    36513721        cls.reset_option('bytea_escaped')
    36523722        cls.reset_option('decimal')
Note: See TracChangeset for help on using the changeset viewer.