123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- """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 sys
- import copy
- import decimal
- import os
- import weakref
- from . import process_connect_string
- from . import ado_consts as adc
- from . import apibase as api
- 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 System import Activator, Type, DBNull, DateTime, Array, Byte
- from System import Decimal as SystemDecimal
- from clr import Reference
- def Dispatch(dispatch):
- type = Type.GetTypeFromProgID(dispatch)
- return Activator.CreateInstance(type)
- def getIndexedValue(obj,index):
- return obj.Item[index]
- else: # try pywin32
- try:
- import win32com.client
- import pythoncom
- import pywintypes
- 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.')
|