Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

root/trunk/storage/storeado.py

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

Even more bypassing of win32com in storeado.

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