Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/trunk/geniusql/providers/ado.py

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

Added isolation tests. Database kwargs now set child attributes if the key includes a dot.

  • Property svn:eol-style set to native
Line 
1 import sys
2 # Put COM in free-threaded mode. This first thread will have
3 # CoInitializeEx called automatically when pythoncom is imported.
4 sys.coinit_flags = 0
5
6 import pythoncom
7 Empty = pythoncom.Empty
8 clsctx = pythoncom.CLSCTX_SERVER
9
10 import win32com.client
11
12 # InvokeTypes args (always pass as *args)
13 BOF = (1002, 0, 2, (11, 0), ())
14 EOF = (1006, 0, 2, (11, 0), ())
15 Recordset_Fields = (0, 0, 2, (9, 0), ())
16 # This assumes no arguments passed to GetRows
17 Recordset_GetRows = (1016, 0, 1, (12, 0), ((3, 49), (12, 17), (12, 17)), -1, Empty, Empty)
18 Recordset_Close = (1014, 0, 1, (24, 0), (),)
19 Fields_Count = (1, 0, 2, (3, 0), ())
20 Field_Name = (1100, 0, 2, (8, 0), ())
21 Field_Type = (1102, 0, 2, (3, 0), ())
22 Field_Properties = (500, 0, 2, (9, 0), ())
23 Property_Value = (0, 0, 2, (12, 0), ())
24
25 import pywintypes
26 import datetime
27 import time
28
29 try:
30     import cPickle as pickle
31 except ImportError:
32     import pickle
33
34 import threading
35 import warnings
36
37
38 import geniusql
39 from geniusql import adapters, conns, errors, select, typerefs
40 from geniusql import isolation as _isolation
41
42 adOpenForwardOnly = 0
43 adOpenKeyset = 1
44 adOpenDynamic = 2
45 adOpenStatic = 3
46
47 adLockReadOnly = 1
48 adLockPessimistic = 2
49 adLockOptimistic = 3
50 adLockBatchOptimistic = 4
51
52 adSchemaColumns = 4
53 adSchemaIndexes = 12
54 adSchemaTables = 20
55 adSchemaPrimaryKeys = 28
56
57 adUseClient = 3
58
59 # 12/30/1899, the zero-Date for ADO = 693594
60 # "Sure, there are two 4-byte integers stored. But they are
61 # packed together into a BINARY(8). The first 4-byte being
62 # the elapsed number days since SQL Server's base date of
63 # 1900-01-01. The Second 4-bytes Store the Time of Day
64 # Represented as the Number of Milliseconds After Midnight."
65 # http://www.sql-server-performance.com/fk_datetime.asp
66 zeroHour = datetime.date(1899, 12, 30).toordinal()
67
68 # Note also that SQL Server allows DATETIME in the range:
69 # "1753-01-01 00:00:00.0" to "9999-12-31 23:59:59.997".
70
71 dbtypes = {
72     0: 'EMPTY',                     2: 'SMALLINT',
73     3: 'INTEGER',                   4: 'SINGLE',
74     5: 'DOUBLE',                    6: 'CURRENCY',
75     7: 'DATE',                      8: 'BSTR',
76     9: 'IDISPATCH',                 10: 'ERROR',
77     11: 'BOOLEAN',                  12: 'VARIANT',
78     13: 'IUNKNOWN',                 14: 'DECIMAL',
79     16: 'TINYINT',                  17: 'UNSIGNEDTINYINT',
80     18: 'UNSIGNEDSMALLINT',         19: 'UNSIGNEDINT',
81     20: 'BIGINT',                   21: 'UNSIGNEDBIGINT',
82     72: 'GUID',                     128: 'BINARY',
83     129: 'CHAR',                    130: 'WCHAR',
84     131: 'NUMERIC',                 132: 'USERDEFINED',
85     133: 'DBDATE',                  134: 'DBTIME',
86     135: 'DBTIMESTAMP',             200: 'VARCHAR',
87     201: 'LONGVARCHAR',             202: 'VARWCHAR',
88     203: 'LONGVARWCHAR',            204: 'VARBINARY',
89     205: 'LONGVARBINARY'
90 }
91
92 DBCOLUMNFLAGS_WRITE = 0x4
93 DBCOLUMNFLAGS_WRITEUNKNOWN = 0x8
94 DBCOLUMNFLAGS_ISFIXEDLENGTH = 0x10
95 DBCOLUMNFLAGS_ISNULLABLE = 0x20
96 DBCOLUMNFLAGS_MAYBENULL = 0x40
97 DBCOLUMNFLAGS_ISLONG = 0x80
98 DBCOLUMNFLAGS_ISROWID = 0x100
99 DBCOLUMNFLAGS_ISROWVER = 0x200
100 DBCOLUMNFLAGS_CACHEDEFERRED = 0x1000
101
102
103 def time_from_com(com_date):
104     """Return a valid datetime.time from a COM date or time object."""
105     hour, minute = divmod(86400 * (float(com_date) % 1), 3600)
106     minute, second = divmod(minute, 60)
107     # Must do both int() and round() or we'll be up to 1 second off.
108     hour = int(round(hour))
109     minute = int(round(minute))
110     second = int(round(second))
111    
112     while second > 59:
113         second -= 60
114         minute += 1
115     while second < 0:
116         second += 60
117         minute -= 1
118     while minute > 59:
119         minute -= 60
120         hour += 1
121     while minute < 0:
122         minute += 60
123         hour -= 1
124     while hour > 23:
125         hour -= 24
126         day += 1
127     while hour < 0:
128         hour += 24
129    
130     return datetime.time(hour, minute, second)
131
132
133 class AdapterFromADO(adapters.AdapterFromDB):
134     """Coerce incoming values from ADO to Python datatypes."""
135    
136     encoding = 'ISO-8859-1'
137    
138     def coerce_any_to_datetime_datetime(self, value):
139         # Illegal Date/Time values will crash the
140         # app when using value.Format(). Therefore,
141         # grab the value and figure the date ourselves.
142         # Use 1-second resolution only.
143         if isinstance(value, basestring):
144             if value:
145                 try:
146                     return datetime.datetime(int(value[0:4]), int(value[4:6]),
147                                              int(value[6:8]))
148                 except Exception:
149                     raise ValueError("'%s' %s" % (value, type(value)))
150             else:
151                 return None
152         else:
153             # For some reason, we need both float and int.
154             aDate = datetime.date.fromordinal(int(float(value)) + zeroHour)
155             return datetime.datetime.combine(aDate, time_from_com(value))
156    
157     def coerce_any_to_datetime_date(self, value):
158         # See coerce_any_to_datetime
159         if isinstance(value, basestring):
160             if value:
161                 try:
162                     return datetime.date(int(value[0:4]), int(value[4:6]),
163                                          int(value[6:8]))
164                 except Exception:
165                     raise ValueError("'%s' %s" % (value, type(value)))
166             else:
167                 return None
168         else:
169             return datetime.date.fromordinal(int(float(value)) + zeroHour)
170    
171     def coerce_any_to_datetime_time(self, value):
172         # See coerce_any_to_datetime
173         return time_from_com(value)
174    
175     def coerce_any_to_decimal_Decimal(self, value):
176         # pywin32 build 205 began support for returning
177         # COM Currency objects as decimal objects.
178         # See http://pywin32.cvs.sourceforge.net/pywin32/pywin32/CHANGES.txt?view=markup
179         if not isinstance(value, typerefs.decimal.Decimal):
180             value = str(value)
181             value = typerefs.decimal.Decimal(value)
182         return value
183    
184     def coerce_CURRENCY_to_float(self, value):
185         if isinstance(value, tuple):
186             # See http://groups.google.com/group/comp.lang.python/
187             #           browse_frm/thread/fed03c64735c9e9c
188             value = map(long, value)
189             return ((value[1] & 0xFFFFFFFFL) | (value[0] << 32)) / 1e4
190         return float(value)
191    
192     def coerce_CURRENCY_to_decimal_Decimal(self, value):
193         # pywin32 build 205 began support for returning
194         # COM Currency objects as decimal objects.
195         # See http://pywin32.cvs.sourceforge.net/pywin32/pywin32/CHANGES.txt?view=markup
196         if not isinstance(value, typerefs.decimal.Decimal):
197             # See http://groups.google.com/group/comp.lang.python/
198             #           browse_frm/thread/fed03c64735c9e9c
199             value = map(long, value)
200             value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)
201             return typerefs.decimal.Decimal(value) / 10000
202         return value
203    
204     def coerce_CURRENCY_to_fixedpoint_FixedPoint(self, value):
205         if isinstance(value, typerefs.decimal.Decimal):
206             value = str(value)
207             scale = 0
208             atoms = value.rsplit(".", 1)
209             if len(atoms) > 1:
210                 scale = len(atoms[-1])
211             return typerefs.fixedpoint.FixedPoint(value, scale)
212         else:
213             # See http://groups.google.com/group/comp.lang.python/
214             #           browse_frm/thread/fed03c64735c9e9c
215             value = map(long, value)
216             value = (value[1] & 0xFFFFFFFFL) | (value[0] << 32)
217             return typerefs.fixedpoint.FixedPoint(value, 4) / 1e4
218    
219     def coerce_any_to_unicode(self, value):
220         if isinstance(value, unicode):
221             # For some reason, value is already a unicode object.
222             return value
223        
224         if isinstance(value, (basestring, buffer)):
225             try:
226                 return unicode(value, self.encoding)
227             except UnicodeError, exc:
228                 exc.args += (type(value),)
229         return unicode(value)
230
231
232
233 class ADOSQLDecompiler(select.SQLDecompiler):
234    
235     def visit_COMPARE_OP(self, lo, hi):
236         op2, op1 = self.stack.pop(), self.stack.pop()
237         if op1 is select.cannot_represent or op2 is select.cannot_represent:
238             self.stack.append(select.cannot_represent)
239             return
240        
241         op = lo + (hi << 8)
242         if op in (6, 7):     # in, not in
243             # Looking for text in a field. Use Like (reverse terms).
244             # LIKE is case-insensitive in MS SQL Server (and there
245             # doesn't seem to be a way around it). Use icontainedby
246             # and just mark imperfect.
247             value = self.dejavu_icontainedby(op1, op2)
248             if op == 7:
249                 value = "NOT " + value
250             self.stack.append(value)
251             self.imperfect = True
252         elif op1 == 'NULL':
253             if op in (2, 8):    # '==', is
254                 self.stack.append(op2 + " IS NULL")
255             elif op in (3, 9):  # '!=', 'is not'
256                 self.stack.append(op2 + " IS NOT NULL")
257             else:
258                 raise ValueError("Non-equality Null comparisons not allowed.")
259         elif op2 == 'NULL':
260             if op in (2, 8):    # '==', 'is'
261                 self.stack.append(op1 + " IS NULL")
262             elif op in (3, 9):  # '!=', 'is not'
263                 self.stack.append(op1 + " IS NOT NULL")
264             else:
265                 raise ValueError("Non-equality Null comparisons not allowed.")
266         else:
267             try:
268                 op1, op2 = self._compare_constants(op1, op2)
269             except TypeError:
270                 self.stack.append(select.cannot_represent)
271                 self.imperfect = True
272                 return
273            
274             if (isinstance(op2, select.ConstWrapper)
275                 and isinstance(op2.basevalue, basestring)):
276                 atom = self._compare_strings(op1, op, op2)
277                 if atom:
278                     self.stack.append(atom)
279                     return
280             self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2)
281    
282     def _compare_strings(self, op1, op, op2):
283         # ADO comparison operators for strings are case-insensitive
284         # by default. Rather than determine which columns in the DB
285         # might be case-sensitive, just flag them all as imperfect.
286         # TODO: might be possible to cast both to varbinary, but
287         # that may cause problems with unicode columns.
288         self.imperfect = True
289    
290    
291     # --------------------------- Dispatchees --------------------------- #
292    
293     def attr_startswith(self, tos, arg):
294         self.imperfect = True
295         return tos + " LIKE '" + self.adapter.escape_like(arg) + "%'"
296    
297     def attr_endswith(self, tos, arg):
298         self.imperfect = True
299         return tos + " LIKE '%" + self.adapter.escape_like(arg) + "'"
300    
301     def containedby(self, op1, op2):
302         self.imperfect = True
303         if isinstance(op1, select.ConstWrapper):
304             # Looking for text in a field. Use Like (reverse terms).
305             return op2 + " LIKE '%" + self.adapter.escape_like(op1) + "%'"
306         else:
307             # Looking for field in (a, b, c)
308             atoms = [self.adapter.coerce(x) for x in op2.basevalue]
309             if atoms:
310                 return op1 + " IN (" + ", ".join(atoms) + ")"
311             else:
312                 # Nothing will match the empty list, so return none.
313                 return self.adapter.bool_false
314    
315     def dejavu_icontainedby(self, op1, op2):
316         if isinstance(op1, select.ConstWrapper):
317             # Looking for text in a field. Use Like (reverse terms).
318             # LIKE is already case-insensitive in MS SQL Server;
319             # so don't use LOWER().
320             value = op2 + " LIKE '%" + self.adapter.escape_like(op1) + "%'"
321         else:
322             # Looking for field in (a, b, c)
323             atoms = [self.adapter.coerce(x) for x in op2.basevalue]
324             if atoms:
325                 return op1 + " IN (" + ", ".join(atoms) + ")"
326             else:
327                 # Nothing will match the empty list, so return none.
328                 return self.adapter.bool_false
329         return value
330    
331     def dejavu_istartswith(self, x, y):
332         # Like is already case-insensitive in ADO; so don't use LOWER().
333         return x + " LIKE '" + self.adapter.escape_like(y) + "%'"
334    
335     def dejavu_iendswith(self, x, y):
336         # Like is already case-insensitive in ADO; so don't use LOWER().
337         return x + " LIKE '%" + self.adapter.escape_like(y) + "'"
338    
339     def dejavu_ieq(self, x, y):
340         # = is already case-insensitive in ADO.
341         return x + " = " + y
342    
343     def dejavu_now(self):
344         return "getdate()"
345    
346     def dejavu_today(self):
347         return "DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)"
348    
349     def dejavu_year(self, x):
350         return "DATEPART(year, " + x + ")"
351    
352     def dejavu_month(self, x):
353         return "DATEPART(month, " + x + ")"
354    
355     def dejavu_day(self, x):
356         return "DATEPART(day, " + x + ")"
357    
358     def func__builtin___len(self, x):
359         return "Len(" + x + ")"
360
361
362 class ADOTable(geniusql.Table):
363    
364     def _add_column(self, column):
365         """Internal function to add the column to the database."""
366         coldef = self.schema.columnclause(column)
367         # SQL Server doesn't use the "COLUMN" keyword with "ADD"
368         self.schema.db.execute_ddl("ALTER TABLE %s ADD %s;" %
369                                    (self.qname, coldef))
370    
371     def _rename(self, oldcol, newcol):
372         conn = self.schema.db.connections.get()
373         try:
374             cat = win32com.client.Dispatch(r'ADOX.Catalog')
375             cat.ActiveConnection = conn
376             cat.Tables(self.name).Columns(oldcol.name).Name = newcol.name
377         finally:
378             conn = None
379             cat = None
380
381
382 def connatoms(connstring):
383     atoms = {}
384     for pair in connstring.split(";"):
385         if pair:
386             k, v = pair.split("=", 1)
387             atoms[k.upper().strip()] = v.strip()
388     return atoms
389
390
391 class ADOConnectionManager(conns.ConnectionManager):
392    
393     # the amount of time to try to close the db connection
394     # before raising an exception
395     shutdowntimeout = 1 # sec.
396    
397     ConnectionTimeout = None
398     CommandTimeout = None
399    
400     def _get_conn(self, master=False):
401         if master:
402             # Must shut down all connections to avoid
403             # "being accessed by other users" error.
404             self.shutdown()
405            
406             atoms = connatoms(self.Connect)
407             atoms['INITIAL CATALOG'] = "tempdb"
408             connectstr = "; ".join(["%s=%s" % (k, v)
409                                     for k, v in atoms.iteritems()])
410         else:
411             connectstr = self.Connect
412        
413         conn = win32com.client.Dispatch(r'ADODB.Connection')
414         conn.Open(connectstr)
415         if self.ConnectionTimeout is not None:
416             conn.ConnectionTimeout = self.ConnectionTimeout
417         if self.CommandTimeout is not None:
418             conn.CommandTimeout = self.CommandTimeout
419         return conn
420    
421     def _del_conn(self, conn):
422         for trial in xrange(self.shutdowntimeout * 10):
423             try:
424                 # This may raise "Operation cannot be performed
425                 # while executing asynchronously"
426                 # if a prior operation has not yet completed.
427                 conn.Close()
428                 return
429             except pywintypes.com_error, e:
430                 try:
431                     ecode = e.args[2][-1]
432                 except IndexError:
433                     ecode = None
434                 if ecode == -2146824577:
435                     # "Operation cannot be performed while executing asynchronously"
436                     # Try again...
437                     time.sleep(0.1)
438                     continue
439                 raise
440    
441     #                            Transactions                             #
442    
443     def start(self, isolation=None):
444         """Start a transaction. Not needed if self.implicit_trans is True."""
445         conn = self.get(started=True)
446         self.db.execute("BEGIN TRANSACTION;", conn)
447         self.isolate(conn, isolation)
448
449
450 class ADOSchema(geniusql.Schema):
451    
452     tableclass = ADOTable
453    
454     #                              Discovery                              #
455    
456     def _get_tables(self, conn=None):
457         # cols will be
458         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
459         # (u'TABLE_TYPE', 202), (u'TABLE_GUID', 72), (u'DESCRIPTION', 203),
460         # (u'TABLE_PROPID', 19), (u'DATE_CREATED', 7), (u'DATE_MODIFIED', 7)]
461         data, _ = self.db.fetch(adSchemaTables, conn=conn, schema=True)
462         return [self.tableclass(str(row[2]), self.db.quote(str(row[2])),
463                                 self, created=True)
464                 for row in data
465                 # Ignore linked and system tables
466                 if row[3] == "TABLE"]
467    
468     def _get_table(self, tablename, conn=None):
469         # cols will be
470         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
471         # (u'TABLE_TYPE', 202), (u'TABLE_GUID', 72), (u'DESCRIPTION', 203),
472         # (u'TABLE_PROPID', 19), (u'DATE_CREATED', 7), (u'DATE_MODIFIED', 7)]
473         data, _ = self.db.fetch(adSchemaTables, conn=conn, schema=True)
474         for row in data:
475             name = str(row[2])
476             if name == tablename:
477                 return self.tableclass(name, self.db.quote(name),
478                                        self, created=True)
479         raise errors.MappingError(tablename)
480    
481     def _get_columns(self, tablename, conn=None):
482         # For some reason, adSchemaPrimaryKeys would only return a single
483         # record for a PK that had multiple columns. Use adSchemaIndexes.
484         # coldefs will be:
485         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
486         # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202),
487         # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18),
488         # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3),
489         # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3),
490         # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72),
491         # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21),
492         # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)]
493         data, _ = self.db.fetch(adSchemaIndexes, conn=conn, schema=True)
494         pknames = [row[17] for row in data
495                    if (tablename == row[2]) and row[6]]
496        
497         # columns will be
498         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
499         # (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72), (u'COLUMN_PROPID', 19),
500         # (u'ORDINAL_POSITION', 19), (u'COLUMN_HASDEFAULT', 11),
501         # (u'COLUMN_DEFAULT', 203), (u'COLUMN_FLAGS', 19), (u'IS_NULLABLE', 11),
502         # (u'DATA_TYPE', 18), (u'TYPE_GUID', 72), (u'CHARACTER_MAXIMUM_LENGTH', 19),
503         # (u'CHARACTER_OCTET_LENGTH', 19), (u'NUMERIC_PRECISION', 18),
504         # (u'NUMERIC_SCALE', 2), (u'DATETIME_PRECISION', 19),
505         # (u'CHARACTER_SET_CATALOG', 202), (u'CHARACTER_SET_SCHEMA', 202),
506         # (u'CHARACTER_SET_NAME', 202), (u'COLLATION_CATALOG', 202),
507         # (u'COLLATION_SCHEMA', 202), (u'COLLATION_NAME', 202),
508         # (u'DOMAIN_CATALOG', 202), (u'DOMAIN_SCHEMA', 202),
509         # (u'DOMAIN_NAME', 202), (u'DESCRIPTION', 203)]
510         data, _ = self.db.fetch(adSchemaColumns, conn=conn, schema=True)
511        
512         cols = []
513         for row in data:
514             # I tried passing criteria to OpenSchema, but passing None is
515             # not the same as passing pythoncom.Empty (which errors).
516             if row[2] != tablename:
517                 continue
518            
519             dbtype = dbtypes[row[11]]
520             default = row[8]
521             if default is not None:
522                 deftype = self.db.python_type(dbtype)
523                 if issubclass(deftype, (int, long)):
524                     # We may have stuck extraneous quotes in the default
525                     # value when using numeric defaults with MSAccess.
526                     if default.startswith("'") and default.endswith("'"):
527                         default = default[1:-1]
528                 default = deftype(default)
529            
530             name = str(row[3])
531             c = geniusql.Column(self.db.python_type(dbtype), dbtype,
532                                 default, hints={}, key=(name in pknames),
533                                 name=name, qname=self.db.quote(name))
534            
535             # This only works for SQL Server. The MSAccessDatabase will
536             # wrap this method and override autoincrement.
537             colflags = int(row[9])
538             if ((colflags & DBCOLUMNFLAGS_ISFIXEDLENGTH)
539                 and not (colflags & DBCOLUMNFLAGS_WRITE)):
540                 c.autoincrement = True
541            
542             if dbtype in ("SMALLINT", "INTEGER", "TINYINT",
543                           "UNSIGNEDTINYINT", "UNSIGNEDSMALLINT",
544                           "UNSIGNEDINT", "BIGINT", "UNSIGNEDBIGINT"):
545                 c.hints['bytes'] = row[15]
546             elif dbtype in ("SINGLE", "DOUBLE"):
547                 c.hints['precision'] = row[15]
548                 c.hints['scale'] = row[16]
549             elif dbtype == "CURRENCY":
550                 # CURRENCY allows 15 places to the left of the decimal point,
551                 # and 4 places to the right.
552                 c.hints['precision'] = 19
553                 c.hints['scale'] = 4
554             elif dbtype in ("DECIMAL", "NUMERIC"):
555                 c.hints['precision'] = row[15]
556                 c.hints['scale'] = row[16]
557                 c.dbtype = "%s(%s, %s)" % (dbtype, row[15], row[16])
558             elif dbtype in ("BSTR", "VARIANT", "BINARY", "CHAR",
559                             "VARCHAR", "VARBINARY", "WCHAR", "VARWCHAR"):
560                 if row[13]:
561                     # row[13] will be a float
562                     c.hints['bytes'] = b = int(row[13])
563                 else:
564                     # I'm kinda guessing on this. If we use "MEMO" in an
565                     # MSAccess CREATE statement, it comes back as "WCHAR",
566                     # and seems to support over 65536 bytes.
567                     c.hints['bytes'] = b = (2 ** 31) - 1
568                 c.dbtype = "%s(%s)" % (c.dbtype, b)
569             elif dbtype in ("LONGVARCHAR", "LONGVARBINARY", "LONGVARWCHAR"):
570                 if row[13]:
571                     # row[13] will be a float
572                     c.hints['bytes'] = b = int(row[13])
573                     c.dbtype = "%s(%s)" % (c.dbtype, b)
574                 else:
575                     c.hints['bytes'] = 65535
576            
577             cols.append(c)
578         return cols
579    
580     def _get_indices(self, tablename=None, conn=None):
581         # cols will be
582         # [(u'TABLE_CATALOG', 202), (u'TABLE_SCHEMA', 202), (u'TABLE_NAME', 202),
583         # (u'INDEX_CATALOG', 202), (u'INDEX_SCHEMA', 202), (u'INDEX_NAME', 202),
584         # (u'PRIMARY_KEY', 11), (u'UNIQUE', 11), (u'CLUSTERED', 11), (u'TYPE', 18),
585         # (u'FILL_FACTOR', 3), (u'INITIAL_SIZE', 3), (u'NULLS', 3),
586         # (u'SORT_BOOKMARKS', 11), (u'AUTO_UPDATE', 11), (u'NULL_COLLATION', 3),
587         # (u'ORDINAL_POSITION', 19), (u'COLUMN_NAME', 202), (u'COLUMN_GUID', 72),
588         # (u'COLUMN_PROPID', 19), (u'COLLATION', 2), (u'CARDINALITY', 21),
589         # (u'PAGES', 3), (u'FILTER_CONDITION', 202), (u'INTEGRATED', 11)]
590         data, _ = self.db.fetch(adSchemaIndexes, conn=conn, schema=True)
591         indices = []
592         for row in data:
593             # I tried passing criteria to OpenSchema, but passing None is
594             # not the same as passing pythoncom.Empty (which errors).
595             if tablename and row[2] != tablename:
596                 continue
597             i = geniusql.Index(row[5], self.db.quote(row[5]),
598                                row[2], row[17], row[7])
599             indices.append(i)
600         return indices
601    
602     #                              Container                              #
603    
604     def _rename(self, oldtable, newtable):
605         conn = self.db.connections.get()
606         try:
607             cat = win32com.client.Dispatch(r'ADOX.Catalog')
608             cat.ActiveConnection = conn
609             cat.Tables(oldtable.name).Name = newtable.name
610         finally:
611             conn = None
612             cat = None
613
614
615 class ADODatabase(geniusql.Database):
616    
617     decompiler = ADOSQLDecompiler
618     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    
658    
659     #                               Naming                                #
660    
661     def quote(self, name):
662         """Return name, quoted for use in an SQL statement."""
663         return '[' + name + ']'
664    
665     def execute(self, query, conn=None):
666         if conn is None:
667             conn = self.connections.get()
668         if isinstance(query, unicode):
669             query = query.encode(self.adaptertosql.encoding)
670        
671         self.log(repr((id(conn), id(getattr(conn, "conn", None)), query)))
672         try:
673             bareconn = conn
674             if hasattr(conn, 'conn'):
675                 # 'conn' is a ConnectionWrapper object, which .Open
676                 # won't accept. Pass the unwrapped connection instead.
677                 # Note that we CANNOT write "conn = conn.conn", because
678                 # if we called get() above, we'd lose our only
679                 # reference to the wrapper and our weakref callback
680                 # would close the conn before we've executed the SQL.
681                 bareconn = conn.conn
682            
683             # Call Execute directly, skipping win32com overhead.
684             bareconn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
685                                           ((8, 1), (16396, 18), (3, 49)),
686                                           query, pythoncom.Missing, -1)
687         except pywintypes.com_error, x:
688             x.args += (query, )
689             conn = None
690             raise
691    
692     def fetch(self, query, conn=None, schema=False):
693         """fetch(query, conn=None) -> rowdata, columns."""
694         if conn is None:
695             conn = self.connections.get()
696        
697         try:
698             if schema:
699                 # Call OpenSchema(query) directly, skipping win32com overhead.
700                 res = conn._oleobj_.InvokeTypes(19, 0, 1, (9, 0),
701                                                 ((3, 1), (12, 17), (12, 17)),
702                                                 query, Empty, Empty)
703             else:
704                 self.log(repr((id(conn), id(getattr(conn, "conn", None)), query)))
705                 bareconn = conn
706                 if hasattr(conn, 'conn'):
707                     # 'conn' is a ConnectionWrapper object, which .Open
708                     # won't accept. Pass the unwrapped connection instead.
709                     bareconn = conn.conn
710                
711                 # Call conn.Open(query) directly, skipping win32com overhead.
712                 res, rows_affected = bareconn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
713                                                 ((8, 1), (16396, 18), (3, 49)),
714                                                 # *args =
715                                                 query, pythoncom.Missing, -1)
716         except pywintypes.com_error, x:
717             try:
718                 # Close
719                 res.InvokeTypes(*Recordset_Close)
720             except:
721                 pass
722             res = None
723             x.args += (query, )
724             conn = None
725             # "raise x" here or we could get the traceback of the inner try.
726             raise x
727        
728         # Using xrange(Count) is slightly faster than "for x in resFields".
729         resFields = res.InvokeTypes(*Recordset_Fields)
730         fieldcount = resFields.InvokeTypes(*Fields_Count)
731         columns = []
732         for i in xrange(fieldcount):
733             # Wow. Calling this directly (instead of resFields(i))
734             # results in a 29% speedup for a 1-row fetch() of 48 fields.
735             x = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), i)
736            
737             # Wow. Calling these directly (instead of x.Name, x.Type)
738             # results in a 40% speedup for a 1-row fetch() of 48 fields.
739             name = x.InvokeTypes(*Field_Name)
740             typ = x.InvokeTypes(*Field_Type)
741             columns.append((name, typ))
742        
743         data = []
744         if not (res.InvokeTypes(*BOF) and res.InvokeTypes(*EOF)):
745             # We tried .MoveNext() and lots of Fields.Item() calls.
746             # Using GetRows() beats that time by about 2/3.
747             # Inlining GetRows results in a 14% speedup for fetch().
748             data = res.InvokeTypes(*Recordset_GetRows)
749            
750             # Convert cols x rows -> rows x cols
751             data = zip(*data)
752         try:
753             # Close
754             res.InvokeTypes(*Recordset_Close)
755         except:
756             pass
757         conn = None
758        
759         return data, columns
760
761
762
763 ###########################################################################
764 ##                                                                       ##
765 ##                             SQL Server                                ##
766 ##                                                                       ##
767 ###########################################################################
768
769
770 class ADOSQLDecompiler_SQLServer(ADOSQLDecompiler):
771    
772     def _compare_strings(self, op1, op, op2):
773         # ADO comparison operators for strings are case-insensitive.
774         if op < 6:
775             # ('<', '<=', '==', '!=', '>', '>=')
776             # Some operations on strings can be emulated with the
777             # Convert function.
778             return ("Convert(binary, %s) %s Convert(binary, %s)" %
779                     (op1, self.sql_cmp_op[op], op2))
780         else:
781             return ADOSQLDecompiler._compare_strings(self, op1, op, op2)
782
783
784 class AdapterToADOSQL_SQLServer(adapters.AdapterToSQL):
785    
786     encoding = 'ISO-8859-1'
787    
788     escapes = [("'", "''")]
789     like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"),
790                     ("?", "[?]"), ("#", "[#]")]
791    
792     # These are not the same as coerce_bool_to_any (which is used on one side of
793     # a comparison). Instead, these are used when the whole (sub)expression
794     # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3".
795     bool_true = "(1=1)"
796     bool_false = "(1=0)"
797    
798     def coerce_bool_to_any(self, value):
799         if value:
800             return '1'
801         return '0'
802    
803     def cast_VARCHAR_to_int(self, colref):
804         return ("(CASE WHEN ISNUMERIC(%s)=1 THEN CAST(%s AS int) END)"
805                 % (colref, colref))
806
807
808 class TypeAdapter_SQLServer(adapters.TypeAdapter):
809    
810     # Hm. Docs say 38, but I can't seem to get more than 12 working.
811     # They must mean 38 binary digits; math.log(2 ** 38, 10) = 11.4+
812     numeric_max_precision = 12
813     numeric_max_bytes = 6
814    
815     def coerce_bool(self, col):
816         return "BIT"
817    
818     def coerce_datetime_datetime(self, col):
819         return "DATETIME"
820    
821     def coerce_datetime_date(self, col):
822         return "DATETIME"
823    
824     def coerce_datetime_time(self, col):
825         return "DATETIME"
826    
827     def int_type(self, bytes):
828         """Return a datatype which can handle the given number of bytes."""
829         if bytes <= 2:
830             return "SMALLINT"
831         elif bytes <= 4:
832             return "INTEGER"
833         elif bytes <= 8:
834             # BIGINT is usually 8 bytes
835             return "BIGINT"
836         else:
837             # Anything larger than 8 bytes, use decimal/numeric.
838             return "NUMERIC(%s, 0)" % (bytes * 2)
839    
840     def coerce_str(self, col):
841         # The bytes hint does not reflect the usual 4-byte base for varchar.
842         bytes = int(col.hints.get('bytes', 255))
843        
844         if bytes == 0 or bytes > 8000:
845             # Okay, what the @#$%& is wrong with Redmond??!?! We can't even
846             # compare TEXT or NTEXT fields??!? Fine. We'll deny such, and
847             # warn the deployer with less swearing and exclamation points.
848             errors.warn("You have defined a string property without "
849                         "limiting its length. Microsoft SQL Server does "
850                         "not allow comparisons on string fields larger "
851                         "than 8000 characters. Some of your data may be "
852                         "truncated.")
853             bytes = 8000
854        
855         # 8000 *bytes* is the absolute upper limit, based on T_SQL docs for
856         # varchar/varbinary. If there are further fields defined for the
857         # class, or the codepage uses a double-byte character set, we still
858         # might exceed the max size (8060) for a record. We could calc the
859         # total requested record size, and adjust accordingly. Meh.
860         return "VARCHAR(%s)" % bytes
861
862
863 class SQLServerTable(ADOTable):
864    
865     def _rename(self, oldcol, newcol):
866         self.schema.db.execute_ddl("EXEC sp_rename '%s.%s', '%s', 'COLUMN'" %
867                                    (self.name, oldcol.name, newcol.name))
868    
869     def _grab_new_ids(self, idkeys, conn):
870         """Insert a row using the table's SERIAL field."""
871         # For some reason, using SCOPE_IDENTITY or IDENTITY failed (returned
872         # None) when retrieving ID's just after a 99-thread-test ran. Moving
873         # the multithreading test fixed it. IDENT_CURRENT worked regardless.
874         data, _ = self.schema.db.fetch("SELECT IDENT_CURRENT('%s');"
875                                        % self.qname, conn)
876         return {idkeys[0]: data[0][0]}
877
878
879 class SQLServerConnectionManager(ADOConnectionManager):
880    
881     default_isolation = "READ COMMITTED"
882
883
884 class SQLServerSchema(ADOSchema):
885    
886     tableclass = SQLServerTable
887    
888     def create_database(self):
889         conn = self.db.connections._get_conn(master=True)
890         self.db.execute_ddl("CREATE DATABASE %s;" % self.qname, conn)
891         conn.Close()
892         self.clear()
893    
894     def drop_database(self):
895         conn = self.db.connections._get_conn(master=True)
896         self.db.execute_ddl("DROP DATABASE %s;" % self.qname, conn)
897         conn.Close()
898         self.clear()
899    
900     def columnclause(self, column):
901         """Return a clause for the given column for CREATE or ALTER TABLE.
902         
903         This will be of the form:
904             name type [DEFAULT x|IDENTITY(initial, 1) NOT NULL]
905         """
906         dbtype = column.dbtype
907        
908         clause = ""
909         if column.autoincrement:
910             if dbtype not in ("BOOLEAN", "SMALLINT", "INTEGER", "BIGINT"):
911                 raise ValueError("SQL Server does not allow IDENTITY "
912                                  "columns of type %r" % dbtype)
913             clause = " IDENTITY(%s, 1) NOT NULL" % column.initial
914         else:
915             # SQL Server does not allow a column to have
916             # both an IDENTITY clause and a DEFAULT clause.
917             default = column.default or ""
918             if default:
919                 clause = self.db.adaptertosql.coerce(default, dbtype)
920                 clause = " DEFAULT %s" % clause
921        
922         return '%s %s%s' % (column.qname, dbtype, clause)
923
924
925 class SQLServerDatabase(ADODatabase):
926    
927     decompiler = ADOSQLDecompiler_SQLServer
928     adaptertosql = AdapterToADOSQL_SQLServer()
929     typeadapter = TypeAdapter_SQLServer()
930     connectionmanager = SQLServerConnectionManager
931     schemaclass = SQLServerSchema
932    
933     def __init__(self, **kwargs):
934         ADODatabase.__init__(self, **kwargs)
935         if "2005" in self.version():
936             self.connections.isolation_levels.append("SNAPSHOT")
937    
938     def version(self):
939         conn = self.connections._get_conn(master=True)
940         adov = conn.Version
941         data, coldefs = self.fetch("SELECT @@VERSION;", conn)
942         sqlv, = data[0]
943         conn.Close()
944         del conn
945         return "ADO Version: %s\n%s" % (adov, sqlv)
946    
947     def is_timeout_error(self, exc):
948         """If the given exception instance is a lock timeout, return True.
949         
950         This should return True for errors which arise from transaction
951         locking timeouts; for example, if the database prevents 'dirty
952         reads' by raising an error.
953         """
954         # com_error: (-2147352567, 'Exception occurred.',
955         #   (0, 'Microsoft OLE DB Provider for SQL Server',
956         #    'Timeout expired', None, 0, -2147217871), None,
957         #    "UPDATE [testVet] SET [City] = 'Tehachapi' ... ;")
958         if not isinstance(exc, pywintypes.com_error):
959             return False
960         return exc.args[2][5] == -2147217871
961
962
963
964 ###########################################################################
965 ##                                                                       ##
966 ##                             MS Access                                 ##
967 ##                                                                       ##
968 ###########################################################################
969
970
971 class ADOSQLDecompiler_MSAccess(ADOSQLDecompiler):
972     sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in')
973    
974     def _compare_strings(self, op1, op, op2):
975         # ADO comparison operators for strings are case-insensitive.
976         if op < 6:
977             # ('<', '<=', '==', '!=', '>', '>=')
978             # Some operations on strings can be emulated with the
979             # StrComp function. Oddly enough, "StrComp(x, y) op 0"
980             # is the same as "x op y" in most cases.
981             return "StrComp(%s, %s) %s 0" % (op1, op2, self.sql_cmp_op[op])
982         else:
983             return ADOSQLDecompiler._compare_strings(self, op1, op, op2)
984    
985     def dejavu_now(self):
986         return "Now()"
987    
988     def dejavu_today(self):
989         return "DateValue(Now())"
990    
991     def dejavu_year(self, x):
992         return "Year(" + x + ")"
993    
994     def dejavu_month(self, x):
995         return "Month(" + x + ")"
996    
997     def dejavu_day(self, x):
998         return "Day(" + x + ")"
999
1000
1001 class TypeAdapter_MSAccess(adapters.TypeAdapter):
1002     # http://msdn2.microsoft.com/en-us/library/ms714540.aspx
1003     # http://office.microsoft.com/en-us/access/HP010322481033.aspx
1004    
1005     # Hm. Docs say 28/38, but I can't seem to get more than 12 working.
1006     numeric_max_precision = 12
1007     numeric_max_bytes = 6
1008    
1009     def coerce_bool(self, col): return "BIT"
1010    
1011     def coerce_datetime_datetime(self, col): return "DATETIME"
1012     def coerce_datetime_date(self, col): return "DATETIME"
1013     def coerce_datetime_time(self, col): return "DATETIME"
1014    
1015     def int_type(self, bytes):
1016         if bytes <= 2:
1017             return "INTEGER"
1018         elif bytes <= 4:
1019             return "LONG"
1020         else:
1021             # Anything larger than 4 bytes, use decimal/numeric.
1022             return "DECIMAL"
1023    
1024     def coerce_str(self, col):
1025         # The bytes hint shall not reflect the usual 4-byte base for varchar.
1026         bytes = int(col.hints.get('bytes', 255))
1027        
1028         # 255 chars is the upper limit for TEXT / VARCHAR in MS Access.
1029         if bytes == 0 or bytes > 255:
1030             # MEMO is 1 GB max when set programatically (only 64K when set
1031             # in Access UI). But then, 1 GB is the limit for the whole DB.
1032             # Note that OpenSchema will return a DATA_TYPE of "WCHAR".
1033             return "MEMO"
1034        
1035         return "VARCHAR(%s)" % bytes
1036
1037
1038 class AdapterToADOSQL_MSAccess(adapters.AdapterToSQL):
1039     """Coerce Expression constants to ADO SQL."""
1040    
1041     encoding = 'ISO-8859-1'
1042    
1043     escapes = [("'", "''")]
1044     like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"),
1045                     ("?", "[?]"), ("#", "[#]")]
1046    
1047     def coerce_datetime_datetime_to_any(self, value):
1048         return ('#%s/%s/%s %02d:%02d:%02d#' %
1049                 (value.month, value.day, value.year,
1050                  value.hour, value.minute, value.second))
1051    
1052     def coerce_datetime_date_to_any(self, value):
1053         return '#%s/%s/%s#' % (value.month, value.day, value.year)
1054    
1055     def coerce_datetime_time_to_any(self, value):
1056         return '#%02d:%02d:%02d#' % (value.hour, value.minute, value.second)
1057
1058
1059 class MSAccessTable(ADOTable):
1060    
1061     use_asterisk_to_delete_all = True
1062    
1063     def _grab_new_ids(self, idkeys, conn):
1064         data, _ = self.schema.db.fetch("SELECT @@IDENTITY;", conn)
1065         return {idkeys[0]: data[0][0]}
1066
1067
1068 class MSAccessConnectionManager(ADOConnectionManager):
1069    
1070     poolsize = 0
1071     default_isolation = "READ UNCOMMITTED"
1072     isolation_levels = ["READ UNCOMMITTED",]
1073    
1074     def __init__(self, db):
1075         self.transactions = {}
1076         self.db = db
1077         # MS Access can't use a pool, because there doesn't seem
1078         # to be a commit timeout. See http://support.microsoft.com/kb/200300
1079         # for additional synchronization issues.
1080         self._factory = conns.SingleConnection(self._get_conn, self._del_conn)
1081    
1082     def isolate(self, conn, isolation=None):
1083         """Set the isolation level of the given connection.
1084         
1085         If 'isolation' is None, our default_isolation will be used for new
1086         connections. Valid values for the 'isolation' argument may be native
1087         values for your particular database. However, it is recommended you
1088         pass items from the global 'levels' list instead; these will be
1089         automatically replaced with native values.
1090         
1091         For many databases, this must be executed after START TRANSACTION.
1092         """
1093         if isolation is None:
1094             isolation = self.default_isolation
1095        
1096         if isinstance(isolation, _isolation.IsolationLevel):
1097             # Map the given IsolationLevel object to a native value.
1098             isolation = isolation.name
1099             if isolation not in self.isolation_levels:
1100                 raise ValueError("IsolationLevel %r not allowed by %s."
1101                                  % (isolation, self.__class__.__name__))
1102        
1103         # No action to take, since you can't actually set iso level.
1104         pass
1105
1106
1107 class MSAccessSchema(ADOSchema):
1108    
1109     tableclass = MSAccessTable
1110    
1111     def _get_columns(self, tablename, conn=None):
1112         cols = ADOSchema._get_columns(self, tablename, conn)
1113         if conn is None:
1114             conn = self.db.connections._factory()
1115        
1116         try:
1117             # Horrible hack to get autoincrement property
1118             query = "SELECT * FROM %s WHERE FALSE" % self.db.quote(tablename)
1119             bareconn = conn
1120             if hasattr(conn, 'conn'):
1121                 # 'conn' is a ConnectionWrapper object, which .Open
1122                 # won't accept. Pass the unwrapped connection instead.
1123                 bareconn = conn.conn
1124            
1125             # Call conn.Open(query) directly, skipping win32com overhead.
1126             res, rows_affected = conn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
1127                                             ((8, 1), (16396, 18), (3, 49)),
1128                                             # *args =
1129                                             query, pythoncom.Missing, -1)
1130         except pywintypes.com_error, x:
1131             try:
1132                 res.InvokeTypes(*Recordset_Close)
1133             except:
1134                 pass
1135             res = None
1136             x.args += (query, )
1137             conn = None
1138            
1139             try:
1140                 if "no read permission" in x.args[2][2]:
1141                     conn = None
1142                     return []
1143             except IndexError:
1144                 pass
1145            
1146             # "raise x" here or we could get the traceback of the inner try.
1147             raise x
1148        
1149         resFields = res.InvokeTypes(*Recordset_Fields)
1150         for c in cols:
1151             f = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), c.name)
1152             fprops = f.InvokeTypes(*Field_Properties)
1153             fprop = fprops.InvokeTypes(0, 0, 2, (9, 0), ((12, 1), ), "ISAUTOINCREMENT")
1154             c.autoincrement = fprop.InvokeTypes(*Property_Value)
1155        
1156         try:
1157             res.InvokeTypes(*Recordset_Close)
1158         except:
1159             pass
1160         conn = None
1161        
1162         return cols
1163    
1164     def columnclause(self, column):
1165         """Return a clause for the given column for CREATE or ALTER TABLE.
1166         
1167         This will be of the form:
1168             name type [DEFAULT x|AUTOINCREMENT(initial, 1)]
1169         """
1170         dbtype = column.dbtype
1171        
1172         if column.autoincrement:
1173             # MS Access does not allow a column to have
1174             # both an AUTOINCREMENT clause and a DEFAULT clause.
1175             # It also needs no type in this case.
1176             dbtype = "AUTOINCREMENT(%s, 1)" % column.initial
1177         else:
1178             default = column.default or ""
1179             if default:
1180                 defspec = self.db.adaptertosql.coerce(default, dbtype)
1181                 if isinstance(default, (int, long)):
1182                     # Crazy quote hack to get a numeric default to work.
1183                     defspec = "'%s'" % defspec
1184                 dbtype = "%s DEFAULT %s" % (dbtype, defspec)
1185        
1186         return '%s %s' % (column.qname, dbtype)
1187    
1188     def create_database(self):
1189         # By not providing an Engine Type, it defaults to 5 = Access 2000.
1190         cat = win32com.client.Dispatch(r'ADOX.Catalog')
1191         cat.Create(self.db.connections.Connect)
1192         cat.ActiveConnection.Close()
1193         self.clear()
1194    
1195     def drop_database(self):
1196         # Must shut down our only connection to avoid
1197         # "Permission denied" error on os.remove call below.
1198         self.db.connections.shutdown()
1199        
1200         import os
1201         # This should accept relative or absolute paths
1202         if os.path.exists(self.name):
1203             os.remove(self.name)
1204        
1205         self.clear()
1206
1207
1208 class MSAccessDatabase(ADODatabase):
1209    
1210     decompiler = ADOSQLDecompiler_MSAccess
1211     adaptertosql = AdapterToADOSQL_MSAccess()
1212     typeadapter = TypeAdapter_MSAccess()
1213     connectionmanager = MSAccessConnectionManager
1214     schemaclass = MSAccessSchema
1215    
1216     def version(self):
1217         conn = win32com.client.Dispatch(r'ADODB.Connection')
1218         v = conn.Version
1219         del conn
1220         return "ADO Version: %s" % v
1221    
1222     def python_type(self, dbtype):
1223         if dbtype == "LONG":
1224             return int
1225         return ADODatabase.python_type(self, dbtype)
1226
1227
1228 def gen_py():
1229     """Auto generate .py support for ADO 2.7+"""
1230     print 'Please wait while support for ADO 2.7+ is verified...'
1231    
1232     # Microsoft ActiveX Data Objects 2.8 Library
1233     result = win32com.client.gencache.EnsureModule('{2A75196C-D9EB-4129-B803-931327F72D5C}', 0, 2, 8)
1234     if result is not None:
1235         return
1236    
1237     # Microsoft ActiveX Data Objects 2.7 Library
1238     result = win32com.client.gencache.EnsureModule('{EF53050B-882E-4776-B643-EDA472E8E3F2}', 0, 2, 7)
1239     if result is not None:
1240         return
1241    
1242     raise ImportError("ADO 2.7 support could not be imported/cached")
1243
1244
1245 if __name__ == '__main__':
1246     gen_py()
Note: See TracBrowser for help on using the browser.