Changeset 798


Ignore:
Timestamp:
Jan 30, 2016, 2:55:18 PM (3 years ago)
Author:
cito
Message:

Port type cache and typecasting from pgdb to pg

So far, the typecasting in the classic module was been only done by
the C extension module and was not extensible through typecasting
functions in Python. This has now been made extensible by adding
a cast hook to the C extension module which has been hooked up to
a new type cache object that holds information on the types and the
associated typecast functions. All of this works very similar to the
pgdb module now, except that the basic types are still handled by
the C extension module and the Python typecast functions are only
called via the hook for types which are not supported internally.

Also added tests and a chapter on the type cache in the documentation,
and cleaned up the error messages in the C extension module.

Location:
trunk
Files:
14 edited
1 copied

Legend:

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

    r796 r798  
    8585    types have been refactored and swapped out to separate classes.
    8686- Changes concerning both modules:
     87  - The modules now provide get_typecast() and set_typecast() methods
     88    allowing to control the typecasting on the global level.  The connection
     89    objects have got type caches with the same methods which give control
     90    over the typecasting on the level of the current connection.
     91    See the documentation on details about the type cache and the typecast
     92    mechanisms provided by PyGreSQL.
    8793  - PyGreSQL now supports the JSON and JSONB data types, converting such
    8894    columns automatically to and from Python objects. If you want to insert
  • trunk/docs/contents/pg/connection.rst

    r751 r798  
    191191.. versionadded:: 4.1
    192192
    193 .. method:: Connection.set_notice_receiver(proc)
     193.. method:: Connection.set_notice_receiver(func)
    194194
    195195    Set a custom notice receiver
    196196
    197     :param proc: the custom notice receiver callback function
     197    :param func: the custom notice receiver callback function
    198198    :rtype: None
    199199    :raises TypeError: the specified notice receiver is not callable
  • trunk/docs/contents/pg/db_types.rst

    r797 r798  
    1 TypeCache -- The internal cache for database types
    2 ==================================================
     1DbTypes -- The internal cache for database types
     2================================================
    33
    4 .. py:currentmodule:: pgdb
     4.. py:currentmodule:: pg
    55
    6 .. class:: TypeCache
     6.. class:: DbTypes
    77
    88.. versionadded:: 5.0
    99
    10 The internal :class:`TypeCache` of PyGreSQL is not part of the DB-API 2
    11 standard, but is documented here in case you need full control and
    12 understanding of the internal handling of database types.
     10The :class:`DbTypes` object is essentially a dictionary mapping PostgreSQL
     11internal type names and type OIDs to PyGreSQL "type names" (which are also
     12returned by :meth:`DB.get_attnames` as dictionary values).
    1313
    14 The TypeCache is essentially a dictionary mapping PostgreSQL internal
    15 type names and type OIDs to DB-API 2 "type codes" (which are also returned
    16 as the *type_code* field of the :attr:`Cursor.description` attribute).
    17 
    18 These type codes are strings which are equal to the PostgreSQL internal
    19 type name, but they are also carrying additional information about the
     14These type names are strings which are equal to the simple PyGreSQL name or
     15to regular type names if these have been enabled with :meth:`DB.use_regtypes`.
     16Besides being strings, they are also carrying additional information about the
    2017associated PostgreSQL type in the following attributes:
    2118
    22         - *oid* -- the OID of the type
    23         - *len*  -- the internal size
    24         - *type*  -- ``'b'`` = base, ``'c'`` = composite, ...
    25         - *category*  -- ``'A'`` = Array, ``'B'`` = Boolean, ...
    26         - *delim*  -- delimiter to be used when parsing arrays
    27         - *relid*  -- the table OID for composite types
     19        - *oid* -- the PostgreSQL type OID
     20        - *pgtype* -- the PostgreSQL type name
     21        - *regtype* -- the regular type name
     22        - *simple* -- the simple PyGreSQL type name
     23        - *typtype* -- `b` = base type, `c` = composite type etc.
     24        - *category* -- `A` = Array, `b` =Boolean, `C` = Composite etc.
     25        - *delim* -- delimiter for array types
     26        - *relid* -- corresponding table for composite types
     27        - *attnames* -- attributes for composite types
    2828
    2929For details, see the PostgreSQL documentation on `pg_type
    3030<http://www.postgresql.org/docs/current/static/catalog-pg-type.html>`_.
    3131
    32 In addition to the dictionary methods, the :class:`TypeCache` provides
    33 the following methods:
     32In addition to the dictionary methods, the :class:`DbTypes` class also
     33provides the following methods:
    3434
    35 .. method:: TypeCache.get_fields(typ)
     35.. method:: DbTypes.get_attnames(typ)
    3636
    3737    Get the names and types of the fields of composite types
     
    3939    :param typ: PostgreSQL type name or OID of a composite type
    4040    :type typ: str or int
    41     :returns: a list of pairs of field names and types
    42     :rtype: list
     41    :returns: an ordered dictionary mapping field names to type names
    4342
    44 .. method:: TypeCache.get_typecast(typ)
     43.. method:: DbTypes.get_typecast(typ)
    4544
    4645    Get the cast function for the given database type
    4746
    48     :param str typ: PostgreSQL type name or type code
     47    :param str typ: PostgreSQL type name
    4948    :returns: the typecast function for the specified type
    5049    :rtype: function or None
    5150
    52 .. method:: TypeCache.set_typecast(typ, cast)
     51.. method:: DbTypes.set_typecast(typ, cast)
    5352
    5453    Set a typecast function for the given database type(s)
    5554
    56     :param typ: PostgreSQL type name or type code, or list of such
     55    :param typ: PostgreSQL type name or list of type names
    5756    :type typ: str or list
    5857    :param cast: the typecast function to be set for the specified type(s)
    5958    :type typ: str or int
    6059
    61 .. method:: TypeCache.reset_typecast([typ])
     60.. method:: DbTypes.reset_typecast([typ])
    6261
    6362    Reset the typecasts for the specified (or all) type(s) to their defaults
    6463
    65     :param str typ: PostgreSQL type name or type code, or list of such,
     64    :param str typ: PostgreSQL type name or list of type names,
    6665        or None to reset all typecast functions
    6766    :type typ: str, list or None
    6867
    69 .. method:: TypeCache.typecast(typ, value)
     68.. method:: DbTypes.typecast(value, typ)
    7069
    7170    Cast the given value according to the given database type
     
    7473    :returns: the casted value
    7574
    76 
    7775.. note::
    7876
    79     Note that the :class:`TypeCache` is always bound to a database connection.
    80     You can also get, set and reset typecast functions on a global level using
    81     the functions :func:`pgdb.get_typecast`, :func:`pgdb.set_typecast` and
    82     :func:`pgdb.reset_typecast`.  If you do this, the current database
    83     connections will continue to use their already cached typecast functions
    84     unless you call the :meth:`TypeCache.reset_typecast` method on the
    85     :attr:`Connection.type_cache` of the running connections.
     77    Note that :class:`DbTypes` object is always bound to a database connection.
     78    You can also get and set and reset typecast functions on a global level
     79    using the functions :func:`pg.get_typecast` and :func:`pg.set_typecast`.
     80    If you do this, the current database connections will continue to use their
     81    already cached typecast functions unless you reset the typecast functions
     82    by calling the :meth:`DbTypes.reset_typecast` method on :attr:`DB.dbtypes`
     83    objects of the running connections.
     84
     85    Also note that the typecasting for all of the basic types happens already
     86    in the C extension module.  The typecast functions that can be set with
     87    the above methods are only called for the types that are not already
     88    supported by the C extension module.
  • trunk/docs/contents/pg/db_wrapper.rst

    r781 r798  
    765765
    766766.. versionadded:: 4.1.1
     767
     768Attributes of the DB wrapper class
     769----------------------------------
     770
     771.. attribute:: DB.db
     772
     773    The wrapped :class:`Connection` object
     774
     775You normally don't need this, since all of the members can be accessed
     776from the :class:`DB` wrapper class as well.
     777
     778.. attribute:: DB.dbname
     779
     780    The name of the database that the connection is using
     781
     782
     783.. attribute:: DB.dbtypes
     784
     785    A dictionary with the various type names for the PostgreSQL types
     786
     787This can be used for getting more information on the PostgreSQL database
     788types or changing the typecast functions used for the connection.  See the
     789description of the :class:`DbTypes` class for details.
     790
     791.. versionadded:: 5.0
  • trunk/docs/contents/pg/index.rst

    r762 r798  
    1616    large_objects
    1717    notification
     18    db_types
  • trunk/docs/contents/pg/module.rst

    r781 r798  
    453453.. versionadded:: 5.0
    454454
     455get/set_typecast -- custom typecasting
     456--------------------------------------
     457
     458PyGreSQL uses typecast functions to cast the raw data coming from the
     459database to Python objects suitable for the particular database type.
     460These functions take a single string argument that represents the data
     461to be casted and must return the casted value.
     462
     463PyGreSQL provides through its C extension module basic typecast functions
     464for the common database types, but if you want to add more typecast functions,
     465you can set these using the following functions.
     466
     467.. method:: get_typecast(typ)
     468
     469    Get the global cast function for the given database type
     470
     471    :param str typ: PostgreSQL type name
     472    :returns: the typecast function for the specified type
     473    :rtype: function or None
     474
     475.. versionadded:: 5.0
     476
     477.. method:: set_typecast(typ, cast)
     478
     479    Set a global typecast function for the given database type(s)
     480
     481    :param typ: PostgreSQL type name or list of type names
     482    :type typ: str or list
     483    :param cast: the typecast function to be set for the specified type(s)
     484    :type typ: str or int
     485
     486.. versionadded:: 5.0
     487
     488Note that database connections cache types and their cast functions using
     489connection specific :class:`DbTypes` objects.  You can also get, set and
     490reset typecast functions on the connection level using the methods
     491:meth:`DbTypes.get_typecast`, :meth:`DbTypes.set_typecast` and
     492:meth:`DbTypes.reset_typecast` of the :attr:`DB.dbtypes` object.  This will
     493not affect other connections or future connections.  In order to be sure
     494a global change is picked up by a running connection, you must reopen it or
     495call :meth:`DbTypes.reset_typecast` on the :attr:`DB.dbtypes` object.
     496
     497Also note that the typecasting for all of the basic types happens already
     498in the C extension module.  The typecast functions that can be set with
     499the above methods are only called for the types that are not already
     500supported by the C extension module.
     501
    455502
    456503Module constants
  • trunk/docs/contents/pgdb/module.rst

    r797 r798  
    4747
    4848PyGreSQL provides built-in typecast functions for the common database types,
    49 but if you want to change these or add more typecast functions, you can use
    50 the following functions.
     49but if you want to change these or add more typecast functions, you can set
     50these up using the following functions.
    5151
    5252.. note::
     
    9090:meth:`TypeCache.get_typecast`, :meth:`TypeCache.set_typecast` and
    9191:meth:`TypeCache.reset_typecast` of the :attr:`Connection.type_cache`.  This
    92 will not affect other connections or future connections. In order to be sure
     92will not affect other connections or future connections.  In order to be sure
    9393a global change is picked up by a running connection, you must reopen it or
    9494call :meth:`TypeCache.reset_typecast` on the :attr:`Connection.type_cache`.
  • trunk/docs/contents/pgdb/typecache.rst

    r797 r798  
    6767    :type typ: str, list or None
    6868
    69 .. method:: TypeCache.typecast(typ, value)
     69.. method:: TypeCache.typecast(value, typ)
    7070
    7171    Cast the given value according to the given database type
     
    7373    :param str typ: PostgreSQL type name or type code
    7474    :returns: the casted value
    75 
    7675
    7776.. note::
     
    8281    :func:`pgdb.reset_typecast`.  If you do this, the current database
    8382    connections will continue to use their already cached typecast functions
    84     unless you call the :meth:`TypeCache.reset_typecast` method on the
    85     :attr:`Connection.type_cache` of the running connections.
     83    unless call the :meth:`TypeCache.reset_typecast` method on the
     84    :attr:`Connection.type_cache` objects of the running connections.
  • trunk/pg.py

    r793 r798  
    4141from re import compile as regex
    4242from json import loads as jsondecode, dumps as jsonencode
     43
     44try:
     45    long
     46except NameError:  # Python >= 3.0
     47    long = int
    4348
    4449try:
     
    376381
    377382
    378 class _CastRecord:
    379     """Class providing methods for casting records and record elements.
    380 
    381     This is needed when getting result values from one of the higher level DB
    382     methods, since the lower level query method only casts the other types.
     383def cast_bool(value):
     384    """Cast a boolean value."""
     385    if not get_bool():
     386        return value
     387    return value[0] == 't'
     388
     389
     390def cast_json(value):
     391    """Cast a JSON value."""
     392    cast = get_jsondecode()
     393    if not cast:
     394        return value
     395    return cast(value)
     396
     397
     398def cast_num(value):
     399    """Cast a numeric value."""
     400    return (get_decimal() or float)(value)
     401
     402
     403def cast_money(value):
     404    """Cast a money value."""
     405    point = get_decimal_point()
     406    if not point:
     407        return value
     408    if point != '.':
     409        value = value.replace(point, '.')
     410    value = value.replace('(', '-')
     411    value = ''.join(c for c in value if c.isdigit() or c in '.-')
     412    return (get_decimal() or float)(value)
     413
     414
     415def cast_int2vector(value):
     416    """Cast an int2vector value."""
     417    return [int(v) for v in value.split()]
     418
     419
     420class Typecasts(dict):
     421    """Dictionary mapping database types to typecast functions.
     422
     423    The cast functions get passed the string representation of a value in
     424    the database which they need to convert to a Python object.  The
     425    passed string will never be None since NULL values are already be
     426    handled before the cast function is called.
     427
     428    Note that the basic types are already handled by the C extension.
     429    They only need to be handled here as record or array components.
    383430    """
    384431
    385     @staticmethod
    386     def cast_bool(v):
    387         if not get_bool():
    388             return v
    389         return v[0] == 't'
    390 
    391     @staticmethod
    392     def cast_bytea(v):
    393         return unescape_bytea(v)
    394 
    395     @staticmethod
    396     def cast_float(v):
    397         return float(v)
    398 
    399     @staticmethod
    400     def cast_int(v):
    401         return int(v)
    402 
    403     @staticmethod
    404     def cast_json(v):
    405         cast = get_jsondecode()
    406         if not cast:
    407             return v
    408         return cast(v)
    409 
    410     @staticmethod
    411     def cast_num(v):
    412         return (get_decimal() or float)(v)
    413 
    414     @staticmethod
    415     def cast_money(v):
    416         point = get_decimal_point()
    417         if not point:
    418             return v
    419         if point != '.':
    420             v = v.replace(point, '.')
    421         v = v.replace('(', '-')
    422         v = ''.join(c for c in v if c.isdigit() or c in '.-')
    423         return (get_decimal() or float)(v)
     432    # the default cast functions
     433    # (str functions are ignored but have been added for faster access)
     434    defaults = {'char': str, 'bpchar': str, 'name': str,
     435        'text': str, 'varchar': str,
     436        'bool': cast_bool, 'bytea': unescape_bytea,
     437        'int2': int, 'int4': int, 'serial': int,
     438        'int8': long, 'json': cast_json, 'jsonb': cast_json,
     439        'oid': long, 'oid8': long,
     440        'float4': float, 'float8': float,
     441        'numeric': cast_num, 'money': cast_money,
     442        'int2vector': cast_int2vector,
     443        'anyarray': cast_array, 'record': cast_record}
     444
     445    def __missing__(self, typ):
     446        """Create a cast function if it is not cached.
     447       
     448        Note that this class never raises a KeyError,
     449        but returns None when no special cast function exists.
     450        """
     451        if not isinstance(typ, str):
     452            raise TypeError('Invalid type: %s' % typ)
     453        cast = self.defaults.get(typ)
     454        if cast:
     455            # store default for faster access
     456            self[typ] = cast
     457        elif typ.startswith('_'):
     458            base_cast = self[typ[1:]]
     459            cast = self.create_array_cast(base_cast)
     460            if base_cast:
     461                self[typ] = cast
     462        else:
     463            attnames = self.get_attnames(typ)
     464            if attnames:
     465                casts = [self[v.pgtype] for v in attnames.values()]
     466                cast = self.create_record_cast(typ, attnames, casts)
     467                self[typ] = cast
     468        return cast
     469
     470    def get(self, typ, default=None):
     471        """Get the typecast function for the given database type."""
     472        return self[typ] or default
     473
     474    def set(self, typ, cast):
     475        """Set a typecast function for the specified database type(s)."""
     476        if isinstance(typ, basestring):
     477            typ = [typ]
     478        if cast is None:
     479            for t in typ:
     480                self.pop(t, None)
     481                self.pop('_%s' % t, None)
     482        else:
     483            if not callable(cast):
     484                raise TypeError("Cast parameter must be callable")
     485            for t in typ:
     486                self[t] = cast
     487                self.pop('_%s % t', None)
     488
     489    def reset(self, typ=None):
     490        """Reset the typecasts for the specified type(s) to their defaults.
     491
     492        When no type is specified, all typecasts will be reset.
     493        """
     494        defaults = self.defaults
     495        if typ is None:
     496            self.clear()
     497            self.update(defaults)
     498        else:
     499            if isinstance(typ, basestring):
     500                typ = [typ]
     501            for t in typ:
     502                self.set(t, defaults.get(t))
    424503
    425504    @classmethod
    426     def cast(cls, v, typ):
    427         types = typ.attnames.values()
    428         cast = [getattr(cls, 'cast_%s' % t.simple, None) for t in types]
    429         v = cast_record(v, cast)
    430         return typ.namedtuple(*v)
    431 
    432 
    433 class _PgType(str):
    434     """Class augmenting the simple type name with additional info."""
    435 
    436     _num_types = frozenset('int float num money'
    437         ' int2 int4 int8 float4 float8 numeric money'.split())
     505    def get_default(cls, typ):
     506        """Get the default typecast function for the given database type."""
     507        return cls.defaults.get(typ)
    438508
    439509    @classmethod
    440     def create(cls, db, pgtype, regtype, typrelid):
    441         """Create a PostgreSQL type name with additional info."""
    442         simple = 'record' if typrelid else _simpletype[pgtype]
    443         self = cls(regtype if db._regtypes else simple)
    444         self.db = db
    445         self.simple = simple
    446         self.pgtype = pgtype
    447         self.regtype = regtype
    448         self.typrelid = typrelid
    449         self._attnames = self._namedtuple = None
    450         return self
     510    def set_default(cls, typ, cast):
     511        """Set a default typecast function for the given database type(s)."""
     512        if isinstance(typ, basestring):
     513            typ = [typ]
     514        defaults = cls.defaults
     515        if cast is None:
     516            for t in typ:
     517                defaults.pop(t, None)
     518                defaults.pop('_%s' % t, None)
     519        else:
     520            if not callable(cast):
     521                raise TypeError("Cast parameter must be callable")
     522            for t in typ:
     523                defaults[t] = cast
     524                defaults.pop('_%s % t', None)
     525
     526    def get_attnames(self, typ):
     527        """Return the fields for the given record type.
     528
     529        This method will be replaced with the get_attnames() method of DbTypes.
     530        """
     531        return {}
     532
     533    def create_array_cast(self, cast):
     534        """Create an array typecast for the given base cast."""
     535        return lambda v: cast_array(v, cast)
     536
     537    def create_record_cast(self, name, fields, casts):
     538        """Create a named record typecast for the given fields and casts."""
     539        record = namedtuple(name, fields)
     540        return lambda v: record(*cast_record(v, casts))
     541
     542
     543def get_typecast(typ):
     544    """Get the global typecast function for the given database type(s)."""
     545    return Typecasts.get_default(typ)
     546
     547
     548def set_typecast(typ, cast):
     549    """Set a global typecast function for the given database type(s).
     550
     551    Note that connections cache cast functions. To be sure a global change
     552    is picked up by a running connection, call db.db_types.reset_typecast().
     553    """
     554    Typecasts.set_default(typ, cast)
     555
     556
     557class DbType(str):
     558    """Class augmenting the simple type name with additional info.
     559
     560    The following additional information is provided:
     561
     562        oid: the PostgreSQL type OID
     563        pgtype: the PostgreSQL type name
     564        regtype: the regular type name
     565        simple: the simple PyGreSQL type name
     566        typtype: b = base type, c = composite type etc.
     567        category: A = Array, b =Boolean, C = Composite etc.
     568        delim: delimiter for array types
     569        relid: corresponding table for composite types
     570        attnames: attributes for composite types
     571    """
    451572
    452573    @property
    453574    def attnames(self):
    454575        """Get names and types of the fields of a composite type."""
    455         if not self.typrelid:
     576        return self._get_attnames(self)
     577
     578
     579class DbTypes(dict):
     580    """Cache for PostgreSQL data types.
     581
     582    This cache maps type OIDs and names to DbType objects containing
     583    information on the associated database type.
     584    """
     585
     586    _num_types = frozenset('int float num money'
     587        ' int2 int4 int8 float4 float8 numeric money'.split())
     588
     589    def __init__(self, db):
     590        """Initialize type cache for connection."""
     591        super(DbTypes, self).__init__()
     592        self._get_attnames = db.get_attnames
     593        db = db.db
     594        self.query = db.query
     595        self.escape_string = db.escape_string
     596        self._typecasts = Typecasts()
     597        self._typecasts.get_attnames = self.get_attnames
     598        self._regtypes = False
     599
     600    def add(self, oid, pgtype, regtype,
     601               typtype, category, delim, relid):
     602        """Create a PostgreSQL type name with additional info."""
     603        if oid in self:
     604            return self[oid]
     605        simple = 'record' if relid else _simpletype[pgtype]
     606        typ = DbType(regtype if self._regtypes else simple)
     607        typ.oid = oid
     608        typ.simple = simple
     609        typ.pgtype = pgtype
     610        typ.regtype = regtype
     611        typ.typtype = typtype
     612        typ.category = category
     613        typ.delim = delim
     614        typ.relid = relid
     615        typ._get_attnames = self.get_attnames
     616        return typ
     617
     618    def __missing__(self, key):
     619        """Get the type info from the database if it is not cached."""
     620        try:
     621            res = self.query("SELECT oid, typname, typname::regtype,"
     622                " typtype, typcategory, typdelim, typrelid"
     623                " FROM pg_type WHERE oid=%s::regtype" %
     624                (DB._adapt_qualified_param(key, 1),), (key,)).getresult()
     625        except ProgrammingError:
     626            res = None
     627        if not res:
     628            raise KeyError('Type %s could not be found' % key)
     629        res = res[0]
     630        typ = self.add(*res)
     631        self[typ.oid] = self[typ.pgtype] = typ
     632        return typ
     633
     634    def get(self, key, default=None):
     635        """Get the type even if it is not cached."""
     636        try:
     637            return self[key]
     638        except KeyError:
     639            return default
     640
     641    def get_attnames(self, typ):
     642        """Get names and types of the fields of a composite type."""
     643        if not isinstance(typ, DbType):
     644            typ = self.get(typ)
     645            if not typ:
     646                return None
     647        if not typ.relid:
    456648            return None
    457         if not self._attnames:
    458             self._attnames = self.db.get_attnames(self.typrelid)
    459         return self._attnames
    460 
    461     @property
    462     def namedtuple(self):
    463         """Return named tuple class representing a composite type."""
    464         if not self._namedtuple:
    465             self._namedtuple = namedtuple(self, self.attnames)
    466         return self._namedtuple
    467 
    468     def cast(self, value):
    469         if value is not None and self.typrelid:
    470             value = _CastRecord.cast(value, self)
    471         return value
     649        return self._get_attnames(typ.relid, with_oid=False)
     650
     651    def get_typecast(self, typ):
     652        """Get the typecast function for the given database type."""
     653        return self._typecasts.get(typ)
     654
     655    def set_typecast(self, typ, cast):
     656        """Set a typecast function for the specified database type(s)."""
     657        self._typecasts.set(typ, cast)
     658
     659    def reset_typecast(self, typ=None):
     660        """Reset the typecast function for the specified database type(s)."""
     661        self._typecasts.reset(typ)
     662
     663    def typecast(self, value, typ):
     664        """Cast the given value according to the given database type."""
     665        if value is None:
     666            # for NULL values, no typecast is necessary
     667            return None
     668        if not isinstance(typ, DbType):
     669            typ = self.get(typ)
     670            if typ:
     671                typ = typ.pgtype
     672        cast = self.get_typecast(typ) if typ else None
     673        if not cast or cast is str:
     674            # no typecast is necessary
     675            return value
     676        return cast(value)
    472677
    473678
     
    691896        self._privileges = {}
    692897        self._args = args, kw
     898        self.dbtypes = DbTypes(self)
     899        db.set_cast_hook(self.dbtypes.typecast)
    693900        self.debug = None  # For debugging scripts, this can be set
    694901            # * to a string format specification (e.g. in CGI set to "%s<BR>"),
     
    10481255            # not the order as defined by the columns in the table
    10491256            if len(pkey) > 1:
    1050                 indkey = [int(k) for k in pkey[0][2].split()]
     1257                indkey = pkey[0][2]
    10511258                pkey = sorted(pkey, key=lambda row: indkey.index(row[1]))
    10521259                pkey = tuple(row[0] for row in pkey)
     
    10841291        return self.get_relations('r')
    10851292
    1086     def get_attnames(self, table, flush=False):
     1293    def get_attnames(self, table, with_oid=True, flush=False):
    10871294        """Given the name of a table, dig out the set of attribute names.
    10881295
     
    11051312            names = attnames[table]
    11061313        except KeyError:  # cache miss, check the database
    1107             q = ("SELECT a.attname, t.typname, t.typname::regtype, t.typrelid"
     1314            q = "a.attnum > 0"
     1315            if with_oid:
     1316                q = "(%s OR a.attname = 'oid')" % q
     1317            q = ("SELECT a.attname, t.oid, t.typname, t.typname::regtype,"
     1318                " t.typtype, t.typcategory, t.typdelim, t.typrelid"
    11081319                " FROM pg_attribute a"
    11091320                " JOIN pg_type t ON t.oid = a.atttypid"
    1110                 " WHERE a.attrelid = %s::regclass"
    1111                 " AND (a.attnum > 0 OR a.attname = 'oid')"
     1321                " WHERE a.attrelid = %s::regclass AND %s"
    11121322                " AND NOT a.attisdropped ORDER BY a.attnum") % (
    1113                     self._adapt_qualified_param(table, 1))
     1323                    self._adapt_qualified_param(table, 1), q)
    11141324            names = self.db.query(q, (table,)).getresult()
    1115             names = ((name, _PgType.create(self, pgtype, regtype, typrelid))
    1116                 for name, pgtype, regtype, typrelid in names)
     1325            types = self.dbtypes
     1326            names = ((name[0], types.add(*name[1:])) for name in names)
    11171327            names = AttrDict(names)
    11181328            attnames[table] = names  # cache it
     
    11221332        """Use regular type names instead of simplified type names."""
    11231333        if regtypes is None:
    1124             return self._regtypes
     1334            return self.dbtypes._regtypes
    11251335        else:
    11261336            regtypes = bool(regtypes)
    1127             if regtypes != self._regtypes:
    1128                 self._regtypes = regtypes
     1337            if regtypes != self.dbtypes._regtypes:
     1338                self.dbtypes._regtypes = regtypes
    11291339                self._attnames.clear()
     1340                self.dbtypes.clear()
    11301341            return regtypes
    11311342
     
    12151426            if qoid and n == 'oid':
    12161427                n = qoid
    1217             else:
    1218                 value = attnames[n].cast(value)
    12191428            row[n] = value
    12201429        return row
     
    12631472                if qoid and n == 'oid':
    12641473                    n = qoid
    1265                 else:
    1266                     value = attnames[n].cast(value)
    12671474                row[n] = value
    12681475        return row
     
    13321539                if qoid and n == 'oid':
    13331540                    n = qoid
    1334                 else:
    1335                     value = attnames[n].cast(value)
    13361541                row[n] = value
    13371542        return row
     
    14351640                if qoid and n == 'oid':
    14361641                    n = qoid
    1437                 else:
    1438                     value = attnames[n].cast(value)
    14391642                row[n] = value
    14401643        else:
     
    14581661                continue
    14591662            t = t.simple
    1460             if t in _PgType._num_types:
     1663            if t in DbTypes._num_types:
    14611664                row[n] = 0
    14621665            elif t == 'bool':
  • trunk/pgdb.py

    r797 r798  
    145145
    146146
     147def cast_int2vector(value):
     148    """Cast an int2vector value."""
     149    return [int(v) for v in value.split()]
     150
     151
    147152class Typecasts(dict):
    148153    """Dictionary mapping database types to typecast functions.
    149154
    150     The cast functions must accept one Python object as an argument and
    151     convert that object to a string representation of the corresponding type
    152     in the database.  The Python None object is always converted to NULL,
    153     so the cast functions can assume they never get passed None as argument.
    154     However, they may get passed an empty string or a numeric null value.
     155    The cast functions get passed the string representation of a value in
     156    the database which they need to convert to a Python object.  The
     157    passed string will never be None since NULL values are already be
     158    handled before the cast function is called.
    155159    """
    156160
     
    165169        'float4': float, 'float8': float,
    166170        'numeric': Decimal, 'money': cast_money,
     171        'int2vector': cast_int2vector,
    167172        'anyarray': cast_array, 'record': cast_record}
    168173
     
    171176
    172177        Note that this class never raises a KeyError,
    173         but return None when no special cast function exists.
     178        but returns None when no special cast function exists.
    174179        """
     180        if not isinstance(typ, str):
     181            raise TypeError('Invalid type: %s' % typ)
    175182        cast = self.defaults.get(typ)
    176183        if cast:
     
    334341            oid = key
    335342        else:
    336             if not '.' in key and not '"' in key:
     343            if '.' not in key and '"' not in key:
    337344                key = '"%s"' % key
    338345            oid = "'%s'::regtype" % self._escape_string(key)
     
    386393        self._typecasts.reset(typ)
    387394
    388     def typecast(self, typ, value):
     395    def typecast(self, value, typ):
    389396        """Cast the given value according to the given database type."""
    390397        if value is None:
     
    655662            raise _db_error(str(err))
    656663        typecast = self.type_cache.typecast
    657         return [self.row_factory([typecast(typ, value)
     664        return [self.row_factory([typecast(value, typ)
    658665            for typ, value in zip(self.coltypes, row)]) for row in result]
    659666
  • trunk/pgmodule.c

    r794 r798  
    142142        PyObject_HEAD
    143143        int                     valid;                          /* validity flag */
    144         PGconn          *cnx;                           /* PostGres connection handle */
    145         PyObject        *notice_receiver;       /* current notice receiver */
     144        PGconn     *cnx;                                /* PostGres connection handle */
     145        PyObject   *cast_hook;                  /* external typecast method */
     146        PyObject   *notice_receiver;    /* current notice receiver */
    146147}       connObject;
    147148#define is_connObject(v) (PyType(v) == &connType)
     
    151152        PyObject_HEAD
    152153        int                     valid;                  /* validity flag */
    153         connObject      *pgcnx;                 /* parent connection object */
     154        connObject *pgcnx;                      /* parent connection object */
    154155        PGresult        *result;                /* result content */
    155156        int                     encoding;               /* client encoding */
     
    165166{
    166167        PyObject_HEAD
    167         connObject      *pgcnx;         /* parent connection object */
    168         PGresult        const *res;     /* an error or warning */
     168        connObject *pgcnx;                      /* parent connection object */
     169        PGresult        const *res;             /* an error or warning */
    169170}       noticeObject;
    170171#define is_noticeObject(v) (PyType(v) == &noticeType)
     
    173174{
    174175        PyObject_HEAD
    175         PGresult        *result;                /* result content */
     176        connObject *pgcnx;                      /* parent connection object */
     177        PGresult   *result;                     /* result content */
    176178        int                     encoding;               /* client encoding */
    177179}       queryObject;
     
    182184{
    183185        PyObject_HEAD
    184         connObject      *pgcnx;                 /* parent connection object */
     186        connObject *pgcnx;                      /* parent connection object */
    185187        Oid                     lo_oid;                 /* large object oid */
    186188        int                     lo_fd;                  /* large object fd */
     
    202204#define PYGRES_BYTEA 9
    203205#define PYGRES_JSON 10
    204 #define PYGRES_EXTERNAL 11
     206#define PYGRES_OTHER 11
    205207/* array types */
    206208#define PYGRES_ARRAY 16
     
    242244/* helper functions */
    243245
    244 /* get PyGreSQL internal types for  a PostgreSQL type */
     246/* get PyGreSQL internal types for a PostgreSQL type */
    245247static int
    246248get_type(Oid pgtype)
     
    290292                        break;
    291293
     294                case BPCHAROID:
     295                case CHAROID:
     296                case TEXTOID:
     297                case VARCHAROID:
     298                case NAMEOID:
     299                case DATEOID:
     300                case INTERVALOID:
     301                case TIMEOID:
     302                case TIMETZOID:
     303                case TIMESTAMPOID:
     304                case TIMESTAMPTZOID:
     305                case REGTYPEOID:
     306                        t = PYGRES_TEXT;
     307                        break;
     308
    292309                /* array types */
    293310
     
    331348                        break;
    332349
    333                 case ANYARRAYOID:
    334350                case BPCHARARRAYOID:
    335351                case CHARARRAYOID:
    336352                case TEXTARRAYOID:
    337353                case VARCHARARRAYOID:
     354                case NAMEARRAYOID:
    338355                case DATEARRAYOID:
    339356                case INTERVALARRAYOID:
     
    342359                case TIMESTAMPARRAYOID:
    343360                case TIMESTAMPTZARRAYOID:
     361                case REGTYPEARRAYOID:
    344362                        t = PYGRES_TEXT | PYGRES_ARRAY;
    345363                        break;
    346364
    347365                default:
    348                         t = PYGRES_TEXT;
     366                        t = PYGRES_OTHER;
    349367        }
    350368
     
    427445        }
    428446
     447        return obj;
     448}
     449
     450/* Cast an arbitrary type to a Python object using a callback function.
     451   This needs the character string, size, encoding, the Postgres type
     452   and the external typecast function to be called. */
     453static PyObject *
     454cast_other(char *s, Py_ssize_t size, int encoding, int pgtype,
     455        PyObject *cast_hook)
     456{
     457        PyObject *obj;
     458
     459        obj = cast_sized_text(s, size, encoding, PYGRES_TEXT);
     460
     461        if (cast_hook)
     462        {
     463                PyObject *tmp_obj = obj;
     464                obj = PyObject_CallFunction(cast_hook, "(Oi)", obj, pgtype);
     465                Py_DECREF(tmp_obj);
     466        }
    429467        return obj;
    430468}
     
    10531091        PyGILState_STATE gstate = PyGILState_Ensure();
    10541092        connObject *self = (connObject*) arg;
    1055         PyObject *proc = self->notice_receiver;
    1056         if (proc && PyCallable_Check(proc))
     1093        PyObject *func = self->notice_receiver;
     1094
     1095        if (func)
    10571096        {
    10581097                noticeObject *notice = PyObject_NEW(noticeObject, &noticeType);
     
    10681107                        notice = (noticeObject *)(void *)Py_None;
    10691108                }
    1070                 ret = PyObject_CallFunction(proc, "(O)", notice);
     1109                ret = PyObject_CallFunction(func, "(O)", notice);
    10711110                Py_XDECREF(ret);
    10721111        }
     
    13591398        if (!PyArg_ParseTuple(args, "i", &mode))
    13601399        {
    1361                 PyErr_SetString(PyExc_TypeError, "open(mode), with mode(integer)");
     1400                PyErr_SetString(PyExc_TypeError,
     1401                        "The open() method takes an integer argument");
    13621402                return NULL;
    13631403        }
     
    13911431        {
    13921432                PyErr_SetString(PyExc_TypeError,
    1393                         "Method close() takes no parameters");
     1433                        "Method close() takes no arguments");
    13941434                return NULL;
    13951435        }
     
    14261466        if (!PyArg_ParseTuple(args, "i", &size))
    14271467        {
    1428                 PyErr_SetString(PyExc_TypeError, "read(size), with size (integer)");
     1468                PyErr_SetString(PyExc_TypeError,
     1469                        "Method read() takes an integer argument");
    14291470                return NULL;
    14301471        }
     
    14321473        if (size <= 0)
    14331474        {
    1434                 PyErr_SetString(PyExc_ValueError, "Parameter size must be positive");
     1475                PyErr_SetString(PyExc_ValueError,
     1476                        "Method read() takes a positive integer as argument");
    14351477                return NULL;
    14361478        }
     
    14721514        {
    14731515                PyErr_SetString(PyExc_TypeError,
    1474                         "write(buffer), with buffer (sized string)");
     1516                        "Method write() expects a sized string as argument");
    14751517                return NULL;
    14761518        }
     
    15111553        {
    15121554                PyErr_SetString(PyExc_TypeError,
    1513                         "lseek(offset, whence), with offset and whence (integers)");
     1555                        "Method lseek() expects two integer arguments");
    15141556                return NULL;
    15151557        }
     
    15451587        {
    15461588                PyErr_SetString(PyExc_TypeError,
    1547                         "Method size() takes no parameters");
     1589                        "Method size() takes no arguments");
    15481590                return NULL;
    15491591        }
     
    15941636        {
    15951637                PyErr_SetString(PyExc_TypeError,
    1596                         "Method tell() takes no parameters");
     1638                        "Method tell() takes no arguments");
    15971639                return NULL;
    15981640        }
     
    16581700        {
    16591701                PyErr_SetString(PyExc_TypeError,
    1660                         "Method unlink() takes no parameters");
     1702                        "Method unlink() takes no arguments");
    16611703                return NULL;
    16621704        }
     
    18051847                Py_END_ALLOW_THREADS
    18061848        }
    1807         if (self->notice_receiver)
    1808         {
    1809                 Py_DECREF(self->notice_receiver);
    1810         }
     1849        Py_XDECREF(self->cast_hook);
     1850        Py_XDECREF(self->notice_receiver);
    18111851        PyObject_Del(self);
    18121852}
     
    18291869        {
    18301870                PyErr_SetString(PyExc_TypeError,
    1831                         "Method source() takes no parameter");
     1871                        "Method source() takes no arguments");
    18321872                return NULL;
    18331873        }
     
    18931933        {
    18941934                PyErr_SetString(PyExc_TypeError,
    1895                         "Query command must be a string");
     1935                        "Method query() expects a string as first argument");
    18961936                return NULL;
    18971937        }
     
    19041944        {
    19051945                param_obj = PySequence_Fast(param_obj,
    1906                         "Query parameters must be a sequence");
     1946                        "Method query() expects a sequence as second argument");
    19071947                if (!param_obj)
    19081948                {
     
    20702110
    20712111        /* stores result and returns object */
     2112        Py_XINCREF(self);
     2113        npgobj->pgcnx = self;
    20722114        npgobj->result = result;
    20732115        npgobj->encoding = encoding;
     
    21292171        {
    21302172                PyErr_SetString(PyExc_TypeError,
    2131                         "Method getline() takes no parameters");
     2173                        "Method getline() takes no arguments");
    21322174                return NULL;
    21332175        }
     
    21692211        {
    21702212                PyErr_SetString(PyExc_TypeError,
    2171                         "Method endcopy() takes no parameters");
     2213                        "Method endcopy() takes no arguments");
    21722214                return NULL;
    21732215        }
     
    22252267        {
    22262268                PyErr_SetString(PyExc_TypeError,
    2227                         "inserttable(table, content), with table (string) "
    2228                         "and content (list)");
     2269                        "Method inserttable() expects a string and a list as arguments");
    22292270                return NULL;
    22302271        }
     
    22442285        {
    22452286                PyErr_SetString(PyExc_TypeError,
    2246                         "Second arg must be some kind of array");
     2287                        "Method inserttable() expects some kind of array"
     2288                        " as second argument");
    22472289                return NULL;
    22482290        }
     
    24492491        {
    24502492                PyErr_SetString(PyExc_TypeError,
    2451                         "Method transaction() takes no parameters");
     2493                        "Method transaction() takes no arguments");
    24522494                return NULL;
    24532495        }
     
    24582500/* get parameter setting */
    24592501static char connParameter__doc__[] =
    2460 "parameter() -- look up a current parameter setting";
     2502"parameter(name) -- look up a current parameter setting";
    24612503
    24622504static PyObject *
     
    24752517        {
    24762518                PyErr_SetString(PyExc_TypeError,
    2477                         "parameter(name), with name (string)");
     2519                        "Method parameter() takes a string as argument");
    24782520                return NULL;
    24792521        }
     
    25232565        else
    25242566        {
    2525                 PyErr_SetString(PyExc_TypeError, "escape_literal() expects a string");
     2567                PyErr_SetString(PyExc_TypeError,
     2568                        "Method escape_literal() expects a string as argument");
    25262569                return NULL;
    25272570        }
     
    25742617        {
    25752618                PyErr_SetString(PyExc_TypeError,
    2576                         "escape_identifier() expects a string");
     2619                        "Method escape_identifier() expects a string as argument");
    25772620                return NULL;
    25782621        }
     
    26262669        else
    26272670        {
    2628                 PyErr_SetString(PyExc_TypeError, "escape_string() expects a string");
     2671                PyErr_SetString(PyExc_TypeError,
     2672                        "Method escape_string() expects a string as argument");
    26292673                return NULL;
    26302674        }
     
    26822726        else
    26832727        {
    2684                 PyErr_SetString(PyExc_TypeError, "escape_bytea() expects a string");
     2728                PyErr_SetString(PyExc_TypeError,
     2729                        "Method escape_bytea() expects a string as argument");
    26852730                return NULL;
    26862731        }
     
    28162861        {
    28172862                PyErr_SetString(PyExc_TypeError,
    2818                         "Method reset() takes no parameters");
     2863                        "Method reset() takes no arguments");
    28192864                return NULL;
    28202865        }
     
    28432888        {
    28442889                PyErr_SetString(PyExc_TypeError,
    2845                         "Method cancel() takes no parameters");
     2890                        "Method cancel() takes no arguments");
    28462891                return NULL;
    28472892        }
     
    28682913        {
    28692914                PyErr_SetString(PyExc_TypeError,
    2870                         "Method fileno() takes no parameters");
     2915                        "Method fileno() takes no arguments");
    28712916                return NULL;
    28722917        }
     
    28792924}
    28802925
     2926/* set external typecast callback function */
     2927static char connSetCastHook__doc__[] =
     2928"set_cast_hook(func) -- set a fallback typecast function";
     2929
     2930static PyObject *
     2931connSetCastHook(connObject * self, PyObject * args)
     2932{
     2933        PyObject *ret = NULL;
     2934        PyObject *func;
     2935
     2936        if (PyArg_ParseTuple(args, "O", &func))
     2937        {
     2938                if (func == Py_None)
     2939                {
     2940                        Py_XDECREF(self->cast_hook);
     2941                        self->cast_hook = NULL;
     2942                        Py_INCREF(Py_None); ret = Py_None;
     2943                }
     2944                else if (PyCallable_Check(func))
     2945                {
     2946                        Py_XINCREF(func); Py_XDECREF(self->cast_hook);
     2947                        self->cast_hook = func;
     2948                        Py_INCREF(Py_None); ret = Py_None;
     2949                }
     2950                else
     2951                        PyErr_SetString(PyExc_TypeError,
     2952                                "Method set_cast_hook() expects"
     2953                                 " a callable or None as argument");
     2954        }
     2955        return ret;
     2956}
     2957
     2958/* get notice receiver callback function */
     2959static char connGetCastHook__doc__[] =
     2960"get_cast_hook() -- get the fallback typecast function";
     2961
     2962static PyObject *
     2963connGetCastHook(connObject * self, PyObject * args)
     2964{
     2965        PyObject *ret = NULL;
     2966
     2967        if (PyArg_ParseTuple(args, ""))
     2968        {
     2969                ret = self->cast_hook;
     2970                if (!ret)
     2971                        ret = Py_None;
     2972                Py_INCREF(ret);
     2973        }
     2974        else
     2975                PyErr_SetString(PyExc_TypeError,
     2976                        "Method get_cast_hook() takes no arguments");
     2977
     2978        return ret;
     2979}
     2980
    28812981/* set notice receiver callback function */
    28822982static char connSetNoticeReceiver__doc__[] =
    2883 "set_notice_receiver() -- set the current notice receiver";
     2983"set_notice_receiver(func) -- set the current notice receiver";
    28842984
    28852985static PyObject *
     
    28872987{
    28882988        PyObject *ret = NULL;
    2889         PyObject *proc;
    2890 
    2891         if (PyArg_ParseTuple(args, "O", &proc))
    2892         {
    2893                 if (PyCallable_Check(proc))
    2894                 {
    2895                         Py_XINCREF(proc);
    2896                         self->notice_receiver = proc;
     2989        PyObject *func;
     2990
     2991        if (PyArg_ParseTuple(args, "O", &func))
     2992        {
     2993                if (func == Py_None)
     2994                {
     2995                        Py_XDECREF(self->notice_receiver);
     2996                        self->notice_receiver = NULL;
     2997                        Py_INCREF(Py_None); ret = Py_None;
     2998                }
     2999                else if (PyCallable_Check(func))
     3000                {
     3001                        Py_XINCREF(func); Py_XDECREF(self->notice_receiver);
     3002                        self->notice_receiver = func;
    28973003                        PQsetNoticeReceiver(self->cnx, notice_receiver, self);
    28983004                        Py_INCREF(Py_None); ret = Py_None;
     
    29003006                else
    29013007                        PyErr_SetString(PyExc_TypeError,
    2902                                 "Notice receiver must be callable");
     3008                                "Method set_notice_receiver() expects"
     3009                                 " a callable or None as argument");
    29033010        }
    29043011        return ret;
     
    29223029        }
    29233030        else
    2924         {
    2925                 PyErr_SetString(PyExc_TypeError,
    2926                         "Method get_notice_receiver() takes no parameters");
    2927         }
     3031                PyErr_SetString(PyExc_TypeError,
     3032                        "Method get_notice_receiver() takes no arguments");
     3033
    29283034        return ret;
    29293035}
     
    29803086        {
    29813087                PyErr_SetString(PyExc_TypeError,
    2982                         "Method getnotify() takes no parameters");
     3088                        "Method getnotify() takes no arguments");
    29833089                return NULL;
    29843090        }
     
    30523158        {"close", (PyCFunction) connClose, METH_VARARGS, connClose__doc__},
    30533159        {"fileno", (PyCFunction) connFileno, METH_VARARGS, connFileno__doc__},
     3160        {"get_cast_hook", (PyCFunction) connGetCastHook, METH_VARARGS,
     3161                        connGetCastHook__doc__},
     3162        {"set_cast_hook", (PyCFunction) connSetCastHook, METH_VARARGS,
     3163                        connSetCastHook__doc__},
    30543164        {"get_notice_receiver", (PyCFunction) connGetNoticeReceiver, METH_VARARGS,
    30553165                        connGetNoticeReceiver__doc__},
     
    32423352        if (!PyArg_ParseTuple(args, ""))
    32433353        {
    3244                 PyErr_SetString(PyExc_TypeError, "Method close() takes no parameter");
     3354                PyErr_SetString(PyExc_TypeError, "Method close() takes no arguments");
    32453355                return NULL;
    32463356        }
     
    33043414        else
    33053415        {
    3306                 PyErr_SetString(PyExc_TypeError, "Executed SQL must be a string");
     3416                PyErr_SetString(PyExc_TypeError,
     3417                        "Method execute() expects a string as argument");
    33073418                return NULL;
    33083419        }
     
    33993510        {
    34003511                PyErr_SetString(PyExc_TypeError,
    3401                         "Method oidstatus() takes no parameters");
     3512                        "Method oidstatus() takes no arguments");
    34023513                return NULL;
    34033514        }
     
    35173628                char            errbuf[256];
    35183629                PyOS_snprintf(errbuf, sizeof(errbuf),
    3519                         "Method %s() takes no parameter", __movename[move]);
     3630                        "Method %s() takes no arguments", __movename[move]);
    35203631                PyErr_SetString(PyExc_TypeError, errbuf);
    35213632                return NULL;
     
    36483759        {
    36493760                PyErr_SetString(PyExc_TypeError,
    3650                         "putdata() expects a buffer, None or an exception");
     3761                        "Method putdata() expects a buffer, None"
     3762                         " or an exception as argument");
    36513763                return NULL;
    36523764        }
     
    38713983        {
    38723984                PyErr_SetString(PyExc_TypeError,
    3873                         "Method listinfo() takes no parameter");
     3985                        "Method listinfo() takes no arguments");
    38743986                return NULL;
    38753987        }
     
    41234235        int                     pgport;
    41244236        char            port_buffer[20];
    4125         connObject   *npgobj;
     4237        connObject *npgobj;
    41264238
    41274239        pghost = pgopt = pgdbname = pguser = pgpasswd = NULL;
     
    41674279        npgobj->valid = 1;
    41684280        npgobj->cnx = NULL;
     4281        npgobj->cast_hook = NULL;
    41694282        npgobj->notice_receiver = NULL;
    41704283
     
    41934306queryDealloc(queryObject *self)
    41944307{
     4308        Py_XDECREF(self->pgcnx);
    41954309        if (self->result)
    41964310                PQclear(self->result);
     
    42104324        {
    42114325                PyErr_SetString(PyExc_TypeError,
    4212                         "Method ntuples() takes no parameters");
     4326                        "Method ntuples() takes no arguments");
    42134327                return NULL;
    42144328        }
     
    42344348        {
    42354349                PyErr_SetString(PyExc_TypeError,
    4236                         "Method listfields() takes no parameters");
     4350                        "Method listfields() takes no arguments");
    42374351                return NULL;
    42384352        }
     
    43264440        {
    43274441                PyErr_SetString(PyExc_TypeError,
    4328                         "Method getresult() takes no parameters");
     4442                        "Method getresult() takes no arguments");
    43294443                return NULL;
    43304444        }
     
    43714485                                else if (type == PYGRES_BYTEA)
    43724486                                        val = cast_bytea_text(s);
     4487                                else if (type == PYGRES_OTHER)
     4488                                        val = cast_other(s,
     4489                                                PQgetlength(self->result, i, j), encoding,
     4490                                                PQftype(self->result, j), self->pgcnx->cast_hook);
    43734491                                else if (type & PYGRES_TEXT)
    43744492                                        val = cast_sized_text(s, PQgetlength(self->result, i, j),
     
    44194537        {
    44204538                PyErr_SetString(PyExc_TypeError,
    4421                         "Method dictresult() takes no parameters");
     4539                        "Method dictresult() takes no arguments");
    44224540                return NULL;
    44234541        }
     
    44644582                                else if (type == PYGRES_BYTEA)
    44654583                                        val = cast_bytea_text(s);
     4584                                else if (type == PYGRES_OTHER)
     4585                                        val = cast_other(s,
     4586                                                PQgetlength(self->result, i, j), encoding,
     4587                                                PQftype(self->result, j), self->pgcnx->cast_hook);
    44664588                                else if (type & PYGRES_TEXT)
    44674589                                        val = cast_sized_text(s, PQgetlength(self->result, i, j),
     
    45104632                {
    45114633                        PyErr_SetString(PyExc_TypeError,
    4512                                 "Method namedresult() takes no parameters");
     4634                                "Method namedresult() takes no arguments");
    45134635                        return NULL;
    45144636                }
     
    47314853        else
    47324854        {
    4733                 PyErr_SetString(PyExc_TypeError, "escape_string() expects a string");
     4855                PyErr_SetString(PyExc_TypeError,
     4856                        "Method escape_string() expects a string as argument");
    47344857                return NULL;
    47354858        }
     
    47864909        else
    47874910        {
    4788                 PyErr_SetString(PyExc_TypeError, "escape_bytea() expects a string");
     4911                PyErr_SetString(PyExc_TypeError,
     4912                        "Method escape_bytea() expects a string as argument");
    47894913                return NULL;
    47904914        }
     
    48344958        else
    48354959        {
    4836                 PyErr_SetString(PyExc_TypeError, "unescape_bytea() expects a string");
     4960                PyErr_SetString(PyExc_TypeError,
     4961                        "Method unescape_bytea() expects a string as argument");
    48374962                return NULL;
    48384963        }
     
    48734998        }
    48744999        else
    4875         {
    4876                 PyErr_SetString(PyExc_TypeError,
    4877                         "get_decimal_point() takes no parameter");
    4878         }
     5000                PyErr_SetString(PyExc_TypeError,
     5001                        "Function get_decimal_point() takes no arguments");
    48795002
    48805003        return ret;
     
    49065029        }
    49075030        else
    4908         {
    4909                 PyErr_SetString(PyExc_TypeError,
    4910                         "set_decimal_point() expects a decimal mark character");
    4911         }
     5031                PyErr_SetString(PyExc_TypeError,
     5032                        "Function set_decimal_mark() expects"
     5033                        " a decimal mark character as argument");
    49125034
    49135035        return ret;
     
    49165038/* get decimal type */
    49175039static char pgGetDecimal__doc__[] =
    4918 "get_decimal() -- set a decimal type to be used for numeric values";
     5040"get_decimal() -- get the decimal type to be used for numeric values";
    49195041
    49205042static PyObject *
     
    49285050                Py_INCREF(ret);
    49295051        }
     5052        else
     5053                PyErr_SetString(PyExc_TypeError,
     5054                        "Function get_decimal() takes no arguments");
    49305055
    49315056        return ret;
     
    49565081                else
    49575082                        PyErr_SetString(PyExc_TypeError,
    4958                                 "Decimal type must be None or callable");
     5083                                "Function set_decimal() expects"
     5084                                 " a callable or None as argument");
    49595085        }
    49605086
     
    49765102                Py_INCREF(ret);
    49775103        }
     5104        else
     5105                PyErr_SetString(PyExc_TypeError,
     5106                        "Function get_bool() takes no arguments");
    49785107
    49795108        return ret;
     
    49965125                Py_INCREF(Py_None); ret = Py_None;
    49975126        }
     5127        else
     5128                PyErr_SetString(PyExc_TypeError,
     5129                        "Function set_bool() expects a boolean value as argument");
    49985130
    49995131        return ret;
     
    50025134/* get named result factory */
    50035135static char pgGetNamedresult__doc__[] =
    5004 "get_namedresult(cls) -- get the function used for getting named results";
     5136"get_namedresult() -- get the function used for getting named results";
    50055137
    50065138static PyObject *
     
    50145146                Py_INCREF(ret);
    50155147        }
     5148        else
     5149                PyErr_SetString(PyExc_TypeError,
     5150                        "Function get_namedresult() takes no arguments");
    50165151
    50175152        return ret;
     
    50205155/* set named result factory */
    50215156static char pgSetNamedresult__doc__[] =
    5022 "set_namedresult(cls) -- set a function to be used for getting named results";
     5157"set_namedresult(func) -- set a function to be used for getting named results";
    50235158
    50245159static PyObject *
     
    50415176                }
    50425177                else
    5043                         PyErr_SetString(PyExc_TypeError, "Parameter must be callable");
     5178                        PyErr_SetString(PyExc_TypeError,
     5179                                "Function set_namedresult() expectst"
     5180                                 " a callable or None as argument");
    50445181        }
    50455182
     
    50495186/* get json decode function */
    50505187static char pgGetJsondecode__doc__[] =
    5051 "get_jsondecode(cls) -- get the function used for decoding json results";
     5188"get_jsondecode() -- get the function used for decoding json results";
    50525189
    50535190static PyObject *
     
    50585195        if (PyArg_ParseTuple(args, ""))
    50595196        {
    5060                 ret = jsondecode ? jsondecode : Py_None;
     5197                ret = jsondecode;
     5198                if (!ret)
     5199                        ret = Py_None;
    50615200                Py_INCREF(ret);
    50625201        }
    5063 
     5202        else
     5203        {
     5204                PyErr_SetString(PyExc_TypeError,
     5205                        "Function get_jsondecode() takes no arguments");
     5206        }
    50645207        return ret;
    50655208}
     
    50675210/* set json decode function */
    50685211static char pgSetJsondecode__doc__[] =
    5069 "set_jsondecode(cls) -- set a function to be used for decoding json results";
     5212"set_jsondecode() -- set a function to be used for decoding json results";
    50705213
    50715214static PyObject *
     
    50885231                }
    50895232                else
    5090                         PyErr_SetString(PyExc_TypeError, "Parameter must be callable");
     5233                        PyErr_SetString(PyExc_TypeError,
     5234                                "Function jsondecode() expects"
     5235                                 " a callable or None as argument");
    50915236        }
    50925237
     
    51075252        {
    51085253                PyErr_SetString(PyExc_TypeError,
    5109                         "Method get_defhost() takes no parameter");
     5254                        "Function get_defhost() takes no arguments");
    51105255                return NULL;
    51115256        }
     
    51295274        {
    51305275                PyErr_SetString(PyExc_TypeError,
    5131                         "set_defhost(name), with name (string/None)");
     5276                        "Function set_defhost() expects a string or None as argument");
    51325277                return NULL;
    51335278        }
     
    51585303        {
    51595304                PyErr_SetString(PyExc_TypeError,
    5160                         "Method get_defbase() takes no parameter");
     5305                        "Function get_defbase() takes no arguments");
    51615306                return NULL;
    51625307        }
     
    51805325        {
    51815326                PyErr_SetString(PyExc_TypeError,
    5182                         "set_defbase(name), with name (string/None)");
     5327                        "Function set_defbase() Argument a string or None as argument");
    51835328                return NULL;
    51845329        }
     
    52095354        {
    52105355                PyErr_SetString(PyExc_TypeError,
    5211                         "Method get_defopt() takes no parameter");
     5356                        "Function get_defopt() takes no arguments");
    52125357                return NULL;
    52135358        }
     
    52315376        {
    52325377                PyErr_SetString(PyExc_TypeError,
    5233                         "set_defopt(name), with name (string/None)");
     5378                        "Function set_defopt() expects a string or None as argument");
    52345379                return NULL;
    52355380        }
     
    52605405        {
    52615406                PyErr_SetString(PyExc_TypeError,
    5262                         "Method get_defuser() takes no parameter");
    5263 
     5407                        "Function get_defuser() takes no arguments");
    52645408                return NULL;
    52655409        }
     
    52845428        {
    52855429                PyErr_SetString(PyExc_TypeError,
    5286                         "set_defuser(name), with name (string/None)");
     5430                        "Function set_defuser() expects a string or None as argument");
    52875431                return NULL;
    52885432        }
     
    53155459        {
    53165460                PyErr_SetString(PyExc_TypeError,
    5317                         "set_defpasswd(password), with password (string/None)");
     5461                        "Function set_defpasswd() expects a string or None as argument");
    53185462                return NULL;
    53195463        }
     
    53425486        {
    53435487                PyErr_SetString(PyExc_TypeError,
    5344                         "Method get_defport() takes no parameter");
     5488                        "Function get_defport() takes no arguments");
    53455489                return NULL;
    53465490        }
     
    53635507        if ((!PyArg_ParseTuple(args, "l", &port)) || (port < -1))
    53645508        {
    5365                 PyErr_SetString(PyExc_TypeError, "set_defport(port), with port "
    5366                         "(positive integer/-1)");
     5509                PyErr_SetString(PyExc_TypeError,
     5510                        "Function set_deport expects"
     5511                         " a positive integer or -1 as argument");
    53675512                return NULL;
    53685513        }
     
    54155560        else
    54165561        {
    5417                 PyErr_SetString(PyExc_TypeError, "cast_array() expects a string");
     5562                PyErr_SetString(PyExc_TypeError,
     5563                        "Function cast_array() expects a string as first argument");
    54185564                return NULL;
    54195565        }
     
    54285574        else if (!PyCallable_Check(cast_obj))
    54295575        {
    5430                 PyErr_SetString(PyExc_TypeError, "The cast argument must be callable");
     5576                PyErr_SetString(PyExc_TypeError,
     5577                        "Function cast_array() expects a callable as second argument");
    54315578                return NULL;
    54325579        }
     
    54715618        else
    54725619        {
    5473                 PyErr_SetString(PyExc_TypeError, "cast_record() expects a string");
     5620                PyErr_SetString(PyExc_TypeError,
     5621                        "Function cast_record() expects a string as first argument");
    54745622                return NULL;
    54755623        }
     
    54945642        {
    54955643                PyErr_SetString(PyExc_TypeError,
    5496                         "The cast argument must be callable or a tuple or list of such");
     5644                        "Function cast_record() expects a callable"
     5645                         " or tuple or list of callables as second argument");
    54975646                return NULL;
    54985647        }
  • trunk/pgtypes.h

    r781 r798  
    108108#define BYTEAARRAYOID 1001
    109109#define CHARARRAYOID 1002
     110#define NAMEARRAYOID 1003
    110111#define XIDARRAYOID 1011
    111112#define CIDARRAYOID 1012
  • trunk/tests/test_classic_connection.py

    r781 r798  
    123123        methods = '''cancel close endcopy
    124124            escape_bytea escape_identifier escape_literal escape_string
    125             fileno get_notice_receiver getline getlo getnotify
     125            fileno get_cast_hook get_notice_receiver getline getlo getnotify
    126126            inserttable locreate loimport parameter putline query reset
    127             set_notice_receiver source transaction'''.split()
     127            set_cast_hook set_notice_receiver source transaction'''.split()
    128128        connection_methods = [a for a in dir(self.connection)
    129129            if not a.startswith('__') and self.is_method(a)]
     
    13251325
    13261326    def testSetNoticeReceiver(self):
    1327         self.assertRaises(TypeError, self.c.set_notice_receiver, None)
    13281327        self.assertRaises(TypeError, self.c.set_notice_receiver, 42)
     1328        self.assertRaises(TypeError, self.c.set_notice_receiver, 'invalid')
    13291329        self.assertIsNone(self.c.set_notice_receiver(lambda notice: None))
     1330        self.assertIsNone(self.c.set_notice_receiver(None))
    13301331
    13311332    def testSetAndGetNoticeReceiver(self):
     
    13331334        self.assertIsNone(self.c.set_notice_receiver(r))
    13341335        self.assertIs(self.c.get_notice_receiver(), r)
     1336        self.assertIsNone(self.c.set_notice_receiver(None))
     1337        self.assertIsNone(self.c.get_notice_receiver())
    13351338
    13361339    def testNoticeReceiver(self):
  • trunk/tests/test_classic_dbwrapper.py

    r793 r798  
    180180            'begin',
    181181            'cancel', 'clear', 'close', 'commit',
    182             'db', 'dbname', 'debug', 'decode_json', 'delete',
     182            'db', 'dbname', 'dbtypes',
     183            'debug', 'decode_json', 'delete',
    183184            'encode_json', 'end', 'endcopy', 'error',
    184185            'escape_bytea', 'escape_identifier',
     
    186187            'fileno',
    187188            'get', 'get_as_dict', 'get_as_list',
    188             'get_attnames', 'get_databases',
    189             'get_notice_receiver', 'get_parameter',
    190             'get_relations', 'get_tables',
     189            'get_attnames', 'get_cast_hook',
     190            'get_databases', 'get_notice_receiver',
     191            'get_parameter', 'get_relations', 'get_tables',
    191192            'getline', 'getlo', 'getnotify',
    192193            'has_table_privilege', 'host',
     
    200201            'release', 'reopen', 'reset', 'rollback',
    201202            'savepoint', 'server_version',
    202             'set_notice_receiver', 'set_parameter',
     203            'set_cast_hook', 'set_notice_receiver',
     204            'set_parameter',
    203205            'source', 'start', 'status',
    204206            'transaction', 'truncate',
     
    387389        db.query("drop table if exists test cascade")
    388390        db.query("create table test ("
    389             "i2 smallint, i4 integer, i8 bigint,"
    390             " d numeric, f4 real, f8 double precision, m money,"
    391             " v4 varchar(4), c4 char(4), t text)")
     391                 "i2 smallint, i4 integer, i8 bigint,"
     392                 " d numeric, f4 real, f8 double precision, m money,"
     393                 " v4 varchar(4), c4 char(4), t text)")
    392394        db.query("create or replace view test_view as"
    393             " select i4, v4 from test")
     395                 " select i4, v4 from test")
    394396        db.close()
    395397        cls.cls_set_up = True
     
    420422
    421423    def createTable(self, table, definition,
    422             temporary=True, oids=None, values=None):
     424                    temporary=True, oids=None, values=None):
    423425        query = self.db.query
    424426        if not '"' in table or '.' in table:
     
    470472        self.assertEqual(r, u"'that''s kÀse'")
    471473        self.assertEqual(f(r"It's fine to have a \ inside."),
    472             r" E'It''s fine to have a \\ inside.'")
     474                         r" E'It''s fine to have a \\ inside.'")
    473475        self.assertEqual(f('No "quotes" must be escaped.'),
    474             "'No \"quotes\" must be escaped.'")
     476                         "'No \"quotes\" must be escaped.'")
    475477
    476478    def testEscapeIdentifier(self):
     
    489491        self.assertEqual(r, u'"that\'s kÀse"')
    490492        self.assertEqual(f(r"It's fine to have a \ inside."),
    491             '"It\'s fine to have a \\ inside."')
     493                         '"It\'s fine to have a \\ inside."')
    492494        self.assertEqual(f('All "quotes" must be escaped.'),
    493             '"All ""quotes"" must be escaped."')
     495                         '"All ""quotes"" must be escaped."')
    494496
    495497    def testEscapeString(self):
     
    508510        self.assertEqual(r, u"that''s kÀse")
    509511        self.assertEqual(f(r"It's fine to have a \ inside."),
    510             r"It''s fine to have a \ inside.")
     512                         r"It''s fine to have a \ inside.")
    511513
    512514    def testEscapeBytea(self):
     
    545547        self.assertEqual(f(r'\\x706c61696e'), b'\\x706c61696e')
    546548        self.assertEqual(f(r'\\x746861742773206be47365'),
    547             b'\\x746861742773206be47365')
     549                         b'\\x746861742773206be47365')
    548550        self.assertEqual(f(r'\\x4f007073ff21'), b'\\x4f007073ff21')
    549551
     
    552554        self.assertIsNone(f('null'))
    553555        data = {
    554           "id": 1, "name": "Foo", "price": 1234.5,
    555           "new": True, "note": None,
    556           "tags": ["Bar", "Eek"],
    557           "stock": {"warehouse": 300, "retail": 20}}
     556            "id": 1, "name": "Foo", "price": 1234.5,
     557            "new": True, "note": None,
     558            "tags": ["Bar", "Eek"],
     559            "stock": {"warehouse": 300, "retail": 20}}
    558560        text = json.dumps(data)
    559561        r = f(text)
     
    571573        self.assertEqual(f(None), 'null')
    572574        data = {
    573           "id": 1, "name": "Foo", "price": 1234.5,
    574           "new": True, "note": None,
    575           "tags": ["Bar", "Eek"],
    576           "stock": {"warehouse": 300, "retail": 20}}
     575            "id": 1, "name": "Foo", "price": 1234.5,
     576            "new": True, "note": None,
     577            "tags": ["Bar", "Eek"],
     578            "stock": {"warehouse": 300, "retail": 20}}
    577579        text = json.dumps(data)
    578580        r = f(data)
     
    666668        self.assertEqual(g('standard_conforming_strings'), 'on')
    667669        self.assertRaises(ValueError, f, set([ 'default_with_oids',
    668             'standard_conforming_strings']), ['off', 'on'])
     670                                               'standard_conforming_strings']), ['off', 'on'])
    669671        f(set(['default_with_oids', 'standard_conforming_strings']),
    670             ['off', 'off'])
     672          ['off', 'off'])
    671673        self.assertEqual(g('default_with_oids'), 'off')
    672674        self.assertEqual(g('standard_conforming_strings'), 'off')
     
    797799        values = [(2, "World!"), (1, "Hello")]
    798800        self.createTable(table, "n smallint, t varchar",
    799             temporary=True, oids=True, values=values)
     801                         temporary=True, oids=True, values=values)
    800802        r = self.db.query('select t from "%s" order by n' % table).getresult()
    801803        r = ', '.join(row[0] for row in r)
     
    853855        q = "select * from test_table order by 1, 2"
    854856        self.assertEqual(query(q).getresult(),
    855             [(1, 2), (3, 4), (5, 6)])
     857                         [(1, 2), (3, 4), (5, 6)])
    856858        q = "select * from test_table where n1=$1 and n2=$2"
    857859        self.assertEqual(query(q, 3, 4).getresult(), [(3, 4)])
     
    861863        q = "select * from test_table order by 1, 2"
    862864        self.assertEqual(query(q).getresult(),
    863             [(1, 2), (3, 7), (5, 6)])
     865                         [(1, 2), (3, 7), (5, 6)])
    864866        q = "delete from test_table where n2!=$1"
    865867        r = query(q, 4)
     
    883885            self.createTable('%s1' % t, 'b smallint primary key')
    884886            self.createTable('%s2' % t,
    885                 'c smallint, d smallint primary key')
     887                             'c smallint, d smallint primary key')
    886888            self.createTable('%s3' % t,
    887                 'e smallint, f smallint, g smallint, h smallint, i smallint,'
    888                 ' primary key (f, h)')
     889                             'e smallint, f smallint, g smallint, h smallint, i smallint,'
     890                             ' primary key (f, h)')
    889891            self.createTable('%s4' % t,
    890                 'e smallint, f smallint, g smallint, h smallint, i smallint,'
    891                 ' primary key (h, f)')
     892                             'e smallint, f smallint, g smallint, h smallint, i smallint,'
     893                             ' primary key (h, f)')
    892894            self.createTable('%s5' % t,
    893                 'more_than_one_letter varchar primary key')
     895                             'more_than_one_letter varchar primary key')
    894896            self.createTable('%s6' % t,
    895                 '"with space" date primary key')
     897                             '"with space" date primary key')
    896898            self.createTable('%s7' % t,
    897                 'a_very_long_column_name varchar, "with space" date, "42" int,'
    898                 ' primary key (a_very_long_column_name, "with space", "42")')
     899                             'a_very_long_column_name varchar, "with space" date, "42" int,'
     900                             ' primary key (a_very_long_column_name, "with space", "42")')
    899901            self.assertRaises(KeyError, pkey, '%s0' % t)
    900902            self.assertEqual(pkey('%s1' % t), 'b')
     
    940942        get_tables = self.db.get_tables
    941943        tables = ('A very Special Name', 'A_MiXeD_quoted_NaMe',
    942             'Hello, Test World!', 'Zoro', 'a1', 'a2', 'a321',
    943             'averyveryveryveryveryveryveryreallyreallylongtablename',
    944             'b0', 'b3', 'x', 'xXx', 'xx', 'y', 'z')
     944                  'Hello, Test World!', 'Zoro', 'a1', 'a2', 'a321',
     945                  'averyveryveryveryveryveryveryreallyreallylongtablename',
     946                  'b0', 'b3', 'x', 'xXx', 'xx', 'y', 'z')
    945947        for t in tables:
    946948            self.db.query('drop table if exists "%s" cascade' % t)
     
    987989        get_attnames = self.db.get_attnames
    988990        self.assertRaises(pg.ProgrammingError,
    989             self.db.get_attnames, 'does_not_exist')
     991                          self.db.get_attnames, 'does_not_exist')
    990992        self.assertRaises(pg.ProgrammingError,
    991             self.db.get_attnames, 'has.too.many.dots')
     993                          self.db.get_attnames, 'has.too.many.dots')
    992994        r = get_attnames('test')
    993995        self.assertIsInstance(r, dict)
     
    10031005                v4='text', c4='text', t='text'))
    10041006        self.createTable('test_table',
    1005             'n int, alpha smallint, beta bool,'
    1006             ' gamma char(5), tau text, v varchar(3)')
     1007                         'n int, alpha smallint, beta bool,'
     1008                         ' gamma char(5), tau text, v varchar(3)')
    10071009        r = get_attnames('test_table')
    10081010        self.assertIsInstance(r, dict)
     
    10201022        table = 'test table for get_attnames()'
    10211023        self.createTable(table,
    1022             '"Prime!" smallint, "much space" integer, "Questions?" text')
     1024                         '"Prime!" smallint, "much space" integer, "Questions?" text')
    10231025        r = get_attnames(table)
    10241026        self.assertIsInstance(r, dict)
     
    10321034        table = 'yet another test table for get_attnames()'
    10331035        self.createTable(table,
    1034             'a smallint, b integer, c bigint,'
    1035             ' e numeric, f real, f2 double precision, m money,'
    1036             ' x smallint, y smallint, z smallint,'
    1037             ' Normal_NaMe smallint, "Special Name" smallint,'
    1038             ' t text, u char(2), v varchar(2),'
    1039             ' primary key (y, u)', oids=True)
     1036                         'a smallint, b integer, c bigint,'
     1037                         ' e numeric, f real, f2 double precision, m money,'
     1038                         ' x smallint, y smallint, z smallint,'
     1039                         ' Normal_NaMe smallint, "Special Name" smallint,'
     1040                         ' t text, u char(2), v varchar(2),'
     1041                         ' primary key (y, u)', oids=True)
    10401042        r = get_attnames(table)
    10411043        self.assertIsInstance(r, dict)
     
    10501052        else:
    10511053            self.assertEqual(r, {'a': 'int', 'b': 'int', 'c': 'int',
    1052                 'e': 'num', 'f': 'float', 'f2': 'float', 'm': 'money',
    1053                 'normal_name': 'int', 'Special Name': 'int',
    1054                 'u': 'text', 't': 'text', 'v': 'text',
    1055                 'y': 'int', 'x': 'int', 'z': 'int', 'oid': 'int'})
     1054                                 'e': 'num', 'f': 'float', 'f2': 'float', 'm': 'money',
     1055                                 'normal_name': 'int', 'Special Name': 'int',
     1056                                 'u': 'text', 't': 'text', 'v': 'text',
     1057                                 'y': 'int', 'x': 'int', 'z': 'int', 'oid': 'int'})
    10561058
    10571059    def testGetAttnamesWithRegtypes(self):
    10581060        get_attnames = self.db.get_attnames
    10591061        self.createTable('test_table',
    1060             ' n int, alpha smallint, beta bool,'
    1061             ' gamma char(5), tau text, v varchar(3)')
     1062                         ' n int, alpha smallint, beta bool,'
     1063                         ' gamma char(5), tau text, v varchar(3)')
    10621064        use_regtypes = self.db.use_regtypes
    10631065        regtypes = use_regtypes()
     
    10761078        get_attnames = self.db.get_attnames
    10771079        self.createTable('test_table',
    1078             ' n int, alpha smallint, beta bool,'
    1079             ' gamma char(5), tau text, v varchar(3)')
     1080                         ' n int, alpha smallint, beta bool,'
     1081                         ' gamma char(5), tau text, v varchar(3)')
    10801082        use_regtypes = self.db.use_regtypes
    10811083        regtypes = use_regtypes()
     
    11371139        table = 'test table for get_attnames'
    11381140        self.createTable(table,
    1139             ' n int, alpha smallint, v varchar(3),'
    1140             ' gamma char(5), tau text, beta bool')
     1141                         ' n int, alpha smallint, v varchar(3),'
     1142                         ' gamma char(5), tau text, beta bool')
    11411143        r = get_attnames(table)
    11421144        self.assertIsInstance(r, OrderedDict)
     
    11761178        table = 'test table for get_attnames'
    11771179        self.createTable(table,
    1178             ' n int, alpha smallint, v varchar(3),'
    1179             ' gamma char(5), tau text, beta bool')
     1180                         ' n int, alpha smallint, v varchar(3),'
     1181                         ' gamma char(5), tau text, beta bool')
    11801182        r = get_attnames(table)
    11811183        self.assertIsInstance(r, AttrDict)
     
    12131215        self.assertRaises(TypeError, get, table)
    12141216        self.createTable(table, 'n integer, t text',
    1215             values=enumerate('xyz', start=1))
     1217                         values=enumerate('xyz', start=1))
    12161218        self.assertRaises(pg.ProgrammingError, get, table, 2)
    12171219        r = get(table, 2, 'n')
     
    12661268        table = 'get_with_oid_test_table'
    12671269        self.createTable(table, 'n integer, t text', oids=True,
    1268             values=enumerate('xyz', start=1))
     1270                         values=enumerate('xyz', start=1))
    12691271        self.assertRaises(pg.ProgrammingError, get, table, 2)
    12701272        self.assertRaises(KeyError, get, table, {}, 'oid')
     
    13301332        table = 'get_test_table_1'
    13311333        self.createTable(table, 'n integer primary key, t text',
    1332             values=enumerate('abc', start=1))
     1334                         values=enumerate('abc', start=1))
    13331335        self.assertEqual(get(table, 2)['t'], 'b')
    13341336        self.assertEqual(get(table, 1, 'n')['t'], 'a')
     
    13411343        table = 'get_test_table_2'
    13421344        self.createTable(table,
    1343             'n integer, m integer, t text, primary key (n, m)',
    1344             values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
    1345                 for n in range(3) for m in range(2)])
     1345                         'n integer, m integer, t text, primary key (n, m)',
     1346                         values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
     1347                                 for n in range(3) for m in range(2)])
    13461348        self.assertRaises(KeyError, get, table, 2)
    13471349        self.assertEqual(get(table, (1, 1))['t'], 'a')
     
    13621364        table = 'test table for get()'
    13631365        self.createTable(table, '"Prime!" smallint primary key,'
    1364             ' "much space" integer, "Questions?" text',
    1365             values=[(17, 1001, 'No!')])
     1366                                ' "much space" integer, "Questions?" text',
     1367                         values=[(17, 1001, 'No!')])
    13661368        r = get(table, 17)
    13671369        self.assertIsInstance(r, dict)
     
    13731375        self.db.query('delete from test where i4=14')
    13741376        self.db.query('insert into test (i4, v4) values('
    1375             "14, 'abc4')")
     1377                      "14, 'abc4')")
    13761378        r = self.db.get('test_view', 14, 'i4')
    13771379        self.assertIn('v4', r)
     
    13821384        query = self.db.query
    13831385        self.createTable('test_students',
    1384             'firstname varchar primary key, nickname varchar, grade char(2)',
    1385             values=[("D'Arcy", 'Darcey', 'A+'), ('Sheldon', 'Moonpie', 'A+'),
    1386                     ('Robert', 'Little Bobby Tables', 'D-')])
     1386                         'firstname varchar primary key, nickname varchar, grade char(2)',
     1387                         values=[("D'Arcy", 'Darcey', 'A+'), ('Sheldon', 'Moonpie', 'A+'),
     1388                                 ('Robert', 'Little Bobby Tables', 'D-')])
    13871389        r = get('test_students', 'Sheldon')
    13881390        self.assertEqual(r, dict(
     
    13981400        except pg.DatabaseError as error:
    13991401            self.assertEqual(str(error),
    1400                 'No such record in test_students\nwhere "firstname" = $1\n'
    1401                 'with $1="D\' Arcy"')
     1402                             'No such record in test_students\nwhere "firstname" = $1\n'
     1403                             'with $1="D\' Arcy"')
    14021404        try:
    14031405            get('test_students', "Robert'); TRUNCATE TABLE test_students;--")
    14041406        except pg.DatabaseError as error:
    14051407            self.assertEqual(str(error),
    1406                 'No such record in test_students\nwhere "firstname" = $1\n'
    1407                 'with $1="Robert\'); TRUNCATE TABLE test_students;--"')
     1408                             'No such record in test_students\nwhere "firstname" = $1\n'
     1409                             'with $1="Robert\'); TRUNCATE TABLE test_students;--"')
    14081410        q = "select * from test_students order by 1 limit 4"
    14091411        r = query(q).getresult()
     
    14181420        table = 'insert_test_table'
    14191421        self.createTable(table,
    1420             'i2 smallint, i4 integer, i8 bigint,'
    1421             ' d numeric, f4 real, f8 double precision, m money,'
    1422             ' v4 varchar(4), c4 char(4), t text,'
    1423             ' b boolean, ts timestamp', oids=True)
     1422                         'i2 smallint, i4 integer, i8 bigint,'
     1423                         ' d numeric, f4 real, f8 double precision, m money,'
     1424                         ' v4 varchar(4), c4 char(4), t text,'
     1425                         ' b boolean, ts timestamp', oids=True)
    14241426        oid_table = 'oid(%s)' % table
    14251427        tests = [dict(i2=None, i4=None, i8=None),
    1426             (dict(i2='', i4='', i8=''), dict(i2=None, i4=None, i8=None)),
    1427             (dict(i2=0, i4=0, i8=0), dict(i2=0, i4=0, i8=0)),
    1428             dict(i2=42, i4=123456, i8=9876543210),
    1429             dict(i2=2 ** 15 - 1,
    1430                 i4=int(2 ** 31 - 1), i8=long(2 ** 63 - 1)),
    1431             dict(d=None), (dict(d=''), dict(d=None)),
    1432             dict(d=Decimal(0)), (dict(d=0), dict(d=Decimal(0))),
    1433             dict(f4=None, f8=None), dict(f4=0, f8=0),
    1434             (dict(f4='', f8=''), dict(f4=None, f8=None)),
    1435             (dict(d=1234.5, f4=1234.5, f8=1234.5),
     1428                 (dict(i2='', i4='', i8=''), dict(i2=None, i4=None, i8=None)),
     1429                 (dict(i2=0, i4=0, i8=0), dict(i2=0, i4=0, i8=0)),
     1430                 dict(i2=42, i4=123456, i8=9876543210),
     1431                 dict(i2=2 ** 15 - 1,
     1432                      i4=int(2 ** 31 - 1), i8=long(2 ** 63 - 1)),
     1433                 dict(d=None), (dict(d=''), dict(d=None)),
     1434                 dict(d=Decimal(0)), (dict(d=0), dict(d=Decimal(0))),
     1435                 dict(f4=None, f8=None), dict(f4=0, f8=0),
     1436                 (dict(f4='', f8=''), dict(f4=None, f8=None)),
     1437                 (dict(d=1234.5, f4=1234.5, f8=1234.5),
    14361438                  dict(d=Decimal('1234.5'))),
    1437             dict(d=Decimal('123.456789'), f4=12.375, f8=123.4921875),
    1438             dict(d=Decimal('123456789.9876543212345678987654321')),
    1439             dict(m=None), (dict(m=''), dict(m=None)),
    1440             dict(m=Decimal('-1234.56')),
    1441             (dict(m=('-1234.56')), dict(m=Decimal('-1234.56'))),
    1442             dict(m=Decimal('1234.56')), dict(m=Decimal('123456')),
    1443             (dict(m='1234.56'), dict(m=Decimal('1234.56'))),
    1444             (dict(m=1234.5), dict(m=Decimal('1234.5'))),
    1445             (dict(m=-1234.5), dict(m=Decimal('-1234.5'))),
    1446             (dict(m=123456), dict(m=Decimal('123456'))),
    1447             (dict(m='1234567.89'), dict(m=Decimal('1234567.89'))),
    1448             dict(b=None), (dict(b=''), dict(b=None)),
    1449             dict(b='f'), dict(b='t'),
    1450             (dict(b=0), dict(b='f')), (dict(b=1), dict(b='t')),
    1451             (dict(b=False), dict(b='f')), (dict(b=True), dict(b='t')),
    1452             (dict(b='0'), dict(b='f')), (dict(b='1'), dict(b='t')),
    1453             (dict(b='n'), dict(b='f')), (dict(b='y'), dict(b='t')),
    1454             (dict(b='no'), dict(b='f')), (dict(b='yes'), dict(b='t')),
    1455             (dict(b='off'), dict(b='f')), (dict(b='on'), dict(b='t')),
    1456             dict(v4=None, c4=None, t=None),
    1457             (dict(v4='', c4='', t=''), dict(c4=' ' * 4)),
    1458             dict(v4='1234', c4='1234', t='1234' * 10),
    1459             dict(v4='abcd', c4='abcd', t='abcdefg'),
    1460             (dict(v4='abc', c4='abc', t='abc'), dict(c4='abc ')),
    1461             dict(ts=None), (dict(ts=''), dict(ts=None)),
    1462             (dict(ts=0), dict(ts=None)), (dict(ts=False), dict(ts=None)),
    1463             dict(ts='2012-12-21 00:00:00'),
    1464             (dict(ts='2012-12-21'), dict(ts='2012-12-21 00:00:00')),
    1465             dict(ts='2012-12-21 12:21:12'),
    1466             dict(ts='2013-01-05 12:13:14'),
    1467             dict(ts='current_timestamp')]
     1439                 dict(d=Decimal('123.456789'), f4=12.375, f8=123.4921875),
     1440                 dict(d=Decimal('123456789.9876543212345678987654321')),
     1441                 dict(m=None), (dict(m=''), dict(m=None)),
     1442                 dict(m=Decimal('-1234.56')),
     1443                 (dict(m=('-1234.56')), dict(m=Decimal('-1234.56'))),
     1444                 dict(m=Decimal('1234.56')), dict(m=Decimal('123456')),
     1445                 (dict(m='1234.56'), dict(m=Decimal('1234.56'))),
     1446                 (dict(m=1234.5), dict(m=Decimal('1234.5'))),
     1447                 (dict(m=-1234.5), dict(m=Decimal('-1234.5'))),
     1448                 (dict(m=123456), dict(m=Decimal('123456'))),
     1449                 (dict(m='1234567.89'), dict(m=Decimal('1234567.89'))),
     1450                 dict(b=None), (dict(b=''), dict(b=None)),
     1451                 dict(b='f'), dict(b='t'),
     1452                 (dict(b=0), dict(b='f')), (dict(b=1), dict(b='t')),
     1453                 (dict(b=False), dict(b='f')), (dict(b=True), dict(b='t')),
     1454                 (dict(b='0'), dict(b='f')), (dict(b='1'), dict(b='t')),
     1455                 (dict(b='n'), dict(b='f')), (dict(b='y'), dict(b='t')),
     1456                 (dict(b='no'), dict(b='f')), (dict(b='yes'), dict(b='t')),
     1457                 (dict(b='off'), dict(b='f')), (dict(b='on'), dict(b='t')),
     1458                 dict(v4=None, c4=None, t=None),
     1459                 (dict(v4='', c4='', t=''), dict(c4=' ' * 4)),
     1460                 dict(v4='1234', c4='1234', t='1234' * 10),
     1461                 dict(v4='abcd', c4='abcd', t='abcdefg'),
     1462                 (dict(v4='abc', c4='abc', t='abc'), dict(c4='abc ')),
     1463                 dict(ts=None), (dict(ts=''), dict(ts=None)),
     1464                 (dict(ts=0), dict(ts=None)), (dict(ts=False), dict(ts=None)),
     1465                 dict(ts='2012-12-21 00:00:00'),
     1466                 (dict(ts='2012-12-21'), dict(ts='2012-12-21 00:00:00')),
     1467                 dict(ts='2012-12-21 12:21:12'),
     1468                 dict(ts='2013-01-05 12:13:14'),
     1469                 dict(ts='current_timestamp')]
    14681470        for test in tests:
    14691471            if isinstance(test, dict):
     
    14901492            self.assertIsInstance(oid, int)
    14911493            data = dict(item for item in data.items()
    1492                 if item[0] in expect)
     1494                        if item[0] in expect)
    14931495            ts = expect.get('ts')
    14941496            if ts == 'current_timestamp':
     
    15091511            self.assertEqual(data['oid'], oid)
    15101512            data = dict(item for item in data.items()
    1511                 if item[0] in expect)
     1513                        if item[0] in expect)
    15121514            self.assertEqual(data, expect)
    15131515            query('delete from "%s"' % table)
     
    15861588        table = 'test table for insert()'
    15871589        self.createTable(table, '"Prime!" smallint primary key,'
    1588             ' "much space" integer, "Questions?" text')
     1590                                ' "much space" integer, "Questions?" text')
    15891591        r = {'Prime!': 11, 'much space': 2002, 'Questions?': 'What?'}
    15901592        r = insert(table, r)
     
    16361638        query = self.db.query
    16371639        self.assertRaises(pg.ProgrammingError, update,
    1638             'test', i2=2, i4=4, i8=8)
     1640                          'test', i2=2, i4=4, i8=8)
    16391641        table = 'update_test_table'
    16401642        self.createTable(table, 'n integer, t text', oids=True,
    1641             values=enumerate('xyz', start=1))
     1643                         values=enumerate('xyz', start=1))
    16421644        self.assertRaises(pg.ProgrammingError, self.db.get, table, 2)
    16431645        r = self.db.get(table, 2, 'n')
     
    16841686        query("insert into test_table values (1)")
    16851687        self.assertRaises(pg.ProgrammingError,
    1686             update, 'test_table', dict(oid=oid, n=4))
     1688                          update, 'test_table', dict(oid=oid, n=4))
    16871689        r = update('test_table', dict(n=4), oid=oid)
    16881690        self.assertEqual(r['n'], 4)
     
    17241726        query = self.db.query
    17251727        self.assertRaises(pg.ProgrammingError, update,
    1726             'test', i2=2, i4=4, i8=8)
     1728                          'test', i2=2, i4=4, i8=8)
    17271729        table = 'update_test_table'
    17281730        self.createTable(table, 'n integer primary key, t text', oids=False,
    1729             values=enumerate('xyz', start=1))
     1731                         values=enumerate('xyz', start=1))
    17301732        r = self.db.get(table, 2)
    17311733        r['t'] = 'u'
     
    17411743        table = 'update_test_table_1'
    17421744        self.createTable(table, 'n integer primary key, t text',
    1743             values=enumerate('abc', start=1))
     1745                         values=enumerate('abc', start=1))
    17441746        self.assertRaises(KeyError, update, table, dict(t='b'))
    17451747        s = dict(n=2, t='d')
     
    17641766        table = 'update_test_table_2'
    17651767        self.createTable(table,
    1766             'n integer, m integer, t text, primary key (n, m)',
    1767             values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
    1768                 for n in range(3) for m in range(2)])
     1768                         'n integer, m integer, t text, primary key (n, m)',
     1769                         values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
     1770                                 for n in range(3) for m in range(2)])
    17691771        self.assertRaises(KeyError, update, table, dict(n=2, t='b'))
    17701772        self.assertEqual(update(table,
    1771             dict(n=2, m=2, t='x'))['t'], 'x')
     1773                                dict(n=2, m=2, t='x'))['t'], 'x')
    17721774        q = 'select t from "%s" where n=2 order by m' % table
    17731775        r = [r[0] for r in query(q).getresult()]
     
    17791781        table = 'test table for update()'
    17801782        self.createTable(table, '"Prime!" smallint primary key,'
    1781             ' "much space" integer, "Questions?" text',
    1782             values=[(13, 3003, 'Why!')])
     1783                                ' "much space" integer, "Questions?" text',
     1784                         values=[(13, 3003, 'Why!')])
    17831785        r = {'Prime!': 13, 'much space': 7007, 'Questions?': 'When?'}
    17841786        r = update(table, r)
     
    17981800        query = self.db.query
    17991801        self.assertRaises(pg.ProgrammingError, upsert,
    1800             'test', i2=2, i4=4, i8=8)
     1802                          'test', i2=2, i4=4, i8=8)
    18011803        table = 'upsert_test_table'
    18021804        self.createTable(table, 'n integer primary key, t text', oids=True)
     
    18721874        self.createTable('test_table', 'n int', oids=True, values=[1])
    18731875        self.assertRaises(pg.ProgrammingError,
    1874             upsert, 'test_table', dict(n=2))
     1876                          upsert, 'test_table', dict(n=2))
    18751877        r = get('test_table', 1, 'n')
    18761878        self.assertIsInstance(r, dict)
     
    18811883        oid = r[qoid]
    18821884        self.assertRaises(pg.ProgrammingError,
    1883             upsert, 'test_table', dict(n=2, oid=oid))
     1885                          upsert, 'test_table', dict(n=2, oid=oid))
    18841886        query("alter table test_table add column m int")
    18851887        query("alter table test_table add primary key (n)")
     
    19551957        table = 'upsert_test_table_2'
    19561958        self.createTable(table,
    1957             'n integer, m integer, t text, primary key (n, m)')
     1959                         'n integer, m integer, t text, primary key (n, m)')
    19581960        s = dict(n=1, m=2, t='x')
    19591961        try:
     
    20222024        table = 'test table for upsert()'
    20232025        self.createTable(table, '"Prime!" smallint primary key,'
    2024             ' "much space" integer, "Questions?" text')
     2026                                ' "much space" integer, "Questions?" text')
    20252027        s = {'Prime!': 31, 'much space': 9009, 'Questions?': 'Yes.'}
    20262028        try:
     
    20552057        table = 'clear_test_table'
    20562058        self.createTable(table,
    2057             'n integer, f float, b boolean, d date, t text', oids=True)
     2059                         'n integer, f float, b boolean, d date, t text', oids=True)
    20582060        r = clear(table)
    20592061        result = dict(n=0, f=0, b=f, d='', t='')
     
    20712073        table = 'test table for clear()'
    20722074        self.createTable(table, '"Prime!" smallint primary key,'
    2073             ' "much space" integer, "Questions?" text')
     2075                                ' "much space" integer, "Questions?" text')
    20742076        r = clear(table)
    20752077        self.assertIsInstance(r, dict)
     
    20822084        query = self.db.query
    20832085        self.assertRaises(pg.ProgrammingError, delete,
    2084             'test', dict(i2=2, i4=4, i8=8))
     2086                          'test', dict(i2=2, i4=4, i8=8))
    20852087        table = 'delete_test_table'
    20862088        self.createTable(table, 'n integer, t text', oids=True,
    2087             values=enumerate('xyz', start=1))
     2089                         values=enumerate('xyz', start=1))
    20882090        self.assertRaises(pg.ProgrammingError, self.db.get, table, 2)
    20892091        r = self.db.get(table, 1, 'n')
     
    22042206        table = 'delete_test_table_1'
    22052207        self.createTable(table, 'n integer primary key, t text',
    2206             values=enumerate('abc', start=1))
     2208                         values=enumerate('abc', start=1))
    22072209        self.assertRaises(KeyError, self.db.delete, table, dict(t='b'))
    22082210        self.assertEqual(self.db.delete(table, dict(n=2)), 1)
     
    22142216        table = 'delete_test_table_2'
    22152217        self.createTable(table,
    2216             'n integer, m integer, t text, primary key (n, m)',
    2217             values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
    2218                 for n in range(3) for m in range(2)])
     2218                         'n integer, m integer, t text, primary key (n, m)',
     2219                         values=[(n + 1, m + 1, chr(ord('a') + 2 * n + m))
     2220                                 for n in range(3) for m in range(2)])
    22192221        self.assertRaises(KeyError, self.db.delete, table, dict(n=2, t='b'))
    22202222        self.assertEqual(self.db.delete(table, dict(n=2, m=2)), 1)
    22212223        r = [r[0] for r in query('select t from "%s" where n=2'
    2222             ' order by m' % table).getresult()]
     2224                                 ' order by m' % table).getresult()]
    22232225        self.assertEqual(r, ['c'])
    22242226        self.assertEqual(self.db.delete(table, dict(n=2, m=2)), 0)
    22252227        r = [r[0] for r in query('select t from "%s" where n=3'
    2226             ' order by m' % table).getresult()]
     2228                                 ' order by m' % table).getresult()]
    22272229        self.assertEqual(r, ['e', 'f'])
    22282230        self.assertEqual(self.db.delete(table, dict(n=3, m=1)), 1)
    22292231        r = [r[0] for r in query('select t from "%s" where n=3'
    2230             ' order by m' % table).getresult()]
     2232                                 ' order by m' % table).getresult()]
    22312233        self.assertEqual(r, ['f'])
    22322234
     
    22362238        table = 'test table for delete()'
    22372239        self.createTable(table, '"Prime!" smallint primary key,'
    2238             ' "much space" integer, "Questions?" text',
    2239             values=[(19, 5005, 'Yes!')])
     2240                                ' "much space" integer, "Questions?" text',
     2241                         values=[(19, 5005, 'Yes!')])
    22402242        r = {'Prime!': 17}
    22412243        r = delete(table, r)
     
    22532255        query = self.db.query
    22542256        self.createTable('test_parent',
    2255             'n smallint primary key', values=range(3))
     2257                         'n smallint primary key', values=range(3))
    22562258        self.createTable('test_child',
    2257             'n smallint primary key references test_parent', values=range(3))
     2259                         'n smallint primary key references test_parent', values=range(3))
    22582260        q = ("select (select count(*) from test_parent),"
    2259             " (select count(*) from test_child)")
     2261             " (select count(*) from test_child)")
    22602262        self.assertEqual(query(q).getresult()[0], (3, 3))
    22612263        self.assertRaises(pg.ProgrammingError,
    2262             delete, 'test_parent', None, n=2)
     2264                          delete, 'test_parent', None, n=2)
    22632265        self.assertRaises(pg.ProgrammingError,
    2264             delete, 'test_parent *', None, n=2)
     2266                          delete, 'test_parent *', None, n=2)
    22652267        r = delete('test_child', None, n=2)
    22662268        self.assertEqual(r, 1)
     
    22702272        self.assertEqual(query(q).getresult()[0], (2, 2))
    22712273        self.assertRaises(pg.ProgrammingError,
    2272             delete, 'test_parent', dict(n=0))
     2274                          delete, 'test_parent', dict(n=0))
    22732275        self.assertRaises(pg.ProgrammingError,
    2274             delete, 'test_parent *', dict(n=0))
     2276                          delete, 'test_parent *', dict(n=0))
    22752277        r = delete('test_child', dict(n=0))
    22762278        self.assertEqual(r, 1)
     
    22932295        query = self.db.query
    22942296        self.createTable('test_table', 'n smallint',
    2295             temporary=False, values=[1] * 3)
     2297                         temporary=False, values=[1] * 3)
    22962298        q = "select count(*) from test_table"
    22972299        r = query(q).getresult()[0][0]
     
    23132315                query("insert into test_table_2 values (2)")
    23142316            q = ("select (select count(*) from test_table),"
    2315                 " (select count(*) from test_table_2)")
     2317                 " (select count(*) from test_table_2)")
    23162318            r = query(q).getresult()[0]
    23172319            self.assertEqual(r, (3, 3))
     
    23502352        query = self.db.query
    23512353        self.createTable('test_parent', 'n smallint primary key',
    2352             values=range(3))
     2354                         values=range(3))
    23532355        self.createTable('test_child',
    2354             'n smallint primary key references test_parent (n)',
    2355             values=range(3))
     2356                         'n smallint primary key references test_parent (n)',
     2357                         values=range(3))
    23562358        q = ("select (select count(*) from test_parent),"
    2357             " (select count(*) from test_child)")
     2359             " (select count(*) from test_child)")
    23582360        r = query(q).getresult()[0]
    23592361        self.assertEqual(r, (3, 3))
     
    23932395            query("insert into test_child (n, m) values (2, 3)")
    23942396        q = ("select (select count(*) from test_parent),"
    2395             " (select count(*) from test_child)")
     2397             " (select count(*) from test_child)")
    23962398        r = query(q).getresult()[0]
    23972399        self.assertEqual(r, (6, 3))
     
    24272429                query("insert into test_child%s (n, m) values (2, 3)" % t)
    24282430        q = ("select (select count(*) from test_parent),"
    2429             " (select count(*) from test_child),"
    2430             " (select count(*) from test_parent_2),"
    2431             " (select count(*) from test_child_2)")
     2431             " (select count(*) from test_child),"
     2432             " (select count(*) from test_parent_2),"
     2433             " (select count(*) from test_child_2)")
    24322434        r = query(q).getresult()[0]
    24332435        self.assertEqual(r, (6, 3, 6, 3))
     
    24392441        self.assertEqual(r, (0, 0, 0, 0))
    24402442        self.assertRaises(ValueError, truncate,
    2441             ['test_parent*', 'test_child'], only=[True, False])
     2443                          ['test_parent*', 'test_child'], only=[True, False])
    24422444        truncate(['test_parent*', 'test_child'], only=[False, True])
    24432445
     
    24712473        named = hasattr(r, 'colname')
    24722474        names = [(1, 'Homer'), (2, 'Marge'),
    2473                 (3, 'Bart'), (4, 'Lisa'), (5, 'Maggie')]
     2475                 (3, 'Bart'), (4, 'Lisa'), (5, 'Maggie')]
    24742476        self.createTable(table,
    2475             'id smallint primary key, name varchar', values=names)
     2477                         'id smallint primary key, name varchar', values=names)
    24762478        r = get_as_list(table)
    24772479        self.assertIsInstance(r, list)
     
    25872589                  (3, '#b2ffff', 'Celeste'), (4, '#c19a6b', 'Desert')]
    25882590        self.createTable(table,
    2589             'id smallint primary key, rgb char(7), name varchar',
    2590             values=colors)
     2591                         'id smallint primary key, rgb char(7), name varchar',
     2592                         values=colors)
    25912593        # keyname must be string, list or tuple
    25922594        self.assertRaises(KeyError, get_as_dict, table, 3)
     
    26152617        self.assertIsInstance(r, OrderedDict)
    26162618        expected = OrderedDict((row[1], (row[0], row[2]))
    2617             for row in sorted(colors, key=itemgetter(1)))
     2619                               for row in sorted(colors, key=itemgetter(1)))
    26182620        self.assertEqual(r, expected)
    26192621        for key in r:
     
    26662668        self.assertIsInstance(r, OrderedDict)
    26672669        expected = OrderedDict((row[1], row[2])
    2668             for row in sorted(colors, key=itemgetter(1)))
     2670                               for row in sorted(colors, key=itemgetter(1)))
    26692671        self.assertEqual(r, expected)
    26702672        for key in r:
     
    26922694        r = get_as_dict(table, what=['name', 'id'],
    26932695                        where=['id > 1', 'id < 4', "rgb like '#b%'",
    2694                    "name not like 'A%'", "name not like '%t'"], scalar=True)
     2696                               "name not like 'A%'", "name not like '%t'"], scalar=True)
    26952697        self.assertEqual(r, expected)
    26962698        r = get_as_dict(table, what='name, id', limit=2, offset=1, scalar=True)
     
    27732775        self.db.begin(mode='read only')
    27742776        self.assertRaises(pg.ProgrammingError,
    2775             query, "insert into test_table values (0)")
     2777                          query, "insert into test_table values (0)")
    27762778        self.db.rollback()
    27772779        self.db.start(mode='Read Only')
    27782780        self.assertRaises(pg.ProgrammingError,
    2779             query, "insert into test_table values (0)")
     2781                          query, "insert into test_table values (0)")
    27802782        self.db.abort()
    27812783
     
    29322934        # insert JSON object
    29332935        data = {
    2934           "id": 1, "name": "Foo", "price": 1234.5,
    2935           "new": True, "note": None,
    2936           "tags": ["Bar", "Eek"],
    2937           "stock": {"warehouse": 300, "retail": 20}}
     2936            "id": 1, "name": "Foo", "price": 1234.5,
     2937            "new": True, "note": None,
     2938            "tags": ["Bar", "Eek"],
     2939            "stock": {"warehouse": 300, "retail": 20}}
    29382940        r = self.db.insert('json_test', n=1, data=data)
    29392941        self.assertIsInstance(r, dict)
     
    29812983        try:
    29822984            self.createTable('jsonb_test',
    2983                 'n smallint primary key, data jsonb')
     2985                             'n smallint primary key, data jsonb')
    29842986        except pg.ProgrammingError as error:
    29852987            if self.db.server_version < 90400:
     
    30023004        # insert JSON object
    30033005        data = {
    3004           "id": 1, "name": "Foo", "price": 1234.5,
    3005           "new": True, "note": None,
    3006           "tags": ["Bar", "Eek"],
    3007           "stock": {"warehouse": 300, "retail": 20}}
     3006            "id": 1, "name": "Foo", "price": 1234.5,
     3007            "new": True, "note": None,
     3008            "tags": ["Bar", "Eek"],
     3009            "stock": {"warehouse": 300, "retail": 20}}
    30083010        r = self.db.insert('jsonb_test', n=1, data=data)
    30093011        self.assertIsInstance(r, dict)
     
    30433045    def testArray(self):
    30443046        self.createTable('arraytest',
    3045             'id smallint, i2 smallint[], i4 integer[], i8 bigint[],'
    3046             ' d numeric[], f4 real[], f8 double precision[], m money[],'
    3047             ' b bool[], v4 varchar(4)[], c4 char(4)[], t text[]')
     3047                         'id smallint, i2 smallint[], i4 integer[], i8 bigint[],'
     3048                         ' d numeric[], f4 real[], f8 double precision[], m money[],'
     3049                         ' b bool[], v4 varchar(4)[], c4 char(4)[], t text[]')
    30483050        r = self.db.get_attnames('arraytest')
    30493051        if self.regtypes:
     
    30673069        t, f = (True, False) if pg.get_bool() else ('t', 'f')
    30683070        data = dict(id=42, i2=[42, 1234, None, 0, -1],
    3069             i4=[42, 123456789, None, 0, 1, -1],
    3070             i8=[long(42), long(123456789123456789), None,
    3071                 long(0), long(1), long(-1)],
    3072             d=[decimal(42), long_decimal, None,
    3073                decimal(0), decimal(1), decimal(-1), -long_decimal],
    3074             f4=[42.0, 1234.5, None, 0.0, 1.0, -1.0,
    3075                 float('inf'), float('-inf')],
    3076             f8=[42.0, 12345671234.5, None, 0.0, 1.0, -1.0,
    3077                 float('inf'), float('-inf')],
    3078             m=[decimal('42.00'), odd_money, None,
    3079                decimal('0.00'), decimal('1.00'), decimal('-1.00'), -odd_money],
    3080             b=[t, f, t, None, f, t, None, None, t],
    3081             v4=['abc', '"Hi"', '', None], c4=['abc ', '"Hi"', '    ', None],
    3082             t=['abc', 'Hello, World!', '"Hello, World!"', '', None])
     3071                    i4=[42, 123456789, None, 0, 1, -1],
     3072                    i8=[long(42), long(123456789123456789), None,
     3073                        long(0), long(1), long(-1)],
     3074                    d=[decimal(42), long_decimal, None,
     3075                       decimal(0), decimal(1), decimal(-1), -long_decimal],
     3076                    f4=[42.0, 1234.5, None, 0.0, 1.0, -1.0,
     3077                        float('inf'), float('-inf')],
     3078                    f8=[42.0, 12345671234.5, None, 0.0, 1.0, -1.0,
     3079                        float('inf'), float('-inf')],
     3080                    m=[decimal('42.00'), odd_money, None,
     3081                       decimal('0.00'), decimal('1.00'), decimal('-1.00'), -odd_money],
     3082                    b=[t, f, t, None, f, t, None, None, t],
     3083                    v4=['abc', '"Hi"', '', None], c4=['abc ', '"Hi"', '    ', None],
     3084                    t=['abc', 'Hello, World!', '"Hello, World!"', '', None])
    30833085        r = data.copy()
    30843086        self.db.insert('arraytest', r)
     
    32533255        query = self.db.query
    32543256        query('create type test_person_type as'
    3255             ' (name varchar, age smallint, married bool,'
     3257              ' (name varchar, age smallint, married bool,'
    32563258              ' weight real, salary money)')
    32573259        self.addCleanup(query, 'drop type test_person_type')
    32583260        self.createTable('test_person', 'person test_person_type',
    3259             temporary=False, oids=True)
     3261                         temporary=False, oids=True)
    32603262        attnames = self.db.get_attnames('test_person')
    32613263        self.assertEqual(len(attnames), 2)
     
    32693271        if self.regtypes:
    32703272            self.assertEqual(person_typ.attnames,
    3271                 dict(name='character varying', age='smallint',
    3272                     married='boolean', weight='real', salary='money'))
     3273                             dict(name='character varying', age='smallint',
     3274                                  married='boolean', weight='real', salary='money'))
    32733275        else:
    32743276            self.assertEqual(person_typ.attnames,
    3275                 dict(name='text', age='int', married='bool',
    3276                     weight='float', salary='money'))
     3277                             dict(name='text', age='int', married='bool',
     3278                                  weight='float', salary='money'))
    32773279        decimal = pg.get_decimal()
    32783280        if pg.get_bool():
     
    33433345        query = self.db.query
    33443346        query('create type test_person_type as'
    3345             ' (name text, picture bytea)')
     3347              ' (name text, picture bytea)')
    33463348        self.addCleanup(query, 'drop type test_person_type')
    33473349        self.createTable('test_person', 'person test_person_type',
    3348             temporary=False, oids=True)
     3350                         temporary=False, oids=True)
    33493351        person_typ = self.db.get_attnames('test_person')['person']
    33503352        self.assertEqual(person_typ.attnames,
    3351             dict(name='text', picture='bytea'))
     3353                         dict(name='text', picture='bytea'))
    33523354        person = ('John Doe', b'O\x00ps\xff!')
    33533355        r = self.db.insert('test_person', None, person=person)
     
    33643366        try:
    33653367            query('create type test_person_type as'
    3366                 ' (name text, data json)')
     3368                  ' (name text, data json)')
    33673369        except pg.ProgrammingError as error:
    33683370            if self.db.server_version < 90200:
     
    33713373        self.addCleanup(query, 'drop type test_person_type')
    33723374        self.createTable('test_person', 'person test_person_type',
    3373             temporary=False, oids=True)
     3375                         temporary=False, oids=True)
    33743376        person_typ = self.db.get_attnames('test_person')['person']
    33753377        self.assertEqual(person_typ.attnames,
    3376             dict(name='text', data='json'))
     3378                         dict(name='text', data='json'))
    33773379        person = ('John Doe', dict(age=61, married=True, weight=99.5))
    33783380        r = self.db.insert('test_person', None, person=person)
     
    33903392        query = self.db.query
    33913393        query('create type test_person_type as'
    3392             ' (name varchar, age smallint)')
     3394              ' (name varchar, age smallint)')
    33933395        self.addCleanup(query, 'drop type test_person_type')
    33943396        self.createTable('test_person', 'person test_person_type',
    3395             temporary=False, oids=True)
     3397                         temporary=False, oids=True)
    33963398        person_typ = self.db.get_attnames('test_person')['person']
    33973399        if self.regtypes:
     
    34013403        if self.regtypes:
    34023404            self.assertEqual(person_typ.attnames,
    3403                 dict(name='character varying', age='smallint'))
     3405                             dict(name='character varying', age='smallint'))
    34043406        else:
    34053407            self.assertEqual(person_typ.attnames,
    3406                 dict(name='text', age='int'))
     3408                             dict(name='text', age='int'))
    34073409        person = pg._Literal("('John Doe', 61)")
    34083410        r = self.db.insert('test_person', None, person=person)
     
    34133415        self.assertEqual(p.age, 61)
    34143416        self.assertIsInstance(p.age, int)
     3417
     3418    def testDbTypesInfo(self):
     3419        dbtypes = self.db.dbtypes
     3420        self.assertIsInstance(dbtypes, dict)
     3421        self.assertNotIn('numeric', dbtypes)
     3422        typ = dbtypes['numeric']
     3423        self.assertIn('numeric', dbtypes)
     3424        self.assertEqual(typ, 'numeric' if self.regtypes else 'num')
     3425        self.assertEqual(typ.oid, 1700)
     3426        self.assertEqual(typ.pgtype, 'numeric')
     3427        self.assertEqual(typ.regtype, 'numeric')
     3428        self.assertEqual(typ.simple, 'num')
     3429        self.assertEqual(typ.typtype, 'b')
     3430        self.assertEqual(typ.category, 'N')
     3431        self.assertEqual(typ.delim, ',')
     3432        self.assertEqual(typ.relid, 0)
     3433        self.assertIs(dbtypes[1700], typ)
     3434        self.assertNotIn('pg_type', dbtypes)
     3435        typ = dbtypes['pg_type']
     3436        self.assertIn('pg_type', dbtypes)
     3437        self.assertEqual(typ, 'pg_type' if self.regtypes else 'record')
     3438        self.assertIsInstance(typ.oid, int)
     3439        self.assertEqual(typ.pgtype, 'pg_type')
     3440        self.assertEqual(typ.regtype, 'pg_type')
     3441        self.assertEqual(typ.simple, 'record')
     3442        self.assertEqual(typ.typtype, 'c')
     3443        self.assertEqual(typ.category, 'C')
     3444        self.assertEqual(typ.delim, ',')
     3445        self.assertNotEqual(typ.relid, 0)
     3446        attnames = typ.attnames
     3447        self.assertIsInstance(attnames, dict)
     3448        self.assertIs(attnames, dbtypes.get_attnames('pg_type'))
     3449        self.assertEqual(list(attnames)[0], 'typname')
     3450        typname = attnames['typname']
     3451        self.assertEqual(typname, 'name' if self.regtypes else 'text')
     3452        self.assertEqual(typname.typtype, 'b')  # base
     3453        self.assertEqual(typname.category, 'S')  # string
     3454        self.assertEqual(list(attnames)[3], 'typlen')
     3455        typlen = attnames['typlen']
     3456        self.assertEqual(typlen, 'smallint' if self.regtypes else 'int')
     3457        self.assertEqual(typlen.typtype, 'b')  # base
     3458        self.assertEqual(typlen.category, 'N')  # numeric
     3459
     3460    def testDbTypesTypecast(self):
     3461        dbtypes = self.db.dbtypes
     3462        self.assertIsInstance(dbtypes, dict)
     3463        self.assertNotIn('circle', dbtypes)
     3464        cast_circle = dbtypes.get_typecast('circle')
     3465        squared_circle = lambda v: 'Squared Circle: %s' % v
     3466        dbtypes.set_typecast('circle', squared_circle)
     3467        self.assertIs(dbtypes.get_typecast('circle'), squared_circle)
     3468        r = self.db.query("select '0,0,1'::circle").getresult()[0][0]
     3469        self.assertIn('circle', dbtypes)
     3470        self.assertEqual(r, 'Squared Circle: <(0,0),1>')
     3471        self.assertEqual(dbtypes.typecast('Impossible', 'circle'),
     3472            'Squared Circle: Impossible')
     3473        dbtypes.reset_typecast('circle')
     3474        self.assertIs(dbtypes.get_typecast('circle'), cast_circle)
     3475
     3476    def testGetSetTypeCast(self):
     3477        get_typecast = pg.get_typecast
     3478        set_typecast = pg.set_typecast
     3479        dbtypes = self.db.dbtypes
     3480        self.assertIsInstance(dbtypes, dict)
     3481        self.assertNotIn('int4', dbtypes)
     3482        self.assertNotIn('real', dbtypes)
     3483        self.assertNotIn('bool', dbtypes)
     3484        self.assertIs(get_typecast('int4'), int)
     3485        self.assertIs(get_typecast('float4'), float)
     3486        self.assertIs(get_typecast('bool'), pg.cast_bool)
     3487        cast_circle = get_typecast('circle')
     3488        squared_circle = lambda v: 'Squared Circle: %s' % v
     3489        self.assertNotIn('circle', dbtypes)
     3490        set_typecast('circle', squared_circle)
     3491        self.assertNotIn('circle', dbtypes)
     3492        self.assertIs(get_typecast('circle'), squared_circle)
     3493        r = self.db.query("select '0,0,1'::circle").getresult()[0][0]
     3494        self.assertIn('circle', dbtypes)
     3495        self.assertEqual(r, 'Squared Circle: <(0,0),1>')
     3496        set_typecast('circle', cast_circle)
     3497        self.assertIs(get_typecast('circle'), cast_circle)
    34153498
    34163499    def testNotificationHandler(self):
  • trunk/tests/test_dbapi20.py

    r797 r798  
    309309            self.assertNotIn('pg_type', type_cache)
    310310            type_info = type_cache['pg_type']
    311             self.assertIn('numeric', type_cache)
     311            self.assertIn('pg_type', type_cache)
    312312            self.assertEqual(type_info.type, 'c')  # composite
    313313            self.assertEqual(type_info.category, 'C')  # composite
     
    357357            self.assertEqual(i4, 'int(4)')
    358358            self.assertEqual(i8, 8)
    359             self.assertEqual(type_cache.typecast('int4', 42), 'int(42)')
     359            self.assertEqual(type_cache.typecast(42, 'int4'), 'int(42)')
    360360            type_cache.set_typecast(['int2', 'int8'], cast_int)
    361361            cur.execute(query)
Note: See TracChangeset for help on using the changeset viewer.