Spaces:
Sleeping
Sleeping
"""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 | |
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.") | |