codekingpro/portable-devtools
114k
1"""adodbapi.apibase - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
2
3Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
4* http://sourceforge.net/projects/pywin32
5* http://sourceforge.net/projects/adodbapi
6"""
7
8import datetime
9import decimal
10import numbers
11import sys
12import time
13
14# noinspection PyUnresolvedReferences
15from . import ado_consts as adc
16
17verbose = False # debugging flag
18
19onIronPython = sys.platform == "cli"
20if onIronPython: # we need type definitions for odd data we may need to convert
21 # noinspection PyUnresolvedReferences
22 from System import DateTime, DBNull
23
24 NullTypes = (type(None), DBNull)
25else:
26 DateTime = type(NotImplemented) # should never be seen on win32
27 NullTypes = type(None)
28
29# --- define objects to smooth out Python3 <-> Python 2.x differences
30unicodeType = str
31longType = int
32StringTypes = str
33makeByteBuffer = bytes
34memoryViewType = memoryview
35_BaseException = Exception
36
37try: # jdhardy -- handle bytes under IronPython & Py3
38 bytes
39except NameError:
40 bytes = str # define it for old Pythons
41
42
43# ------- Error handlers ------
44def standardErrorHandler(connection, cursor, errorclass, errorvalue):
45 err = (errorclass, errorvalue)
46 try:
47 connection.messages.append(err)
48 except:
49 pass
50 if cursor is not None:
51 try:
52 cursor.messages.append(err)
53 except:
54 pass
55 raise errorclass(errorvalue)
56
57
58# Note: _BaseException is defined differently between Python 2.x and 3.x
59class Error(_BaseException):
60 pass # Exception that is the base class of all other error
61 # exceptions. You can use this to catch all errors with one
62 # single 'except' statement. Warnings are not considered
63 # errors and thus should not use this class as base. It must
64 # be a subclass of the Python StandardError (defined in the
65 # module exceptions).
66
67
68class Warning(_BaseException):
69 pass
70
71
72class InterfaceError(Error):
73 pass
74
75
76class DatabaseError(Error):
77 pass
78
79
80class InternalError(DatabaseError):
81 pass
82
83
84class OperationalError(DatabaseError):
85 pass
86
87
88class ProgrammingError(DatabaseError):
89 pass
90
91
92class IntegrityError(DatabaseError):
93 pass
94
95
96class DataError(DatabaseError):
97 pass
98
99
100class NotSupportedError(DatabaseError):
101 pass
102
103
104class FetchFailedError(OperationalError):
105 """
106 Error is used by RawStoredProcedureQuerySet to determine when a fetch
107 failed due to a connection being closed or there is no record set
108 returned. (Non-standard, added especially for django)
109 """
110
111 pass
112
113
114# # # # # ----- Type Objects and Constructors ----- # # # # #
115# Many databases need to have the input in a particular format for binding to an operation's input parameters.
116# For example, if an input is destined for a DATE column, then it must be bound to the database in a particular
117# string format. Similar problems exist for "Row ID" columns or large binary items (e.g. blobs or RAW columns).
118# This presents problems for Python since the parameters to the executeXXX() method are untyped.
119# When the database module sees a Python string object, it doesn't know if it should be bound as a simple CHAR
120# column, as a raw BINARY item, or as a DATE.
121#
122# To overcome this problem, a module must provide the constructors defined below to create objects that can
123# hold special values. When passed to the cursor methods, the module can then detect the proper type of
124# the input parameter and bind it accordingly.
125
126# A Cursor Object's description attribute returns information about each of the result columns of a query.
127# The type_code must compare equal to one of Type Objects defined below. Type Objects may be equal to more than
128# one type code (e.g. DATETIME could be equal to the type codes for date, time and timestamp columns;
129# see the Implementation Hints below for details).
130
131# SQL NULL values are represented by the Python None singleton on input and output.
132
133# Note: Usage of Unix ticks for database interfacing can cause troubles because of the limited date range they cover.
134
135
136# def Date(year,month,day):
137# "This function constructs an object holding a date value. "
138# return dateconverter.date(year,month,day) #dateconverter.Date(year,month,day)
139#
140# def Time(hour,minute,second):
141# "This function constructs an object holding a time value. "
142# return dateconverter.time(hour, minute, second) # dateconverter.Time(hour,minute,second)
143#
144# def Timestamp(year,month,day,hour,minute,second):
145# "This function constructs an object holding a time stamp value. "
146# return dateconverter.datetime(year,month,day,hour,minute,second)
147#
148# def DateFromTicks(ticks):
149# """This function constructs an object holding a date value from the given ticks value
150# (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
151# return Date(*time.gmtime(ticks)[:3])
152#
153# def TimeFromTicks(ticks):
154# """This function constructs an object holding a time value from the given ticks value
155# (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
156# return Time(*time.gmtime(ticks)[3:6])
157#
158# def TimestampFromTicks(ticks):
159# """This function constructs an object holding a time stamp value from the given
160# ticks value (number of seconds since the epoch;
161# see the documentation of the standard Python time module for details). """
162# return Timestamp(*time.gmtime(ticks)[:6])
163#
164# def Binary(aString):
165# """This function constructs an object capable of holding a binary (long) string value. """
166# b = makeByteBuffer(aString)
167# return b
168# ----- Time converters ----------------------------------------------
169class TimeConverter(object): # this is a generic time converter skeleton
170 def __init__(self): # the details will be filled in by instances
171 self._ordinal_1899_12_31 = datetime.date(1899, 12, 31).toordinal() - 1
172 # Use cls.types to compare if an input parameter is a datetime
173 self.types = {
174 type(self.Date(2000, 1, 1)),
175 type(self.Time(12, 1, 1)),
176 type(self.Timestamp(2000, 1, 1, 12, 1, 1)),
177 datetime.datetime,
178 datetime.time,
179 datetime.date,
180 }
181
182 def COMDate(self, obj):
183 """Returns a ComDate from a date-time"""
184 try: # most likely a datetime
185 tt = obj.timetuple()
186
187 try:
188 ms = obj.microsecond
189 except:
190 ms = 0
191 return self.ComDateFromTuple(tt, ms)
192 except: # might be a tuple
193 try:
194 return self.ComDateFromTuple(obj)
195 except: # try an mxdate
196 try:
197 return obj.COMDate()
198 except:
199 raise ValueError('Cannot convert "%s" to COMdate.' % repr(obj))
200
201 def ComDateFromTuple(self, t, microseconds=0):
202 d = datetime.date(t[0], t[1], t[2])
203 integerPart = d.toordinal() - self._ordinal_1899_12_31
204 ms = (t[3] * 3600 + t[4] * 60 + t[5]) * 1000000 + microseconds
205 fractPart = float(ms) / 86400000000.0
206 return integerPart + fractPart
207
208 def DateObjectFromCOMDate(self, comDate):
209 "Returns an object of the wanted type from a ComDate"
210 raise NotImplementedError # "Abstract class"
211
212 def Date(self, year, month, day):
213 "This function constructs an object holding a date value."
214 raise NotImplementedError # "Abstract class"
215
216 def Time(self, hour, minute, second):
217 "This function constructs an object holding a time value."
218 raise NotImplementedError # "Abstract class"
219
220 def Timestamp(self, year, month, day, hour, minute, second):
221 "This function constructs an object holding a time stamp value."
222 raise NotImplementedError # "Abstract class"
223 # all purpose date to ISO format converter
224
225 def DateObjectToIsoFormatString(self, obj):
226 "This function should return a string in the format 'YYYY-MM-dd HH:MM:SS:ms' (ms optional)"
227 try: # most likely, a datetime.datetime
228 s = obj.isoformat(" ")
229 except (TypeError, AttributeError):
230 if isinstance(obj, datetime.date):
231 s = obj.isoformat() + " 00:00:00" # return exact midnight
232 else:
233 try: # maybe it has a strftime method, like mx
234 s = obj.strftime("%Y-%m-%d %H:%M:%S")
235 except AttributeError:
236 try: # but may be time.struct_time
237 s = time.strftime("%Y-%m-%d %H:%M:%S", obj)
238 except:
239 raise ValueError('Cannot convert "%s" to isoformat' % repr(obj))
240 return s
241
242
243# -- Optional: if mx extensions are installed you may use mxDateTime ----
244try:
245 import mx.DateTime
246
247 mxDateTime = True
248except:
249 mxDateTime = False
250if mxDateTime:
251
252 class mxDateTimeConverter(TimeConverter): # used optionally if installed
253 def __init__(self):
254 TimeConverter.__init__(self)
255 self.types.add(type(mx.DateTime))
256
257 def DateObjectFromCOMDate(self, comDate):
258 return mx.DateTime.DateTimeFromCOMDate(comDate)
259
260 def Date(self, year, month, day):
261 return mx.DateTime.Date(year, month, day)
262
263 def Time(self, hour, minute, second):
264 return mx.DateTime.Time(hour, minute, second)
265
266 def Timestamp(self, year, month, day, hour, minute, second):
267 return mx.DateTime.Timestamp(year, month, day, hour, minute, second)
268
269else:
270
271 class mxDateTimeConverter(TimeConverter):
272 pass # if no mx is installed
273
274
275class pythonDateTimeConverter(TimeConverter): # standard since Python 2.3
276 def __init__(self):
277 TimeConverter.__init__(self)
278
279 def DateObjectFromCOMDate(self, comDate):
280 if isinstance(comDate, datetime.datetime):
281 odn = comDate.toordinal()
282 tim = comDate.time()
283 new = datetime.datetime.combine(datetime.datetime.fromordinal(odn), tim)
284 return new
285 # return comDate.replace(tzinfo=None) # make non aware
286 elif isinstance(comDate, DateTime):
287 fComDate = comDate.ToOADate() # ironPython clr Date/Time
288 else:
289 fComDate = float(comDate) # ComDate is number of days since 1899-12-31
290 integerPart = int(fComDate)
291 floatpart = fComDate - integerPart
292 ##if floatpart == 0.0:
293 ## return datetime.date.fromordinal(integerPart + self._ordinal_1899_12_31)
294 dte = datetime.datetime.fromordinal(
295 integerPart + self._ordinal_1899_12_31
296 ) + datetime.timedelta(milliseconds=floatpart * 86400000)
297 # millisecondsperday=86400000 # 24*60*60*1000
298 return dte
299
300 def Date(self, year, month, day):
301 return datetime.date(year, month, day)
302
303 def Time(self, hour, minute, second):
304 return datetime.time(hour, minute, second)
305
306 def Timestamp(self, year, month, day, hour, minute, second):
307 return datetime.datetime(year, month, day, hour, minute, second)
308
309
310class pythonTimeConverter(TimeConverter): # the old, ?nix type date and time
311 def __init__(self): # caution: this Class gets confised by timezones and DST
312 TimeConverter.__init__(self)
313 self.types.add(time.struct_time)
314
315 def DateObjectFromCOMDate(self, comDate):
316 "Returns ticks since 1970"
317 if isinstance(comDate, datetime.datetime):
318 return comDate.timetuple()
319 elif isinstance(comDate, DateTime): # ironPython clr date/time
320 fcomDate = comDate.ToOADate()
321 else:
322 fcomDate = float(comDate)
323 secondsperday = 86400 # 24*60*60
324 # ComDate is number of days since 1899-12-31, gmtime epoch is 1970-1-1 = 25569 days
325 t = time.gmtime(secondsperday * (fcomDate - 25569.0))
326 return t # year,month,day,hour,minute,second,weekday,julianday,daylightsaving=t
327
328 def Date(self, year, month, day):
329 return self.Timestamp(year, month, day, 0, 0, 0)
330
331 def Time(self, hour, minute, second):
332 return time.gmtime((hour * 60 + minute) * 60 + second)
333
334 def Timestamp(self, year, month, day, hour, minute, second):
335 return time.localtime(
336 time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
337 )
338
339
340base_dateconverter = pythonDateTimeConverter()
341
342# ------ DB API required module attributes ---------------------
343threadsafety = 1 # TODO -- find out whether this module is actually BETTER than 1.
344
345apilevel = "2.0" # String constant stating the supported DB API level.
346
347paramstyle = "qmark" # the default parameter style
348
349# ------ control for an extension which may become part of DB API 3.0 ---
350accepted_paramstyles = ("qmark", "named", "format", "pyformat", "dynamic")
351
352# ------------------------------------------------------------------------------------------
353# define similar types for generic conversion routines
354adoIntegerTypes = (
355 adc.adInteger,
356 adc.adSmallInt,
357 adc.adTinyInt,
358 adc.adUnsignedInt,
359 adc.adUnsignedSmallInt,
360 adc.adUnsignedTinyInt,
361 adc.adBoolean,
362 adc.adError,
363) # max 32 bits
364adoRowIdTypes = (adc.adChapter,) # v2.1 Rose
365adoLongTypes = (adc.adBigInt, adc.adFileTime, adc.adUnsignedBigInt)
366adoExactNumericTypes = (
367 adc.adDecimal,
368 adc.adNumeric,
369 adc.adVarNumeric,
370 adc.adCurrency,
371) # v2.3 Cole
372adoApproximateNumericTypes = (adc.adDouble, adc.adSingle) # v2.1 Cole
373adoStringTypes = (
374 adc.adBSTR,
375 adc.adChar,
376 adc.adLongVarChar,
377 adc.adLongVarWChar,
378 adc.adVarChar,
379 adc.adVarWChar,
380 adc.adWChar,
381)
382adoBinaryTypes = (adc.adBinary, adc.adLongVarBinary, adc.adVarBinary)
383adoDateTimeTypes = (adc.adDBTime, adc.adDBTimeStamp, adc.adDate, adc.adDBDate)
384adoRemainingTypes = (
385 adc.adEmpty,
386 adc.adIDispatch,
387 adc.adIUnknown,
388 adc.adPropVariant,
389 adc.adArray,
390 adc.adUserDefined,
391 adc.adVariant,
392 adc.adGUID,
393)
394
395
396# this class is a trick to determine whether a type is a member of a related group of types. see PEP notes
397class DBAPITypeObject(object):
398 def __init__(self, valuesTuple):
399 self.values = frozenset(valuesTuple)
400
401 def __eq__(self, other):
402 return other in self.values
403
404 def __ne__(self, other):
405 return other not in self.values
406
407
408"""This type object is used to describe columns in a database that are string-based (e.g. CHAR). """
409STRING = DBAPITypeObject(adoStringTypes)
410
411"""This type object is used to describe (long) binary columns in a database (e.g. LONG, RAW, BLOBs). """
412BINARY = DBAPITypeObject(adoBinaryTypes)
413
414"""This type object is used to describe numeric columns in a database. """
415NUMBER = DBAPITypeObject(
416 adoIntegerTypes + adoLongTypes + adoExactNumericTypes + adoApproximateNumericTypes
417)
418
419"""This type object is used to describe date/time columns in a database. """
420
421DATETIME = DBAPITypeObject(adoDateTimeTypes)
422"""This type object is used to describe the "Row ID" column in a database. """
423ROWID = DBAPITypeObject(adoRowIdTypes)
424
425OTHER = DBAPITypeObject(adoRemainingTypes)
426
427# ------- utilities for translating python data types to ADO data types ---------------------------------
428typeMap = {
429 memoryViewType: adc.adVarBinary,
430 float: adc.adDouble,
431 type(None): adc.adEmpty,
432 str: adc.adBSTR,
433 bool: adc.adBoolean, # v2.1 Cole
434 decimal.Decimal: adc.adDecimal,
435 int: adc.adBigInt,
436 bytes: adc.adVarBinary,
437}
438
439
440def pyTypeToADOType(d):
441 tp = type(d)
442 try:
443 return typeMap[tp]
444 except KeyError: # The type was not defined in the pre-computed Type table
445 from . import dateconverter
446
447 if (
448 tp in dateconverter.types
449 ): # maybe it is one of our supported Date/Time types
450 return adc.adDate
451 # otherwise, attempt to discern the type by probing the data object itself -- to handle duck typing
452 if isinstance(d, StringTypes):
453 return adc.adBSTR
454 if isinstance(d, numbers.Integral):
455 return adc.adBigInt
456 if isinstance(d, numbers.Real):
457 return adc.adDouble
458 raise DataError('cannot convert "%s" (type=%s) to ADO' % (repr(d), tp))
459
460
461# # # # # # # # # # # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
462# functions to convert database values to Python objects
463# ------------------------------------------------------------------------
464# variant type : function converting variant to Python value
465def variantConvertDate(v):
466 from . import dateconverter # this function only called when adodbapi is running
467
468 return dateconverter.DateObjectFromCOMDate(v)
469
470
471def cvtString(variant): # use to get old action of adodbapi v1 if desired
472 if onIronPython:
473 try:
474 return variant.ToString()
475 except:
476 pass
477 return str(variant)
478
479
480def cvtDecimal(variant): # better name
481 return _convertNumberWithCulture(variant, decimal.Decimal)
482
483
484def cvtNumeric(variant): # older name - don't break old code
485 return cvtDecimal(variant)
486
487
488def cvtFloat(variant):
489 return _convertNumberWithCulture(variant, float)
490
491
492def _convertNumberWithCulture(variant, f):
493 try:
494 return f(variant)
495 except (ValueError, TypeError, decimal.InvalidOperation):
496 try:
497 europeVsUS = str(variant).replace(",", ".")
498 return f(europeVsUS)
499 except (ValueError, TypeError, decimal.InvalidOperation):
500 pass
501
502
503def cvtInt(variant):
504 return int(variant)
505
506
507def cvtLong(variant): # only important in old versions where long and int differ
508 return int(variant)
509
510
511def cvtBuffer(variant):
512 return bytes(variant)
513
514
515def cvtUnicode(variant):
516 return str(variant)
517
518
519def identity(x):
520 return x
521
522
523def cvtUnusual(variant):
524 if verbose > 1:
525 sys.stderr.write("Conversion called for Unusual data=%s\n" % repr(variant))
526 if isinstance(variant, DateTime): # COMdate or System.Date
527 from .adodbapi import ( # this will only be called when adodbapi is in use, and very rarely
528 dateconverter,
529 )
530
531 return dateconverter.DateObjectFromCOMDate(variant)
532 return variant # cannot find conversion function -- just give the data to the user
533
534
535def convert_to_python(variant, func): # convert DB value into Python value
536 if isinstance(variant, NullTypes): # IronPython Null or None
537 return None
538 return func(variant) # call the appropriate conversion function
539
540
541class MultiMap(dict): # builds a dictionary from {(sequence,of,keys) : function}
542 """A dictionary of ado.type : function -- but you can set multiple items by passing a sequence of keys"""
543
544 # useful for defining conversion functions for groups of similar data types.
545 def __init__(self, aDict):
546 for k, v in list(aDict.items()):
547 self[k] = v # we must call __setitem__
548
549 def __setitem__(self, adoType, cvtFn):
550 "set a single item, or a whole sequence of items"
551 try: # user passed us a sequence, set them individually
552 for type in adoType:
553 dict.__setitem__(self, type, cvtFn)
554 except TypeError: # a single value fails attempt to iterate
555 dict.__setitem__(self, adoType, cvtFn)
556
557
558# initialize variantConversions dictionary used to convert SQL to Python
559# this is the dictionary of default conversion functions, built by the class above.
560# this becomes a class attribute for the Connection, and that attribute is used
561# to build the list of column conversion functions for the Cursor
562variantConversions = MultiMap(
563 {
564 adoDateTimeTypes: variantConvertDate,
565 adoApproximateNumericTypes: cvtFloat,
566 adoExactNumericTypes: cvtDecimal, # use to force decimal rather than unicode
567 adoLongTypes: cvtLong,
568 adoIntegerTypes: cvtInt,
569 adoRowIdTypes: cvtInt,
570 adoStringTypes: identity,
571 adoBinaryTypes: cvtBuffer,
572 adoRemainingTypes: cvtUnusual,
573 }
574)
575
576# # # # # classes to emulate the result of cursor.fetchxxx() as a sequence of sequences # # # # #
577# "an ENUM of how my low level records are laid out"
578RS_WIN_32, RS_ARRAY, RS_REMOTE = list(range(1, 4))
579
580
581class SQLrow(object): # a single database row
582 # class to emulate a sequence, so that a column may be retrieved by either number or name
583 def __init__(self, rows, index): # "rows" is an _SQLrows object, index is which row
584 self.rows = rows # parent 'fetch' container object
585 self.index = index # my row number within parent
586
587 def __getattr__(self, name): # used for row.columnName type of value access
588 try:
589 return self._getValue(self.rows.columnNames[name.lower()])
590 except KeyError:
591 raise AttributeError('Unknown column name "{}"'.format(name))
592
593 def _getValue(self, key): # key must be an integer
594 if (
595 self.rows.recordset_format == RS_ARRAY
596 ): # retrieve from two-dimensional array
597 v = self.rows.ado_results[key, self.index]
598 elif self.rows.recordset_format == RS_REMOTE:
599 v = self.rows.ado_results[self.index][key]
600 else: # pywin32 - retrieve from tuple of tuples
601 v = self.rows.ado_results[key][self.index]
602 if self.rows.converters is NotImplemented:
603 return v
604 return convert_to_python(v, self.rows.converters[key])
605
606 def __len__(self):
607 return self.rows.numberOfColumns
608
609 def __getitem__(self, key): # used for row[key] type of value access
610 if isinstance(key, int): # normal row[1] designation
611 try:
612 return self._getValue(key)
613 except IndexError:
614 raise
615 if isinstance(key, slice):
616 indices = key.indices(self.rows.numberOfColumns)
617 vl = [self._getValue(i) for i in range(*indices)]
618 return tuple(vl)
619 try:
620 return self._getValue(
621 self.rows.columnNames[key.lower()]
622 ) # extension row[columnName] designation
623 except (KeyError, TypeError):
624 er, st, tr = sys.exc_info()
625 raise er(
626 'No such key as "%s" in %s' % (repr(key), self.__repr__())
627 ).with_traceback(tr)
628
629 def __iter__(self):
630 return iter(self.__next__())
631
632 def __next__(self):
633 for n in range(self.rows.numberOfColumns):
634 yield self._getValue(n)
635
636 def __repr__(self): # create a human readable representation
637 taglist = sorted(list(self.rows.columnNames.items()), key=lambda x: x[1])
638 s = "<SQLrow={"
639 for name, i in taglist:
640 s += name + ":" + repr(self._getValue(i)) + ", "
641 return s[:-2] + "}>"
642
643 def __str__(self): # create a pretty human readable representation
644 return str(
645 tuple(str(self._getValue(i)) for i in range(self.rows.numberOfColumns))
646 )
647
648 # TO-DO implement pickling an SQLrow directly
649 # def __getstate__(self): return self.__dict__
650 # def __setstate__(self, d): self.__dict__.update(d)
651 # which basically tell pickle to treat your class just like a normal one,
652 # taking self.__dict__ as representing the whole of the instance state,
653 # despite the existence of the __getattr__.
654 # # # #
655
656
657class SQLrows(object):
658 # class to emulate a sequence for multiple rows using a container object
659 def __init__(self, ado_results, numberOfRows, cursor):
660 self.ado_results = ado_results # raw result of SQL get
661 try:
662 self.recordset_format = cursor.recordset_format
663 self.numberOfColumns = cursor.numberOfColumns
664 self.converters = cursor.converters
665 self.columnNames = cursor.columnNames
666 except AttributeError:
667 self.recordset_format = RS_ARRAY
668 self.numberOfColumns = 0
669 self.converters = []
670 self.columnNames = {}
671 self.numberOfRows = numberOfRows
672
673 def __len__(self):
674 return self.numberOfRows
675
676 def __getitem__(self, item): # used for row or row,column access
677 if not self.ado_results:
678 return []
679 if isinstance(item, slice): # will return a list of row objects
680 indices = item.indices(self.numberOfRows)
681 return [SQLrow(self, k) for k in range(*indices)]
682 elif isinstance(item, tuple) and len(item) == 2:
683 # d = some_rowsObject[i,j] will return a datum from a two-dimension address
684 i, j = item
685 if not isinstance(j, int):
686 try:
687 j = self.columnNames[j.lower()] # convert named column to numeric
688 except KeyError:
689 raise KeyError('adodbapi: no such column name as "%s"' % repr(j))
690 if self.recordset_format == RS_ARRAY: # retrieve from two-dimensional array
691 v = self.ado_results[j, i]
692 elif self.recordset_format == RS_REMOTE:
693 v = self.ado_results[i][j]
694 else: # pywin32 - retrieve from tuple of tuples
695 v = self.ado_results[j][i]
696 if self.converters is NotImplemented:
697 return v
698 return convert_to_python(v, self.converters[j])
699 else:
700 row = SQLrow(self, item) # new row descriptor
701 return row
702
703 def __iter__(self):
704 return iter(self.__next__())
705
706 def __next__(self):
707 for n in range(self.numberOfRows):
708 row = SQLrow(self, n)
709 yield row
710 # # # # #
711
712 # # # # # functions to re-format SQL requests to other paramstyle requirements # # # # # # # # # #
713
714
715def changeNamedToQmark(
716 op,
717): # convert from 'named' paramstyle to ADO required '?'mark parameters
718 outOp = ""
719 outparms = []
720 chunks = op.split(
721 "'"
722 ) # quote all literals -- odd numbered list results are literals.
723 inQuotes = False
724 for chunk in chunks:
725 if inQuotes: # this is inside a quote
726 if chunk == "": # double apostrophe to quote one apostrophe
727 outOp = outOp[:-1] # so take one away
728 else:
729 outOp += "'" + chunk + "'" # else pass the quoted string as is.
730 else: # is SQL code -- look for a :namedParameter
731 while chunk: # some SQL string remains
732 sp = chunk.split(":", 1)
733 outOp += sp[0] # concat the part up to the :
734 s = ""
735 try:
736 chunk = sp[1]
737 except IndexError:
738 chunk = None
739 if chunk: # there was a parameter - parse it out
740 i = 0
741 c = chunk[0]
742 while c.isalnum() or c == "_":
743 i += 1
744 try:
745 c = chunk[i]
746 except IndexError:
747 break
748 s = chunk[:i]
749 chunk = chunk[i:]
750 if s:
751 outparms.append(s) # list the parameters in order
752 outOp += "?" # put in the Qmark
753 inQuotes = not inQuotes
754 return outOp, outparms
755
756
757def changeFormatToQmark(
758 op,
759): # convert from 'format' paramstyle to ADO required '?'mark parameters
760 outOp = ""
761 outparams = []
762 chunks = op.split(
763 "'"
764 ) # quote all literals -- odd numbered list results are literals.
765 inQuotes = False
766 for chunk in chunks:
767 if inQuotes:
768 if (
769 outOp != "" and chunk == ""
770 ): # he used a double apostrophe to quote one apostrophe
771 outOp = outOp[:-1] # so take one away
772 else:
773 outOp += "'" + chunk + "'" # else pass the quoted string as is.
774 else: # is SQL code -- look for a %s parameter
775 if "%(" in chunk: # ugh! pyformat!
776 while chunk: # some SQL string remains
777 sp = chunk.split("%(", 1)
778 outOp += sp[0] # concat the part up to the %
779 if len(sp) > 1:
780 try:
781 s, chunk = sp[1].split(")s", 1) # find the ')s'
782 except ValueError:
783 raise ProgrammingError(
784 'Pyformat SQL has incorrect format near "%s"' % chunk
785 )
786 outparams.append(s)
787 outOp += "?" # put in the Qmark
788 else:
789 chunk = None
790 else: # proper '%s' format
791 sp = chunk.split("%s") # make each %s
792 outOp += "?".join(sp) # into ?
793 inQuotes = not inQuotes # every other chunk is a quoted string
794 return outOp, outparams
795 