Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/trunk/geniusql/providers/ado.py

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

Package reorganization.

  • 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 __init__(self, **kwargs):
621         geniusql.Database.__init__(self, **kwargs)
622         self.connections.Connect = self.Connect
623    
624     def python_type(self, dbtype):
625         """Return a Python type which can store values of the given dbtype."""
626         if dbtype in ("DATE", "DBDATE"):
627             return datetime.date
628         elif dbtype == "DBTIME":
629             return datetime.time
630         elif dbtype in ("DATETIME", "DBTIMESTAMP"):
631             return datetime.datetime
632         elif dbtype in ("SMALLINT", "INTEGER", "TINYINT",
633                         "UNSIGNEDTINYINT", "UNSIGNEDSMALLINT",
634                         "UNSIGNEDINT"):
635             return int
636         elif dbtype in ("BIT", "BOOLEAN"):
637             return bool
638         elif dbtype in ("BIGINT", "UNSIGNEDBIGINT", "LONG"):
639             return long
640         elif dbtype in ("SINGLE", "DOUBLE", "DOUBLE PRECISION", "REAL"):
641             return float
642        
643         for t in ("DECIMAL", "NUMERIC", "CURRENCY"):
644             if dbtype.startswith(t):
645                 if typerefs.decimal:
646                     return typerefs.decimal.Decimal
647                 elif typerefs.fixedpoint:
648                     return typerefs.fixedpoint.FixedPoint
649        
650         for t in ("BSTR", "VARIANT", "BINARY", "CHAR", "MEMO", "TEXT",
651                   "VARCHAR", "LONGVARCHAR", "VARBINARY", "LONGVARBINARY"):
652             if dbtype.startswith(t):
653                 return str
654        
655         for t in ("WCHAR", "VARWCHAR", "LONGVARWCHAR"):
656             if dbtype.startswith(t):
657                 return unicode
658        
659         raise TypeError("Database type %r could not be converted "
660                         "to a Python type." % dbtype)
661    
662    
663     #                               Naming                                #
664    
665     def quote(self, name):
666         """Return name, quoted for use in an SQL statement."""
667         return '[' + name + ']'
668    
669     def execute(self, query, conn=None):
670         if conn is None:
671             conn = self.connections.get()
672         if isinstance(query, unicode):
673             query = query.encode(self.adaptertosql.encoding)
674        
675         self.log(repr((id(conn), id(getattr(conn, "conn", None)), query)))
676         try:
677             bareconn = conn
678             if hasattr(conn, 'conn'):
679                 # 'conn' is a ConnectionWrapper object, which .Open
680                 # won't accept. Pass the unwrapped connection instead.
681                 # Note that we CANNOT write "conn = conn.conn", because
682                 # if we called get() above, we'd lose our only
683                 # reference to the wrapper and our weakref callback
684                 # would close the conn before we've executed the SQL.
685                 bareconn = conn.conn
686            
687             # Call Execute directly, skipping win32com overhead.
688             bareconn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
689                                           ((8, 1), (16396, 18), (3, 49)),
690                                           query, pythoncom.Missing, -1)
691         except pywintypes.com_error, x:
692             x.args += (query, )
693             conn = None
694             raise
695    
696     def fetch(self, query, conn=None, schema=False):
697         """fetch(query, conn=None) -> rowdata, columns."""
698         if conn is None:
699             conn = self.connections.get()
700        
701         try:
702             if schema:
703                 # Call OpenSchema(query) directly, skipping win32com overhead.
704                 res = conn._oleobj_.InvokeTypes(19, 0, 1, (9, 0),
705                                                 ((3, 1), (12, 17), (12, 17)),
706                                                 query, Empty, Empty)
707             else:
708                 self.log(repr((id(conn), id(getattr(conn, "conn", None)), query)))
709                 bareconn = conn
710                 if hasattr(conn, 'conn'):
711                     # 'conn' is a ConnectionWrapper object, which .Open
712                     # won't accept. Pass the unwrapped connection instead.
713                     bareconn = conn.conn
714                
715                 # Call conn.Open(query) directly, skipping win32com overhead.
716                 res, rows_affected = bareconn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
717                                                 ((8, 1), (16396, 18), (3, 49)),
718                                                 # *args =
719                                                 query, pythoncom.Missing, -1)
720         except pywintypes.com_error, x:
721             try:
722                 # Close
723                 res.InvokeTypes(*Recordset_Close)
724             except:
725                 pass
726             res = None
727             x.args += (query, )
728             conn = None
729             # "raise x" here or we could get the traceback of the inner try.
730             raise x
731        
732         # Using xrange(Count) is slightly faster than "for x in resFields".
733         resFields = res.InvokeTypes(*Recordset_Fields)
734         fieldcount = resFields.InvokeTypes(*Fields_Count)
735         columns = []
736         for i in xrange(fieldcount):
737             # Wow. Calling this directly (instead of resFields(i))
738             # results in a 29% speedup for a 1-row fetch() of 48 fields.
739             x = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), i)
740            
741             # Wow. Calling these directly (instead of x.Name, x.Type)
742             # results in a 40% speedup for a 1-row fetch() of 48 fields.
743             name = x.InvokeTypes(*Field_Name)
744             typ = x.InvokeTypes(*Field_Type)
745             columns.append((name, typ))
746        
747         data = []
748         if not (res.InvokeTypes(*BOF) and res.InvokeTypes(*EOF)):
749             # We tried .MoveNext() and lots of Fields.Item() calls.
750             # Using GetRows() beats that time by about 2/3.
751             # Inlining GetRows results in a 14% speedup for fetch().
752             data = res.InvokeTypes(*Recordset_GetRows)
753            
754             # Convert cols x rows -> rows x cols
755             data = zip(*data)
756         try:
757             # Close
758             res.InvokeTypes(*Recordset_Close)
759         except:
760             pass
761         conn = None
762        
763         return data, columns
764
765
766
767 ###########################################################################
768 ##                                                                       ##
769 ##                             SQL Server                                ##
770 ##                                                                       ##
771 ###########################################################################
772
773
774 class ADOSQLDecompiler_SQLServer(ADOSQLDecompiler):
775    
776     def _compare_strings(self, op1, op, op2):
777         # ADO comparison operators for strings are case-insensitive.
778         if op < 6:
779             # ('<', '<=', '==', '!=', '>', '>=')
780             # Some operations on strings can be emulated with the
781             # Convert function.
782             return ("Convert(binary, %s) %s Convert(binary, %s)" %
783                     (op1, self.sql_cmp_op[op], op2))
784         else:
785             return ADOSQLDecompiler._compare_strings(self, op1, op, op2)
786
787
788 class AdapterToADOSQL_SQLServer(adapters.AdapterToSQL):
789    
790     encoding = 'ISO-8859-1'
791    
792     escapes = [("'", "''")]
793     like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"),
794                     ("?", "[?]"), ("#", "[#]")]
795    
796     # These are not the same as coerce_bool_to_any (which is used on one side of
797     # a comparison). Instead, these are used when the whole (sub)expression
798     # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3".
799     bool_true = "(1=1)"
800     bool_false = "(1=0)"
801    
802     def coerce_bool_to_any(self, value):
803         if value:
804             return '1'
805         return '0'
806    
807     def cast_VARCHAR_to_int(self, colref):
808         return ("(CASE WHEN ISNUMERIC(%s)=1 THEN CAST(%s AS int) END)"
809                 % (colref, colref))
810
811
812 class TypeAdapter_SQLServer(adapters.TypeAdapter):
813    
814     # Hm. Docs say 38, but I can't seem to get more than 12 working.
815     # They must mean 38 binary digits; math.log(2 ** 38, 10) = 11.4+
816     numeric_max_precision = 12
817     numeric_max_bytes = 6
818    
819     def coerce_bool(self, col):
820         return "BIT"
821    
822     def coerce_datetime_datetime(self, col):
823         return "DATETIME"
824    
825     def coerce_datetime_date(self, col):
826         return "DATETIME"
827    
828     def coerce_datetime_time(self, col):
829         return "DATETIME"
830    
831     def int_type(self, bytes):
832         """Return a datatype which can handle the given number of bytes."""
833         if bytes <= 2:
834             return "SMALLINT"
835         elif bytes <= 4:
836             return "INTEGER"
837         elif bytes <= 8:
838             # BIGINT is usually 8 bytes
839             return "BIGINT"
840         else:
841             # Anything larger than 8 bytes, use decimal/numeric.
842             return "NUMERIC(%s, 0)" % (bytes * 2)
843    
844     def coerce_str(self, col):
845         # The bytes hint does not reflect the usual 4-byte base for varchar.
846         bytes = int(col.hints.get('bytes', 255))
847        
848         if bytes == 0 or bytes > 8000:
849             # Okay, what the @#$%& is wrong with Redmond??!?! We can't even
850             # compare TEXT or NTEXT fields??!? Fine. We'll deny such, and
851             # warn the deployer with less swearing and exclamation points.
852             errors.warn("You have defined a string property without "
853                         "limiting its length. Microsoft SQL Server does "
854                         "not allow comparisons on string fields larger "
855                         "than 8000 characters. Some of your data may be "
856                         "truncated.")
857             bytes = 8000
858        
859         # 8000 *bytes* is the absolute upper limit, based on T_SQL docs for
860         # varchar/varbinary. If there are further fields defined for the
861         # class, or the codepage uses a double-byte character set, we still
862         # might exceed the max size (8060) for a record. We could calc the
863         # total requested record size, and adjust accordingly. Meh.
864         return "VARCHAR(%s)" % bytes
865
866
867 class SQLServerTable(ADOTable):
868    
869     def _rename(self, oldcol, newcol):
870         self.schema.db.execute_ddl("EXEC sp_rename '%s.%s', '%s', 'COLUMN'" %
871                                    (self.name, oldcol.name, newcol.name))
872    
873     def _grab_new_ids(self, idkeys, conn):
874         """Insert a row using the table's SERIAL field."""
875         # For some reason, using SCOPE_IDENTITY or IDENTITY failed (returned
876         # None) when retrieving ID's just after a 99-thread-test ran. Moving
877         # the multithreading test fixed it. IDENT_CURRENT worked regardless.
878         data, _ = self.schema.db.fetch("SELECT IDENT_CURRENT('%s');"
879                                        % self.qname, conn)
880         return {idkeys[0]: data[0][0]}
881
882
883 class SQLServerConnectionManager(ADOConnectionManager):
884    
885     default_isolation = "READ COMMITTED"
886
887
888 class SQLServerSchema(ADOSchema):
889    
890     tableclass = SQLServerTable
891    
892     def create_database(self):
893         conn = self.db.connections._get_conn(master=True)
894         self.db.execute_ddl("CREATE DATABASE %s;" % self.qname, conn)
895         conn.Close()
896         self.clear()
897    
898     def drop_database(self):
899         conn = self.db.connections._get_conn(master=True)
900         self.db.execute_ddl("DROP DATABASE %s;" % self.qname, conn)
901         conn.Close()
902         self.clear()
903    
904     def columnclause(self, column):
905         """Return a clause for the given column for CREATE or ALTER TABLE.
906         
907         This will be of the form:
908             name type [DEFAULT x|IDENTITY(initial, 1) NOT NULL]
909         """
910         dbtype = column.dbtype
911        
912         clause = ""
913         if column.autoincrement:
914             if dbtype not in ("BOOLEAN", "SMALLINT", "INTEGER", "BIGINT"):
915                 raise ValueError("SQL Server does not allow IDENTITY "
916                                  "columns of type %r" % dbtype)
917             clause = " IDENTITY(%s, 1) NOT NULL" % column.initial
918         else:
919             # SQL Server does not allow a column to have
920             # both an IDENTITY clause and a DEFAULT clause.
921             default = column.default or ""
922             if default:
923                 clause = self.db.adaptertosql.coerce(default, dbtype)
924                 clause = " DEFAULT %s" % clause
925        
926         return '%s %s%s' % (column.qname, dbtype, clause)
927
928
929 class SQLServerDatabase(ADODatabase):
930    
931     decompiler = ADOSQLDecompiler_SQLServer
932     adaptertosql = AdapterToADOSQL_SQLServer()
933     typeadapter = TypeAdapter_SQLServer()
934     connectionmanager = SQLServerConnectionManager
935     schemaclass = SQLServerSchema
936    
937     def __init__(self, **kwargs):
938         ADODatabase.__init__(self, **kwargs)
939         if "2005" in self.version():
940             self.connections.isolation_levels.append("SNAPSHOT")
941    
942     def version(self):
943         conn = self.connections._get_conn(master=True)
944         adov = conn.Version
945         data, coldefs = self.fetch("SELECT @@VERSION;", conn)
946         sqlv, = data[0]
947         conn.Close()
948         del conn
949         return "ADO Version: %s\n%s" % (adov, sqlv)
950    
951     def is_timeout_error(self, exc):
952         """If the given exception instance is a lock timeout, return True.
953         
954         This should return True for errors which arise from transaction
955         locking timeouts; for example, if the database prevents 'dirty
956         reads' by raising an error.
957         """
958         # com_error: (-2147352567, 'Exception occurred.',
959         #   (0, 'Microsoft OLE DB Provider for SQL Server',
960         #    'Timeout expired', None, 0, -2147217871), None,
961         #    "UPDATE [testVet] SET [City] = 'Tehachapi' ... ;")
962         if not isinstance(exc, pywintypes.com_error):
963             return False
964         return exc.args[2][5] == -2147217871
965
966
967
968 ###########################################################################
969 ##                                                                       ##
970 ##                             MS Access                                 ##
971 ##                                                                       ##
972 ###########################################################################
973
974
975 class ADOSQLDecompiler_MSAccess(ADOSQLDecompiler):
976     sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in')
977    
978     def _compare_strings(self, op1, op, op2):
979         # ADO comparison operators for strings are case-insensitive.
980         if op < 6:
981             # ('<', '<=', '==', '!=', '>', '>=')
982             # Some operations on strings can be emulated with the
983             # StrComp function. Oddly enough, "StrComp(x, y) op 0"
984             # is the same as "x op y" in most cases.
985             return "StrComp(%s, %s) %s 0" % (op1, op2, self.sql_cmp_op[op])
986         else:
987             return ADOSQLDecompiler._compare_strings(self, op1, op, op2)
988    
989     def dejavu_now(self):
990         return "Now()"
991    
992     def dejavu_today(self):
993         return "DateValue(Now())"
994    
995     def dejavu_year(self, x):
996         return "Year(" + x + ")"
997    
998     def dejavu_month(self, x):
999         return "Month(" + x + ")"
1000    
1001     def dejavu_day(self, x):
1002         return "Day(" + x + ")"
1003
1004
1005 class TypeAdapter_MSAccess(adapters.TypeAdapter):
1006     # http://msdn2.microsoft.com/en-us/library/ms714540.aspx
1007     # http://office.microsoft.com/en-us/access/HP010322481033.aspx
1008    
1009     # Hm. Docs say 28/38, but I can't seem to get more than 12 working.
1010     numeric_max_precision = 12
1011     numeric_max_bytes = 6
1012    
1013     def coerce_bool(self, col): return "BIT"
1014    
1015     def coerce_datetime_datetime(self, col): return "DATETIME"
1016     def coerce_datetime_date(self, col): return "DATETIME"
1017     def coerce_datetime_time(self, col): return "DATETIME"
1018    
1019     def int_type(self, bytes):
1020         if bytes <= 2:
1021             return "INTEGER"
1022         elif bytes <= 4:
1023             return "LONG"
1024         else:
1025             # Anything larger than 4 bytes, use decimal/numeric.
1026             return "DECIMAL"
1027    
1028     def coerce_str(self, col):
1029         # The bytes hint shall not reflect the usual 4-byte base for varchar.
1030         bytes = int(col.hints.get('bytes', 255))
1031        
1032         # 255 chars is the upper limit for TEXT / VARCHAR in MS Access.
1033         if bytes == 0 or bytes > 255:
1034             # MEMO is 1 GB max when set programatically (only 64K when set
1035             # in Access UI). But then, 1 GB is the limit for the whole DB.
1036             # Note that OpenSchema will return a DATA_TYPE of "WCHAR".
1037             return "MEMO"
1038        
1039         return "VARCHAR(%s)" % bytes
1040
1041
1042 class AdapterToADOSQL_MSAccess(adapters.AdapterToSQL):
1043     """Coerce Expression constants to ADO SQL."""
1044    
1045     encoding = 'ISO-8859-1'
1046    
1047     escapes = [("'", "''")]
1048     like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"),
1049                     ("?", "[?]"), ("#", "[#]")]
1050    
1051     def coerce_datetime_datetime_to_any(self, value):
1052         return ('#%s/%s/%s %02d:%02d:%02d#' %
1053                 (value.month, value.day, value.year,
1054                  value.hour, value.minute, value.second))
1055    
1056     def coerce_datetime_date_to_any(self, value):
1057         return '#%s/%s/%s#' % (value.month, value.day, value.year)
1058    
1059     def coerce_datetime_time_to_any(self, value):
1060         return '#%02d:%02d:%02d#' % (value.hour, value.minute, value.second)
1061
1062
1063 class MSAccessTable(ADOTable):
1064    
1065     use_asterisk_to_delete_all = True
1066    
1067     def _grab_new_ids(self, idkeys, conn):
1068         data, _ = self.schema.db.fetch("SELECT @@IDENTITY;", conn)
1069         return {idkeys[0]: data[0][0]}
1070
1071
1072 class MSAccessConnectionManager(ADOConnectionManager):
1073    
1074     poolsize = 0
1075     default_isolation = "READ UNCOMMITTED"
1076     isolation_levels = ["READ UNCOMMITTED",]
1077    
1078     def __init__(self, db, poolsize=0):
1079         self.transactions = {}
1080         self.db = db
1081         self.poolsize = 0
1082         # MS Access can't use a pool, because there doesn't seem
1083         # to be a commit timeout. See http://support.microsoft.com/kb/200300
1084         # for additional synchronization issues.
1085         self._factory = conns.SingleConnection(self._get_conn, self._del_conn)
1086    
1087     def isolate(self, conn, isolation=None):
1088         """Set the isolation level of the given connection.
1089         
1090         If 'isolation' is None, our default_isolation will be used for new
1091         connections. Valid values for the 'isolation' argument may be native
1092         values for your particular database. However, it is recommended you
1093         pass items from the global 'levels' list instead; these will be
1094         automatically replaced with native values.
1095         
1096         For many databases, this must be executed after START TRANSACTION.
1097         """
1098         if isolation is None:
1099             isolation = self.default_isolation
1100        
1101         if isinstance(isolation, _isolation.IsolationLevel):
1102             # Map the given IsolationLevel object to a native value.
1103             isolation = isolation.name
1104             if isolation not in self.isolation_levels:
1105                 raise ValueError("IsolationLevel %r not allowed by %s."
1106                                  % (isolation, self.__class__.__name__))
1107        
1108         # No action to take, since you can't actually set iso level.
1109         pass
1110
1111
1112 class MSAccessSchema(ADOSchema):
1113    
1114     tableclass = MSAccessTable
1115    
1116     def _get_columns(self, tablename, conn=None):
1117         cols = ADOSchema._get_columns(self, tablename, conn)
1118         if conn is None:
1119             conn = self.db.connections._factory()
1120        
1121         try:
1122             # Horrible hack to get autoincrement property
1123             query = "SELECT * FROM %s WHERE FALSE" % self.db.quote(tablename)
1124             bareconn = conn
1125             if hasattr(conn, 'conn'):
1126                 # 'conn' is a ConnectionWrapper object, which .Open
1127                 # won't accept. Pass the unwrapped connection instead.
1128                 bareconn = conn.conn
1129            
1130             # Call conn.Open(query) directly, skipping win32com overhead.
1131             res, rows_affected = conn._oleobj_.InvokeTypes(6, 0, 1, (9, 0),
1132                                             ((8, 1), (16396, 18), (3, 49)),
1133                                             # *args =
1134                                             query, pythoncom.Missing, -1)
1135         except pywintypes.com_error, x:
1136             try:
1137                 res.InvokeTypes(*Recordset_Close)
1138             except:
1139                 pass
1140             res = None
1141             x.args += (query, )
1142             conn = None
1143            
1144             try:
1145                 if "no read permission" in x.args[2][2]:
1146                     conn = None
1147                     return []
1148             except IndexError:
1149                 pass
1150            
1151             # "raise x" here or we could get the traceback of the inner try.
1152             raise x
1153        
1154         resFields = res.InvokeTypes(*Recordset_Fields)
1155         for c in cols:
1156             f = resFields.InvokeTypes(0, 0, 2, (9, 0), ((12, 1),), c.name)
1157             fprops = f.InvokeTypes(*Field_Properties)
1158             fprop = fprops.InvokeTypes(0, 0, 2, (9, 0), ((12, 1), ), "ISAUTOINCREMENT")
1159             c.autoincrement = fprop.InvokeTypes(*Property_Value)
1160        
1161         try:
1162             res.InvokeTypes(*Recordset_Close)
1163         except:
1164             pass
1165         conn = None
1166        
1167         return cols
1168    
1169     def columnclause(self, column):
1170         """Return a clause for the given column for CREATE or ALTER TABLE.
1171         
1172         This will be of the form:
1173             name type [DEFAULT x|AUTOINCREMENT(initial, 1)]
1174         """
1175         dbtype = column.dbtype
1176        
1177         if column.autoincrement:
1178             # MS Access does not allow a column to have
1179             # both an AUTOINCREMENT clause and a DEFAULT clause.
1180             # It also needs no type in this case.
1181             dbtype = "AUTOINCREMENT(%s, 1)" % column.initial
1182         else:
1183             default = column.default or ""
1184             if default:
1185                 defspec = self.db.adaptertosql.coerce(default, dbtype)
1186                 if isinstance(default, (int, long)):
1187                     # Crazy quote hack to get a numeric default to work.
1188                     defspec = "'%s'" % defspec
1189                 dbtype = "%s DEFAULT %s" % (dbtype, defspec)
1190        
1191         return '%s %s' % (column.qname, dbtype)
1192    
1193     def create_database(self):
1194         # By not providing an Engine Type, it defaults to 5 = Access 2000.
1195         cat = win32com.client.Dispatch(r'ADOX.Catalog')
1196         cat.Create(self.db.Connect)
1197         cat.ActiveConnection.Close()
1198         self.clear()
1199    
1200     def drop_database(self):
1201         # Must shut down our only connection to avoid
1202         # "Permission denied" error on os.remove call below.
1203         self.db.connections.shutdown()
1204        
1205         import os
1206         # This should accept relative or absolute paths
1207         if os.path.exists(self.name):
1208             os.remove(self.name)
1209        
1210         self.clear()
1211
1212
1213 class MSAccessDatabase(ADODatabase):
1214    
1215     decompiler = ADOSQLDecompiler_MSAccess
1216     adaptertosql = AdapterToADOSQL_MSAccess()
1217     typeadapter = TypeAdapter_MSAccess()
1218     connectionmanager = MSAccessConnectionManager
1219     schemaclass = MSAccessSchema
1220    
1221     def version(self):
1222         conn = win32com.client.Dispatch(r'ADODB.Connection')
1223         v = conn.Version
1224         del conn
1225         return "ADO Version: %s" % v
1226    
1227     def python_type(self, dbtype):
1228         if dbtype == "LONG":
1229             return int
1230         return ADODatabase.python_type(self, dbtype)
1231
1232
1233 def gen_py():
1234     """Auto generate .py support for ADO 2.7+"""
1235     print 'Please wait while support for ADO 2.7+ is verified...'
1236    
1237     # Microsoft ActiveX Data Objects 2.8 Library
1238     result = win32com.client.gencache.EnsureModule('{2A75196C-D9EB-4129-B803-931327F72D5C}', 0, 2, 8)
1239     if result is not None:
1240         return
1241    
1242     # Microsoft ActiveX Data Objects 2.7 Library
1243     result = win32com.client.gencache.EnsureModule('{EF53050B-882E-4776-B643-EDA472E8E3F2}', 0, 2, 7)
1244     if result is not None:
1245         return
1246    
1247     raise ImportError("ADO 2.7 support could not be imported/cached")
1248
1249
1250 if __name__ == '__main__':
1251     gen_py()
Note: See TracBrowser for help on using the browser.