Changeset 55
- Timestamp:
- 04/02/07 18:07:48
- Files:
-
- trunk/geniusql/adapters.py (modified) (4 diffs)
- trunk/geniusql/codewalk.py (modified) (1 diff)
- trunk/geniusql/dbtypes.py (modified) (10 diffs)
- trunk/geniusql/objects.py (modified) (1 diff)
- trunk/geniusql/providers/__init__.py (modified) (1 diff)
- trunk/geniusql/providers/ado.py (modified) (4 diffs)
- trunk/geniusql/providers/msaccess.py (modified) (11 diffs)
- trunk/geniusql/providers/sqlserver.py (modified) (14 diffs)
- trunk/geniusql/test/test_msaccess.py (modified) (3 diffs)
- trunk/geniusql/typerefs.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/adapters.py
r54 r55 143 143 144 144 def database_type(self, pytype, hints=None): 145 """Re commend a DatabaseType for the given Python type.145 """Return a DatabaseType instance for the given Python type. 146 146 147 147 hints: if provided, this should be a dict of property attributes … … 173 173 for dbtype in self.known_types['varchar']: 174 174 if bytes <= dbtype.max_bytes: 175 print dbtype, bytes176 175 return dbtype(bytes=bytes) 177 176 raise ValueError("%r is greater than the maximum bytes %r." … … 332 331 """Return the SQL for the given binary operation.""" 333 332 return "%s %s %s" % (op1.sql, op, op2.sql) 333 334 def __copy__(self): 335 return self.__class__() 336 copy = __copy__ 334 337 335 338 … … 500 503 # Coerce to str for pickle.loads restriction. 501 504 if isinstance(value, unicode): 502 value = value.encode( self.encoding)505 value = value.encode('raw-unicode-escape') 503 506 else: 504 507 value = str(value) trunk/geniusql/codewalk.py
r54 r55 175 175 b = self._bytecode 176 176 if verbose: 177 self.debug("\n WALKING: ", b)177 self.debug("\n\nWALKING: ", b) 178 178 b_len = len(b) # Speed hack 179 179 while self.cursor < b_len: trunk/geniusql/dbtypes.py
r54 r55 8 8 """A database type, such as 'REAL' or 'DATE' (with size details).""" 9 9 10 # This isn't the most elegant solution, but it's better than making 11 # producers of each subclass write their own repr and copy methods. 12 _initargs = () 13 14 # These are class-level attributes and should not be overridden or copied 15 # (they're only used at the class level, so it wouldn't make much sense). 10 16 default_pytype = None 11 17 default_adapters = None … … 17 23 18 24 def __repr__(self): 19 return "%s.%s()" % (self.__module__, self.__class__.__name__) 25 attrs = ", ".join(["%s=%r" % (k, getattr(self, k)) 26 for k in self._initargs]) 27 return "%s.%s(%s)" % (self.__module__, self.__class__.__name__, attrs) 28 29 def __copy__(self): 30 attrs = dict([(k, getattr(self, k)) for k in self._initargs]) 31 return self.__class__(**attrs) 32 copy = __copy__ 20 33 21 34 def cast(self, sql, totype): … … 28 41 29 42 30 class FixableByteType(DatabaseType): 31 """DatabaseType which specifies a 'bytes' attribute.""" 43 class FrozenByteType(DatabaseType): 44 """DatabaseType which specifies a frozen 'bytes' attribute.""" 45 46 _initargs = DatabaseType._initargs + ("bytes", "max_bytes") 47 48 _bytes = max_bytes = 255 49 def _get_bytes(self): 50 return self._bytes 51 def _set_bytes(self, value): 52 pass 53 bytes = property(_get_bytes, _set_bytes, 54 doc="The bytes used for this type.") 55 56 57 class AdjustableByteType(DatabaseType): 58 """DatabaseType which specifies an adjustable 'bytes' attribute.""" 59 60 _initargs = DatabaseType._initargs + ("bytes", "max_bytes") 32 61 33 62 # The maximum allowable maximum. 34 63 max_bytes = 255 35 # Whether the type allows the bytes to be configured.36 fixed_bytes = False37 64 38 65 _bytes = 255 … … 40 67 return self._bytes 41 68 def _set_bytes(self, value): 42 if self.fixed_bytes:43 value = self.max_bytes44 69 if value is not None: 45 70 if value < 1: … … 54 79 def ddl(self): 55 80 """Return the type for use in CREATE or ALTER statements.""" 56 if not self.fixed_bytes: 57 return "%s(%s)" % (self.__class__.__name__, self.bytes) 58 return self.__class__.__name__ 59 60 61 class StringType(FixableByteType): 62 63 # Whether the type is a variable-length type. 81 return "%s(%s)" % (self.__class__.__name__, self.bytes) 82 83 84 class FrozenStringType(DatabaseType): 85 """DatabaseType for string types whose byte-length is not adjuatable. 86 87 A FrozenStringType does not imply that the type is a fixed-byte 88 type like CHAR as opposed to a variable-byte type like VARCHAR 89 (that difference is specified in the "variable" attribute). 90 Instead, a Frozen* type implies that the byte limit is not 91 user-specifiable; for example, Microsoft Access' MEMO type 92 is always 65535 bytes, and takes no size argument in DDL. 93 """ 94 95 # CHAR vs VARCHAR 64 96 variable = True 65 97 … … 82 114 83 115 84 class IntegerType(FixableByteType): 116 class AdjustableStringType(AdjustableByteType): 117 """DatabaseType for adjustable-byte string types.""" 118 119 # CHAR vs VARCHAR 120 variable = True 121 122 default_pytype = str 123 default_adapters = {str: adapters.SQL92VARCHAR, 124 unicode: adapters.UNICODE, 125 float: _numtxt(float), 126 int: _numtxt(int), 127 long: _numtxt(long), 128 datetime.timedelta: adapters.INTERVAL, 129 None: adapters.Pickler, 130 } 131 if typerefs.decimal: 132 if hasattr(typerefs.decimal, "Decimal"): 133 default_adapters[typerefs.decimal.Decimal] = adapters.DECIMAL 134 else: 135 default_adapters[typerefs.decimal] = adapters.DECIMAL 136 if typerefs.fixedpoint: 137 default_adapters[typerefs.fixedpoint.FixedPoint] = adapters.FIXEDPOINT 138 139 140 class IntegerType(FrozenByteType): 141 142 _initargs = FrozenByteType._initargs + ("signed", ) 85 143 86 144 default_pytype = int … … 109 167 110 168 111 class FixablePrecisionType(DatabaseType): 112 """DatabaseType which specifies a 'precision' attribute.""" 169 class FrozenPrecisionType(DatabaseType): 170 """DatabaseType which specifies a frozen 'precision' attribute.""" 171 172 _initargs = DatabaseType._initargs + ("precision", "max_precision") 173 174 _precision = max_precision = 255 175 def _get_precision(self): 176 return self._precision 177 def _set_precision(self, value): 178 pass 179 precision = property(_get_precision, _set_precision, 180 doc="The frozen precision in binary digits (bits).") 181 182 183 class AdjustablePrecisionType(DatabaseType): 184 """DatabaseType which specifies an adjustable 'precision' attribute.""" 185 186 _initargs = DatabaseType._initargs + ("precision", "max_precision") 113 187 114 188 # Max binary precision for floating-point columns (= 53 for PostgreSQL 8). … … 118 192 max_precision = 53 119 193 120 # Whether the type allows the precision to be configured.121 fixed_precision = False122 123 194 _precision = 255 124 195 def _get_precision(self): 125 196 return self._precision 126 197 def _set_precision(self, value): 127 if self.fixed_precision:128 value = self.max_precision129 198 if value is not None: 130 199 if value > 1: … … 140 209 def ddl(self): 141 210 """Return the type for use in CREATE or ALTER statements.""" 142 if not self.fixed_precision: 143 return "%s(%s)" % (self.__class__.__name__, self.precision) 144 return self.__class__.__name__ 145 146 147 class InexactNumericType(FixablePrecisionType): 211 return "%s(%s)" % (self.__class__.__name__, self.precision) 212 213 214 class InexactNumericType(FrozenPrecisionType): 148 215 default_pytype = float 149 216 150 217 151 218 class ExactNumericType(DatabaseType): 219 220 _initargs = DatabaseType._initargs + ("precision", "max_precision", 221 "scale") 152 222 153 223 # For exact numeric types, 'precision' refers to decimal digits … … 215 285 _max = None 216 286 287 _initargs = DatabaseType._initargs + ("_min", "_max") 288 217 289 def min(self): 218 290 """Return the minimum value allowed.""" trunk/geniusql/objects.py
r54 r55 155 155 156 156 def __copy__(self): 157 newcol = self.__class__(self.pytype, self.dbtype, self.default, 158 self.key, self.name, self.qname) 157 newcol = self.__class__(self.pytype, self.dbtype.copy(), 158 self.default, self.key, 159 self.name, self.qname) 159 160 newcol.autoincrement = self.autoincrement 160 161 newcol.initial = self.initial 161 newcol.adapter = self.adapter 162 newcol.adapter = self.adapter.copy() 162 163 return newcol 163 164 copy = __copy__ trunk/geniusql/providers/__init__.py
r54 r55 50 50 51 51 registry = _Registry({ 52 "access": "geniusql.providers. ado.MSAccessDatabase",53 "msaccess": "geniusql.providers. ado.MSAccessDatabase",52 "access": "geniusql.providers.msaccess.MSAccessDatabase", 53 "msaccess": "geniusql.providers.msaccess.MSAccessDatabase", 54 54 55 55 "firebird": "geniusql.providers.firebird.FirebirdDatabase", trunk/geniusql/providers/ado.py
r54 r55 25 25 import pywintypes 26 26 import datetime 27 28 27 import time 29 28 30 import threading31 import warnings32 33 34 29 import geniusql 35 from geniusql import adapters, conns, decompile, errors , select, typerefs30 from geniusql import adapters, conns, decompile, errors 36 31 from geniusql import isolation as _isolation 37 32 … … 52 47 53 48 adUseClient = 3 54 55 # See http://www.carlprothman.net/Technology/DataTypeMapping/tabid/97/Default.aspx56 dbtypes = {# ADO Name SQL Server MS Access57 0: 'EMPTY',58 2: 'SMALLINT', # SMALLINT INTEGER59 3: 'INTEGER', # IDENTITY (6.5), INT AUTONUMBER, LONG60 4: 'SINGLE', # REAL SINGLE61 5: 'DOUBLE', # FLOAT DOUBLE62 6: 'CURRENCY', # MONEY, SMALLMONEY CURRENCY63 7: 'DATE', # DATETIME (Access 97)64 8: 'BSTR', 9: 'IDISPATCH', 10: 'ERROR',65 11: 'BOOLEAN', # BIT YESNO66 12: 'VARIANT', # SQL_VARIANT (2000 +)67 13: 'IUNKNOWN', 14: 'DECIMAL', 16: 'TINYINT',68 17: 'UNSIGNEDTINYINT',# TINYINT BYTE69 18: 'UNSIGNEDSMALLINT', 19: 'UNSIGNEDINT',70 20: 'BIGINT', # BIGINT71 21: 'UNSIGNEDBIGINT',72 72: 'GUID',73 128: 'BINARY', # BINARY, TIMESTAMP74 129: 'CHAR', # CHAR75 130: 'WCHAR', # NCHAR (7.0+)76 131: 'NUMERIC', # DECIMAL, NUMERIC DECIMAL (Access 2000)77 132: 'USERDEFINED',78 133: 'DBDATE', 134: 'DBTIME',79 135: 'DBTIMESTAMP', # DATETIME, SMALLDATETIME DATETIME (ODBC 97)80 200: 'VARCHAR', # VARCHAR TEXT (Access 97)81 201: 'LONGVARCHAR', # TEXT MEMO (Access 97)82 202: 'VARWCHAR', # NVARCHAR TEXT (Access 2000)83 203: 'LONGVARWCHAR', # NTEXT (7.0+) MEMO (Access 2000+)84 204: 'VARBINARY', # VARBINARY85 205: 'LONGVARBINARY', # IMAGE OLEOBJECT86 }87 49 88 50 DBCOLUMNFLAGS_WRITE = 0x4 … … 264 226 raise TypeError("unsupported operand type(s) for %s: " 265 227 "%r and %r" % (op, op1.pytype, op2.pytype)) 266 267 268 class CURRENCY(adapters.SQL92DOUBLE):269 def pull(self, value):270 if isinstance(value, tuple):271 # See http://groups.google.com/group/comp.lang.python/272 # browse_frm/thread/fed03c64735c9e9c273 value = map(long, value)274 return ((value[1] & 0xFFFFFFFFL) | (value[0] << 32)) / 1e4275 return float(value)276 277 class decimalCURRENCY(adapters.DECIMAL):278 def pull(self, value):279 # pywin32 build 205 began support for returning280 # COM Currency objects as decimal objects.281 # See http://pywin32.cvs.sourceforge.net/pywin32/pywin32/CHANGES.txt?view=markup282 if not isinstance(value, typerefs.decimal.Decimal):283 # See http://groups.google.com/group/comp.lang.python/284 # browse_frm/thread/fed03c64735c9e9c285 value = map(long, value)286 value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)287 return typerefs.decimal.Decimal(value) / 10000288 return value289 290 class fpCURRENCY(adapters.FIXEDPOINT):291 def pull(self, value):292 if isinstance(value, typerefs.decimal.Decimal):293 value = str(value)294 scale = 0295 atoms = value.rsplit(".", 1)296 if len(atoms) > 1:297 scale = len(atoms[-1])298 return typerefs.fixedpoint.FixedPoint(value, scale)299 else:300 # See http://groups.google.com/group/comp.lang.python/301 # browse_frm/thread/fed03c64735c9e9c302 value = map(long, value)303 value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)304 return typerefs.fixedpoint.FixedPoint(value, 4) / 1e4305 228 306 229 … … 555 478 raise errors.MappingError(tablename) 556 479 557 def _get_columns(self, tablename, conn=None):558 # For some reason, adSchemaPrimaryKeys would only return a single559 # record for a PK that had multiple columns. Use adSchemaIndexes.560 # coldefs will be:561 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),562 # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202),563 # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18),564 # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3),565 # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3),566 # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72),567 # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21),568 # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)]569 data, _ = self.db.fetch(adSchemaIndexes, conn=conn, schema=True)570 pknames = [row[17] for row in data571 if (tablename == row[2]) and row[6]]572 573 # columns will be574 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),575 # (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), (u'COLUMN_PROPID', 19),576 # (u'ORDINAL_POSITION', 19), (u'COLUMN_HASDEFAULT', 11),577 # (u'COLUMN_DEFAULT', 203), (u'COLUMN_FLAGS', 19), (u'IS_NULLABLE', 11),578 # (u'DATA_TYPE', 18), (u'TYPE_GUID', 72), (u'CHARACTER_MAXIMUM_LENGTH', 19),579 # (u'CHARACTER_OCTET_LENGTH', 19), (u'NUMERIC_PRECISION', 18),580 # (u'NUMERIC_SCALE', 2), (u'DATETIME_PRECISION', 19),581 # (u'CHARACTER_SET_CATALOG', 202), (u'CHARACTER_SET_SCHEMA', 202),582 # (u'CHARACTER_SET_NAME', 202), (u'COLLATION_CATALOG', 202),583 # (u'COLLATION_SCHEMA', 202), (u'COLLATION_NAME', 202),584 # (u'DOMAIN_CATALOG', 202), (u'DOMAIN_SCHEMA', 202),585 # (u'DOMAIN_NAME', 202), (u'DESCRIPTION', 203)]586 data, _ = self.db.fetch(adSchemaColumns, conn=conn, schema=True)587 588 cols = []589 typer = self.db.adapterset590 for row in data:591 # I tried passing criteria to OpenSchema, but passing None is592 # not the same as passing pythoncom.Empty (which errors).593 if row[2] != tablename:594 continue595 596 dbtypename = dbtypes[row[11]]597 dbtype = typer.canonicalize(dbtypename)()598 pytype = dbtype.default_pytype599 if pytype is None:600 raise TypeError("%r has no default pytype." % dbtype)601 602 default = row[8]603 if default is not None:604 if issubclass(pytype, (int, long, float)):605 # We may have stuck extraneous quotes in the default606 # value when using numeric defaults with MSAccess.607 if default.startswith("'") and default.endswith("'"):608 default = default[1:-1]609 default = pytype(default)610 611 name = str(row[3])612 c = geniusql.Column(pytype, dbtype, default,613 key=(name in pknames),614 name=name, qname=self.db.quote(name))615 616 # This only works for SQL Server. The MSAccessDatabase will617 # wrap this method and override autoincrement.618 colflags = int(row[9])619 if ((colflags & DBCOLUMNFLAGS_ISFIXEDLENGTH)620 and not (colflags & DBCOLUMNFLAGS_WRITE)):621 c.autoincrement = True622 623 if dbtype in typer.known_types['int']:624 dbtype.bytes = row[15]625 elif dbtype in typer.known_types['float']:626 dbtype.precision = row[15]627 dbtype.scale = row[16]628 ## elif dbtype == "CURRENCY":629 ## # CURRENCY allows 15 places to the left of the decimal point,630 ## # and 4 places to the right.631 ## c.hints['precision'] = 19632 ## c.hints['scale'] = 4633 elif dbtype in typer.known_types['numeric']:634 dbtype.precision = row[15]635 dbtype.scale = row[16]636 elif (dbtype in typer.known_types['char'] or637 dbtype in typer.known_types['varchar']):638 if row[13]:639 # row[13] will be a float640 dbtype.bytes = b = int(row[13])641 ## else:642 ## # I'm kinda guessing on this. If we use "MEMO" in an643 ## # MSAccess CREATE statement, it comes back as "WCHAR",644 ## # and seems to support over 65536 bytes.645 ## dbtype.bytes = b = (2 ** 31) - 1646 ## elif dbtype in ("LONGVARCHAR", "LONGVARBINARY", "LONGVARWCHAR"):647 ## if row[13]:648 ## # row[13] will be a float649 ## c.hints['bytes'] = b = int(row[13])650 ## c.dbtype = "%s(%s)" % (c.dbtype, b)651 ## else:652 ## c.hints['bytes'] = 65535653 654 c.adapter = typer.default(pytype, dbtype)655 cols.append(c)656 return cols657 658 480 def _get_indices(self, tablename=None, conn=None): 659 481 # cols will be trunk/geniusql/providers/msaccess.py
r54 r55 1 import datetime 2 3 from geniusql import adapters, conns, dbtypes, objects, typerefs 1 4 from geniusql.providers import ado 5 6 import win32com.client 7 8 9 # ------------------------------ Adapters ------------------------------ # 2 10 3 11 … … 29 37 (value.days, (value.seconds / 86400.0))) 30 38 31 32 39 class MSAccess_date(ado.COM_date): 33 40 … … 50 57 return '#%s/%s/%s#' % (value.month, value.day, value.year) 51 58 52 53 59 class MSAccess_datetime(ado.COM_datetime): 54 60 … … 69 75 value.hour, value.minute, value.second)) 70 76 71 72 77 class MSAccess_time(ado.COM_time): 73 78 … … 76 81 77 82 83 class CURRENCY_float(adapters.SQL92DOUBLE): 84 85 def pull(self, value): 86 if isinstance(value, tuple): 87 # See http://groups.google.com/group/comp.lang.python/ 88 # browse_frm/thread/fed03c64735c9e9c 89 value = map(long, value) 90 return ((value[1] & 0xFFFFFFFFL) | (value[0] << 32)) / 1e4 91 return float(value) 92 93 class CURRENCY_decimal(adapters.DECIMAL): 94 95 def pull(self, value): 96 # pywin32 build 205 began support for returning 97 # COM Currency objects as decimal objects. 98 # See http://pywin32.cvs.sourceforge.net/pywin32/pywin32/CHANGES.txt?view=markup 99 if not isinstance(value, typerefs.decimal.Decimal): 100 # See http://groups.google.com/group/comp.lang.python/ 101 # browse_frm/thread/fed03c64735c9e9c 102 value = map(long, value) 103 value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32) 104 return typerefs.decimal.Decimal(value) / 10000 105 return value 106 107 class CURRENCY_FixedPoint(adapters.FIXEDPOINT): 108 109 def pull(self, value): 110 if isinstance(value, typerefs.decimal.Decimal): 111 value = str(value) 112 scale = 0 113 atoms = value.rsplit(".", 1) 114 if len(atoms) > 1: 115 scale = len(atoms[-1]) 116 return typerefs.fixedpoint.FixedPoint(value, scale) 117 else: 118 # See http://groups.google.com/group/comp.lang.python/ 119 # browse_frm/thread/fed03c64735c9e9c 120 value = map(long, value) 121 value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32) 122 return typerefs.fixedpoint.FixedPoint(value, 4) / 1e4 123 124 125 # ---------------------------- DatabaseTypes ---------------------------- # 126 127 # These are Access 2000+ types. 128 # See http://msdn2.microsoft.com/en-us/library/aa140015(office.10).aspx 129 # http://msdn2.microsoft.com/en-us/library/ms714540.aspx 130 # http://office.microsoft.com/en-us/access/HP010322481033.aspx 131 132 # "A Text field can store up to 255 characters, but the default field size 133 # is 50 characters. A Memo field can store up to 65,536 characters. If you 134 # want to store formatted text or long documents, you should create an OLE 135 # Object field instead of a Memo field. Both Text and Memo data types store 136 # only the characters entered in a field; space characters for unused 137 # positions in the field aren't stored. You can sort or group on a Text 138 # field or a Memo field, but Access only uses the first 255 characters 139 # when you sort or group on a Memo field." 140 141 class TEXT(dbtypes.AdjustableStringType): 142 143 _initargs = dbtypes.AdjustableStringType._initargs + ("with_compression",) 144 145 # Actually 255 chars, 2 bytes per char unless compressed 146 max_bytes = 255 147 bytes = 255 148 variable = True 149 150 # "With the Microsoft Jet 4.0 database engine, all data for the TEXT 151 # data types are now stored in the Unicode 2-byte character 152 # representation format. It replaces the Multi-byte Character Set 153 # (MBCS) format that was used in previous versions. Although Unicode 154 # representation requires more space to store each character, columns 155 # with TEXT data types can be defined to automatically compress the 156 # data if it is possible to do so. 157 # 158 # When you create TEXT data types with SQL, the Unicode compression 159 # property defaults to No. To set the Unicode compression property 160 # to Yes, use the WITH COMPRESSION (or WITH COMP) keywords at the 161 # field-level declaration." 162 with_compression = False 163 164 def ddl(self): 165 """Return the type for use in CREATE or ALTER statements.""" 166 withcomp = "" 167 if self.with_compression: 168 withcomp = " WITH COMPRESSION" 169 return "%s(%s)%s" % (self.__class__.__name__, self.bytes, withcomp) 170 171 172 class MEMO(dbtypes.FrozenStringType): 173 # MEMO is 1 GB max when set programatically (only 64K when set 174 # in Access UI). But then, 1 GB is the limit for the whole DB. 175 # Note that OpenSchema will return a DATA_TYPE of "WCHAR". 176 synonyms = ['WCHAR'] 177 bytes = max_bytes = 65535 # (2.14 GB if not binary data) 178 variable = True 179 180 181 class TINYINT(dbtypes.IntegerType): 182 synonyms = ['INTEGER1', 'BYTE'] 183 bytes = max_bytes = 1 184 signed = False 185 186 class SMALLINT(dbtypes.IntegerType): 187 synonyms = ['SHORT', 'INTEGER2'] 188 bytes = max_bytes = 2 189 190 class INTEGER(dbtypes.IntegerType): 191 synonyms = ['LONG', 'INT', 'INTEGER4'] 192 bytes = max_bytes = 4 193 194 195 class FLOAT(dbtypes.InexactNumericType): 196 synonyms = ['DOUBLE', 'FLOAT8', 'IEEEDOUBLE', 'NUMBER'] 197 precision = max_precision = 53 198 default_adapters = {float: adapters.SQL92DOUBLE} 199 200 class REAL(dbtypes.InexactNumericType): 201 synonyms = ['SINGLE', 'FLOAT4', 'IEEESINGLE'] 202 precision = max_precision = 24 203 default_adapters = {float: adapters.SQL92REAL} 204 205 206 class DECIMAL(dbtypes.ExactNumericType): 207 synonyms = ['NUMERIC', 'DEC'] 208 209 # "...precision, the default is 18 and the maximum allowed value is 28. 210 # For the scale, the default is 0 and the maximum allowed value is 28." 211 precision = 18 212 max_precision = 28 213 scale = 0 214 ## 215 ## # Hm. Docs say 28, but I can't seem to get more than 12 working. 216 ## numeric_max_precision = 12 217 ## numeric_max_bytes = 6 218 219 220 class CURRENCY(dbtypes.FrozenPrecisionType): 221 222 _initargs = dbtypes.FrozenPrecisionType._initargs + ("scale", ) 223 224 # "The CURRENCY data type is used to store numeric data that contains 225 # up to 15 digits on the left side of the decimal point, and up to 4 226 # digits on the right. It uses 8 bytes of memory for storage, and its 227 # only synonym is MONEY." 228 synonyms = ['MONEY'] 229 230 precision = max_precision = 19 231 scale = property(lambda self: 4, lambda self, value: None) 232 233 default_adapters = {float: CURRENCY_float} 234 if typerefs.decimal: 235 if hasattr(typerefs.decimal, "Decimal"): 236 default_adapters[typerefs.decimal.Decimal] = CURRENCY_decimal 237 else: 238 default_adapters[typerefs.decimal] = CURRENCY_decimal 239 if typerefs.fixedpoint: 240 default_adapters[typerefs.fixedpoint.FixedPoint] = CURRENCY_FixedPoint 241 242 243 class YESNO(dbtypes.DatabaseType): 244 # "The BOOLEAN data types are logical types that result in either True 245 # or False values. They use 1 byte of memory for storage, and their 246 # synonyms are BIT, LOGICAL, LOGICAL1, and YESNO. A True value is 247 # equal to -1 while a False value is equal to 0." 248 default_adapters = {bool: adapters.BOOLEAN} 249 250 251 class BINARY(dbtypes.AdjustableByteType): 252 # "The BINARY data type is used to store a small amount of any type 253 # of data in its native, binary format. It uses 1 byte of memory for 254 # each character stored, and you can optionally specify the number 255 # of bytes to be allocated. If the number of bytes is not specified, 256 # it defaults to 510 bytes, which is the maximum number of bytes 257 # allowed. Its synonyms are BINARY, VARBINARY, and BINARY VARYING. 258 # The BINARY data type is not available in the Access user interface." 259 synonyms = ['VARBINARY', 'BINARY VARYING'] 260 bytes = 510 261 max_bytes = 510 262 variable = False 263 264 265 class DATETIME(dbtypes.DateTimeType): 266 # "The DATETIME data type is used to store date, time, and combination 267 # date/time values for the years ranging from 100 to 9999. 268 # It uses 8 bytes of memory for storage, and its synonyms are 269 # DATE, TIME, DATETIME, and TIMESTAMP. 270 synonyms = ['DATE', 'TIME', 'TIMESTAMP'] 271 _min = datetime.datetime(100, 1, 1) 272 _max = datetime.datetime(9999, 12, 31) 273 274 default_adapters = {datetime.datetime: MSAccess_datetime, 275 datetime.date: MSAccess_date, 276 datetime.time: MSAccess_time, 277 datetime.timedelta: MSAccess_timedelta, 278 } 279 280 78 281 class MSAccessAdapterSet(ado.ADOAdapterSet): 79 282 80 # http://msdn2.microsoft.com/en-us/library/ms714540.aspx 81 # http://office.microsoft.com/en-us/access/HP010322481033.aspx 82 83 # Hm. Docs say 28/38, but I can't seem to get more than 12 working. 84 numeric_max_precision = 12 85 numeric_max_bytes = 6 86 87 def pytype_for_LONG(self, hints): return int 88 def pytype_for_MEMO(self, hints): str 89 90 def dbtype_for_bool(self, hints): return "BIT" 91 def dbtype_for_datetime_datetime(self, hints): return "DATETIME" 92 def dbtype_for_datetime_date(self, hints): return "DATETIME" 93 def dbtype_for_datetime_time(self, hints): return "DATETIME" 94 def dbtype_for_datetime_timedelta(self, hints): return "DATETIME" 95 96 def int_type(self, bytes): 97 if bytes <= 2: 98 return "INTEGER" 99 elif bytes <= 4: 100 return "LONG" 101 else: 102 # Anything larger than 4 bytes, use decimal/numeric. 103 return "DECIMAL" 104 105 def float_type(self, precision): 106 """Return a datatype which can handle floats of the given binary precision.""" 107 if precision <= 24: 108 return "SINGLE" 109 else: 110 return "DOUBLE" 111 112 def dbtype_for_str(self, hints): 113 # The bytes hint shall not reflect the usual 4-byte base for varchar. 114 bytes = int(hints.get('bytes', 255)) 115 116 # 255 chars is the upper limit for TEXT / VARCHAR in MS Access. 117 if bytes == 0 or bytes > 255: 118 # MEMO is 1 GB max when set programatically (only 64K when set 119 # in Access UI). But then, 1 GB is the limit for the whole DB. 120 # Note that OpenSchema will return a DATA_TYPE of "WCHAR". 121 return "MEMO" 122 123 return "VARCHAR(%s)" % bytes 124 125 MSAccessAdapterSet.defaults.update({ 126 (datetime.datetime, "any"): MSAccess_datetime, 127 (datetime.date, "any"): MSAccess_date, 128 (datetime.time, "any"): MSAccess_time, 129 (datetime.timedelta, "any"): MSAccess_timedelta, 130 }) 131 if typerefs.decimal: 132 if hasattr(typerefs.decimal, "Decimal"): 133 MSAccessAdapterSet.defaults[(typerefs.decimal.Decimal, "CURRENCY") 134 ] = decimalCURRENCY() 135 else: 136 MSAccessAdapterSet.defaults[(typerefs.decimal, "CURRENCY") 137 ] = decimalCURRENCY() 138 if typerefs.fixedpoint: 139 MSAccessAdapterSet.defaults[(typerefs.fixedpoint.FixedPoint, "CURRENCY") 140 ] = fpCURRENCY() 283 known_types = {'float': [REAL, FLOAT], 284 'varchar': [TEXT, MEMO], 285 'char': [BINARY], 286 'int': [TINYINT, SMALLINT, INTEGER], 287 'bool': [YESNO], 288 'datetime': [DATETIME], 289 'date': [DATETIME], 290 'time': [DATETIME], 291 'timedelta': [DATETIME], 292 'numeric': [DECIMAL], 293 'other': [CURRENCY], 294 } 141 295 142 296 … … 236 390 tableclass = MSAccessTable 237 391 392 # See http://www.carlprothman.net/Technology/DataTypeMapping/tabid/97/Default.aspx 393 adotypes = { 394 # MS Access ADO Name 395 2: SMALLINT, # SMALLINT 396 3: INTEGER, # INTEGER 397 4: REAL, # SINGLE 398 5: FLOAT, # DOUBLE 399 6: CURRENCY, # CURRENCY 400 7: DATETIME, # DATE (Access 97) 401 11: YESNO, # BOOLEAN 402 17: TINYINT, # UNSIGNEDTINYINT 403 128: BINARY, # BINARY 404 130: MEMO, # WCHAR 405 131: DECIMAL, # NUMERIC (Access 2000) 406 135: DATETIME, # DBTIMESTAMP (ODBC 97) 407 200: TEXT, # VARCHAR (Access 97) 408 201: MEMO, # LONGVARCHAR (Access 97) 409 202: TEXT, # VARWCHAR (Access 2000) 410 203: MEMO, # LONGVARWCHAR (Access 2000+) 411 # 205: OLEOBJECT, # LONGVARBINARY 412 # 0: EMPTY, 413 # 8: BSTR, 9: IDISPATCH, 10: ERROR, 414 # 12: VARIANT, 13: IUNKNOWN, 14: DECIMAL, 16: TINYINT, 415 # 18: UNSIGNEDSMALLINT, 19: UNSIGNEDINT, 20: BIGINT, 416 # 21: UNSIGNEDBIGINT, 72: GUID, 417 # 129: CHAR, 418 # 132: USERDEFINED, 133: DBDATE, 134: DBTIME, 419 # 204: VARBINARY, 420 } 421 238 422 def _get_columns(self, tablename, conn=None): 239 cols = ado.ADOSchema._get_columns(self, tablename, conn) 423 # For some reason, adSchemaPrimaryKeys would only return a single 424 # record for a PK that had multiple columns. Use adSchemaIndexes. 425 # coldefs will be: 426 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202), 427 # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202), 428 # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18), 429 # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3), 430 # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3), 431 # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), 432 # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21), 433 # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)] 434 data, _ = self.db.fetch(ado.adSchemaIndexes, conn=conn, schema=True) 435 pknames = [row[17] for row in data 436 if (tablename == row[2]) and row[6]] 437 438 # columns will be 439 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202), 440 # (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), (u'COLUMN_PROPID', 19), 441 # (u'ORDINAL_POSITION', 19), (u'COLUMN_HASDEFAULT', 11), 442 # (u'COLUMN_DEFAULT', 203), (u'COLUMN_FLAGS', 19), (u'IS_NULLABLE', 11), 443 # (u'DATA_TYPE', 18), (u'TYPE_GUID', 72), (u'CHARACTER_MAXIMUM_LENGTH', 19), 444 # (u'CHARACTER_OCTET_LENGTH', 19), (u'NUMERIC_PRECISION', 18), 445 # (u'NUMERIC_SCALE', 2), (u'DATETIME_PRECISION', 19), 446 # (u'CHARACTER_SET_CATALOG', 202), (u'CHARACTER_SET_SCHEMA', 202), 447 # (u'CHARACTER_SET_NAME', 202), (u'COLLATION_CATALOG', 202), 448 # (u'COLLATION_SCHEMA', 202), (u'COLLATION_NAME', 202), 449 # (u'DOMAIN_CATALOG', 202), (u'DOMAIN_SCHEMA', 202), 450 # (u'DOMAIN_NAME', 202), (u'DESCRIPTION', 203)] 451 data, _ = self.db.fetch(ado.adSchemaColumns, conn=conn, schema=True) 452 453 cols = [] 454 typer = self.db.adapterset 455 for row in data: 456 # I tried passing criteria to OpenSchema, but passing None is 457 # not the same as passing pythoncom.Empty (which errors). 458 if row[2] != tablename: 459 continue 460 461 dbtype = self.adotypes[row[11]]() 462 pytype = dbtype.default_pytype 463 if pytype is None: 464 raise TypeError("%r has no default pytype." % dbtype) 465 466 default = row[8] 467 if default is not None: 468 if issubclass(pytype, (int, long, float)): 469 # We may have stuck extraneous quotes in the default 470 # value when using numeric defaults with MSAccess. 471 if default.startswith("'") and default.endswith("'"): 472 default = default[1:-1] 473 default = pytype(default) 474 475 name = str(row[3]) 476 c = objects.Column(pytype, dbtype, default, 477 key=(name in pknames), 478 name=name, qname=self.db.quote(name)) 479 480 if dbtype in typer.known_types['int']: 481 dbtype.bytes = row[15] 482 elif dbtype in typer.known_types['float']: 483 dbtype.precision = row[15] 484 dbtype.scale = row[16] 485 elif dbtype in typer.known_types['numeric']: 486 dbtype.precision = row[15] 487 dbtype.scale = row[16] 488 elif isinstance(dbtype, MEMO): 489 pass 490 elif (dbtype in typer.known_types['char'] or 491 dbtype in typer.known_types['varchar']): 492 if row[13]: 493 # row[13] will be a float 494 dbtype.bytes = int(row[13]) 495 else: 496 # I'm kinda guessing on this. If we use "MEMO" in an 497 # MSAccess CREATE statement, it comes back as "WCHAR", 498 # and seems to support over 65536 bytes. 499 dbtype.bytes = (2 ** 31) - 1 500 501 c.adapter = typer.default(pytype, dbtype) 502 cols.append(c) 503 504 # Horrible hack to get autoincrement property 240 505 if conn is None: 241 506 conn = self.db.connections._factory() 242 243 507 try: 244 # Horrible hack to get autoincrement property245 508 query = "SELECT * FROM %s WHERE FALSE;" % self.db.quote(tablename) 246 509 bareconn = conn … … 254 517 ((8, 1), (16396, 18), (3, 49)), 255 518 # *args = 256 query, pythoncom.Missing, -1)257 except pywintypes.com_error, x:519 query, ado.pythoncom.Missing, -1) 520 except ado.pywintypes.com_error, x: 258 521 try: 259 res.InvokeTypes(* Recordset_Close)522 res.InvokeTypes(*ado.Recordset_Close) 260 523 except: 261 524 pass … … 265 528 266 529 try: 530 # Return no columns when inspecting system tables 267 531 if "no read permission" in x.args[2][2]: 268 532 conn = None … … 274 538 raise x 275 539 276 resFields = res.InvokeTypes(* Recordset_Fields)540 resFields = res.InvokeTypes(*ado.Recordset_Fields) 277 541 for c in cols: 278 542 f = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), c.name) 279 fprops = f.InvokeTypes(* Field_Properties)543 fprops = f.InvokeTypes(*ado.Field_Properties) 280 544 fprop = fprops.InvokeTypes(0, 0, 2, (9, 0), ((12, 1), ), "ISAUTOINCREMENT") 281 c.autoincrement = fprop.InvokeTypes(*Property_Value) 545 c.autoincrement = fprop.InvokeTypes(*ado.Property_Value) 546 if c.autoincrement: 547 # Grumble. Get the Seed value from ADOX. 548 try: 549 cat = win32com.client.Dispatch(r'ADOX.Catalog') 550 cat.ActiveConnection = conn 551 adoxcol = cat.Tables(tablename).Columns(c.name) 552 c.initial = adoxcol.Properties('Seed').Value 553 adoxcol = None 554 finally: 555 cat = None 282 556 283 557 try: 284 res.InvokeTypes(* Recordset_Close)558 res.InvokeTypes(*ado.Recordset_Close) 285 559 except: 286 560 pass … … 295 569 name type [DEFAULT x|AUTOINCREMENT(initial, 1)] 296 570 """ 297 d btype = column.dbtype571 ddl = column.dbtype.ddl() 298 572 299 573 if column.autoincrement: 574 if column.dbtype.default_pytype not in (int, long): 575 raise ValueError("Microsoft Access does not allow COUNTER " 576 "columns of type %r" % dbtype) 577 ddl = " COUNTER(%s, 1) NOT NULL" % column.initial 578 else: 300 579 # MS Access does not allow a column to have 301 580 # both an AUTOINCREMENT clause and a DEFAULT clause. 302 # It also needs no type in this case.303 dbtype = "AUTOINCREMENT(%s, 1)" % column.initial304 else:305 581 default = column.default or "" 306 582 if default: … … 309 585 # Crazy quote hack to get a numeric default to work. 310 586 defspec = "'%s'" % defspec 311 d btype = "%s DEFAULT %s" % (dbtype, defspec)312 313 return '%s %s' % (column.qname, d btype)587 ddl = "%s DEFAULT %s" % (ddl, defspec) 588 589 return '%s %s' % (column.qname, ddl) 314 590 315 591 def create_database(self): trunk/geniusql/providers/sqlserver.py
r54 r55 1 1 import datetime 2 2 3 from geniusql import adapters, dbtypes 3 from geniusql import adapters, dbtypes, objects 4 4 from geniusql.providers import ado 5 5 … … 19 19 20 20 21 class BINARY(dbtypes. StringType):21 class BINARY(dbtypes.AdjustableStringType): 22 22 max_bytes = 8000 23 23 variable = False 24 24 25 class VARBINARY(dbtypes. StringType):25 class VARBINARY(dbtypes.AdjustableStringType): 26 26 max_bytes = 8000 27 27 variable = True 28 28 29 class CHAR(dbtypes. StringType):29 class CHAR(dbtypes.AdjustableStringType): 30 30 max_bytes = 8000 31 31 variable = False 32 32 33 class VARCHAR(dbtypes. StringType):33 class VARCHAR(dbtypes.AdjustableStringType): 34 34 max_bytes = 8000 35 35 variable = True … … 43 43 ## % (sql, self, totype)) 44 44 45 class NCHAR(dbtypes. StringType):45 class NCHAR(dbtypes.AdjustableStringType): 46 46 synonyms=['WCHAR'] 47 47 max_bytes = 4000 48 48 variable = False 49 49 50 class NVARCHAR(dbtypes. StringType):50 class NVARCHAR(dbtypes.AdjustableStringType): 51 51 synonyms=['VARWCHAR'] 52 52 max_bytes = 4000 … … 58 58 # Use nvarchar(max), varchar(max), and varbinary(max) instead." 59 59 # http://msdn2.microsoft.com/en-us/library/ms187993.aspx 60 class NTEXT(dbtypes. StringType):60 class NTEXT(dbtypes.FrozenStringType): 61 61 synonyms = ['LONGVARWCHAR'] 62 62 bytes = max_bytes = ((2 ** 30) - 1) * 2 63 fixed_bytes = True 64 65 class TEXT(dbtypes.StringType): 63 64 class TEXT(dbtypes.FrozenStringType): 66 65 synonyms = ['LONGVARCHAR'] 67 66 bytes = max_bytes = (2 ** 31) - 1 68 fixed_bytes = True 69 70 class IMAGE(dbtypes.StringType): 67 68 class IMAGE(dbtypes.FrozenStringType): 71 69 synonyms = ['LONGVARBINARY'] 72 70 bytes = max_bytes = (2 ** 31) - 1 73 fixed_bytes = True74 71 75 72 … … 77 74 synonyms=['BOOLEAN'] 78 75 bytes = max_bytes = 1 79 fixed_bytes = True80 76 default_pytype = bool 81 77 default_adapters = {bool: adapters.SQL92BIT} … … 99 95 } 100 96 101 102 97 class SMALLDATETIME(dbtypes.DateTimeType): 103 98 _min=datetime.datetime(1900, 1, 1) 104 99 _max=datetime.datetime(2079, 6, 6) 105 100 101 106 102 class FLOAT(dbtypes.InexactNumericType): 107 103 synonyms = ['DOUBLE'] 108 104 precision = max_precision = 53 109 fixed_precision = True110 105 default_adapters = {float: adapters.SQL92DOUBLE} 111 106 … … 121 116 122 117 class REAL(dbtypes.InexactNumericType): 123 synonyms =['SINGLE']118 synonyms = ['SINGLE'] 124 119 precision = max_precision = 24 125 fixed_precision = True126 120 default_adapters = {float: adapters.SQL92REAL} 127 121 … … 139 133 synonyms = ['UNSIGNEDTINYINT'] 140 134 bytes = max_bytes = 1 141 fixed_bytes = True135 signed = False 142 136 default_adapters = {int: adapters.SQL92SMALLINT, 143 137 long: adapters.SQL92SMALLINT, … … 146 140 class SMALLINT(dbtypes.IntegerType): 147 141 bytes = max_bytes = 2 148 fixed_bytes = True149 142 default_adapters = {int: adapters.SQL92SMALLINT, 150 143 long: adapters.SQL92SMALLINT, … … 154 147 synonyms = ['INTEGER', 'IDENTITY'] 155 148 bytes = max_bytes = 4 156 fixed_bytes = True157 149 158 150 class BIGINT(dbtypes.IntegerType): 159 151 """8-byte BIGINT type for SQL Server (2000+ only).""" 160 152 bytes = max_bytes = 8 161 fixed_bytes = True162 153 default_pytype = long 163 154 … … 168 159 default_adapters[datetime.timedelta] = ado.COM_timedelta 169 160 170 class MoneyType(dbtypes.FixableByteType): 161 class MoneyType(dbtypes.FrozenByteType): 162 163 _initargs = dbtypes.FrozenByteType._initargs + ("scale", "_min", "_max") 171 164 172 165 scale = 4 … … 253 246 254 247 255 256 248 class SQLServerDecompiler(ado.ADOSQLDecompiler): 257 249 … … 326 318 tableclass = SQLServerTable 327 319 320 # See http://www.carlprothman.net/Technology/DataTypeMapping/tabid/97/Default.aspx 321 adotypes = { 322 # SQL Server ADO Name 323 2: SMALLINT, # SMALLINT 324 3: INT, # INTEGER (also for IDENTITY (SQL Server 6.5)) 325 4: REAL, # SINGLE 326 5: FLOAT, # DOUBLE 327 6: MONEY, # CURRENCY (also could be SMALLMONEY) 328 11: BIT, # BOOLEAN 329 # 12: SQL_VARIANT, # VARIANT (2000 +) 330 17: TINYINT, # UNSIGNEDTINYINT 331 20: BIGINT, # BIGINT 332 128: BINARY, # BINARY (also could be TIMESTAMP) 333 129: CHAR, # CHAR 334 130: NCHAR, # WCHAR (7.0+) 335 131: NUMERIC, # DECIMAL, NUMERIC 336 135: DATETIME, # DBTIMESTAMP (also could be SMALLDATETIME) 337 200: VARCHAR, # VARCHAR 338 201: TEXT, # LONGVARCHAR 339 202: NVARCHAR, # VARWCHAR 340 203: NTEXT, # LONGVARWCHAR (7.0+) 341 204: VARBINARY, # VARBINARY 342 205: IMAGE, # LONGVARBINARY 343 # 0: EMPTY, 344 # 7: DATE, 8: BSTR, 9: IDISPATCH, 10: ERROR, 345 # 13: IUNKNOWN, 14: DECIMAL, 16: TINYINT, 346 # 18: UNSIGNEDSMALLINT, 19: UNSIGNEDINT, 21: UNSIGNEDBIGINT, 347 # 72: GUID, 348 # 132: USERDEFINED, 133: DBDATE, 134: DBTIME, 349 } 350 351 def _get_columns(self, tablename, conn=None): 352 # For some reason, adSchemaPrimaryKeys would only return a single 353 # record for a PK that had multiple columns. Use adSchemaIndexes. 354 # coldefs will be: 355 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202), 356 # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202), 357 # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18), 358 # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3), 359 # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3), 360 # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), 361 # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21), 362 # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)] 363 data, _ = self.db.fetch(ado.adSchemaIndexes, conn=conn, schema=True) 364 pknames = [row[17] for row in data 365 if (tablename == row[2]) and row[6]] 366 367 # columns will be 368 # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202), 369 # (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), (u'COLUMN_PROPID', 19), 370 # (u'ORDINAL_POSITION', 19), (u'COLUMN_HASDEFAULT', 11), 371 # (u'COLUMN_DEFAULT', 203), (u'COLUMN_FLAGS', 19), (u'IS_NULLABLE', 11), 372 # (u'DATA_TYPE', 18), (u'TYPE_GUID', 72), (u'CHARACTER_MAXIMUM_LENGTH', 19), 373 # (u'CHARACTER_OCTET_LENGTH', 19), (u'NUMERIC_PRECISION', 18), 374 # (u'NUMERIC_SCALE', 2), (u'DATETIME_PRECISION', 19), 375 # (u'CHARACTER_SET_CATALOG', 202), (u'CHARACTER_SET_SCHEMA', 202), 376 # (u'CHARACTER_SET_NAME', 202), (u'COLLATION_CATALOG', 202), 377 # (u'COLLATION_SCHEMA', 202), (u'COLLATION_NAME', 202), 378 # (u'DOMAIN_CATALOG', 202), (u'DOMAIN_SCHEMA', 202), 379 # (u'DOMAIN_NAME', 202), (u'DESCRIPTION', 203)] 380 data, _ = self.db.fetch(ado.adSchemaColumns, conn=conn, schema=True) 381 382 cols = [] 383 typer = self.db.adapterset 384 for row in data: 385 # I tried passing criteria to OpenSchema, but passing None is 386 # not the same as passing pythoncom.Empty (which errors). 387 if row[2] != tablename: 388 continue 389 390 dbtype = self.adotypes[row[11]]() 391 pytype = dbtype.default_pytype 392 if pytype is None: 393 raise TypeError("%r has no default pytype." % dbtype) 394 395 default = row[8] 396 if default is not None: 397 default = pytype(default) 398 399 name = str(row[3]) 400 c = objects.Column(pytype, dbtype, default, 401 key=(name in pknames), 402 name=name, qname=self.db.quote(name)) 403 404 colflags = int(row[9]) 405 if ((colflags & ado.DBCOLUMNFLAGS_ISFIXEDLENGTH) 406 and not (colflags & ado.DBCOLUMNFLAGS_WRITE)): 407 c.autoincrement = True 408 409 if dbtype in typer.known_types['int']: 410 dbtype.bytes = row[15] 411 elif dbtype in typer.known_types['float']: 412 dbtype.precision = row[15] 413 dbtype.scale = row[16] 414 elif dbtype in typer.known_types['numeric']: 415 dbtype.precision = row[15] 416 dbtype.scale = row[16] 417 elif (dbtype in typer.known_types['char'] or 418 dbtype in typer.known_types['varchar']): 419 if row[13]: 420 # row[13] will be a float 421 dbtype.bytes = b = int(row[13]) 422 423 c.adapter = typer.default(pytype, dbtype) 424 cols.append(c) 425 return cols 426 328 427 def create_database(self): 329 428 conn = self.db.connections._get_conn(master=True) … … 337 436 conn.Close() 338 437 self.clear() 339 340 def column(self, pytype=unicode, dbtype=None, default=None,341 key=False, autoincrement=False, hints=None):342 """Return a Column object from the given arguments."""343 from geniusql import objects344 col = objects.Column(pytype, dbtype, default, key)345 col.autoincrement = autoincrement346 347 typer = self.db.adapterset348 if autoincrement:349 col.dbtype = INT()350 if col.dbtype is None:351 col.dbtype = typer.database_type(pytype, hints or {})352 col.adapter = typer.default(pytype, col.dbtype)353 354 return col355 438 356 439 def columnclause(self, column): trunk/geniusql/test/test_msaccess.py
r54 r55 16 16 "The MSAccess test will not be run.") 17 17 else: 18 19 class CurrencyAdapter(ado.TypeAdapter_MSAccess):20 """Stores Decimal and FixedPoint objects as CURRENCY."""21 22 def decimal_type(self, precision, scale):23 if precision == 0:24 precision = 1925 if scale > precision:26 scale = precision27 if scale > 4 or precision - scale > 15:28 return "TEXT"29 return "CURRENCY"30 31 18 DB_class = "access" 32 19 opts = {'connections.Connect': … … 37 24 import zoo_fixture 38 25 39 def test_currency_dbtypes(obj):40 db = zoo_fixture.db41 fta = db.typeadapter.__class__.__name__42 schema = zoo_fixture.schema43 44 for c, p in [('Exhibit', 'Acreage'), ('Zoo', 'Admission')]:45 dbtype = schema[c][p].dbtype46 if fta == "CurrencyAdapter":47 if not (dbtype == "CURRENCY" or dbtype.startswith("WCHAR")):48 obj.fail("%s wrong type for %s.%s" % (dbtype, c, p))49 else:50 if not (dbtype.startswith("NUMERIC") or dbtype.startswith("WCHAR")):51 obj.fail("%s wrong type for %s.%s" % (dbtype, c, p))52 26 ## def test_currency_dbtypes(obj): 27 ## db = zoo_fixture.db 28 ## fta = db.adapterset.__class__.__name__ 29 ## schema = zoo_fixture.schema 30 ## 31 ## for c, p in [('Exhibit', 'Acreage'), ('Zoo', 'Admission')]: 32 ## dbtype = schema[c][p].dbtype 33 ## if fta == "CurrencyAdapter": 34 ## if not (dbtype == "CURRENCY" or dbtype.startswith("WCHAR")): 35 ## obj.fail("%s wrong type for %s.%s" % (dbtype, c, p)) 36 ## else: 37 ## if not (dbtype.startswith("NUMERIC") or dbtype.startswith("WCHAR")): 38 ## obj.fail("%s wrong type for %s.%s" % (dbtype, c, p)) 39 ## 53 40 # test the standard MS Access setup where Decimal and FixedPoint 54 41 # objects are stored in the database as INTEGERS, LONGS or NUMERIC … … 56 43 print 57 44 print "Standard MSAccess test." 58 zoo_fixture.ZooTests.test_currency = test_currency_dbtypes45 ## zoo_fixture.ZooTests.test_currency = test_currency_dbtypes 59 46 zoo_fixture.run(DB_class, "zoo.mdb", opts) 60 47 trunk/geniusql/typerefs.py
r54 r55 26 26 fixedpoint = None 27 27 28 28 try: 29 # Builtin in Python 2.4+ 30 set 31 except NameError: 32 try: 33 # Module in Python 2.3 34 from sets import Set as set 35 except ImportError: 36 set = None
