Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 57

Show
Ignore:
Timestamp:
04/03/07 23:10:58
Author:
fumanchu
Message:

PostgreSQL now works with the new types and adapters.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/geniusql/dbtypes.py

    r56 r57  
    8282 
    8383 
    84 class FrozenStringType(DatabaseType): 
     84class FrozenStringType(FrozenByteType): 
    8585    """DatabaseType for string types whose byte-length is not adjuatable. 
    8686     
     
    172172    _initargs = DatabaseType._initargs + ("precision", "max_precision") 
    173173     
    174     _precision = max_precision = 255 
     174    _precision = max_precision = 53 
    175175    def _get_precision(self): 
    176176        return self._precision 
     
    192192    max_precision = 53 
    193193     
    194     _precision = 255 
     194    _precision = 53 
    195195    def _get_precision(self): 
    196196        return self._precision 
    197197    def _set_precision(self, value): 
    198198        if value is not None: 
    199             if value > 1: 
    200                 raise ValueError("%r is less than min precision 1." % value) 
     199            if value < 1: 
     200                raise ValueError("%r: %r is less than min precision 1." % 
     201                                 (self, value)) 
    201202            elif value > self.max_precision: 
    202                 raise ValueError("%r is greater than max precision %r." % 
    203                                  (value, self.max_precision)) 
     203                raise ValueError("%r: %r is greater than max precision %r." % 
     204                                 (self, value, self.max_precision)) 
    204205        self._precision = value 
    205206    precision = property(_get_precision, _set_precision, 
     
    279280 
    280281 
    281 class DateTimeType(DatabaseType): 
    282      
    283     default_pytype = datetime.datetime 
     282class FixedRangeType(DatabaseType): 
     283    """DatabaseType which represents values within a fixed range.""" 
    284284    _min = None 
    285285    _max = None 
    286286     
    287287    _initargs = DatabaseType._initargs + ("_min", "_max") 
     288     
     289    def range(self): 
     290        """Return self.max - self.min.""" 
     291        return self._max - self._min 
    288292     
    289293    def min(self): 
     
    295299        return self._max 
    296300 
     301class DateTimeType(FixedRangeType): 
     302    default_pytype = datetime.datetime 
     303    default_adapters = {datetime.datetime: adapters.SQL92TIMESTAMP} 
     304 
     305class DateType(FixedRangeType): 
     306    default_pytype = datetime.date 
     307    default_adapters = {datetime.date: adapters.SQL92DATE} 
     308 
     309class TimeType(DatabaseType): 
     310    default_pytype = datetime.time 
     311    default_adapters = {datetime.time: adapters.SQL92TIME} 
     312 
     313 
     314class BooleanType(DatabaseType): 
     315    """DatabaseType which uses boolean values (True, False, Null).""" 
     316    default_pytype = bool 
     317    default_adapters = {bool: adapters.BOOLEAN} 
     318 
  • trunk/geniusql/providers/msaccess.py

    r56 r57  
    241241 
    242242 
    243 class YESNO(dbtypes.DatabaseType): 
     243class YESNO(dbtypes.BooleanType): 
    244244    # "The BOOLEAN data types are logical types that result in either True 
    245245    # or False values. They use 1 byte of memory for storage, and their 
    246246    # synonyms are BIT, LOGICAL, LOGICAL1, and YESNO. A True value is 
    247247    # equal to -1 while a False value is equal to 0." 
    248     default_adapters = {bool: adapters.BOOLEAN} 
     248    pass 
    249249 
    250250 
  • trunk/geniusql/providers/postgres.py

    r56 r57  
    1313 
    1414import geniusql 
    15 from geniusql import adapters, decompile, errors, typerefs 
    16  
    17  
    18 class PgTIMESTAMP(adapters.SQL92TIMESTAMP): 
     15from geniusql import adapters, dbtypes, decompile, errors 
     16 
     17 
     18# ------------------------------ Adapters ------------------------------ # 
     19 
     20 
     21class PgDATE_Adapter(adapters.SQL92DATE): 
     22     
    1923    def binary_op(self, op1, op, op2): 
    20         sql1 = op1.sql 
    21         if op1.value is not None and op2.pytype is datetime.date: 
     24        if op2.pytype is datetime.timedelta: 
    2225            # Postgres assumes a "date" is actually midnight, so we 
    2326            # need to drop any h:m:s from our interval. 
    24             sql1 = "interval '%s days'" % op1.value.days 
    25         return "(%s %s %s)" % (sql1, op, op2.sql) 
    26  
    27  
    28 class PgDATE(adapters.SQL92DATE): 
    29      
    30     def binary_op(self, op1, op, op2): 
    31         sql1, sql2 = op1.sql, op2.sql 
    32         if op2.pytype is datetime.timedelta and op2.value is not None: 
    33             # Postgres assumes a "date" is actually midnight, so we 
    34             # need to drop any h:m:s from our interval. 
    35             sql2 = "interval '%s days'" % op2.value.days 
     27            return "(%s %s date_trunc('day', %s))" % (op1.sql, op, op2.sql) 
    3628        elif op2.pytype is datetime.date: 
    3729            # Cast to timestamp to achieve an INTERVAL result 
    38             sql1 = "%s::TIMESTAMP" % sql1 
    39             sql2 = "%s::TIMESTAMP" % sql2 
    40         return "(%s %s %s)" % (sql1, op, sql2) 
    41  
    42  
    43 class INTERVAL(adapters.Adapter): 
     30            return "(%s::TIMESTAMP %s %s::TIMESTAMP)" % (op1.sql, op, op2.sql) 
     31        return "(%s %s %s)" % (op1.sql, op, op2.sql) 
     32 
     33 
     34class PgINTERVAL_Adapter(adapters.Adapter): 
    4435     
    4536    def push(self, value): 
     
    8576         
    8677        return datetime.timedelta(days, s) 
    87  
    88  
    89 class BYTEA(adapters.Pickler): 
     78     
     79    def binary_op(self, op1, op, op2): 
     80        if op1.pytype == datetime.date: 
     81            # Postgres assumes a "date" is actually midnight, so we 
     82            # need to drop any h:m:s from our interval. 
     83            return "(date_trunc('day', %s) %s %s)" % (op1.sql, op, op2.sql) 
     84        return "(%s %s %s)" % (op1.sql, op, op2.sql) 
     85 
     86 
     87class PgBYTEA_Adapter(adapters.Pickler): 
    9088     
    9189    def push(self, value): 
     
    10098 
    10199 
    102 class PgVARCHAR(adapters.SQL92VARCHAR): 
     100class PgVARCHAR_Adapter(adapters.SQL92VARCHAR): 
    103101     
    104102    def push(self, value): 
     
    121119 
    122120 
    123 class PgUNICODE(adapters.UNICODE): 
     121class PgUNICODE_Adapter(adapters.UNICODE): 
    124122     
    125123    def push(self, value): 
     
    142140 
    143141 
    144 class Pickler(adapters.Pickler): 
     142class PgPickler(adapters.Pickler): 
    145143     
    146144    def push(self, value): 
     
    169167 
    170168 
    171 class REAL(adapters.SQL92REAL): 
     169class PgFLOAT4_Adapter(adapters.SQL92REAL): 
    172170     
    173171    def push(self, value): 
     
    178176 
    179177 
     178 
     179# ---------------------------- DatabaseTypes ---------------------------- # 
     180 
     181# See http://www.postgresql.org/docs/8.1/static/datatype.html 
     182 
     183# Not implemented here: 
     184#  
     185# box         rectangular box in the plane 
     186# cidr        IPv4 or IPv6 network address 
     187# circle      circle in the plane 
     188# inet        IPv4 or IPv6 host address 
     189# line        infinite line in the plane 
     190# lseg        line segment in the plane 
     191# macaddr     MAC address 
     192# path        geometric path in the plane 
     193# point       geometric point in the plane 
     194# polygon     closed geometric path in the plane 
     195# timetz      time of day, including time zone 
     196# timestamptz date and time, including time zone 
     197 
     198 
     199class BOOLEAN(dbtypes.BooleanType): 
     200    """A logical Boolean (true/false).""" 
     201    synonyms = ['BOOL'] 
     202 
     203 
     204class BYTEA(dbtypes.FrozenByteType): 
     205    """A type for binary data ("byte array").""" 
     206    default_adapters = {str: PgBYTEA_Adapter} 
     207    default_pytype = str 
     208 
     209class BIT(dbtypes.AdjustableStringType): 
     210    """A fixed-length bit string""" 
     211    variable = False 
     212    default_adapters = {str: PgVARCHAR_Adapter, 
     213                        unicode: PgUNICODE_Adapter, 
     214                        None: PgPickler, 
     215                        } 
     216 
     217class VARBIT(dbtypes.AdjustableStringType): 
     218    """A variable-length bit string.""" 
     219    synonyms = ['BIT VARYING'] 
     220    variable = True 
     221    default_adapters = {str: PgVARCHAR_Adapter, 
     222                        unicode: PgUNICODE_Adapter, 
     223                        None: PgPickler, 
     224                        } 
     225 
     226class CHAR(dbtypes.AdjustableStringType): 
     227    """A fixed-length character string.""" 
     228    synonyms = ['CHARACTER'] 
     229    variable = False 
     230    default_adapters = {str: PgVARCHAR_Adapter, 
     231                        unicode: PgUNICODE_Adapter, 
     232                        None: PgPickler, 
     233                        } 
     234 
     235class VARCHAR(dbtypes.AdjustableStringType): 
     236    """A variable-length character string.""" 
     237    synonyms = ['CHARACTER VARYING'] 
     238    variable = True 
     239    default_adapters = {str: PgVARCHAR_Adapter, 
     240                        unicode: PgUNICODE_Adapter, 
     241                        None: PgPickler, 
     242                        } 
     243 
     244 
     245class ComparableInfinity(object): 
     246     
     247    def __cmp__(self, other): 
     248        if isinstance(other, self.__class__): 
     249            return False 
     250        return True 
     251 
     252 
     253class TEXT(dbtypes.FrozenStringType): 
     254    """A variable-length character string.""" 
     255    # TEXT has no hard byte limit. 
     256    bytes = max_bytes = ComparableInfinity() 
     257    default_adapters = {str: PgVARCHAR_Adapter, 
     258                        unicode: PgUNICODE_Adapter, 
     259                        None: PgPickler, 
     260                        } 
     261 
     262 
     263 
     264# Float types. 
     265# "In addition to ordinary numeric values, the floating-point types 
     266# have several special values: Infinity, -Infinity, NaN." 
     267 
     268class FLOAT4(dbtypes.InexactNumericType): 
     269    """A single precision floating-point number.""" 
     270    synonyms = ['REAL'] 
     271    precision = max_precision = 24 
     272    default_adapters = {float: PgFLOAT4_Adapter} 
     273     
     274    def cast(self, sql, totype): 
     275        """Cast the given SQL expression from this type to another.""" 
     276        if isinstance(totype, FLOAT4): 
     277            return sql 
     278        elif isinstance(totype, FLOAT8): 
     279            # Upcast to the larger type 
     280            return "(%s)::FLOAT8" % sql 
     281        raise TypeError("Could not cast %r from %r to %r." 
     282                        % (sql, self, totype)) 
     283 
     284class FLOAT8(dbtypes.InexactNumericType): 
     285    """A double precision floating-point number.""" 
     286    synonyms = ['DOUBLE PRECISION'] 
     287    precision = max_precision = 53 
     288    default_adapters = {float: adapters.SQL92DOUBLE} 
     289     
     290    def cast(self, sql, totype): 
     291        """Cast the given SQL expression from this type to another.""" 
     292        if isinstance(totype, FLOAT8): 
     293            return sql 
     294        elif isinstance(totype, FLOAT4): 
     295            # Downcast to the smaller type 
     296            return "(%s)::FLOAT4" % sql 
     297        raise TypeError("Could not cast %r from %r to %r." 
     298                        % (sql, self, totype)) 
     299 
     300 
     301class INT2(dbtypes.IntegerType): 
     302    """A signed two-byte integer.""" 
     303    synonyms = ['SMALLINT'] 
     304    signed = True 
     305    bytes = max_bytes = 2 
     306 
     307class INT4(dbtypes.IntegerType): 
     308    """A signed four-byte integer.""" 
     309     
     310    # "The data types serial and bigserial are not true types, but merely 
     311    # a notational convenience for setting up unique identifier columns 
     312    # (similar to the AUTO_INCREMENT property supported by some other 
     313    # databases). 
     314    synonyms = ['INT', 'INTEGER', 'SERIAL', 'SERIAL4'] 
     315     
     316    signed = True 
     317    bytes = max_bytes = 4 
     318 
     319class INT8(dbtypes.IntegerType): 
     320    synonyms = ['BIGINT', 'BIGSERIAL', 'SERIAL8'] 
     321    signed = True 
     322    bytes = max_bytes = 8 
     323 
     324 
     325class TIMESTAMP(dbtypes.DateTimeType): 
     326    """A date and time.""" 
     327    pass 
     328 
     329class DATE(dbtypes.DateType): 
     330    """A calendar date (year, month, day).""" 
     331    default_adapters = {datetime.date: PgDATE_Adapter} 
     332 
     333class TIME(dbtypes.TimeType): 
     334    """A time of day.""" 
     335    pass 
     336 
     337class INTERVAL(dbtypes.AdjustablePrecisionType): 
     338    """A time span.""" 
     339    default_adapters = {datetime.timedelta: PgINTERVAL_Adapter} 
     340    default_pytype = datetime.timedelta 
     341 
     342 
     343class NUMERIC(dbtypes.ExactNumericType): 
     344    """An exact numeric of selectable precision.""" 
     345     
     346    # "In addition to ordinary numeric values, the numeric type allows the 
     347    # special value NaN, meaning "not-a-number". Any operation on NaN yields 
     348    # another NaN. When writing this value as a constant in a SQL command, 
     349    # you must put quotes around it, for example UPDATE table SET x = 'NaN'. 
     350    # On input, the string NaN is recognized in a case-insensitive manner." 
     351     
     352    synonyms = ['DECIMAL'] 
     353    precision = max_precision = 1000 
     354 
     355 
     356class MONEY(dbtypes.FrozenPrecisionType): 
     357    """A currency amount.""" 
     358    default_pytype = dbtypes.ExactNumericType.default_pytype 
     359 
     360 
     361 
    180362class PgAdapterSet(adapters.AdapterSet): 
    181363     
    182     def pytype_for_TIMESTAMPTZ(self, hints): 
    183         return datetime.datetime 
    184     def pytype_for_TIMETZ(self, hints): 
    185         return datetime.time 
    186     def pytype_for_INT2(self, hints): 
    187         return int 
    188     def pytype_for_INT4(self, hints): 
    189         return int 
    190     def pytype_for_INT8(self, hints): 
    191         return long 
    192     def pytype_for_FLOAT4(self, hints): 
    193         return float 
    194     def pytype_for_FLOAT8(self, hints): 
    195         return float 
    196     def pytype_for_MONEY(self, hints): 
    197         return float 
    198     def pytype_for_BYTEA(self, hints): 
    199         return str 
    200     def pytype_for_BPCHAR(self, hints): 
    201         return str 
    202     def pytype_for_INTERVAL(self, hints): 
    203         return datetime.timedelta 
    204      
    205     # Postgres has a wonderful interval type we can use. 
    206     def dbtype_for_datetime_timedelta(self, hints): 
    207         return "INTERVAL" 
    208  
    209  
    210 PgAdapterSet.defaults.update({(str, "BYTEA"): BYTEA, 
    211                               (float, "REAL"): REAL, 
    212                               (float, "FLOAT4"): REAL, 
    213                               (str, "any"): PgVARCHAR, 
    214                               (unicode, "any"): PgUNICODE, 
    215                               (datetime.datetime, "any"): PgTIMESTAMP, 
    216                               (datetime.date, "any"): PgDATE, 
    217                               (datetime.timedelta, "INTERVAL"): INTERVAL, 
    218                               }) 
    219 for k, v in PgAdapterSet.defaults.items(): 
    220     if issubclass(v, adapters.Pickler): 
    221         PgAdapterSet.defaults[k] = Pickler 
    222 del k, v 
     364    known_types = {'float': [FLOAT4, FLOAT8], 
     365                   'varchar': [VARCHAR, TEXT, VARBIT, BYTEA], 
     366                   'char': [CHAR, BIT], 
     367                   'int': [INT2, INT4, INT8], 
     368                   'bool': [BOOLEAN], 
     369                   'datetime': [TIMESTAMP], 
     370                   'date': [DATE], 
     371                   'time': [TIME], 
     372                   'timedelta': [INTERVAL], 
     373                   'numeric': [NUMERIC], 
     374                   'other': [MONEY], 
     375                   } 
     376 
    223377 
    224378 
     
    274428    def builtins_utcnow(self): 
    275429        return self.get_expr("NOW()", datetime.datetime) 
     430     
     431    def builtins_today(self): 
     432        neg, h, m = adapters.localtime_offset 
     433        sign = "" 
     434        if neg: 
     435            sign = "-" 
     436        offset = "%s:%s" % (h, m) 
     437        return self.get_expr("date_trunc('day', NOW() AT TIME ZONE INTERVAL '%s%s')" 
     438                             % (sign, offset), datetime.date) 
    276439 
    277440 
     
    368531            dbtype, _ = self.db.fetch("SELECT typname, typlen FROM pg_type " 
    369532                                      "WHERE oid = %s" % row[1], conn=conn) 
    370             if dbtype: 
    371                 dbtype = dbtype[0][0].upper() 
    372             else: 
    373                 dbtype = None 
    374             c = geniusql.Column(adapterset.python_type(dbtype), dbtype, 
    375                                 None, {}, row[2] in indices, 
    376                                 row[0], self.db.quote(row[0])) 
     533            dbtype = adapterset.canonicalize(dbtype[0][0].upper()) 
     534             
     535            c = geniusql.Column(dbtype.default_pytype, dbtype, 
     536                                None, key=row[2] in indices, 
     537                                name=row[0], qname=self.db.quote(row[0])) 
    377538            c.adapter = adapterset.default(c.pytype, dbtype) 
    378539             
    379             if dbtype in ('FLOAT4', 'FLOAT8'): 
    380                 c.hints['precision'] = row[3] 
    381             elif dbtype in ('MONEY', 'NUMERIC'): 
    382                 c.hints['precision'] = (row[4] >> 16) & 65535 
    383                 c.hints['scale'] = (row[4] & 65535) - 4 
     540            if dbtype in (FLOAT4, FLOAT8): 
     541                dbtype.precision = row[3] 
     542            elif dbtype in (MONEY, NUMERIC): 
     543                dbtype.precision = (row[4] >> 16) & 65535 
     544                dbtype.scale = (row[4] & 65535) - 4 
    384545             
    385546            # Default value 
     
    404565                c.default = None 
    405566             
    406             if dbtype.startswith('BPCHAR') or dbtype.startswith('VARCHAR')
     567            if dbtype is VARCHAR
    407568                # See http://archives.postgresql.org/pgsql-interfaces/2004-07/msg00021.php 
    408                 c.hints['bytes'] = row[4] - 4 
     569                dbtype.bytes = row[4] - 4 
    409570            else: 
    410571                bytes = row[3] 
    411572                if bytes > 0: 
    412                     c.hints['bytes'] = bytes 
    413                 elif dbtype == 'TEXT': 
    414                     c.hints['bytes'] = 0 
     573                    dbtype.bytes = bytes 
    415574             
    416575            cols.append(c) 
     
    461620            default = " DEFAULT %s" % default 
    462621         
    463         return '%s %s%s' % (column.qname, column.dbtype, default) 
     622        return '%s %s%s' % (column.qname, column.dbtype.ddl(), default) 
    464623     
    465624    def create_sequence(self, table, column):