apibase.py 29 KB

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