Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/branches/ast/geniusql/providers/msaccess.py

Revision 111 (checked in by fumanchu, 6 years ago)

Got rid of that awful _initargs with some equally awful __dict__ hackery.

  • Property svn:eol-style set to native
Line 
1 import datetime
2
3 from geniusql import adapters, conns, dbtypes, objects, typerefs
4 from geniusql import isolation as _isolation
5 from geniusql.providers import ado
6
7 import win32com.client
8
9
10 # ------------------------------ Adapters ------------------------------ #
11
12
13 class MSAccess_timedelta(ado.COM_timedelta):
14    
15     def TIMEDELTAADD(op1, op, op2):
16         return "CDate(%s %s %s)" % (op1.sql, op, op2.sql)
17     TIMEDELTAADD = staticmethod(TIMEDELTAADD)
18    
19     def DATETIMEADD(dt, td):
20         """Return the SQL to add a timedelta to a datetime."""
21         return "CDate(%s + %s)" % (dt, td)
22     DATETIMEADD = staticmethod(DATETIMEADD)
23    
24     def DATEADD(dt, td):
25         """Return the SQL to add a timedelta to a date."""
26         # Important to use Fix (instead of CLng, for example)
27         # for negative numbers.
28         return "DateAdd('d', Fix(%s), %s)" % (td, dt)
29     DATEADD = staticmethod(DATEADD)
30    
31     def push(self, value, dbtype):
32         if value is None:
33             return 'NULL'
34         # This took a lot of work to get right, because timedelta
35         # seconds are positive even if the days are negative.
36         # So is the fractional portion of a negative Access Date!
37         # Very important we use repr here so we get all 17 decimal
38         # digits in the float.
39         return ("CDate(#12/30/1899# + (%r) + %r)" %
40                 (value.days, (value.seconds / 86400.0)))
41
42 class MSAccess_date(ado.COM_date):
43    
44     def DATEADD(dt, td):
45         """Return the SQL to add a timedelta to a date."""
46         # Important to use Fix (instead of CLng, for example)
47         # for negative numbers.
48         return "DateAdd('d', Fix(%s), %s)" % (td, dt)
49     DATEADD = staticmethod(DATEADD)
50    
51     def DATEDIFF(d1, d2):
52         """Return the SQL to subtract one date from another."""
53         # Important to use Fix (instead of CLng, for example)
54         # for negative numbers.
55         return "CDate(Fix(%s) - Fix(%s))" % (d1, d2)
56     DATEDIFF = staticmethod(DATEDIFF)
57     DATESUB = DATEDIFF
58    
59     def push(self, value, dbtype):
60         if value is None:
61             return 'NULL'
62         return '#%s/%s/%s#' % (value.month, value.day, value.year)
63
64
65 class MSAccess_datetime(ado.COM_datetime):
66    
67     def DATETIMEADD(dt, td):
68         """Return the SQL to add a timedelta to a datetime."""
69         return "CDate(%s + %s)" % (dt, td)
70     DATETIMEADD = staticmethod(DATETIMEADD)
71    
72     def DATETIMEDIFF(d1, d2):
73         """Return the SQL to subtract one (datetime or date expr) from another."""
74         return "CDate(%s - %s)" % (d1, d2)
75     DATETIMEDIFF = staticmethod(DATETIMEDIFF)
76     DATETIMESUB = DATETIMEDIFF
77    
78     def push(self, value, dbtype):
79         if value is None:
80             return 'NULL'
81         return ('#%s/%s/%s %02d:%02d:%02d#' %
82                 (value.month, value.day, value.year,
83                  value.hour, value.minute, value.second))
84
85 class MSAccess_time(ado.COM_time):
86    
87     def push(self, value, dbtype):
88         if value is None:
89             return 'NULL'
90         return '#%02d:%02d:%02d#' % (value.hour, value.minute, value.second)
91
92
93 class CURRENCY_float(adapters.float_to_SQL92DOUBLE):
94    
95     def pull(self, value, dbtype):
96         if isinstance(value, tuple):
97             # See http://groups.google.com/group/comp.lang.python/
98             #           browse_frm/thread/fed03c64735c9e9c
99             value = map(long, value)
100             return ((value[1] & 0xFFFFFFFFL) | (value[0] << 32)) / 1e4
101         return float(value)
102
103 class CURRENCY_decimal(adapters.decimal_to_SQL92DECIMAL):
104    
105     def pull(self, value, dbtype):
106         # pywin32 build 205 began support for returning
107         # COM Currency objects as decimal objects.
108         # See http://pywin32.cvs.sourceforge.net/pywin32/pywin32/CHANGES.txt?view=markup
109         if not isinstance(value, typerefs.decimal.Decimal):
110             # See http://groups.google.com/group/comp.lang.python/
111             #           browse_frm/thread/fed03c64735c9e9c
112             value = map(long, value)
113             value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)
114             return typerefs.decimal.Decimal(value) / 10000
115         return value
116
117 class CURRENCY_FixedPoint(adapters.fixedpoint_to_SQL92DECIMAL):
118    
119     def pull(self, value, dbtype):
120         if isinstance(value, typerefs.decimal.Decimal):
121             value = str(value)
122             scale = 0
123             atoms = value.rsplit(".", 1)
124             if len(atoms) > 1:
125                 scale = len(atoms[-1])
126             return typerefs.fixedpoint.FixedPoint(value, scale)
127         else:
128             # See http://groups.google.com/group/comp.lang.python/
129             #           browse_frm/thread/fed03c64735c9e9c
130             value = map(long, value)
131             value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)
132             return typerefs.fixedpoint.FixedPoint(value, 4) / 1e4
133
134
135
136 def _compare_strings(self, op1, op, sqlop, op2):
137     """Return the SQL for a comparison operation (or raise TypeError).
138     
139     op1 and op2 will be SQLExpression objects.
140     op will be an index into opcode.cmp_op.
141     sqlop will be the matching SQL for the given operator.
142     """
143     # ADO comparison operators for strings are case-insensitive.
144     if op < 6:
145         # ('<', '<=', '==', '!=', '>', '>=')
146         # Some operations on strings can be emulated with the
147         # StrComp function. Oddly enough, "StrComp(x, y) op 0"
148         # is the same as "x op y" in most cases.
149         return "StrComp(%s, %s) %s 0" % (op1.sql, op2.sql, sqlop)
150     else:
151         raise TypeError("Microsoft Access cannot compare strings "
152                         "using %r in a case-sensitive way." % sqlop)
153
154 class MSAccess_VARCHAR_Adapter(adapters.str_to_SQL92VARCHAR):
155     escapes = [("'", "''")]
156     compare_op = _compare_strings
157
158 class MSAccess_UNICODE_Adapter(adapters.unicode_to_SQL92VARCHAR):
159     escapes = [("'", "''")]
160     compare_op = _compare_strings
161
162 class MSAccess_Pickler(adapters.Pickler):
163     escapes = [("'", "''")]
164     compare_op = _compare_strings
165
166
167 # ---------------------------- DatabaseTypes ---------------------------- #
168
169 # These are Access 2000+ types.
170 # See http://msdn2.microsoft.com/en-us/library/aa140015(office.10).aspx
171 # http://msdn2.microsoft.com/en-us/library/ms714540.aspx
172 # http://office.microsoft.com/en-us/access/HP010322481033.aspx
173
174 # "A Text field can store up to 255 characters, but the default field size
175 # is 50 characters. A Memo field can store up to 65,536 characters. If you
176 # want to store formatted text or long documents, you should create an OLE
177 # Object field instead of a Memo field. Both Text and Memo data types store
178 # only the characters entered in a field; space characters for unused
179 # positions in the field aren't stored. You can sort or group on a Text
180 # field or a Memo field, but Access only uses the first 255 characters
181 # when you sort or group on a Memo field."
182
183 class TEXT(dbtypes.SQL92VARCHAR):
184    
185     # Actually 255 chars, 2 bytes per char unless compressed
186     max_bytes = 255
187     bytes = 255
188     variable = True
189     encoding = 'ISO-8859-1'
190    
191     # "With the Microsoft Jet 4.0 database engine, all data for the TEXT
192     # data types are now stored in the Unicode 2-byte character
193     # representation format. It replaces the Multi-byte Character Set
194     # (MBCS) format that was used in previous versions. Although Unicode
195     # representation requires more space to store each character, columns
196     # with TEXT data types can be defined to automatically compress the
197     # data if it is possible to do so.
198     #
199     # When you create TEXT data types with SQL, the Unicode compression
200     # property defaults to No. To set the Unicode compression property
201     # to Yes, use the WITH COMPRESSION (or WITH COMP) keywords at the
202     # field-level declaration."
203     with_compression = False
204    
205     default_adapters = dbtypes.SQL92VARCHAR.default_adapters.copy()
206     default_adapters.update({str: MSAccess_VARCHAR_Adapter(),
207                              unicode: MSAccess_UNICODE_Adapter(),
208                              None: MSAccess_Pickler(),
209                              })
210    
211     def ddl(self):
212         """Return the type for use in CREATE or ALTER statements."""
213         withcomp = ""
214         if self.with_compression:
215             withcomp = " WITH COMPRESSION"
216         return "%s(%s)%s" % (self.__class__.__name__, self.bytes, withcomp)
217
218
219 class MEMO(dbtypes.TEXT):
220     # MEMO is 1 GB max when set programatically (only 64K when set
221     # in Access UI). But then, 1 GB is the limit for the whole DB.
222     # Note that OpenSchema will return a DATA_TYPE of "WCHAR".
223     synonyms = ['WCHAR']
224     bytes = max_bytes = 65535           # (2.14 GB if not binary data)
225     variable = True
226     encoding = 'ISO-8859-1'
227    
228     default_adapters = dbtypes.TEXT.default_adapters.copy()
229     default_adapters.update({str: MSAccess_VARCHAR_Adapter(),
230                              unicode: MSAccess_UNICODE_Adapter(),
231                              None: MSAccess_Pickler(),
232                              })
233
234
235 class TINYINT(dbtypes.SQL92SMALLINT):
236     synonyms = ['INTEGER1', 'BYTE']
237     bytes = max_bytes = 1
238     signed = False
239
240 class SMALLINT(dbtypes.SQL92SMALLINT):
241     synonyms = ['SHORT', 'INTEGER2']
242
243 class INTEGER(dbtypes.SQL92INTEGER):
244     synonyms = ['LONG', 'INT', 'INTEGER4']
245
246
247 class REAL(dbtypes.SQL92REAL):
248     synonyms = ['SINGLE', 'FLOAT4', 'IEEESINGLE']
249
250 class FLOAT(dbtypes.SQL92DOUBLE):
251     synonyms = ['DOUBLE', 'FLOAT8', 'IEEEDOUBLE', 'NUMBER']
252
253
254 class DECIMAL(dbtypes.SQL92DECIMAL):
255     synonyms = ['NUMERIC', 'DEC']
256    
257     # "...precision, the default is 18 and the maximum allowed value is 28.
258     # For the scale, the default is 0 and the maximum allowed value is 28."
259     _precision = 18
260     max_precision = 28
261     scale = 0
262
263
264 class CURRENCY(dbtypes.FrozenPrecisionType):
265    
266     # "The CURRENCY data type is used to store numeric data that contains
267     # up to 15 digits on the left side of the decimal point, and up to 4
268     # digits on the right. It uses 8 bytes of memory for storage, and its
269     # only synonym is MONEY."
270     synonyms = ['MONEY']
271    
272     precision = max_precision = 19
273     scale = property(lambda self: 4, lambda self, value: None)
274    
275     default_adapters = {float: CURRENCY_float()}
276     default_pytype = float
277     if typerefs.fixedpoint:
278         default_pytype = typerefs.fixedpoint.FixedPoint
279         default_adapters[typerefs.fixedpoint.FixedPoint] = CURRENCY_FixedPoint()
280     if typerefs.decimal:
281         if hasattr(typerefs.decimal, "Decimal"):
282             default_pytype = typerefs.decimal.Decimal
283             default_adapters[typerefs.decimal.Decimal] = CURRENCY_decimal()
284         else:
285             default_pytype = typerefs.decimal.Decimal
286             default_adapters[typerefs.decimal] = CURRENCY_decimal()
287
288
289 class YESNO(dbtypes.SQL99BOOLEAN):
290     # "The BOOLEAN data types are logical types that result in either True
291     # or False values. They use 1 byte of memory for storage, and their
292     # synonyms are BIT, LOGICAL, LOGICAL1, and YESNO. A True value is
293     # equal to -1 while a False value is equal to 0."
294     pass
295
296
297 class BINARY(dbtypes.AdjustableByteType):
298     # "The BINARY data type is used to store a small amount of any type
299     # of data in its native, binary format. It uses 1 byte of memory for
300     # each character stored, and you can optionally specify the number
301     # of bytes to be allocated. If the number of bytes is not specified,
302     # it defaults to 510 bytes, which is the maximum number of bytes
303     # allowed. Its synonyms are BINARY, VARBINARY, and BINARY VARYING.
304     # The BINARY data type is not available in the Access user interface."
305     synonyms = ['VARBINARY', 'BINARY VARYING']
306     bytes = 510
307     max_bytes = 510
308     variable = False
309
310
311 class DATETIME(dbtypes.SQL92TIMESTAMP):
312     # "The DATETIME data type is used to store date, time, and combination
313     # date/time values for the years ranging from 100 to 9999.
314     # It uses 8 bytes of memory for storage, and its synonyms are
315     # DATE, TIME, DATETIME, and TIMESTAMP.
316     synonyms = ['DATE', 'TIME', 'TIMESTAMP']
317     _min = datetime.datetime(100, 1, 1)
318     _max = datetime.datetime(9999, 12, 31)
319    
320     default_adapters = {datetime.datetime: MSAccess_datetime(),
321                         datetime.date: MSAccess_date(),
322                         datetime.time: MSAccess_time(),
323                         datetime.timedelta: MSAccess_timedelta(),
324                         }
325
326
327 class MSAccessTypeSet(dbtypes.DatabaseTypeSet):
328    
329     known_types = {'float': [REAL, FLOAT],
330                    'varchar': [TEXT, MEMO],
331                    'char': [BINARY],
332                    'int': [TINYINT, SMALLINT, INTEGER],
333                    'bool': [YESNO],
334                    'datetime': [DATETIME],
335                    'date': [DATETIME],
336                    'time': [DATETIME],
337                    'timedelta': [DATETIME],
338                    'numeric': [DECIMAL],
339                    'other': [CURRENCY],
340                    }
341
342
343 class MSAccessDecompiler(ado.ADOSQLDecompiler):
344     sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in')
345    
346     like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"),
347                     ("?", "[?]"), ("#", "[#]")]
348    
349     def builtins_now(self):
350         return self.get_expr("Now()", datetime.datetime)
351    
352     def builtins_today(self):
353         return self.get_expr("DateValue(Now())", datetime.date)
354    
355     def builtins_year(self, x):
356         return self.get_expr("Year(" + x.sql + ")", int)
357    
358     def builtins_month(self, x):
359         return self.get_expr("Month(" + x.sql + ")", int)
360    
361     def builtins_day(self, x):
362         return self.get_expr("Day(" + x.sql + ")", int)
363
364
365 class MSAccessTable(ado.ADOTable):
366    
367     def delete(self, **kwargs):
368         """Delete all rows matching the given identifier kwargs."""
369         # MS Access needs an asterisk to delete
370         self.schema.db.execute('DELETE * FROM %s WHERE %s;' %
371                                (self.qname, self.id_clause(**kwargs)))
372    
373     def delete_all(self, restriction=None, **kwargs):
374         """Delete all rows which match the given restriction."""
375         w = self.whereclause(restriction, **kwargs)
376         # MS Access needs an asterisk to delete
377         self.schema.db.execute('DELETE * FROM %s WHERE %s;' % (self.qname, w))
378    
379     def _grab_new_ids(self, idkeys, conn):
380         data, _ = self.schema.db.fetch("SELECT @@IDENTITY;", conn)
381         return {idkeys[0]: data[0][0]}
382
383
384 class MSAccessConnectionManager(ado.ADOConnectionManager):
385    
386     poolsize = 0
387     default_isolation = "READ UNCOMMITTED"
388     isolation_levels = ["READ UNCOMMITTED",]
389    
390     def _set_factory(self):
391         # MS Access can't use a pool, because there doesn't seem
392         # to be a commit timeout. See http://support.microsoft.com/kb/200300
393         # for additional synchronization issues.
394         self._factory = conns.SingleConnection(self._get_conn, self._del_conn)
395    
396     def isolate(self, conn, isolation=None):
397         """Set the isolation level of the given connection.
398         
399         If 'isolation' is None, our default_isolation will be used for new
400         connections. Valid values for the 'isolation' argument may be native
401         values for your particular database. However, it is recommended you
402         pass items from the global 'levels' list instead; these will be
403         automatically replaced with native values.
404         
405         For many databases, this must be executed after START TRANSACTION.
406         """
407         if isolation is None:
408             isolation = self.default_isolation
409        
410         if isinstance(isolation, _isolation.IsolationLevel):
411             # Map the given IsolationLevel object to a native value.
412             isolation = isolation.name
413             if isolation not in self.isolation_levels:
414                 raise ValueError("IsolationLevel %r not allowed by %s."
415                                  % (isolation, self.__class__.__name__))
416        
417         # No action to take, since you can't actually set iso level.
418         pass
419
420
421 class MSAccessSchema(ado.ADOSchema):
422    
423     tableclass = MSAccessTable
424    
425     # See http://www.carlprothman.net/Technology/DataTypeMapping/tabid/97/Default.aspx
426     adotypes = {
427         # MS Access             ADO Name
428         2: SMALLINT,            # SMALLINT
429         3: INTEGER,             # INTEGER
430         4: REAL,                # SINGLE
431         5: FLOAT,               # DOUBLE
432         6: CURRENCY,            # CURRENCY
433         7: DATETIME,            # DATE (Access 97)
434         11: YESNO,              # BOOLEAN
435         17: TINYINT,            # UNSIGNEDTINYINT
436         128: BINARY,            # BINARY
437         130: MEMO,              # WCHAR
438         131: DECIMAL,           # NUMERIC (Access 2000)
439         135: DATETIME,          # DBTIMESTAMP (ODBC 97)
440         200: TEXT,              # VARCHAR (Access 97)
441         201: MEMO,              # LONGVARCHAR (Access 97)
442         202: TEXT,              # VARWCHAR (Access 2000)
443         203: MEMO,              # LONGVARWCHAR (Access 2000+)
444         # 205: OLEOBJECT,       # LONGVARBINARY
445         # 0: EMPTY,
446         # 8: BSTR, 9: IDISPATCH, 10: ERROR,
447         # 12: VARIANT, 13: IUNKNOWN, 14: DECIMAL, 16: TINYINT,
448         # 18: UNSIGNEDSMALLINT, 19: UNSIGNEDINT, 20: BIGINT,
449         # 21: UNSIGNEDBIGINT, 72: GUID,
450         # 129: CHAR,
451         # 132: USERDEFINED, 133: DBDATE, 134: DBTIME,
452         # 204: VARBINARY,
453     }
454    
455     def _get_columns(self, tablename, conn=None):
456         # For some reason, adSchemaPrimaryKeys would only return a single
457         # record for a PK that had multiple columns. Use adSchemaIndexes.
458         # coldefs will be:
459         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
460         # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202),
461         # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18),
462         # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3),
463         # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3),
464         # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72),
465         # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21),
466         # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)]
467         data, _ = self.db.fetch(ado.adSchemaIndexes, conn=conn, schema=True)
468         pknames = [row[17] for row in data
469                    if (tablename == row[2]) and row[6]]
470        
471         # columns will be
472         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
473         # (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), (u'COLUMN_PROPID', 19),
474         # (u'ORDINAL_POSITION', 19), (u'COLUMN_HASDEFAULT', 11),
475         # (u'COLUMN_DEFAULT', 203), (u'COLUMN_FLAGS', 19), (u'IS_NULLABLE', 11),
476         # (u'DATA_TYPE', 18), (u'TYPE_GUID', 72), (u'CHARACTER_MAXIMUM_LENGTH', 19),
477         # (u'CHARACTER_OCTET_LENGTH', 19), (u'NUMERIC_PRECISION', 18),
478         # (u'NUMERIC_SCALE', 2), (u'DATETIME_PRECISION', 19),
479         # (u'CHARACTER_SET_CATALOG', 202), (u'CHARACTER_SET_SCHEMA', 202),
480         # (u'CHARACTER_SET_NAME', 202), (u'COLLATION_CATALOG', 202),
481         # (u'COLLATION_SCHEMA', 202), (u'COLLATION_NAME', 202),
482         # (u'DOMAIN_CATALOG', 202), (u'DOMAIN_SCHEMA', 202),
483         # (u'DOMAIN_NAME', 202), (u'DESCRIPTION', 203)]
484         data, _ = self.db.fetch(ado.adSchemaColumns, conn=conn, schema=True)
485        
486         cols = []
487         typer = self.db.typeset
488         for row in data:
489             # I tried passing criteria to OpenSchema, but passing None is
490             # not the same as passing pythoncom.Empty (which errors).
491             if row[2] != tablename:
492                 continue
493            
494             dbtypetype = self.adotypes[row[11]]
495             dbtype = dbtypetype()
496             pytype = dbtype.default_pytype
497             if pytype is None:
498                 raise TypeError("%r has no default pytype." % dbtype)
499            
500             default = row[8]
501             if default is not None:
502                 if issubclass(pytype, (int, long, float)):
503                     # We may have stuck extraneous quotes in the default
504                     # value when using numeric defaults with MSAccess.
505                     if default.startswith("'") and default.endswith("'"):
506                         default = default[1:-1]
507                 default = pytype(default)
508            
509             name = str(row[3])
510             c = objects.Column(pytype, dbtype, default,
511                                key=(name in pknames),
512                                name=name, qname=self.db.quote(name))
513            
514             if dbtypetype in typer.known_types['int']:
515                 dbtype.bytes = row[15]
516             elif dbtypetype in typer.known_types['float']:
517                 dbtype.precision = row[15]
518                 dbtype.scale = row[16]
519             elif dbtypetype in typer.known_types['numeric']:
520                 dbtype.precision = row[15]
521                 dbtype.scale = row[16]
522             elif dbtypetype is MEMO:
523                 pass
524             elif (dbtypetype in typer.known_types['char'] or
525                   dbtypetype in typer.known_types['varchar']):
526                 if row[13]:
527                     # row[13] will be a float
528                     dbtype.bytes = int(row[13])
529                 else:
530                     # I'm kinda guessing on this. If we use "MEMO" in an
531                     # MSAccess CREATE statement, it comes back as "WCHAR",
532                     # and seems to support over 65536 bytes.
533                     dbtype.bytes = (2 ** 31) - 1
534            
535             c.adapter = dbtype.default_adapter(pytype)
536             cols.append(c)
537        
538         # Horrible hack to get autoincrement property
539         if conn is None:
540             conn = self.db.connections._factory()
541         try:
542             sql = "SELECT * FROM %s WHERE FALSE;" % self.db.quote(tablename)
543             bareconn = conn
544             if hasattr(conn, 'conn'):
545                 # 'conn' is a ConnectionWrapper object, which .Open
546                 # won't accept. Pass the unwrapped connection instead.
547                 bareconn = conn.conn
548            
549             # Call conn.Open(sql) directly, skipping win32com overhead.
550             res, rows_affected = conn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
551                                             ((8, 1), (16396, 18), (3, 49)),
552                                             # *args =
553                                             sql, ado.pythoncom.Missing, -1)
554         except ado.pywintypes.com_error, x:
555             try:
556                 res.InvokeTypes(*ado.Recordset_Close)
557             except:
558                 pass
559             res = None
560             x.args += (sql, )
561             conn = None
562            
563             try:
564                 # Return no columns when inspecting system tables
565                 if "no read permission" in x.args[2][2]:
566                     conn = None
567                     return []
568             except IndexError:
569                 pass
570            
571             # "raise x" here or we could get the traceback of the inner try.
572             raise x
573        
574         resFields = res.InvokeTypes(*ado.Recordset_Fields)
575         for c in cols:
576             f = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), c.name)
577             fprops = f.InvokeTypes(*ado.Field_Properties)
578             fprop = fprops.InvokeTypes(0, 0, 2, (9, 0), ((12, 1), ), "ISAUTOINCREMENT")
579             c.autoincrement = fprop.InvokeTypes(*ado.Property_Value)
580             if c.autoincrement:
581                 # Grumble. Get the Seed value from ADOX.
582                 try:
583                     cat = win32com.client.Dispatch(r'ADOX.Catalog')
584                     cat.ActiveConnection = conn
585                     adoxcol = cat.Tables(tablename).Columns(c.name)
586                     c.initial = adoxcol.Properties('Seed').Value
587                     adoxcol = None
588                 finally:
589                     cat = None
590        
591         try:
592             res.InvokeTypes(*ado.Recordset_Close)
593         except:
594             pass
595         conn = None
596        
597         return cols
598    
599     def columnclause(self, column):
600         """Return a clause for the given column for CREATE or ALTER TABLE.
601         
602         This will be of the form:
603             name type [DEFAULT x|AUTOINCREMENT(initial, 1)]
604         """
605         ddl = column.dbtype.ddl()
606        
607         if column.autoincrement:
608             if column.dbtype.default_pytype not in (int, long):
609                 raise ValueError("Microsoft Access does not allow COUNTER "
610                                  "columns of type %r" % dbtype)
611             ddl = " COUNTER(%s, 1) NOT NULL" % column.initial
612         else:
613             # MS Access does not allow a column to have
614             # both an AUTOINCREMENT clause and a DEFAULT clause.
615             default = column.default or None
616             if default:
617                 defspec = column.adapter.push(default, column.dbtype)
618                 if isinstance(default, (int, long)):
619                     # Crazy quote hack to get a numeric default to work.
620                     defspec = "'%s'" % defspec
621                 ddl = "%s DEFAULT %s" % (ddl, defspec)
622        
623         return '%s %s' % (column.qname, ddl)
624    
625     def create_database(self):
626         # By not providing an Engine Type, it defaults to 5 = Access 2000.
627         cat = win32com.client.Dispatch(r'ADOX.Catalog')
628         cat.Create(self.db.connections.Connect)
629         cat.ActiveConnection.Close()
630         self.clear()
631    
632     def drop_database(self):
633         # Must shut down our only connection to avoid
634         # "Permission denied" error on os.remove call below.
635         self.db.connections.shutdown()
636        
637         import os
638         # This should accept relative or absolute paths
639         if os.path.exists(self.name):
640             os.remove(self.name)
641        
642         self.clear()
643
644
645 class MSAccessDatabase(ado.ADODatabase):
646    
647     decompiler = MSAccessDecompiler
648     typeset = MSAccessTypeSet()
649     connectionmanager = MSAccessConnectionManager
650     schemaclass = MSAccessSchema
651    
652     def version(self):
653         conn = win32com.client.Dispatch(r'ADODB.Connection')
654         v = conn.Version
655         del conn
656         return "ADO Version: %s" % v
657
Note: See TracBrowser for help on using the browser.