Changeset 792 for trunk


Ignore:
Timestamp:
Jan 28, 2016, 2:54:34 PM (4 years ago)
Author:
cito
Message:

Using ARRAY and ROW constructor in pgdb again

Using the special input syntax for quoting arrays and rows had some
advantages, but one big disadvantage, namely the missing type information.
Therefore, this change has been reverted, we now use ARRAY and ROW
constructor syntax again to quote lists and tuples. See comments.

The code has become simpler again and doesn't need the re module any more.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/pgdb.py

    r791 r792  
    7373from math import isnan, isinf
    7474from collections import namedtuple
    75 from re import compile as regex
    7675from json import loads as jsondecode, dumps as jsonencode
    7776
     
    265264
    266265
    267 _re_array_quote = regex(r'[{},"\\\s]|^[Nn][Uu][Ll][Ll]$')
    268 _re_record_quote = regex(r'[(,"\\]')
    269 _re_array_escape = _re_record_escape = regex(r'(["\\])')
    270 
    271 
    272266class _quotedict(dict):
    273267    """Dictionary with auto quoting of its items.
     
    337331            return val
    338332        if isinstance(val, list):
    339             return "'%s'" % self._quote_array(val)
     333            # Quote value as an ARRAY constructor. This is better than using
     334            # an array literal because it carries the information that this is
     335            # an array and not a string.  One issue with this syntax is that
     336            # you need to add an explicit type cast when passing empty arrays.
     337            # The ARRAY keyword is actually only necessary at the top level.
     338            q = self._quote
     339            return 'ARRAY[%s]' % ','.join(str(q(v)) for v in val)
    340340        if isinstance(val, tuple):
    341             return "'%s'" % self._quote_record(val)
     341            # Quote as a ROW constructor.  This is better than using a record
     342            # literal because it carries the information that this is a record
     343            # and not a string.  We don't use the keyword ROW in order to make
     344            # this usable with the IN synntax as well.  It is only necessary
     345            # when the records has a single column which is not really useful.
     346            q = self._quote
     347            return '(%s)' % ','.join(str(q(v)) for v in val)
    342348        try:
    343349            return val.__pg_repr__()
     
    346352                'do not know how to handle type %s' % type(val))
    347353
    348     def _quote_array(self, val):
    349         """Quote value as a literal constant for an array."""
    350         q = self._quote_array_element
    351         return '{%s}' % ','.join(q(v) for v in val)
    352 
    353     def _quote_array_element(self, val):
    354         """Quote value using the output syntax for arrays."""
    355         if isinstance(val, list):
    356             return self._quote_array(val)
    357         if val is None:
    358             return 'null'
    359         if isinstance(val, (int, long, float)):
    360             return str(val)
    361         if isinstance(val, bool):
    362             return 't' if val else 'f'
    363         if isinstance(val, tuple):
    364             val = self._quote_record(val)
    365         if isinstance(val, basestring):
    366             if not val:
    367                 return '""'
    368             if _re_array_quote.search(val):
    369                 return '"%s"' % _re_array_escape.sub(r'\\\1', val)
    370             return val
    371         raise InterfaceError(
    372             'do not know how to handle base type %s' % type(val))
    373 
    374     def _quote_record(self, val):
    375         """Quote value as a literal constant for a record."""
    376         q = self._quote_record_element
    377         return '(%s)' % ','.join(q(v) for v in val)
    378 
    379     def _quote_record_element(self, val):
    380         """Quote value using the output syntax for records."""
    381         if val is None:
    382             return ''
    383         if isinstance(val, (int, long, float)):
    384             return str(val)
    385         if isinstance(val, bool):
    386             return 't' if val else 'f'
    387         if isinstance(val, list):
    388             val = self._quote_array(val)
    389         if isinstance(val, basestring):
    390             if not val:
    391                 return '""'
    392             if _re_record_quote.search(val):
    393                 return '"%s"' % _re_record_escape.sub(r'\\\1', val)
    394             return val
    395         raise InterfaceError(
    396             'do not know how to handle component type %s' % type(val))
    397354
    398355    def _quoteparams(self, string, parameters):
  • trunk/tests/test_dbapi20.py

    r791 r792  
    484484                " (n smallint, i int[], t text[][])" % table)
    485485            params = [(n, v[0], v[1]) for n, v in enumerate(values)]
    486             cur.executemany(
    487                 "insert into %s values (%%d,%%s,%%s)" % table, params)
     486            # Note that we must explicit casts because we are inserting
     487            # empty arrays.  Otherwise this is not necessary.
     488            cur.executemany("insert into %s values"
     489                " (%%d,%%s::int[],%%s::text[][])" % table, params)
    488490            cur.execute("select i, t from %s order by n" % table)
    489491            self.assertEqual(cur.description[0].type_code, pgdb.ARRAY)
     
    541543
    542544    def test_select_record(self):
    543         values = (1, 25000, 2.5, 'hello', 'Hello World!', 'Hello, World!',
     545        value = (1, 25000, 2.5, 'hello', 'Hello World!', 'Hello, World!',
    544546            '(test)', '(x,y)', ' x y ', 'null', None)
    545547        con = self._connect()
    546548        try:
    547549            cur = con.cursor()
    548             # Note that %s::record does not work on input unfortunately
    549             # ("input of anonymous composite types is not implemented").
    550             # so we need to resort to a row constructor instead.
    551             row = ','.join(["%s"] * len(values))
    552             cur.execute("select ROW(%s) as test_record" % row, values)
     550            cur.execute("select %s as test_record", [value])
    553551            self.assertEqual(cur.description[0].name, 'test_record')
    554552            self.assertEqual(cur.description[0].type_code, 'record')
     
    559557        # untyped record (an anonymous composite type). For the same
    560558        # reason this is also a normal tuple, not a named tuple.
    561         text_values = tuple(None if v is None else str(v) for v in values)
    562         self.assertEqual(row, text_values)
     559        text_row = tuple(None if v is None else str(v) for v in value)
     560        self.assertEqual(row, text_row)
    563561
    564562    def test_custom_type(self):
Note: See TracChangeset for help on using the changeset viewer.