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