Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

apibase.py 28KB

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