12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223 |
- """adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
-
- Copyright (C) 2002 Henrik Ekelund, versions 2.1 and later by Vernon Cole
- * http://sourceforge.net/projects/pywin32
- * https://github.com/mhammond/pywin32
- * http://sourceforge.net/projects/adodbapi
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
- django adaptations and refactoring by Adam Vandenberg
-
- DB-API 2.0 specification: http://www.python.org/dev/peps/pep-0249/
-
- This module source should run correctly in CPython versions 2.7 and later,
- or IronPython version 2.7 and later,
- or, after running through 2to3.py, CPython 3.4 or later.
- """
-
- __version__ = "2.6.2.0"
- version = "adodbapi v" + __version__
-
- import copy
- import decimal
- import os
- import sys
- import weakref
-
- from . import ado_consts as adc, apibase as api, process_connect_string
-
- try:
- verbose = int(os.environ["ADODBAPI_VERBOSE"])
- except:
- verbose = False
- if verbose:
- print(version)
-
- # --- define objects to smooth out IronPython <-> CPython differences
- onWin32 = False # assume the worst
- if api.onIronPython:
- from clr import Reference
- from System import (
- Activator,
- Array,
- Byte,
- DateTime,
- DBNull,
- Decimal as SystemDecimal,
- Type,
- )
-
- def Dispatch(dispatch):
- type = Type.GetTypeFromProgID(dispatch)
- return Activator.CreateInstance(type)
-
- def getIndexedValue(obj, index):
- return obj.Item[index]
-
- else: # try pywin32
- try:
- import pythoncom
- import pywintypes
- import win32com.client
-
- onWin32 = True
-
- def Dispatch(dispatch):
- return win32com.client.Dispatch(dispatch)
-
- except ImportError:
- import warnings
-
- warnings.warn(
- "pywin32 package (or IronPython) required for adodbapi.", ImportWarning
- )
-
- def getIndexedValue(obj, index):
- return obj(index)
-
-
- from collections.abc import Mapping
-
- # --- define objects to smooth out Python3000 <-> Python 2.x differences
- unicodeType = str
- longType = int
- StringTypes = str
- maxint = sys.maxsize
-
-
- # ----------------- The .connect method -----------------
- def make_COM_connecter():
- try:
- if onWin32:
- pythoncom.CoInitialize() # v2.1 Paj
- c = Dispatch("ADODB.Connection") # connect _after_ CoIninialize v2.1.1 adamvan
- except:
- raise api.InterfaceError(
- "Windows COM Error: Dispatch('ADODB.Connection') failed."
- )
- return c
-
-
- def connect(*args, **kwargs): # --> a db-api connection object
- """Connect to a database.
-
- call using:
- :connection_string -- An ADODB formatted connection string, see:
- * http://www.connectionstrings.com
- * http://www.asp101.com/articles/john/connstring/default.asp
- :timeout -- A command timeout value, in seconds (default 30 seconds)
- """
- co = Connection() # make an empty connection object
-
- kwargs = process_connect_string.process(args, kwargs, True)
-
- try: # connect to the database, using the connection information in kwargs
- co.connect(kwargs)
- return co
- except Exception as e:
- message = 'Error opening connection to "%s"' % co.connection_string
- raise api.OperationalError(e, message)
-
-
- # so you could use something like:
- # myConnection.paramstyle = 'named'
- # The programmer may also change the default.
- # For example, if I were using django, I would say:
- # import adodbapi as Database
- # Database.adodbapi.paramstyle = 'format'
-
- # ------- other module level defaults --------
- defaultIsolationLevel = adc.adXactReadCommitted
- # Set defaultIsolationLevel on module level before creating the connection.
- # For example:
- # import adodbapi, ado_consts
- # adodbapi.adodbapi.defaultIsolationLevel=ado_consts.adXactBrowse"
- #
- # Set defaultCursorLocation on module level before creating the connection.
- # It may be one of the "adUse..." consts.
- defaultCursorLocation = adc.adUseClient # changed from adUseServer as of v 2.3.0
-
- dateconverter = api.pythonDateTimeConverter() # default
-
-
- def format_parameters(ADOparameters, show_value=False):
- """Format a collection of ADO Command Parameters.
-
- Used by error reporting in _execute_command.
- """
- try:
- if show_value:
- desc = [
- 'Name: %s, Dir.: %s, Type: %s, Size: %s, Value: "%s", Precision: %s, NumericScale: %s'
- % (
- p.Name,
- adc.directions[p.Direction],
- adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
- p.Size,
- p.Value,
- p.Precision,
- p.NumericScale,
- )
- for p in ADOparameters
- ]
- else:
- desc = [
- "Name: %s, Dir.: %s, Type: %s, Size: %s, Precision: %s, NumericScale: %s"
- % (
- p.Name,
- adc.directions[p.Direction],
- adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
- p.Size,
- p.Precision,
- p.NumericScale,
- )
- for p in ADOparameters
- ]
- return "[" + "\n".join(desc) + "]"
- except:
- return "[]"
-
-
- def _configure_parameter(p, value, adotype, settings_known):
- """Configure the given ADO Parameter 'p' with the Python 'value'."""
-
- if adotype in api.adoBinaryTypes:
- p.Size = len(value)
- p.AppendChunk(value)
-
- elif isinstance(value, StringTypes): # v2.1 Jevon
- L = len(value)
- if adotype in api.adoStringTypes: # v2.2.1 Cole
- if settings_known:
- L = min(L, p.Size) # v2.1 Cole limit data to defined size
- p.Value = value[:L] # v2.1 Jevon & v2.1 Cole
- else:
- p.Value = value # dont limit if db column is numeric
- if L > 0: # v2.1 Cole something does not like p.Size as Zero
- p.Size = L # v2.1 Jevon
-
- elif isinstance(value, decimal.Decimal):
- if api.onIronPython:
- s = str(value)
- p.Value = s
- p.Size = len(s)
- else:
- p.Value = value
- exponent = value.as_tuple()[2]
- digit_count = len(value.as_tuple()[1])
- p.Precision = digit_count
- if exponent == 0:
- p.NumericScale = 0
- elif exponent < 0:
- p.NumericScale = -exponent
- if p.Precision < p.NumericScale:
- p.Precision = p.NumericScale
- else: # exponent > 0:
- p.NumericScale = 0
- p.Precision = digit_count + exponent
-
- elif type(value) in dateconverter.types:
- if settings_known and adotype in api.adoDateTimeTypes:
- p.Value = dateconverter.COMDate(value)
- else: # probably a string
- # provide the date as a string in the format 'YYYY-MM-dd'
- s = dateconverter.DateObjectToIsoFormatString(value)
- p.Value = s
- p.Size = len(s)
-
- elif api.onIronPython and isinstance(value, longType): # Iron Python Long
- s = str(value) # feature workaround for IPy 2.0
- p.Value = s
-
- elif adotype == adc.adEmpty: # ADO will not let you specify a null column
- p.Type = (
- adc.adInteger
- ) # so we will fake it to be an integer (just to have something)
- p.Value = None # and pass in a Null *value*
-
- # For any other type, set the value and let pythoncom do the right thing.
- else:
- p.Value = value
-
-
- # # # # # ----- the Class that defines a connection ----- # # # # #
- class Connection(object):
- # include connection attributes as class attributes required by api definition.
- Warning = api.Warning
- Error = api.Error
- InterfaceError = api.InterfaceError
- DataError = api.DataError
- DatabaseError = api.DatabaseError
- OperationalError = api.OperationalError
- IntegrityError = api.IntegrityError
- InternalError = api.InternalError
- NotSupportedError = api.NotSupportedError
- ProgrammingError = api.ProgrammingError
- FetchFailedError = api.FetchFailedError # (special for django)
- # ...class attributes... (can be overridden by instance attributes)
- verbose = api.verbose
-
- @property
- def dbapi(self): # a proposed db-api version 3 extension.
- "Return a reference to the DBAPI module for this Connection."
- return api
-
- def __init__(self): # now define the instance attributes
- self.connector = None
- self.paramstyle = api.paramstyle
- self.supportsTransactions = False
- self.connection_string = ""
- self.cursors = weakref.WeakValueDictionary()
- self.dbms_name = ""
- self.dbms_version = ""
- self.errorhandler = None # use the standard error handler for this instance
- self.transaction_level = 0 # 0 == Not in a transaction, at the top level
- self._autocommit = False
-
- def connect(self, kwargs, connection_maker=make_COM_connecter):
- if verbose > 9:
- print("kwargs=", repr(kwargs))
- try:
- self.connection_string = (
- kwargs["connection_string"] % kwargs
- ) # insert keyword arguments
- except Exception as e:
- self._raiseConnectionError(
- KeyError, "Python string format error in connection string->"
- )
- self.timeout = kwargs.get("timeout", 30)
- self.mode = kwargs.get("mode", adc.adModeUnknown)
- self.kwargs = kwargs
- if verbose:
- print('%s attempting: "%s"' % (version, self.connection_string))
- self.connector = connection_maker()
- self.connector.ConnectionTimeout = self.timeout
- self.connector.ConnectionString = self.connection_string
- self.connector.Mode = self.mode
-
- try:
- self.connector.Open() # Open the ADO connection
- except api.Error:
- self._raiseConnectionError(
- api.DatabaseError,
- "ADO error trying to Open=%s" % self.connection_string,
- )
-
- try: # Stefan Fuchs; support WINCCOLEDBProvider
- if getIndexedValue(self.connector.Properties, "Transaction DDL").Value != 0:
- self.supportsTransactions = True
- except pywintypes.com_error:
- pass # Stefan Fuchs
- self.dbms_name = getIndexedValue(self.connector.Properties, "DBMS Name").Value
- try: # Stefan Fuchs
- self.dbms_version = getIndexedValue(
- self.connector.Properties, "DBMS Version"
- ).Value
- except pywintypes.com_error:
- pass # Stefan Fuchs
- self.connector.CursorLocation = defaultCursorLocation # v2.1 Rose
- if self.supportsTransactions:
- self.connector.IsolationLevel = defaultIsolationLevel
- self._autocommit = bool(kwargs.get("autocommit", False))
- if not self._autocommit:
- self.transaction_level = (
- self.connector.BeginTrans()
- ) # Disables autocommit & inits transaction_level
- else:
- self._autocommit = True
- if "paramstyle" in kwargs:
- self.paramstyle = kwargs["paramstyle"] # let setattr do the error checking
- self.messages = []
- if verbose:
- print("adodbapi New connection at %X" % id(self))
-
- def _raiseConnectionError(self, errorclass, errorvalue):
- eh = self.errorhandler
- if eh is None:
- eh = api.standardErrorHandler
- eh(self, None, errorclass, errorvalue)
-
- def _closeAdoConnection(self): # all v2.1 Rose
- """close the underlying ADO Connection object,
- rolling it back first if it supports transactions."""
- if self.connector is None:
- return
- if not self._autocommit:
- if self.transaction_level:
- try:
- self.connector.RollbackTrans()
- except:
- pass
- self.connector.Close()
- if verbose:
- print("adodbapi Closed connection at %X" % id(self))
-
- def close(self):
- """Close the connection now (rather than whenever __del__ is called).
-
- The connection will be unusable from this point forward;
- an Error (or subclass) exception will be raised if any operation is attempted with the connection.
- The same applies to all cursor objects trying to use the connection.
- """
- for crsr in list(self.cursors.values())[
- :
- ]: # copy the list, then close each one
- crsr.close(dont_tell_me=True) # close without back-link clearing
- self.messages = []
- try:
- self._closeAdoConnection() # v2.1 Rose
- except Exception as e:
- self._raiseConnectionError(sys.exc_info()[0], sys.exc_info()[1])
-
- self.connector = None # v2.4.2.2 fix subtle timeout bug
- # per M.Hammond: "I expect the benefits of uninitializing are probably fairly small,
- # so never uninitializing will probably not cause any problems."
-
- def commit(self):
- """Commit any pending transaction to the database.
-
- Note that if the database supports an auto-commit feature,
- this must be initially off. An interface method may be provided to turn it back on.
- Database modules that do not support transactions should implement this method with void functionality.
- """
- self.messages = []
- if not self.supportsTransactions:
- return
-
- try:
- self.transaction_level = self.connector.CommitTrans()
- if verbose > 1:
- print("commit done on connection at %X" % id(self))
- if not (
- self._autocommit
- or (self.connector.Attributes & adc.adXactAbortRetaining)
- ):
- # If attributes has adXactCommitRetaining it performs retaining commits that is,
- # calling CommitTrans automatically starts a new transaction. Not all providers support this.
- # If not, we will have to start a new transaction by this command:
- self.transaction_level = self.connector.BeginTrans()
- except Exception as e:
- self._raiseConnectionError(api.ProgrammingError, e)
-
- def _rollback(self):
- """In case a database does provide transactions this method causes the the database to roll back to
- the start of any pending transaction. Closing a connection without committing the changes first will
- cause an implicit rollback to be performed.
-
- If the database does not support the functionality required by the method, the interface should
- throw an exception in case the method is used.
- The preferred approach is to not implement the method and thus have Python generate
- an AttributeError in case the method is requested. This allows the programmer to check for database
- capabilities using the standard hasattr() function.
-
- For some dynamically configured interfaces it may not be appropriate to require dynamically making
- the method available. These interfaces should then raise a NotSupportedError to indicate the
- non-ability to perform the roll back when the method is invoked.
- """
- self.messages = []
- if (
- self.transaction_level
- ): # trying to roll back with no open transaction causes an error
- try:
- self.transaction_level = self.connector.RollbackTrans()
- if verbose > 1:
- print("rollback done on connection at %X" % id(self))
- if not self._autocommit and not (
- self.connector.Attributes & adc.adXactAbortRetaining
- ):
- # If attributes has adXactAbortRetaining it performs retaining aborts that is,
- # calling RollbackTrans automatically starts a new transaction. Not all providers support this.
- # If not, we will have to start a new transaction by this command:
- if (
- not self.transaction_level
- ): # if self.transaction_level == 0 or self.transaction_level is None:
- self.transaction_level = self.connector.BeginTrans()
- except Exception as e:
- self._raiseConnectionError(api.ProgrammingError, e)
-
- def __setattr__(self, name, value):
- if name == "autocommit": # extension: allow user to turn autocommit on or off
- if self.supportsTransactions:
- object.__setattr__(self, "_autocommit", bool(value))
- try:
- self._rollback() # must clear any outstanding transactions
- except:
- pass
- return
- elif name == "paramstyle":
- if value not in api.accepted_paramstyles:
- self._raiseConnectionError(
- api.NotSupportedError,
- 'paramstyle="%s" not in:%s'
- % (value, repr(api.accepted_paramstyles)),
- )
- elif name == "variantConversions":
- value = copy.copy(
- value
- ) # make a new copy -- no changes in the default, please
- object.__setattr__(self, name, value)
-
- def __getattr__(self, item):
- if (
- item == "rollback"
- ): # the rollback method only appears if the database supports transactions
- if self.supportsTransactions:
- return (
- self._rollback
- ) # return the rollback method so the caller can execute it.
- else:
- raise AttributeError("this data provider does not support Rollback")
- elif item == "autocommit":
- return self._autocommit
- else:
- raise AttributeError(
- 'no such attribute in ADO connection object as="%s"' % item
- )
-
- def cursor(self):
- "Return a new Cursor Object using the connection."
- self.messages = []
- c = Cursor(self)
- return c
-
- def _i_am_here(self, crsr):
- "message from a new cursor proclaiming its existence"
- oid = id(crsr)
- self.cursors[oid] = crsr
-
- def _i_am_closing(self, crsr):
- "message from a cursor giving connection a chance to clean up"
- try:
- del self.cursors[id(crsr)]
- except:
- pass
-
- def printADOerrors(self):
- j = self.connector.Errors.Count
- if j:
- print("ADO Errors:(%i)" % j)
- for e in self.connector.Errors:
- print("Description: %s" % e.Description)
- print("Error: %s %s " % (e.Number, adc.adoErrors.get(e.Number, "unknown")))
- if e.Number == adc.ado_error_TIMEOUT:
- print(
- "Timeout Error: Try using adodbpi.connect(constr,timeout=Nseconds)"
- )
- print("Source: %s" % e.Source)
- print("NativeError: %s" % e.NativeError)
- print("SQL State: %s" % e.SQLState)
-
- def _suggest_error_class(self):
- """Introspect the current ADO Errors and determine an appropriate error class.
-
- Error.SQLState is a SQL-defined error condition, per the SQL specification:
- http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
-
- The 23000 class of errors are integrity errors.
- Error 40002 is a transactional integrity error.
- """
- if self.connector is not None:
- for e in self.connector.Errors:
- state = str(e.SQLState)
- if state.startswith("23") or state == "40002":
- return api.IntegrityError
- return api.DatabaseError
-
- def __del__(self):
- try:
- self._closeAdoConnection() # v2.1 Rose
- except:
- pass
- self.connector = None
-
- def __enter__(self): # Connections are context managers
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if exc_type:
- self._rollback() # automatic rollback on errors
- else:
- self.commit()
-
- def get_table_names(self):
- schema = self.connector.OpenSchema(20) # constant = adSchemaTables
-
- tables = []
- while not schema.EOF:
- name = getIndexedValue(schema.Fields, "TABLE_NAME").Value
- tables.append(name)
- schema.MoveNext()
- del schema
- return tables
-
-
- # # # # # ----- the Class that defines a cursor ----- # # # # #
- class Cursor(object):
- ## ** api required attributes:
- ## description...
- ## This read-only attribute is a sequence of 7-item sequences.
- ## Each of these sequences contains information describing one result column:
- ## (name, type_code, display_size, internal_size, precision, scale, null_ok).
- ## This attribute will be None for operations that do not return rows or if the
- ## cursor has not had an operation invoked via the executeXXX() method yet.
- ## The type_code can be interpreted by comparing it to the Type Objects specified in the section below.
- ## rowcount...
- ## This read-only attribute specifies the number of rows that the last executeXXX() produced
- ## (for DQL statements like select) or affected (for DML statements like update or insert).
- ## The attribute is -1 in case no executeXXX() has been performed on the cursor or
- ## the rowcount of the last operation is not determinable by the interface.[7]
- ## arraysize...
- ## This read/write attribute specifies the number of rows to fetch at a time with fetchmany().
- ## It defaults to 1 meaning to fetch a single row at a time.
- ## Implementations must observe this value with respect to the fetchmany() method,
- ## but are free to interact with the database a single row at a time.
- ## It may also be used in the implementation of executemany().
- ## ** extension attributes:
- ## paramstyle...
- ## allows the programmer to override the connection's default paramstyle
- ## errorhandler...
- ## allows the programmer to override the connection's default error handler
-
- def __init__(self, connection):
- self.command = None
- self._ado_prepared = False
- self.messages = []
- self.connection = connection
- self.paramstyle = connection.paramstyle # used for overriding the paramstyle
- self._parameter_names = []
- self.recordset_is_remote = False
- self.rs = None # the ADO recordset for this cursor
- self.converters = [] # conversion function for each column
- self.columnNames = {} # names of columns {lowercase name : number,...}
- self.numberOfColumns = 0
- self._description = None
- self.rowcount = -1
- self.errorhandler = connection.errorhandler
- self.arraysize = 1
- connection._i_am_here(self)
- if verbose:
- print(
- "%s New cursor at %X on conn %X"
- % (version, id(self), id(self.connection))
- )
-
- def __iter__(self): # [2.1 Zamarev]
- return iter(self.fetchone, None) # [2.1 Zamarev]
-
- def prepare(self, operation):
- self.command = operation
- self._description = None
- self._ado_prepared = "setup"
-
- def __next__(self):
- r = self.fetchone()
- if r:
- return r
- raise StopIteration
-
- def __enter__(self):
- "Allow database cursors to be used with context managers."
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- "Allow database cursors to be used with context managers."
- self.close()
-
- def _raiseCursorError(self, errorclass, errorvalue):
- eh = self.errorhandler
- if eh is None:
- eh = api.standardErrorHandler
- eh(self.connection, self, errorclass, errorvalue)
-
- def build_column_info(self, recordset):
- self.converters = [] # convertion function for each column
- self.columnNames = {} # names of columns {lowercase name : number,...}
- self._description = None
-
- # if EOF and BOF are true at the same time, there are no records in the recordset
- if (recordset is None) or (recordset.State == adc.adStateClosed):
- self.rs = None
- self.numberOfColumns = 0
- return
- self.rs = recordset # v2.1.1 bkline
- self.recordset_format = api.RS_ARRAY if api.onIronPython else api.RS_WIN_32
- self.numberOfColumns = recordset.Fields.Count
- try:
- varCon = self.connection.variantConversions
- except AttributeError:
- varCon = api.variantConversions
- for i in range(self.numberOfColumns):
- f = getIndexedValue(self.rs.Fields, i)
- try:
- self.converters.append(
- varCon[f.Type]
- ) # conversion function for this column
- except KeyError:
- self._raiseCursorError(
- api.InternalError, "Data column of Unknown ADO type=%s" % f.Type
- )
- self.columnNames[f.Name.lower()] = i # columnNames lookup
-
- def _makeDescriptionFromRS(self):
- # Abort if closed or no recordset.
- if self.rs is None:
- self._description = None
- return
- desc = []
- for i in range(self.numberOfColumns):
- f = getIndexedValue(self.rs.Fields, i)
- if self.rs.EOF or self.rs.BOF:
- display_size = None
- else:
- display_size = (
- f.ActualSize
- ) # TODO: Is this the correct defintion according to the DB API 2 Spec ?
- null_ok = bool(f.Attributes & adc.adFldMayBeNull) # v2.1 Cole
- desc.append(
- (
- f.Name,
- f.Type,
- display_size,
- f.DefinedSize,
- f.Precision,
- f.NumericScale,
- null_ok,
- )
- )
- self._description = desc
-
- def get_description(self):
- if not self._description:
- self._makeDescriptionFromRS()
- return self._description
-
- def __getattr__(self, item):
- if item == "description":
- return self.get_description()
- object.__getattribute__(
- self, item
- ) # may get here on Remote attribute calls for existing attributes
-
- def format_description(self, d):
- """Format db_api description tuple for printing."""
- if self.description is None:
- self._makeDescriptionFromRS()
- if isinstance(d, int):
- d = self.description[d]
- desc = (
- "Name= %s, Type= %s, DispSize= %s, IntSize= %s, Precision= %s, Scale= %s NullOK=%s"
- % (
- d[0],
- adc.adTypeNames.get(d[1], str(d[1]) + " (unknown type)"),
- d[2],
- d[3],
- d[4],
- d[5],
- d[6],
- )
- )
- return desc
-
- def close(self, dont_tell_me=False):
- """Close the cursor now (rather than whenever __del__ is called).
- The cursor will be unusable from this point forward; an Error (or subclass)
- exception will be raised if any operation is attempted with the cursor.
- """
- if self.connection is None:
- return
- self.messages = []
- if (
- self.rs and self.rs.State != adc.adStateClosed
- ): # rs exists and is open #v2.1 Rose
- self.rs.Close() # v2.1 Rose
- self.rs = None # let go of the recordset so ADO will let it be disposed #v2.1 Rose
- if not dont_tell_me:
- self.connection._i_am_closing(
- self
- ) # take me off the connection's cursors list
- self.connection = (
- None # this will make all future method calls on me throw an exception
- )
- if verbose:
- print("adodbapi Closed cursor at %X" % id(self))
-
- def __del__(self):
- try:
- self.close()
- except:
- pass
-
- def _new_command(self, command_type=adc.adCmdText):
- self.cmd = None
- self.messages = []
-
- if self.connection is None:
- self._raiseCursorError(api.InterfaceError, None)
- return
- try:
- self.cmd = Dispatch("ADODB.Command")
- self.cmd.ActiveConnection = self.connection.connector
- self.cmd.CommandTimeout = self.connection.timeout
- self.cmd.CommandType = command_type
- self.cmd.CommandText = self.commandText
- self.cmd.Prepared = bool(self._ado_prepared)
- except:
- self._raiseCursorError(
- api.DatabaseError,
- 'Error creating new ADODB.Command object for "%s"'
- % repr(self.commandText),
- )
-
- def _execute_command(self):
- # Stored procedures may have an integer return value
- self.return_value = None
- recordset = None
- count = -1 # default value
- if verbose:
- print('Executing command="%s"' % self.commandText)
- try:
- # ----- the actual SQL is executed here ---
- if api.onIronPython:
- ra = Reference[int]()
- recordset = self.cmd.Execute(ra)
- count = ra.Value
- else: # pywin32
- recordset, count = self.cmd.Execute()
- # ----- ------------------------------- ---
- except Exception as e:
- _message = ""
- if hasattr(e, "args"):
- _message += str(e.args) + "\n"
- _message += "Command:\n%s\nParameters:\n%s" % (
- self.commandText,
- format_parameters(self.cmd.Parameters, True),
- )
- klass = self.connection._suggest_error_class()
- self._raiseCursorError(klass, _message)
- try:
- self.rowcount = recordset.RecordCount
- except:
- self.rowcount = count
- self.build_column_info(recordset)
-
- # The ADO documentation hints that obtaining the recordcount may be timeconsuming
- # "If the Recordset object does not support approximate positioning, this property
- # may be a significant drain on resources # [ekelund]
- # Therefore, COM will not return rowcount for server-side cursors. [Cole]
- # Client-side cursors (the default since v2.8) will force a static
- # cursor, and rowcount will then be set accurately [Cole]
-
- def get_rowcount(self):
- return self.rowcount
-
- def get_returned_parameters(self):
- """with some providers, returned parameters and the .return_value are not available until
- after the last recordset has been read. In that case, you must coll nextset() until it
- returns None, then call this method to get your returned information."""
-
- retLst = (
- []
- ) # store procedures may return altered parameters, including an added "return value" item
- for p in tuple(self.cmd.Parameters):
- if verbose > 2:
- print(
- 'Returned=Name: %s, Dir.: %s, Type: %s, Size: %s, Value: "%s",'
- " Precision: %s, NumericScale: %s"
- % (
- p.Name,
- adc.directions[p.Direction],
- adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
- p.Size,
- p.Value,
- p.Precision,
- p.NumericScale,
- )
- )
- pyObject = api.convert_to_python(p.Value, api.variantConversions[p.Type])
- if p.Direction == adc.adParamReturnValue:
- self.returnValue = (
- pyObject # also load the undocumented attribute (Vernon's Error!)
- )
- self.return_value = pyObject
- else:
- retLst.append(pyObject)
- return retLst # return the parameter list to the caller
-
- def callproc(self, procname, parameters=None):
- """Call a stored database procedure with the given name.
- The sequence of parameters must contain one entry for each
- argument that the sproc expects. The result of the
- call is returned as modified copy of the input
- sequence. Input parameters are left untouched, output and
- input/output parameters replaced with possibly new values.
-
- The sproc may also provide a result set as output,
- which is available through the standard .fetch*() methods.
- Extension: A "return_value" property may be set on the
- cursor if the sproc defines an integer return value.
- """
- self._parameter_names = []
- self.commandText = procname
- self._new_command(command_type=adc.adCmdStoredProc)
- self._buildADOparameterList(parameters, sproc=True)
- if verbose > 2:
- print(
- "Calling Stored Proc with Params=",
- format_parameters(self.cmd.Parameters, True),
- )
- self._execute_command()
- return self.get_returned_parameters()
-
- def _reformat_operation(self, operation, parameters):
- if self.paramstyle in ("format", "pyformat"): # convert %s to ?
- operation, self._parameter_names = api.changeFormatToQmark(operation)
- elif self.paramstyle == "named" or (
- self.paramstyle == "dynamic" and isinstance(parameters, Mapping)
- ):
- operation, self._parameter_names = api.changeNamedToQmark(
- operation
- ) # convert :name to ?
- return operation
-
- def _buildADOparameterList(self, parameters, sproc=False):
- self.parameters = parameters
- if parameters is None:
- parameters = []
-
- # Note: ADO does not preserve the parameter list, even if "Prepared" is True, so we must build every time.
- parameters_known = False
- if sproc: # needed only if we are calling a stored procedure
- try: # attempt to use ADO's parameter list
- self.cmd.Parameters.Refresh()
- if verbose > 2:
- print(
- "ADO detected Params=",
- format_parameters(self.cmd.Parameters, True),
- )
- print("Program Parameters=", repr(parameters))
- parameters_known = True
- except api.Error:
- if verbose:
- print("ADO Parameter Refresh failed")
- pass
- else:
- if len(parameters) != self.cmd.Parameters.Count - 1:
- raise api.ProgrammingError(
- "You must supply %d parameters for this stored procedure"
- % (self.cmd.Parameters.Count - 1)
- )
- if sproc or parameters != []:
- i = 0
- if parameters_known: # use ado parameter list
- if self._parameter_names: # named parameters
- for i, pm_name in enumerate(self._parameter_names):
- p = getIndexedValue(self.cmd.Parameters, i)
- try:
- _configure_parameter(
- p, parameters[pm_name], p.Type, parameters_known
- )
- except Exception as e:
- _message = (
- "Error Converting Parameter %s: %s, %s <- %s\n"
- % (
- p.Name,
- adc.ado_type_name(p.Type),
- p.Value,
- repr(parameters[pm_name]),
- )
- )
- self._raiseCursorError(
- api.DataError, _message + "->" + repr(e.args)
- )
- else: # regular sequence of parameters
- for value in parameters:
- p = getIndexedValue(self.cmd.Parameters, i)
- if (
- p.Direction == adc.adParamReturnValue
- ): # this is an extra parameter added by ADO
- i += 1 # skip the extra
- p = getIndexedValue(self.cmd.Parameters, i)
- try:
- _configure_parameter(p, value, p.Type, parameters_known)
- except Exception as e:
- _message = (
- "Error Converting Parameter %s: %s, %s <- %s\n"
- % (
- p.Name,
- adc.ado_type_name(p.Type),
- p.Value,
- repr(value),
- )
- )
- self._raiseCursorError(
- api.DataError, _message + "->" + repr(e.args)
- )
- i += 1
- else: # -- build own parameter list
- if (
- self._parameter_names
- ): # we expect a dictionary of parameters, this is the list of expected names
- for parm_name in self._parameter_names:
- elem = parameters[parm_name]
- adotype = api.pyTypeToADOType(elem)
- p = self.cmd.CreateParameter(
- parm_name, adotype, adc.adParamInput
- )
- _configure_parameter(p, elem, adotype, parameters_known)
- try:
- self.cmd.Parameters.Append(p)
- except Exception as e:
- _message = "Error Building Parameter %s: %s, %s <- %s\n" % (
- p.Name,
- adc.ado_type_name(p.Type),
- p.Value,
- repr(elem),
- )
- self._raiseCursorError(
- api.DataError, _message + "->" + repr(e.args)
- )
- else: # expecting the usual sequence of parameters
- if sproc:
- p = self.cmd.CreateParameter(
- "@RETURN_VALUE", adc.adInteger, adc.adParamReturnValue
- )
- self.cmd.Parameters.Append(p)
-
- for elem in parameters:
- name = "p%i" % i
- adotype = api.pyTypeToADOType(elem)
- p = self.cmd.CreateParameter(
- name, adotype, adc.adParamInput
- ) # Name, Type, Direction, Size, Value
- _configure_parameter(p, elem, adotype, parameters_known)
- try:
- self.cmd.Parameters.Append(p)
- except Exception as e:
- _message = "Error Building Parameter %s: %s, %s <- %s\n" % (
- p.Name,
- adc.ado_type_name(p.Type),
- p.Value,
- repr(elem),
- )
- self._raiseCursorError(
- api.DataError, _message + "->" + repr(e.args)
- )
- i += 1
- if self._ado_prepared == "setup":
- self._ado_prepared = (
- True # parameters will be "known" by ADO next loop
- )
-
- def execute(self, operation, parameters=None):
- """Prepare and execute a database operation (query or command).
-
- Parameters may be provided as sequence or mapping and will be bound to variables in the operation.
- Variables are specified in a database-specific notation
- (see the module's paramstyle attribute for details). [5]
- A reference to the operation will be retained by the cursor.
- If the same operation object is passed in again, then the cursor
- can optimize its behavior. This is most effective for algorithms
- where the same operation is used, but different parameters are bound to it (many times).
-
- For maximum efficiency when reusing an operation, it is best to use
- the setinputsizes() method to specify the parameter types and sizes ahead of time.
- It is legal for a parameter to not match the predefined information;
- the implementation should compensate, possibly with a loss of efficiency.
-
- The parameters may also be specified as list of tuples to e.g. insert multiple rows in
- a single operation, but this kind of usage is depreciated: executemany() should be used instead.
-
- Return value is not defined.
-
- [5] The module will use the __getitem__ method of the parameters object to map either positions
- (integers) or names (strings) to parameter values. This allows for both sequences and mappings
- to be used as input.
- The term "bound" refers to the process of binding an input value to a database execution buffer.
- In practical terms, this means that the input value is directly used as a value in the operation.
- The client should not be required to "escape" the value so that it can be used -- the value
- should be equal to the actual database value."""
- if (
- self.command is not operation
- or self._ado_prepared == "setup"
- or not hasattr(self, "commandText")
- ):
- if self.command is not operation:
- self._ado_prepared = False
- self.command = operation
- self._parameter_names = []
- self.commandText = (
- operation
- if (self.paramstyle == "qmark" or not parameters)
- else self._reformat_operation(operation, parameters)
- )
- self._new_command()
- self._buildADOparameterList(parameters)
- if verbose > 3:
- print("Params=", format_parameters(self.cmd.Parameters, True))
- self._execute_command()
-
- def executemany(self, operation, seq_of_parameters):
- """Prepare a database operation (query or command)
- and then execute it against all parameter sequences or mappings found in the sequence seq_of_parameters.
-
- Return values are not defined.
- """
- self.messages = list()
- total_recordcount = 0
-
- self.prepare(operation)
- for params in seq_of_parameters:
- self.execute(self.command, params)
- if self.rowcount == -1:
- total_recordcount = -1
- if total_recordcount != -1:
- total_recordcount += self.rowcount
- self.rowcount = total_recordcount
-
- def _fetch(self, limit=None):
- """Fetch rows from the current recordset.
-
- limit -- Number of rows to fetch, or None (default) to fetch all rows.
- """
- if self.connection is None or self.rs is None:
- self._raiseCursorError(
- api.FetchFailedError, "fetch() on closed connection or empty query set"
- )
- return
-
- if self.rs.State == adc.adStateClosed or self.rs.BOF or self.rs.EOF:
- return list()
- if limit: # limit number of rows retrieved
- ado_results = self.rs.GetRows(limit)
- else: # get all rows
- ado_results = self.rs.GetRows()
- if (
- self.recordset_format == api.RS_ARRAY
- ): # result of GetRows is a two-dimension array
- length = (
- len(ado_results) // self.numberOfColumns
- ) # length of first dimension
- else: # pywin32
- length = len(ado_results[0]) # result of GetRows is tuples in a tuple
- fetchObject = api.SQLrows(
- ado_results, length, self
- ) # new object to hold the results of the fetch
- return fetchObject
-
- def fetchone(self):
- """Fetch the next row of a query result set, returning a single sequence,
- or None when no more data is available.
-
- An Error (or subclass) exception is raised if the previous call to executeXXX()
- did not produce any result set or no call was issued yet.
- """
- self.messages = []
- result = self._fetch(1)
- if result: # return record (not list of records)
- return result[0]
- return None
-
- def fetchmany(self, size=None):
- """Fetch the next set of rows of a query result, returning a list of tuples. An empty sequence is returned when no more rows are available.
-
- The number of rows to fetch per call is specified by the parameter.
- If it is not given, the cursor's arraysize determines the number of rows to be fetched.
- The method should try to fetch as many rows as indicated by the size parameter.
- If this is not possible due to the specified number of rows not being available,
- fewer rows may be returned.
-
- An Error (or subclass) exception is raised if the previous call to executeXXX()
- did not produce any result set or no call was issued yet.
-
- Note there are performance considerations involved with the size parameter.
- For optimal performance, it is usually best to use the arraysize attribute.
- If the size parameter is used, then it is best for it to retain the same value from
- one fetchmany() call to the next.
- """
- self.messages = []
- if size is None:
- size = self.arraysize
- return self._fetch(size)
-
- def fetchall(self):
- """Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples).
-
- Note that the cursor's arraysize attribute
- can affect the performance of this operation.
- An Error (or subclass) exception is raised if the previous call to executeXXX()
- did not produce any result set or no call was issued yet.
- """
- self.messages = []
- return self._fetch()
-
- def nextset(self):
- """Skip to the next available recordset, discarding any remaining rows from the current recordset.
-
- If there are no more sets, the method returns None. Otherwise, it returns a true
- value and subsequent calls to the fetch methods will return rows from the next result set.
-
- An Error (or subclass) exception is raised if the previous call to executeXXX()
- did not produce any result set or no call was issued yet.
- """
- self.messages = []
- if self.connection is None or self.rs is None:
- self._raiseCursorError(
- api.OperationalError,
- ("nextset() on closed connection or empty query set"),
- )
- return None
-
- if api.onIronPython:
- try:
- recordset = self.rs.NextRecordset()
- except TypeError:
- recordset = None
- except api.Error as exc:
- self._raiseCursorError(api.NotSupportedError, exc.args)
- else: # pywin32
- try: # [begin 2.1 ekelund]
- rsTuple = self.rs.NextRecordset() #
- except pywintypes.com_error as exc: # return appropriate error
- self._raiseCursorError(
- api.NotSupportedError, exc.args
- ) # [end 2.1 ekelund]
- recordset = rsTuple[0]
- if recordset is None:
- return None
- self.build_column_info(recordset)
- return True
-
- def setinputsizes(self, sizes):
- pass
-
- def setoutputsize(self, size, column=None):
- pass
-
- def _last_query(self): # let the programmer see what query we actually used
- try:
- if self.parameters == None:
- ret = self.commandText
- else:
- ret = "%s,parameters=%s" % (self.commandText, repr(self.parameters))
- except:
- ret = None
- return ret
-
- query = property(_last_query, None, None, "returns the last query executed")
-
-
- if __name__ == "__main__":
- raise api.ProgrammingError(version + " cannot be run as a main program.")
|