Changeset 18
- Timestamp:
- 02/18/07 23:36:15
- Files:
-
- trunk/geniusql/__init__.py (modified) (1 diff)
- trunk/geniusql/adapters.py (modified) (2 diffs)
- trunk/geniusql/objects.py (modified) (2 diffs)
- trunk/geniusql/providers/ado.py (modified) (8 diffs)
- trunk/geniusql/providers/firebird.py (modified) (4 diffs)
- trunk/geniusql/providers/mysql.py (modified) (4 diffs)
- trunk/geniusql/providers/postgres.py (modified) (5 diffs)
- trunk/geniusql/providers/sqlite.py (modified) (4 diffs)
- trunk/geniusql/test/test_sqlite.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/__init__.py
r17 r18 71 71 that's not How Python Works. Quack. 72 72 ''' 73 type.__init__(name, bases, dct) 73 74 74 75 newdoc = [cls.__doc__ or ""] trunk/geniusql/adapters.py
r17 r18 342 342 """Determine the best database type for a given column + Python type. 343 343 344 When Dejavuis asked to create database tables, it must choose an344 When Geniusql is asked to create database tables, it must choose an 345 345 appropriate column data type for each UnitProperty based on the 346 346 type (and hints) of that property. This class recommends such … … 368 368 # TEXT is not an SQL standard, but it's common. 369 369 numeric_text_type = "TEXT" 370 371 _reverse_types = { 372 "DATE": datetime.date, 373 "DATETIME": datetime.datetime, 374 "TIMESTAMP": datetime.datetime, 375 "TIME": datetime.time, 376 377 "INT": int, 378 "INTEGER": int, 379 "SMALLINT": int, 380 381 "BOOL": bool, 382 "BOOLEAN": bool, 383 384 "BIGINT": long, 385 "LONG": long, 386 387 "DOUBLE": float, 388 "DOUBLE PRECISION": float, 389 "FLOAT": float, 390 "REAL": float, 391 "SINGLE": float, 392 393 "CHAR": str, 394 "BLOB": str, 395 "TEXT": str, 396 "VARCHAR": str, 397 } 398 399 if decimal: 400 _reverse_types["DECIMAL"] = decimal.Decimal 401 _reverse_types["NUMERIC"] = decimal.Decimal 402 elif fixedpoint: 403 _reverse_types["DECIMAL"] = fixedpoint.FixedPoint 404 _reverse_types["NUMERIC"] = fixedpoint.FixedPoint 405 406 def __init__(self): 407 # Make a copy of the class-level 'dict 408 self._reverse_types = self._reverse_types.copy() 409 410 def python_type(self, dbtype): 411 """Return a Python type which can store values of the given dbtype.""" 412 # Strip any size argument (e.g. "VARCHAR(255)"). 413 key = dbtype.upper().split("(", 1)[0] 414 try: 415 return self._reverse_types[key] 416 except KeyError: 417 raise TypeError("Database type %r could not be converted " 418 "to a Python type." % dbtype) 419 420 def related(self, pytype1, pytype2): 421 """If values of both types are expressed with the same SQL, return True.""" 422 if issubclass(pytype1, pytype2) or issubclass(pytype2, pytype1): 423 return True 424 if issubclass(pytype1, basestring) and issubclass(pytype2, basestring): 425 return True 426 if ((issubclass(pytype1, int) or issubclass(pytype1, long)) and 427 (issubclass(pytype2, int) or issubclass(pytype2, long))): 428 return True 429 if fixedpoint: 430 if decimal: 431 if ((issubclass(pytype1, fixedpoint.FixedPoint) 432 or issubclass(pytype1, decimal.Decimal)) and 433 (issubclass(pytype2, fixedpoint.FixedPoint) 434 or issubclass(pytype2, decimal.Decimal))): 435 return True 436 else: 437 if (issubclass(pytype1, fixedpoint.FixedPoint) and 438 issubclass(pytype2, fixedpoint.FixedPoint)): 439 return True 440 else: 441 if decimal: 442 if (issubclass(pytype1, decimal.Decimal) and 443 issubclass(pytype2, decimal.Decimal)): 444 return True 445 return False 370 446 371 447 def coerce(self, col, pytype): trunk/geniusql/objects.py
r17 r18 606 606 if dbtype is None: 607 607 col.dbtype = self.db.typeadapter.coerce(col, pytype) 608 pytype2 = self.db. python_type(col.dbtype)609 col.imperfect_type = not self.db. isrelatedtype(pytype, pytype2)608 pytype2 = self.db.typeadapter.python_type(col.dbtype) 609 col.imperfect_type = not self.db.typeadapter.related(pytype, pytype2) 610 610 611 611 return col … … 773 773 def log(self, msg): 774 774 pass 775 776 def python_type(self, dbtype):777 """Return a Python type which can store values of the given dbtype."""778 raise TypeError("Database type %r could not be converted "779 "to a Python type." % dbtype)780 781 def isrelatedtype(self, pytype1, pytype2):782 """If values of both types are expressed with the same SQL, return True."""783 if issubclass(pytype1, pytype2) or issubclass(pytype2, pytype1):784 return True785 if issubclass(pytype1, basestring) and issubclass(pytype2, basestring):786 return True787 if ((issubclass(pytype1, int) or issubclass(pytype1, long)) and788 (issubclass(pytype2, int) or issubclass(pytype2, long))):789 return True790 if typerefs.fixedpoint:791 if typerefs.decimal:792 if ((issubclass(pytype1, typerefs.fixedpoint.FixedPoint)793 or issubclass(pytype1, typerefs.decimal.Decimal)) and794 (issubclass(pytype2, typerefs.fixedpoint.FixedPoint)795 or issubclass(pytype2, typerefs.decimal.Decimal))):796 return True797 else:798 if (issubclass(pytype1, typerefs.fixedpoint.FixedPoint) and799 issubclass(pytype2, typerefs.fixedpoint.FixedPoint)):800 return True801 else:802 if typerefs.decimal:803 if (issubclass(pytype1, typerefs.decimal.Decimal) and804 issubclass(pytype2, typerefs.decimal.Decimal)):805 return True806 return False807 775 808 776 # Naming # trunk/geniusql/providers/ado.py
r17 r18 511 511 512 512 cols = [] 513 pytype = self.db.typeadapter.python_type 513 514 for row in data: 514 515 # I tried passing criteria to OpenSchema, but passing None is … … 520 521 default = row[8] 521 522 if default is not None: 522 deftype = self.db.python_type(dbtype)523 deftype = pytype(dbtype) 523 524 if issubclass(deftype, (int, long)): 524 525 # We may have stuck extraneous quotes in the default … … 529 530 530 531 name = str(row[3]) 531 c = geniusql.Column( self.db.python_type(dbtype), dbtype,532 c = geniusql.Column(pytype(dbtype), dbtype, 532 533 default, hints={}, key=(name in pknames), 533 534 name=name, qname=self.db.quote(name)) … … 613 614 614 615 616 class ADOTypeAdapter(adapters.TypeAdapter): 617 618 _reverse_types = adapters.TypeAdapter._reverse_types.copy() 619 _reverse_types.update({ 620 "DBDATE": datetime.date, 621 "DBTIME": datetime.time, 622 "DBTIMESTAMP": datetime.datetime, 623 624 "UNSIGNEDTINYINT": int, 625 "UNSIGNEDSMALLINT": int, 626 "UNSIGNEDINT": int, 627 "BIT": bool, 628 629 "UNSIGNEDBIGINT": long, 630 631 "BSTR": str, 632 "VARIANT": str, 633 "BINARY": str, 634 "LONGVARCHAR": str, 635 "VARBINARY": str, 636 "LONGVARBINARY": str, 637 638 "WCHAR": unicode, 639 "VARWCHAR": unicode, 640 "LONGVARWCHAR": unicode, 641 }) 642 643 if typerefs.decimal: 644 _reverse_types["CURRENCY"] = typerefs.decimal.Decimal 645 elif typerefs.fixedpoint: 646 _reverse_types["CURRENCY"] = typerefs.fixedpoint.FixedPoint 647 648 615 649 class ADODatabase(geniusql.Database): 616 650 617 651 decompiler = ADOSQLDecompiler 618 652 adapterfromdb = AdapterFromADO() 619 620 def python_type(self, dbtype): 621 """Return a Python type which can store values of the given dbtype.""" 622 if dbtype in ("DATE", "DBDATE"): 623 return datetime.date 624 elif dbtype == "DBTIME": 625 return datetime.time 626 elif dbtype in ("DATETIME", "DBTIMESTAMP"): 627 return datetime.datetime 628 elif dbtype in ("SMALLINT", "INTEGER", "TINYINT", 629 "UNSIGNEDTINYINT", "UNSIGNEDSMALLINT", 630 "UNSIGNEDINT"): 631 return int 632 elif dbtype in ("BIT", "BOOLEAN"): 633 return bool 634 elif dbtype in ("BIGINT", "UNSIGNEDBIGINT", "LONG"): 635 return long 636 elif dbtype in ("SINGLE", "DOUBLE", "DOUBLE PRECISION", "REAL"): 637 return float 638 639 for t in ("DECIMAL", "NUMERIC", "CURRENCY"): 640 if dbtype.startswith(t): 641 if typerefs.decimal: 642 return typerefs.decimal.Decimal 643 elif typerefs.fixedpoint: 644 return typerefs.fixedpoint.FixedPoint 645 646 for t in ("BSTR", "VARIANT", "BINARY", "CHAR", "MEMO", "TEXT", 647 "VARCHAR", "LONGVARCHAR", "VARBINARY", "LONGVARBINARY"): 648 if dbtype.startswith(t): 649 return str 650 651 for t in ("WCHAR", "VARWCHAR", "LONGVARWCHAR"): 652 if dbtype.startswith(t): 653 return unicode 654 655 raise TypeError("Database type %r could not be converted " 656 "to a Python type." % dbtype) 657 653 typeadapter = ADOTypeAdapter() 658 654 659 655 # Naming # … … 806 802 807 803 808 class TypeAdapter_SQLServer( adapters.TypeAdapter):804 class TypeAdapter_SQLServer(ADOTypeAdapter): 809 805 810 806 # Hm. Docs say 38, but I can't seem to get more than 12 working. … … 999 995 1000 996 1001 class TypeAdapter_MSAccess( adapters.TypeAdapter):997 class TypeAdapter_MSAccess(ADOTypeAdapter): 1002 998 # http://msdn2.microsoft.com/en-us/library/ms714540.aspx 1003 999 # http://office.microsoft.com/en-us/access/HP010322481033.aspx … … 1006 1002 numeric_max_precision = 12 1007 1003 numeric_max_bytes = 6 1004 1005 _reverse_types = ADOTypeAdapter._reverse_types.copy() 1006 _reverse_types.update({ 1007 "LONG": int, 1008 "MEMO": str, 1009 }) 1008 1010 1009 1011 def coerce_bool(self, col): return "BIT" … … 1229 1231 del conn 1230 1232 return "ADO Version: %s" % v 1231 1232 def python_type(self, dbtype):1233 if dbtype == "LONG":1234 return int1235 return ADODatabase.python_type(self, dbtype)1236 1233 1237 1234 trunk/geniusql/providers/firebird.py
r17 r18 113 113 114 114 numeric_text_type = "BLOB" 115 116 _reverse_types = adapters.TypeAdapter._reverse_types.copy() 117 _reverse_types.update({ 118 'LONG': int, 119 'SHORT': int, 120 'INT64': long, 121 'VARYING': str, 122 'NCHAR': unicode, 123 'NATIONAL': unicode, 124 }) 115 125 116 126 def coerce_str(self, col): … … 435 445 "T.RDB$FIELD_NAME='RDB$FIELD_TYPE';" % tablename, conn=conn) 436 446 cols = [] 447 pytype = self.db.typeadapter.python_type 437 448 for name, dbtype, fieldlen, default, prec, scale in data: 438 449 hints = {} … … 463 474 464 475 # Column(pytype, dbtype, default=None, hints=None, key=False, name, qname) 465 col = geniusql.Column( self.db.python_type(dbtype), dbtype, default,476 col = geniusql.Column(pytype(dbtype), dbtype, default, 466 477 hints, key, name, self.db.quote(name)) 467 478 cols.append(col) … … 563 574 sql_name_max_length = 31 564 575 encoding = 'utf8' 565 566 def python_type(self, dbtype):567 """Return a Python type which can store values of the given dbtype."""568 dbtype = dbtype.upper()569 570 if dbtype in ('INTEGER', 'SMALLINT', 'LONG', 'SHORT'):571 return int572 elif dbtype in ('BIGINT', 'INT64'):573 return long574 elif dbtype in ('FLOAT', 'DOUBLE', 'DOUBLE PRECISION', 'REAL'):575 return float576 elif dbtype.startswith('NUMERIC') or dbtype.startswith('DECIMAL'):577 if typerefs.decimal:578 return typerefs.decimal.Decimal579 elif typerefs.fixedpoint:580 return typerefs.fixedpoint.FixedPoint581 elif dbtype == 'DATE':582 return datetime.date583 elif dbtype == 'TIMESTAMP':584 return datetime.datetime585 elif dbtype == 'TIME':586 return datetime.time587 for t in ('CHAR', 'VARCHAR', 'BLOB', 'TEXT', 'VARYING'):588 if dbtype.startswith(t):589 return str590 for t in ('NCHAR', 'NATIONAL'):591 if dbtype.startswith(t):592 return unicode593 594 raise TypeError("Database type %r could not be converted "595 "to a Python type." % dbtype)596 576 597 577 # Naming # trunk/geniusql/providers/mysql.py
r17 r18 88 88 numeric_max_precision = 16 89 89 numeric_max_bytes = 8 90 91 _reverse_types = adapters.TypeAdapter._reverse_types.copy() 92 _reverse_types.update({ 93 'TINYINT': int, 94 'MEDIUMINT': int, 95 96 'BINARY': str, 97 'VARBINARY': str, 98 'TINYBLOB': str, 99 'TINYTEXT': str, 100 'MEDIUMBLOB': str, 101 'MEDIUMTEXT': str, 102 'LONGBLOB': str, 103 'LONGTEXT': str, 104 }) 90 105 91 106 def float_type(self, precision): … … 126 141 return "MEDIUMINT" 127 142 return "INTEGER" 143 144 def related(self, pytype1, pytype2): 145 if pytype1 is float and pytype2 is float: 146 # MySQL provides no reliable method to compare floats in SQL. 147 # Setting this to False will set col.imperfect_type to True, 148 # which will tell the SQL decompiler to mark float comparisons 149 # as imperfect. 150 return False 151 return adapters.TypeAdapter.related(self, pytype1, pytype2) 128 152 129 153 … … 325 349 326 350 key = (row[3] == "PRI") 327 pytype = self.db. python_type(dbtype)351 pytype = self.db.typeadapter.python_type(dbtype) 328 352 329 353 col = geniusql.Column(pytype, dbtype, None, hints, key, … … 419 443 return "MySQL Version: %s" % self._version 420 444 421 def python_type(self, dbtype):422 """Return a Python type which can store values of the given dbtype."""423 dbtype = dbtype.upper()424 parenpos = dbtype.find("(")425 if parenpos > -1:426 dbtype = dbtype[:parenpos]427 428 if dbtype in ('TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER'):429 return int430 elif dbtype == 'BIGINT':431 return long432 elif dbtype in ('BOOL', 'BOOLEAN'):433 return bool434 elif dbtype in ('FLOAT', 'DOUBLE', 'DOUBLE PRECISION', 'REAL'):435 return float436 elif dbtype in ('DECIMAL', 'NUMERIC'):437 if typerefs.decimal:438 return typerefs.decimal.Decimal439 elif typerefs.fixedpoint:440 return typerefs.fixedpoint.Fixedpoint441 elif dbtype == 'DATE':442 return datetime.date443 elif dbtype in ('DATETIME', 'TIMESTAMP'):444 return datetime.datetime445 elif dbtype == 'TIME':446 return datetime.time447 elif dbtype in ('CHAR', 'VARCHAR', 'BINARY', 'VARBINARY',448 'TINYBLOB', 'TINYTEXT', 'BLOB', 'TEXT',449 'MEDIUMBLOB', 'MEDIUMTEXT', 'LONGBLOB', 'LONGTEXT'):450 return str451 452 raise TypeError("Database type %r could not be converted "453 "to a Python type." % dbtype)454 455 def isrelatedtype(self, pytype1, pytype2):456 if pytype1 is float and pytype2 is float:457 # MySQL provides no reliable method to compare floats in SQL.458 # Setting this to False will set col.imperfect_type to True,459 # which will tell the SQL decompiler to mark float comparisons460 # as imperfect.461 return False462 return geniusql.Database.isrelatedtype(self, pytype1, pytype2)463 464 445 def quote(self, name): 465 446 """Return name, quoted for use in an SQL statement.""" trunk/geniusql/providers/postgres.py
r17 r18 178 178 data, _ = self.db.fetch(sql, conn=conn) 179 179 cols = [] 180 pytype = self.db.typeadapter.python_type 180 181 for row in data: 181 182 name = row[0] … … 192 193 else: 193 194 dbtype = None 194 c = geniusql.Column( self.db.python_type(dbtype), dbtype,195 c = geniusql.Column(pytype(dbtype), dbtype, 195 196 None, {}, row[2] in indices, 196 197 row[0], self.db.quote(row[0])) … … 219 220 # our guessed type. Be sure to strip any ::typename 220 221 default = default.split("::", 1)[0] 221 c.default = self.db.python_type(dbtype)(default)222 c.default = pytype(dbtype)(default) 222 223 else: 223 224 c.default = None … … 315 316 316 317 318 class PgTypeAdapter(adapters.TypeAdapter): 319 320 _reverse_types = adapters.TypeAdapter._reverse_types.copy() 321 _reverse_types.update({ 322 "TIMESTAMPTZ": datetime.datetime, 323 "TIMETZ": datetime.time, 324 "INT2": int, 325 "INT4": int, 326 "INT8": long, 327 "FLOAT4": float, 328 "FLOAT8": float, 329 "MONEY": float, 330 "BYTEA": str, 331 "BPCHAR": str, 332 }) 333 317 334 class PgDatabase(geniusql.Database): 318 335 … … 324 341 adaptertosql = AdapterToPgSQL() 325 342 adapterfromdb = AdapterFromPg() 343 typeadapter = PgTypeAdapter() 326 344 327 345 decompiler = PgDecompiler 328 346 schemaclass = PgSchema 329 330 def __init__(self, **kwargs):331 geniusql.Database.__init__(self, **kwargs)332 333 def python_type(self, dbtype):334 """Return a Python type which can store values of the given dbtype."""335 dbtype = dbtype.upper()336 if dbtype in ('INT2', 'INT4', 'INTEGER', 'SMALLINT'):337 return int338 elif dbtype in ('BOOL', 'BOOLEAN'):339 return bool340 elif dbtype in ('INT8', 'BIGINT'):341 return long342 elif dbtype in ('FLOAT4', 'FLOAT8', 'MONEY', 'DOUBLE PRECISION', 'REAL'):343 return float344 elif dbtype.startswith('NUMERIC'):345 if typerefs.decimal:346 return typerefs.decimal.Decimal347 elif typerefs.fixedpoint:348 return typerefs.fixedpoint.FixedPoint349 elif dbtype == 'DATE':350 return datetime.date351 elif dbtype in ('TIMESTAMP', 'TIMESTAMPTZ'):352 return datetime.datetime353 elif dbtype in ('TIME', 'TIMETZ'):354 return datetime.time355 elif dbtype in ('BYTEA'):356 return str357 for t in ('CHAR', 'VARCHAR', 'BPCHAR', 'TEXT'):358 if dbtype.startswith(t):359 return str360 361 raise TypeError("Database type %r could not be converted "362 "to a Python type." % dbtype)363 347 364 348 def quote(self, name): trunk/geniusql/providers/sqlite.py
r17 r18 57 57 return '1' 58 58 return '0' 59 60 61 class AdapterToSQLiteTypeless(AdapterToSQLite):62 63 def cast_any_to_object(self, colref):64 return colref65 59 66 60 … … 238 232 numeric_max_bytes = 0 239 233 234 _reverse_types = adapters.TypeAdapter._reverse_types.copy() 235 _reverse_types.update({ 236 "NONE": unicode, 237 }) 238 239 def python_type(self, dbtype): 240 """Return a Python type which can store values of the given dbtype.""" 241 # Strip any size argument (e.g. "VARCHAR(255)"). 242 key = dbtype.upper().split("(", 1)[0] 243 try: 244 return self._reverse_types[key] 245 except KeyError: 246 return str 247 240 248 def coerce_decimal_Decimal(self, col): 241 249 return "TEXT" … … 265 273 """Return a datatype which can handle the given number of bytes.""" 266 274 return "INTEGER" 267 268 269 class TypeAdapterSQLiteTypeless(adapters.TypeAdapter): 270 271 def coerce(self, col, pytype): 272 # SQLite can be 'typeless' (but we lose the type introspection) 273 # This will be returned inside _get_columns as NUMERIC. 274 return "" 275 276 perfect_dates = False 277 278 def related(self, pytype1, pytype2): 279 if (self.perfect_dates and 280 issubclass(pytype1, (datetime.date, datetime.time, datetime.datetime)) and 281 issubclass(pytype2, self.python_type(self.coerce(None, pytype1)))): 282 return True 283 return adapters.TypeAdapter.related(self, pytype1, pytype2) 275 284 276 285 … … 763 772 kwargs['mode'] = int(kwargs.pop('mode', '0755'), 8) 764 773 geniusql.Database.__init__(self, **kwargs) 765 766 def isrelatedtype(self, pytype1, pytype2):767 if (self.adaptertosql.perfect_dates and768 issubclass(pytype1, (datetime.date, datetime.time, datetime.datetime)) and769 issubclass(pytype2, self.python_type(self.typeadapter.coerce(None, pytype1)))):770 return True771 return geniusql.Database.isrelatedtype(self, pytype1, pytype2)772 773 def python_type(self, dbtype):774 """Return a Python type which can store values of the given dbtype."""775 if isinstance(self.typeadapter, TypeAdapterSQLiteTypeless):776 return str777 778 if dbtype == "REAL":779 return float780 elif dbtype == "NUMERIC":781 if typerefs.decimal:782 return typerefs.decimal.Decimal783 elif typerefs.fixedpoint:784 return typerefs.fixedpoint.FixedPoint785 elif dbtype.startswith("INTEGER"):786 return int787 elif dbtype == "NONE":788 return unicode789 790 return str791 774 792 775 def quote(self, name): trunk/geniusql/test/test_sqlite.py
r17 r18 33 33 print "Testing SQLite with 'Perfect Dates'" 34 34 opts['adaptertosql.perfect_dates'] = True 35 opts['typeadapter.perfect_dates'] = True 35 36 reload(zoo_fixture) 36 37 zoo_fixture.run(DB_class, dbpath, opts) 37 38 opts['adaptertosql.perfect_dates'] = False 39 opts['typeadapter.perfect_dates'] = False 38 40 39 41 print … … 41 43 reload(zoo_fixture) 42 44 zoo_fixture.run(DB_class, ':memory:', opts) 43 44 print45 print "Testing SQLite 'typeless'..."46 from geniusql.providers import sqlite47 opts['typeadapter'] = sqlite.TypeAdapterSQLiteTypeless()48 reload(zoo_fixture)49 zoo_fixture.run(DB_class, dbpath, opts)50 45 51 46 if __name__ == "__main__":
