1693 lines
59 KiB
Python
1693 lines
59 KiB
Python
""" Unit tests version 2.6.1.0 for adodbapi"""
|
|
"""
|
|
adodbapi - A python DB API 2.0 interface to Microsoft ADO
|
|
|
|
Copyright (C) 2002 Henrik Ekelund
|
|
|
|
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
|
|
|
|
Updates by Vernon Cole
|
|
"""
|
|
|
|
import copy
|
|
import datetime
|
|
import decimal
|
|
import random
|
|
import string
|
|
import sys
|
|
import unittest
|
|
|
|
try:
|
|
import win32com.client
|
|
|
|
win32 = True
|
|
except ImportError:
|
|
win32 = False
|
|
|
|
# run the configuration module.
|
|
import adodbapitestconfig as config # will set sys.path to find correct version of adodbapi
|
|
|
|
# in our code below, all our switches are from config.whatever
|
|
import tryconnection
|
|
|
|
import adodbapi
|
|
import adodbapi.apibase as api
|
|
|
|
try:
|
|
import adodbapi.ado_consts as ado_consts
|
|
except ImportError: # we are doing a shortcut import as a module -- so
|
|
try:
|
|
import ado_consts
|
|
except ImportError:
|
|
from adodbapi import ado_consts
|
|
|
|
|
|
def str2bytes(sval):
|
|
return sval.encode("latin1")
|
|
|
|
|
|
long = int
|
|
|
|
|
|
def randomstring(length):
|
|
return "".join([random.choice(string.ascii_letters) for n in range(32)])
|
|
|
|
|
|
class CommonDBTests(unittest.TestCase):
|
|
"Self contained super-simple tests in easy syntax, should work on everything between mySQL and Oracle"
|
|
|
|
def setUp(self):
|
|
self.engine = "unknown"
|
|
|
|
def getEngine(self):
|
|
return self.engine
|
|
|
|
def getConnection(self):
|
|
raise NotImplementedError # "This method must be overriden by a subclass"
|
|
|
|
def getCursor(self):
|
|
return self.getConnection().cursor()
|
|
|
|
def testConnection(self):
|
|
crsr = self.getCursor()
|
|
assert crsr.__class__.__name__ == "Cursor"
|
|
|
|
def testErrorHandlerInherits(self):
|
|
if not self.remote:
|
|
conn = self.getConnection()
|
|
mycallable = lambda connection, cursor, errorclass, errorvalue: 1
|
|
conn.errorhandler = mycallable
|
|
crsr = conn.cursor()
|
|
assert (
|
|
crsr.errorhandler == mycallable
|
|
), "Error handler on crsr should be same as on connection"
|
|
|
|
def testDefaultErrorHandlerConnection(self):
|
|
if not self.remote:
|
|
conn = self.getConnection()
|
|
del conn.messages[:]
|
|
try:
|
|
conn.close()
|
|
conn.commit() # Should not be able to use connection after it is closed
|
|
except:
|
|
assert len(conn.messages) == 1
|
|
assert len(conn.messages[0]) == 2
|
|
assert conn.messages[0][0] == api.ProgrammingError
|
|
|
|
def testOwnErrorHandlerConnection(self):
|
|
if self.remote: # ToDo: use "skip"
|
|
return
|
|
mycallable = (
|
|
lambda connection, cursor, errorclass, errorvalue: 1
|
|
) # does not raise anything
|
|
conn = self.getConnection()
|
|
conn.errorhandler = mycallable
|
|
conn.close()
|
|
conn.commit() # Should not be able to use connection after it is closed
|
|
assert len(conn.messages) == 0
|
|
|
|
conn.errorhandler = None # This should bring back the standard error handler
|
|
try:
|
|
conn.close()
|
|
conn.commit() # Should not be able to use connection after it is closed
|
|
except:
|
|
pass
|
|
# The Standard errorhandler appends error to messages attribute
|
|
assert (
|
|
len(conn.messages) > 0
|
|
), "Setting errorhandler to none should bring back the standard error handler"
|
|
|
|
def testDefaultErrorHandlerCursor(self):
|
|
crsr = self.getConnection().cursor()
|
|
if not self.remote:
|
|
del crsr.messages[:]
|
|
try:
|
|
crsr.execute("SELECT abbtytddrf FROM dasdasd")
|
|
except:
|
|
assert len(crsr.messages) == 1
|
|
assert len(crsr.messages[0]) == 2
|
|
assert crsr.messages[0][0] == api.DatabaseError
|
|
|
|
def testOwnErrorHandlerCursor(self):
|
|
if self.remote: # ToDo: should be a "skip"
|
|
return
|
|
mycallable = (
|
|
lambda connection, cursor, errorclass, errorvalue: 1
|
|
) # does not raise anything
|
|
crsr = self.getConnection().cursor()
|
|
crsr.errorhandler = mycallable
|
|
crsr.execute("SELECT abbtytddrf FROM dasdasd")
|
|
assert len(crsr.messages) == 0
|
|
|
|
crsr.errorhandler = None # This should bring back the standard error handler
|
|
try:
|
|
crsr.execute("SELECT abbtytddrf FROM dasdasd")
|
|
except:
|
|
pass
|
|
# The Standard errorhandler appends error to messages attribute
|
|
assert (
|
|
len(crsr.messages) > 0
|
|
), "Setting errorhandler to none should bring back the standard error handler"
|
|
|
|
def testUserDefinedConversions(self):
|
|
if self.remote: ## Todo: should be a "skip"
|
|
return
|
|
try:
|
|
duplicatingConverter = lambda aStringField: aStringField * 2
|
|
assert duplicatingConverter("gabba") == "gabbagabba"
|
|
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
# the variantConversions attribute should not exist on a normal connection object
|
|
self.assertRaises(AttributeError, lambda x: conn.variantConversions[x], [2])
|
|
if not self.remote:
|
|
# create a variantConversions attribute on the connection
|
|
conn.variantConversions = copy.copy(api.variantConversions)
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"CREATE TABLE xx_%s (fldData VARCHAR(100) NOT NULL, fld2 VARCHAR(20))"
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s(fldData,fld2) VALUES('gabba','booga')"
|
|
% config.tmp
|
|
)
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s(fldData,fld2) VALUES('hey','yo')" % config.tmp
|
|
)
|
|
# change converter for ALL adoStringTypes columns
|
|
conn.variantConversions[api.adoStringTypes] = duplicatingConverter
|
|
crsr.execute(
|
|
"SELECT fldData,fld2 FROM xx_%s ORDER BY fldData" % config.tmp
|
|
)
|
|
|
|
rows = crsr.fetchall()
|
|
row = rows[0]
|
|
self.assertEqual(row[0], "gabbagabba")
|
|
row = rows[1]
|
|
self.assertEqual(row[0], "heyhey")
|
|
self.assertEqual(row[1], "yoyo")
|
|
|
|
upcaseConverter = lambda aStringField: aStringField.upper()
|
|
assert upcaseConverter("upThis") == "UPTHIS"
|
|
|
|
# now use a single column converter
|
|
rows.converters[1] = upcaseConverter # convert second column
|
|
self.assertEqual(row[0], "heyhey") # first will be unchanged
|
|
self.assertEqual(row[1], "YO") # second will convert to upper case
|
|
|
|
finally:
|
|
try:
|
|
del conn.variantConversions # Restore the default
|
|
except:
|
|
pass
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testUserDefinedConversionForExactNumericTypes(self):
|
|
# variantConversions is a dictionary of conversion functions
|
|
# held internally in adodbapi.apibase
|
|
#
|
|
# !!! this test intentionally alters the value of what should be constant in the module
|
|
# !!! no new code should use this example, to is only a test to see that the
|
|
# !!! deprecated way of doing this still works. (use connection.variantConversions)
|
|
#
|
|
if not self.remote and sys.version_info < (3, 0): ### Py3 need different test
|
|
oldconverter = adodbapi.variantConversions[
|
|
ado_consts.adNumeric
|
|
] # keep old function to restore later
|
|
# By default decimal and "numbers" are returned as decimals.
|
|
# Instead, make numbers return as floats
|
|
try:
|
|
adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtFloat
|
|
self.helpTestDataType(
|
|
"decimal(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
|
|
)
|
|
self.helpTestDataType(
|
|
"numeric(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
|
|
)
|
|
# now return strings
|
|
adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtString
|
|
self.helpTestDataType("numeric(18,2)", "NUMBER", "3.45")
|
|
# now a completly weird user defined convertion
|
|
adodbapi.variantConversions[ado_consts.adNumeric] = (
|
|
lambda x: "!!This function returns a funny unicode string %s!!" % x
|
|
)
|
|
self.helpTestDataType(
|
|
"numeric(18,2)",
|
|
"NUMBER",
|
|
"3.45",
|
|
allowedReturnValues=[
|
|
"!!This function returns a funny unicode string 3.45!!"
|
|
],
|
|
)
|
|
finally:
|
|
# now reset the converter to its original function
|
|
adodbapi.variantConversions[
|
|
ado_consts.adNumeric
|
|
] = oldconverter # Restore the original convertion function
|
|
|
|
def helpTestDataType(
|
|
self,
|
|
sqlDataTypeString,
|
|
DBAPIDataTypeString,
|
|
pyData,
|
|
pyDataInputAlternatives=None,
|
|
compareAlmostEqual=None,
|
|
allowedReturnValues=None,
|
|
):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldData """
|
|
% config.tmp
|
|
+ sqlDataTypeString
|
|
+ ")\n"
|
|
)
|
|
|
|
crsr.execute(tabdef)
|
|
|
|
# Test Null values mapped to None
|
|
crsr.execute("INSERT INTO xx_%s (fldId) VALUES (1)" % config.tmp)
|
|
|
|
crsr.execute("SELECT fldId,fldData FROM xx_%s" % config.tmp)
|
|
rs = crsr.fetchone()
|
|
self.assertEqual(rs[1], None) # Null should be mapped to None
|
|
assert rs[0] == 1
|
|
|
|
# Test description related
|
|
descTuple = crsr.description[1]
|
|
assert descTuple[0] in ["fldData", "flddata"], 'was "%s" expected "%s"' % (
|
|
descTuple[0],
|
|
"fldData",
|
|
)
|
|
|
|
if DBAPIDataTypeString == "STRING":
|
|
assert descTuple[1] == api.STRING, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.STRING.values,
|
|
)
|
|
elif DBAPIDataTypeString == "NUMBER":
|
|
assert descTuple[1] == api.NUMBER, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.NUMBER.values,
|
|
)
|
|
elif DBAPIDataTypeString == "BINARY":
|
|
assert descTuple[1] == api.BINARY, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.BINARY.values,
|
|
)
|
|
elif DBAPIDataTypeString == "DATETIME":
|
|
assert descTuple[1] == api.DATETIME, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.DATETIME.values,
|
|
)
|
|
elif DBAPIDataTypeString == "ROWID":
|
|
assert descTuple[1] == api.ROWID, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.ROWID.values,
|
|
)
|
|
elif DBAPIDataTypeString == "UUID":
|
|
assert descTuple[1] == api.OTHER, 'was "%s" expected "%s"' % (
|
|
descTuple[1],
|
|
api.OTHER.values,
|
|
)
|
|
else:
|
|
raise NotImplementedError # "DBAPIDataTypeString not provided"
|
|
|
|
# Test data binding
|
|
inputs = [pyData]
|
|
if pyDataInputAlternatives:
|
|
inputs.extend(pyDataInputAlternatives)
|
|
inputs = set(inputs) # removes redundant string==unicode tests
|
|
fldId = 1
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (fldId,fldData) VALUES (?,?)" % config.tmp,
|
|
(fldId, inParam),
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldData FROM xx_%s WHERE ?=fldID" % config.tmp, [fldId]
|
|
)
|
|
rs = crsr.fetchone()
|
|
if allowedReturnValues:
|
|
allowedTypes = tuple([type(aRV) for aRV in allowedReturnValues])
|
|
assert isinstance(
|
|
rs[0], allowedTypes
|
|
), 'result type "%s" must be one of %s' % (type(rs[0]), allowedTypes)
|
|
else:
|
|
assert isinstance(
|
|
rs[0], type(pyData)
|
|
), 'result type "%s" must be instance of %s' % (
|
|
type(rs[0]),
|
|
type(pyData),
|
|
)
|
|
|
|
if compareAlmostEqual and DBAPIDataTypeString == "DATETIME":
|
|
iso1 = adodbapi.dateconverter.DateObjectToIsoFormatString(rs[0])
|
|
iso2 = adodbapi.dateconverter.DateObjectToIsoFormatString(pyData)
|
|
self.assertEqual(iso1, iso2)
|
|
elif compareAlmostEqual:
|
|
s = float(pyData)
|
|
v = float(rs[0])
|
|
assert (
|
|
abs(v - s) / s < 0.00001
|
|
), "Values not almost equal recvd=%s, expected=%f" % (rs[0], s)
|
|
else:
|
|
if allowedReturnValues:
|
|
ok = False
|
|
self.assertTrue(
|
|
rs[0] in allowedReturnValues,
|
|
'Value "%s" not in %s' % (repr(rs[0]), allowedReturnValues),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
rs[0],
|
|
pyData,
|
|
'Values are not equal recvd="%s", expected="%s"'
|
|
% (rs[0], pyData),
|
|
)
|
|
|
|
def testDataTypeFloat(self):
|
|
self.helpTestDataType("real", "NUMBER", 3.45, compareAlmostEqual=True)
|
|
self.helpTestDataType("float", "NUMBER", 1.79e37, compareAlmostEqual=True)
|
|
|
|
def testDataTypeDecmal(self):
|
|
self.helpTestDataType(
|
|
"decimal(18,2)",
|
|
"NUMBER",
|
|
3.45,
|
|
allowedReturnValues=["3.45", "3,45", decimal.Decimal("3.45")],
|
|
)
|
|
self.helpTestDataType(
|
|
"numeric(18,2)",
|
|
"NUMBER",
|
|
3.45,
|
|
allowedReturnValues=["3.45", "3,45", decimal.Decimal("3.45")],
|
|
)
|
|
self.helpTestDataType(
|
|
"decimal(20,2)",
|
|
"NUMBER",
|
|
444444444444444444,
|
|
allowedReturnValues=[
|
|
"444444444444444444.00",
|
|
"444444444444444444,00",
|
|
decimal.Decimal("444444444444444444"),
|
|
],
|
|
)
|
|
if self.getEngine() == "MSSQL":
|
|
self.helpTestDataType(
|
|
"uniqueidentifier",
|
|
"UUID",
|
|
"{71A4F49E-39F3-42B1-A41E-48FF154996E6}",
|
|
allowedReturnValues=["{71A4F49E-39F3-42B1-A41E-48FF154996E6}"],
|
|
)
|
|
|
|
def testDataTypeMoney(self): # v2.1 Cole -- use decimal for money
|
|
if self.getEngine() == "MySQL":
|
|
self.helpTestDataType(
|
|
"DECIMAL(20,4)", "NUMBER", decimal.Decimal("-922337203685477.5808")
|
|
)
|
|
elif self.getEngine() == "PostgreSQL":
|
|
self.helpTestDataType(
|
|
"money",
|
|
"NUMBER",
|
|
decimal.Decimal("-922337203685477.5808"),
|
|
compareAlmostEqual=True,
|
|
allowedReturnValues=[
|
|
-922337203685477.5808,
|
|
decimal.Decimal("-922337203685477.5808"),
|
|
],
|
|
)
|
|
else:
|
|
self.helpTestDataType("smallmoney", "NUMBER", decimal.Decimal("214748.02"))
|
|
self.helpTestDataType(
|
|
"money", "NUMBER", decimal.Decimal("-922337203685477.5808")
|
|
)
|
|
|
|
def testDataTypeInt(self):
|
|
if self.getEngine() != "PostgreSQL":
|
|
self.helpTestDataType("tinyint", "NUMBER", 115)
|
|
self.helpTestDataType("smallint", "NUMBER", -32768)
|
|
if self.getEngine() not in ["ACCESS", "PostgreSQL"]:
|
|
self.helpTestDataType(
|
|
"bit", "NUMBER", 1
|
|
) # Does not work correctly with access
|
|
if self.getEngine() in ["MSSQL", "PostgreSQL"]:
|
|
self.helpTestDataType(
|
|
"bigint",
|
|
"NUMBER",
|
|
3000000000,
|
|
allowedReturnValues=[3000000000, int(3000000000)],
|
|
)
|
|
self.helpTestDataType("int", "NUMBER", 2147483647)
|
|
|
|
def testDataTypeChar(self):
|
|
for sqlDataType in ("char(6)", "nchar(6)"):
|
|
self.helpTestDataType(
|
|
sqlDataType,
|
|
"STRING",
|
|
"spam ",
|
|
allowedReturnValues=["spam", "spam", "spam ", "spam "],
|
|
)
|
|
|
|
def testDataTypeVarChar(self):
|
|
if self.getEngine() == "MySQL":
|
|
stringKinds = ["varchar(10)", "text"]
|
|
elif self.getEngine() == "PostgreSQL":
|
|
stringKinds = ["varchar(10)", "text", "character varying"]
|
|
else:
|
|
stringKinds = [
|
|
"varchar(10)",
|
|
"nvarchar(10)",
|
|
"text",
|
|
"ntext",
|
|
] # ,"varchar(max)"]
|
|
|
|
for sqlDataType in stringKinds:
|
|
self.helpTestDataType(sqlDataType, "STRING", "spam", ["spam"])
|
|
|
|
def testDataTypeDate(self):
|
|
if self.getEngine() == "PostgreSQL":
|
|
dt = "timestamp"
|
|
else:
|
|
dt = "datetime"
|
|
self.helpTestDataType(
|
|
dt, "DATETIME", adodbapi.Date(2002, 10, 28), compareAlmostEqual=True
|
|
)
|
|
if self.getEngine() not in ["MySQL", "PostgreSQL"]:
|
|
self.helpTestDataType(
|
|
"smalldatetime",
|
|
"DATETIME",
|
|
adodbapi.Date(2002, 10, 28),
|
|
compareAlmostEqual=True,
|
|
)
|
|
if tag != "pythontime" and self.getEngine() not in [
|
|
"MySQL",
|
|
"PostgreSQL",
|
|
]: # fails when using pythonTime
|
|
self.helpTestDataType(
|
|
dt,
|
|
"DATETIME",
|
|
adodbapi.Timestamp(2002, 10, 28, 12, 15, 1),
|
|
compareAlmostEqual=True,
|
|
)
|
|
|
|
def testDataTypeBinary(self):
|
|
binfld = str2bytes("\x07\x00\xE2\x40*")
|
|
arv = [binfld, adodbapi.Binary(binfld), bytes(binfld)]
|
|
if self.getEngine() == "PostgreSQL":
|
|
self.helpTestDataType(
|
|
"bytea", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
|
|
)
|
|
else:
|
|
self.helpTestDataType(
|
|
"binary(5)", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
|
|
)
|
|
self.helpTestDataType(
|
|
"varbinary(100)",
|
|
"BINARY",
|
|
adodbapi.Binary(binfld),
|
|
allowedReturnValues=arv,
|
|
)
|
|
if self.getEngine() != "MySQL":
|
|
self.helpTestDataType(
|
|
"image", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
|
|
)
|
|
|
|
def helpRollbackTblTemp(self):
|
|
self.helpForceDropOnTblTemp()
|
|
|
|
def helpForceDropOnTblTemp(self):
|
|
conn = self.getConnection()
|
|
with conn.cursor() as crsr:
|
|
try:
|
|
crsr.execute("DROP TABLE xx_%s" % config.tmp)
|
|
if not conn.autocommit:
|
|
conn.commit()
|
|
except:
|
|
pass
|
|
|
|
def helpCreateAndPopulateTableTemp(self, crsr):
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldData INTEGER
|
|
)
|
|
"""
|
|
% config.tmp
|
|
)
|
|
try: # EAFP
|
|
crsr.execute(tabdef)
|
|
except api.DatabaseError: # was not dropped before
|
|
self.helpForceDropOnTblTemp() # so drop it now
|
|
crsr.execute(tabdef)
|
|
for i in range(9): # note: this poor SQL code, but a valid test
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES (%i)" % (config.tmp, i))
|
|
# NOTE: building the test table without using parameter substitution
|
|
|
|
def testFetchAll(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 9
|
|
# test slice of rows
|
|
i = 3
|
|
for row in rs[3:-2]: # should have rowid 3..6
|
|
assert row[0] == i
|
|
i += 1
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testPreparedStatement(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.prepare("SELECT fldData FROM xx_%s" % config.tmp)
|
|
crsr.execute(crsr.command) # remember the one that was prepared
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 9
|
|
assert rs[2][0] == 2
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testWrongPreparedStatement(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.prepare("SELECT * FROM nowhere")
|
|
crsr.execute(
|
|
"SELECT fldData FROM xx_%s" % config.tmp
|
|
) # should execute this one, not the prepared one
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 9
|
|
assert rs[2][0] == 2
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testIterator(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
for i, row in enumerate(
|
|
crsr
|
|
): # using cursor as an iterator, rather than fetchxxx
|
|
assert row[0] == i
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testExecuteMany(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
seq_of_values = [(111,), (222,)]
|
|
crsr.executemany(
|
|
"INSERT INTO xx_%s (fldData) VALUES (?)" % config.tmp, seq_of_values
|
|
)
|
|
if crsr.rowcount == -1:
|
|
print(
|
|
self.getEngine()
|
|
+ " Provider does not support rowcount (on .executemany())"
|
|
)
|
|
else:
|
|
self.assertEqual(crsr.rowcount, 2)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 11
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testRowCount(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
if crsr.rowcount == -1:
|
|
# print("provider does not support rowcount on select")
|
|
pass
|
|
else:
|
|
self.assertEqual(crsr.rowcount, 9)
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testRowCountNoRecordset(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("DELETE FROM xx_%s WHERE fldData >= 5" % config.tmp)
|
|
if crsr.rowcount == -1:
|
|
print(self.getEngine() + " Provider does not support rowcount (on DELETE)")
|
|
else:
|
|
self.assertEqual(crsr.rowcount, 4)
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testFetchMany(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
rs = crsr.fetchmany(3)
|
|
assert len(rs) == 3
|
|
rs = crsr.fetchmany(5)
|
|
assert len(rs) == 5
|
|
rs = crsr.fetchmany(5)
|
|
assert len(rs) == 1 # Asked for five, but there is only one left
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testFetchManyWithArraySize(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
|
|
rs = crsr.fetchmany()
|
|
assert len(rs) == 1 # arraysize Defaults to one
|
|
crsr.arraysize = 4
|
|
rs = crsr.fetchmany()
|
|
assert len(rs) == 4
|
|
rs = crsr.fetchmany()
|
|
assert len(rs) == 4
|
|
rs = crsr.fetchmany()
|
|
assert len(rs) == 0
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testErrorConnect(self):
|
|
conn = self.getConnection()
|
|
kw = {}
|
|
if "proxy_host" in conn.kwargs:
|
|
kw["proxy_host"] = conn.kwargs["proxy_host"]
|
|
conn.close()
|
|
self.assertRaises(api.DatabaseError, self.db, "not a valid connect string", kw)
|
|
|
|
def testRowIterator(self):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldTwo integer,
|
|
fldThree integer,
|
|
fldFour integer)
|
|
"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
|
|
inputs = [(2, 3, 4), (102, 103, 104)]
|
|
fldId = 1
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (fldId,fldTwo,fldThree,fldFour) VALUES (?,?,?,?)"
|
|
% config.tmp,
|
|
(fldId, inParam[0], inParam[1], inParam[2]),
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldTwo,fldThree,fldFour FROM xx_%s WHERE ?=fldID" % config.tmp,
|
|
[fldId],
|
|
)
|
|
rec = crsr.fetchone()
|
|
# check that stepping through an emulated row works
|
|
for j in range(len(inParam)):
|
|
assert (
|
|
rec[j] == inParam[j]
|
|
), 'returned value:"%s" != test value:"%s"' % (rec[j], inParam[j])
|
|
# check that we can get a complete tuple from a row
|
|
assert tuple(rec) == inParam, 'returned value:"%s" != test value:"%s"' % (
|
|
repr(rec),
|
|
repr(inParam),
|
|
)
|
|
# test that slices of rows work
|
|
slice1 = tuple(rec[:-1])
|
|
slice2 = tuple(inParam[0:2])
|
|
assert slice1 == slice2, 'returned value:"%s" != test value:"%s"' % (
|
|
repr(slice1),
|
|
repr(slice2),
|
|
)
|
|
# now test named column retrieval
|
|
assert rec["fldTwo"] == inParam[0]
|
|
assert rec.fldThree == inParam[1]
|
|
assert rec.fldFour == inParam[2]
|
|
# test array operation
|
|
# note that the fields vv vv vv are out of order
|
|
crsr.execute("select fldThree,fldFour,fldTwo from xx_%s" % config.tmp)
|
|
recs = crsr.fetchall()
|
|
assert recs[1][0] == 103
|
|
assert recs[0][1] == 4
|
|
assert recs[1]["fldFour"] == 104
|
|
assert recs[0, 0] == 3
|
|
assert recs[0, "fldTwo"] == 2
|
|
assert recs[1, 2] == 102
|
|
for i in range(1):
|
|
for j in range(2):
|
|
assert recs[i][j] == recs[i, j]
|
|
|
|
def testFormatParamstyle(self):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
conn.paramstyle = "format" # test nonstandard use of paramstyle
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldData varchar(10),
|
|
fldConst varchar(30))
|
|
"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
|
|
inputs = ["one", "two", "three"]
|
|
fldId = 2
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
sql = (
|
|
"INSERT INTO xx_"
|
|
+ config.tmp
|
|
+ " (fldId,fldConst,fldData) VALUES (%s,'thi%s :may cause? trouble', %s)"
|
|
)
|
|
try:
|
|
crsr.execute(sql, (fldId, inParam))
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldData, fldConst FROM xx_" + config.tmp + " WHERE %s=fldID",
|
|
[fldId],
|
|
)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(
|
|
rec[0],
|
|
inParam,
|
|
'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
|
|
)
|
|
self.assertEqual(rec[1], "thi%s :may cause? trouble")
|
|
|
|
# now try an operation with a "%s" as part of a literal
|
|
sel = (
|
|
"insert into xx_" + config.tmp + " (fldId,fldData) VALUES (%s,'four%sfive')"
|
|
)
|
|
params = (20,)
|
|
crsr.execute(sel, params)
|
|
|
|
# test the .query implementation
|
|
assert "(?," in crsr.query, 'expected:"%s" in "%s"' % ("(?,", crsr.query)
|
|
# test the .command attribute
|
|
assert crsr.command == sel, 'expected:"%s" but found "%s"' % (sel, crsr.command)
|
|
|
|
# test the .parameters attribute
|
|
if not self.remote: # parameter list will be altered in transit
|
|
self.assertEqual(crsr.parameters, params)
|
|
# now make sure the data made it
|
|
crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=20" % config.tmp)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(rec[0], "four%sfive")
|
|
|
|
def testNamedParamstyle(self):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
crsr.paramstyle = "named" # test nonstandard use of paramstyle
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldData varchar(10))
|
|
"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
|
|
inputs = ["four", "five", "six"]
|
|
fldId = 10
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (fldId,fldData) VALUES (:Id,:f_Val)"
|
|
% config.tmp,
|
|
{"f_Val": inParam, "Id": fldId},
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldData FROM xx_%s WHERE fldID=:Id" % config.tmp, {"Id": fldId}
|
|
)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(
|
|
rec[0],
|
|
inParam,
|
|
'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
|
|
)
|
|
# now a test with a ":" as part of a literal
|
|
crsr.execute(
|
|
"insert into xx_%s (fldId,fldData) VALUES (:xyz,'six:five')" % config.tmp,
|
|
{"xyz": 30},
|
|
)
|
|
crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(rec[0], "six:five")
|
|
|
|
def testPyformatParamstyle(self):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
crsr.paramstyle = "pyformat" # test nonstandard use of paramstyle
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldData varchar(10))
|
|
"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
|
|
inputs = ["four", "five", "six"]
|
|
fldId = 10
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (fldId,fldData) VALUES (%%(Id)s,%%(f_Val)s)"
|
|
% config.tmp,
|
|
{"f_Val": inParam, "Id": fldId},
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldData FROM xx_%s WHERE fldID=%%(Id)s" % config.tmp,
|
|
{"Id": fldId},
|
|
)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(
|
|
rec[0],
|
|
inParam,
|
|
'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
|
|
)
|
|
# now a test with a "%" as part of a literal
|
|
crsr.execute(
|
|
"insert into xx_%s (fldId,fldData) VALUES (%%(xyz)s,'six%%five')"
|
|
% config.tmp,
|
|
{"xyz": 30},
|
|
)
|
|
crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(rec[0], "six%five")
|
|
|
|
def testAutomaticParamstyle(self):
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
conn.paramstyle = "dynamic" # test nonstandard use of paramstyle
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
fldId integer NOT NULL,
|
|
fldData varchar(10),
|
|
fldConst varchar(30))
|
|
"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
inputs = ["one", "two", "three"]
|
|
fldId = 2
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_"
|
|
+ config.tmp
|
|
+ " (fldId,fldConst,fldData) VALUES (?,'thi%s :may cause? troub:1e', ?)",
|
|
(fldId, inParam),
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
trouble = "thi%s :may cause? troub:1e"
|
|
crsr.execute(
|
|
"SELECT fldData, fldConst FROM xx_" + config.tmp + " WHERE ?=fldID",
|
|
[fldId],
|
|
)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(
|
|
rec[0],
|
|
inParam,
|
|
'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
|
|
)
|
|
self.assertEqual(rec[1], trouble)
|
|
# inputs = [u'four',u'five',u'six']
|
|
fldId = 10
|
|
for inParam in inputs:
|
|
fldId += 1
|
|
try:
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (fldId,fldData) VALUES (:Id,:f_Val)"
|
|
% config.tmp,
|
|
{"f_Val": inParam, "Id": fldId},
|
|
)
|
|
except:
|
|
if self.remote:
|
|
for message in crsr.messages:
|
|
print(message)
|
|
else:
|
|
conn.printADOerrors()
|
|
raise
|
|
crsr.execute(
|
|
"SELECT fldData FROM xx_%s WHERE :Id=fldID" % config.tmp, {"Id": fldId}
|
|
)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(
|
|
rec[0],
|
|
inParam,
|
|
'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
|
|
)
|
|
# now a test with a ":" as part of a literal -- and use a prepared query
|
|
ppdcmd = (
|
|
"insert into xx_%s (fldId,fldData) VALUES (:xyz,'six:five')" % config.tmp
|
|
)
|
|
crsr.prepare(ppdcmd)
|
|
crsr.execute(ppdcmd, {"xyz": 30})
|
|
crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
|
|
rec = crsr.fetchone()
|
|
self.assertEqual(rec[0], "six:five")
|
|
|
|
def testRollBack(self):
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
assert not crsr.connection.autocommit, "Unexpected beginning condition"
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.connection.commit() # commit the first bunch
|
|
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
|
|
|
|
selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
|
|
crsr.execute(selectSql)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 1
|
|
self.conn.rollback()
|
|
crsr.execute(selectSql)
|
|
assert (
|
|
crsr.fetchone() == None
|
|
), "cursor.fetchone should return None if a query retrieves no rows"
|
|
crsr.execute("SELECT fldData from xx_%s" % config.tmp)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 9, "the original records should still be present"
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testCommit(self):
|
|
try:
|
|
con2 = self.getAnotherConnection()
|
|
except NotImplementedError:
|
|
return # should be "SKIP" for ACCESS
|
|
assert not con2.autocommit, "default should be manual commit"
|
|
crsr = con2.cursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
|
|
con2.commit()
|
|
|
|
selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
|
|
crsr.execute(selectSql)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 1
|
|
crsr.close()
|
|
con2.close()
|
|
conn = self.getConnection()
|
|
crsr = self.getCursor()
|
|
with conn.cursor() as crsr:
|
|
crsr.execute(selectSql)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 1
|
|
assert rs[0][0] == 100
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testAutoRollback(self):
|
|
try:
|
|
con2 = self.getAnotherConnection()
|
|
except NotImplementedError:
|
|
return # should be "SKIP" for ACCESS
|
|
assert not con2.autocommit, "unexpected beginning condition"
|
|
crsr = con2.cursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
|
|
selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
|
|
crsr.execute(selectSql)
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 1
|
|
crsr.close()
|
|
con2.close()
|
|
crsr = self.getCursor()
|
|
try:
|
|
crsr.execute(
|
|
selectSql
|
|
) # closing the connection should have forced rollback
|
|
row = crsr.fetchone()
|
|
except api.DatabaseError:
|
|
row = None # if the entire table disappeared the rollback was perfect and the test passed
|
|
assert row == None, (
|
|
"cursor.fetchone should return None if a query retrieves no rows. Got %s"
|
|
% repr(row)
|
|
)
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testAutoCommit(self):
|
|
try:
|
|
ac_conn = self.getAnotherConnection({"autocommit": True})
|
|
except NotImplementedError:
|
|
return # should be "SKIP" for ACCESS
|
|
crsr = ac_conn.cursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
|
|
crsr.close()
|
|
with self.getCursor() as crsr:
|
|
selectSql = "SELECT fldData from xx_%s" % config.tmp
|
|
crsr.execute(
|
|
selectSql
|
|
) # closing the connection should _not_ have forced rollback
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 10, "all records should still be present"
|
|
ac_conn.close()
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testSwitchedAutoCommit(self):
|
|
try:
|
|
ac_conn = self.getAnotherConnection()
|
|
except NotImplementedError:
|
|
return # should be "SKIP" for ACCESS
|
|
ac_conn.autocommit = True
|
|
crsr = ac_conn.cursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
|
|
crsr.close()
|
|
conn = self.getConnection()
|
|
ac_conn.close()
|
|
with self.getCursor() as crsr:
|
|
selectSql = "SELECT fldData from xx_%s" % config.tmp
|
|
crsr.execute(
|
|
selectSql
|
|
) # closing the connection should _not_ have forced rollback
|
|
rs = crsr.fetchall()
|
|
assert len(rs) == 10, "all records should still be present"
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testExtendedTypeHandling(self):
|
|
class XtendString(str):
|
|
pass
|
|
|
|
class XtendInt(int):
|
|
pass
|
|
|
|
class XtendFloat(float):
|
|
pass
|
|
|
|
xs = XtendString(randomstring(30))
|
|
xi = XtendInt(random.randint(-100, 500))
|
|
xf = XtendFloat(random.random())
|
|
self.helpForceDropOnTblTemp()
|
|
conn = self.getConnection()
|
|
crsr = conn.cursor()
|
|
tabdef = (
|
|
"""
|
|
CREATE TABLE xx_%s (
|
|
s VARCHAR(40) NOT NULL,
|
|
i INTEGER NOT NULL,
|
|
f REAL NOT NULL)"""
|
|
% config.tmp
|
|
)
|
|
crsr.execute(tabdef)
|
|
crsr.execute(
|
|
"INSERT INTO xx_%s (s, i, f) VALUES (?, ?, ?)" % config.tmp, (xs, xi, xf)
|
|
)
|
|
crsr.close()
|
|
conn = self.getConnection()
|
|
with self.getCursor() as crsr:
|
|
selectSql = "SELECT s, i, f from xx_%s" % config.tmp
|
|
crsr.execute(
|
|
selectSql
|
|
) # closing the connection should _not_ have forced rollback
|
|
row = crsr.fetchone()
|
|
self.assertEqual(row.s, xs)
|
|
self.assertEqual(row.i, xi)
|
|
self.assertAlmostEqual(row.f, xf)
|
|
self.helpRollbackTblTemp()
|
|
|
|
|
|
class TestADOwithSQLServer(CommonDBTests):
|
|
def setUp(self):
|
|
self.conn = config.dbSqlServerconnect(
|
|
*config.connStrSQLServer[0], **config.connStrSQLServer[1]
|
|
)
|
|
self.conn.timeout = 30 # turn timeout back up
|
|
self.engine = "MSSQL"
|
|
self.db = config.dbSqlServerconnect
|
|
self.remote = config.connStrSQLServer[2]
|
|
|
|
def tearDown(self):
|
|
try:
|
|
self.conn.rollback()
|
|
except:
|
|
pass
|
|
try:
|
|
self.conn.close()
|
|
except:
|
|
pass
|
|
self.conn = None
|
|
|
|
def getConnection(self):
|
|
return self.conn
|
|
|
|
def getAnotherConnection(self, addkeys=None):
|
|
keys = dict(config.connStrSQLServer[1])
|
|
if addkeys:
|
|
keys.update(addkeys)
|
|
return config.dbSqlServerconnect(*config.connStrSQLServer[0], **keys)
|
|
|
|
def testVariableReturningStoredProcedure(self):
|
|
crsr = self.conn.cursor()
|
|
spdef = """
|
|
CREATE PROCEDURE sp_DeleteMeOnlyForTesting
|
|
@theInput varchar(50),
|
|
@theOtherInput varchar(50),
|
|
@theOutput varchar(100) OUTPUT
|
|
AS
|
|
SET @theOutput=@theInput+@theOtherInput
|
|
"""
|
|
try:
|
|
crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
|
|
self.conn.commit()
|
|
except: # Make sure it is empty
|
|
pass
|
|
crsr.execute(spdef)
|
|
|
|
retvalues = crsr.callproc(
|
|
"sp_DeleteMeOnlyForTesting", ("Dodsworth", "Anne", " ")
|
|
)
|
|
assert retvalues[0] == "Dodsworth", '%s is not "Dodsworth"' % repr(retvalues[0])
|
|
assert retvalues[1] == "Anne", '%s is not "Anne"' % repr(retvalues[1])
|
|
assert retvalues[2] == "DodsworthAnne", '%s is not "DodsworthAnne"' % repr(
|
|
retvalues[2]
|
|
)
|
|
self.conn.rollback()
|
|
|
|
def testMultipleSetReturn(self):
|
|
crsr = self.getCursor()
|
|
self.helpCreateAndPopulateTableTemp(crsr)
|
|
|
|
spdef = """
|
|
CREATE PROCEDURE sp_DeleteMe_OnlyForTesting
|
|
AS
|
|
SELECT fldData FROM xx_%s ORDER BY fldData ASC
|
|
SELECT fldData From xx_%s where fldData = -9999
|
|
SELECT fldData FROM xx_%s ORDER BY fldData DESC
|
|
""" % (
|
|
config.tmp,
|
|
config.tmp,
|
|
config.tmp,
|
|
)
|
|
try:
|
|
crsr.execute("DROP PROCEDURE sp_DeleteMe_OnlyForTesting")
|
|
self.conn.commit()
|
|
except: # Make sure it is empty
|
|
pass
|
|
crsr.execute(spdef)
|
|
|
|
retvalues = crsr.callproc("sp_DeleteMe_OnlyForTesting")
|
|
row = crsr.fetchone()
|
|
self.assertEqual(row[0], 0)
|
|
assert crsr.nextset() == True, "Operation should succeed"
|
|
assert not crsr.fetchall(), "Should be an empty second set"
|
|
assert crsr.nextset() == True, "third set should be present"
|
|
rowdesc = crsr.fetchall()
|
|
self.assertEqual(rowdesc[0][0], 8)
|
|
assert crsr.nextset() == None, "No more return sets, should return None"
|
|
|
|
self.helpRollbackTblTemp()
|
|
|
|
def testDatetimeProcedureParameter(self):
|
|
crsr = self.conn.cursor()
|
|
spdef = """
|
|
CREATE PROCEDURE sp_DeleteMeOnlyForTesting
|
|
@theInput DATETIME,
|
|
@theOtherInput varchar(50),
|
|
@theOutput varchar(100) OUTPUT
|
|
AS
|
|
SET @theOutput = CONVERT(CHARACTER(20), @theInput, 0) + @theOtherInput
|
|
"""
|
|
try:
|
|
crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
|
|
self.conn.commit()
|
|
except: # Make sure it is empty
|
|
pass
|
|
crsr.execute(spdef)
|
|
|
|
result = crsr.callproc(
|
|
"sp_DeleteMeOnlyForTesting",
|
|
[adodbapi.Timestamp(2014, 12, 25, 0, 1, 0), "Beep", " " * 30],
|
|
)
|
|
|
|
assert result[2] == "Dec 25 2014 12:01AM Beep", 'value was="%s"' % result[2]
|
|
self.conn.rollback()
|
|
|
|
def testIncorrectStoredProcedureParameter(self):
|
|
crsr = self.conn.cursor()
|
|
spdef = """
|
|
CREATE PROCEDURE sp_DeleteMeOnlyForTesting
|
|
@theInput DATETIME,
|
|
@theOtherInput varchar(50),
|
|
@theOutput varchar(100) OUTPUT
|
|
AS
|
|
SET @theOutput = CONVERT(CHARACTER(20), @theInput) + @theOtherInput
|
|
"""
|
|
try:
|
|
crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
|
|
self.conn.commit()
|
|
except: # Make sure it is empty
|
|
pass
|
|
crsr.execute(spdef)
|
|
|
|
# calling the sproc with a string for the first parameter where a DateTime is expected
|
|
result = tryconnection.try_operation_with_expected_exception(
|
|
(api.DataError, api.DatabaseError),
|
|
crsr.callproc,
|
|
["sp_DeleteMeOnlyForTesting"],
|
|
{"parameters": ["this is wrong", "Anne", "not Alice"]},
|
|
)
|
|
if result[0]: # the expected exception was raised
|
|
assert "@theInput" in str(result[1]) or "DatabaseError" in str(
|
|
result
|
|
), "Identifies the wrong erroneous parameter"
|
|
else:
|
|
assert result[0], result[1] # incorrect or no exception
|
|
self.conn.rollback()
|
|
|
|
|
|
class TestADOwithAccessDB(CommonDBTests):
|
|
def setUp(self):
|
|
self.conn = config.dbAccessconnect(
|
|
*config.connStrAccess[0], **config.connStrAccess[1]
|
|
)
|
|
self.conn.timeout = 30 # turn timeout back up
|
|
self.engine = "ACCESS"
|
|
self.db = config.dbAccessconnect
|
|
self.remote = config.connStrAccess[2]
|
|
|
|
def tearDown(self):
|
|
try:
|
|
self.conn.rollback()
|
|
except:
|
|
pass
|
|
try:
|
|
self.conn.close()
|
|
except:
|
|
pass
|
|
self.conn = None
|
|
|
|
def getConnection(self):
|
|
return self.conn
|
|
|
|
def getAnotherConnection(self, addkeys=None):
|
|
raise NotImplementedError("Jet cannot use a second connection to the database")
|
|
|
|
def testOkConnect(self):
|
|
c = self.db(*config.connStrAccess[0], **config.connStrAccess[1])
|
|
assert c != None
|
|
c.close()
|
|
|
|
|
|
class TestADOwithMySql(CommonDBTests):
|
|
def setUp(self):
|
|
self.conn = config.dbMySqlconnect(
|
|
*config.connStrMySql[0], **config.connStrMySql[1]
|
|
)
|
|
self.conn.timeout = 30 # turn timeout back up
|
|
self.engine = "MySQL"
|
|
self.db = config.dbMySqlconnect
|
|
self.remote = config.connStrMySql[2]
|
|
|
|
def tearDown(self):
|
|
try:
|
|
self.conn.rollback()
|
|
except:
|
|
pass
|
|
try:
|
|
self.conn.close()
|
|
except:
|
|
pass
|
|
self.conn = None
|
|
|
|
def getConnection(self):
|
|
return self.conn
|
|
|
|
def getAnotherConnection(self, addkeys=None):
|
|
keys = dict(config.connStrMySql[1])
|
|
if addkeys:
|
|
keys.update(addkeys)
|
|
return config.dbMySqlconnect(*config.connStrMySql[0], **keys)
|
|
|
|
def testOkConnect(self):
|
|
c = self.db(*config.connStrMySql[0], **config.connStrMySql[1])
|
|
assert c != None
|
|
|
|
# def testStoredProcedure(self):
|
|
# crsr=self.conn.cursor()
|
|
# try:
|
|
# crsr.execute("DROP PROCEDURE DeleteMeOnlyForTesting")
|
|
# self.conn.commit()
|
|
# except: #Make sure it is empty
|
|
# pass
|
|
# spdef= """
|
|
# DELIMITER $$
|
|
# CREATE PROCEDURE DeleteMeOnlyForTesting (onein CHAR(10), twoin CHAR(10), OUT theout CHAR(20))
|
|
# DETERMINISTIC
|
|
# BEGIN
|
|
# SET theout = onein //|| twoin;
|
|
# /* (SELECT 'a small string' as result; */
|
|
# END $$
|
|
# """
|
|
#
|
|
# crsr.execute(spdef)
|
|
#
|
|
# retvalues=crsr.callproc('DeleteMeOnlyForTesting',('Dodsworth','Anne',' '))
|
|
# print 'return value (mysql)=',repr(crsr.returnValue) ###
|
|
# assert retvalues[0]=='Dodsworth', '%s is not "Dodsworth"'%repr(retvalues[0])
|
|
# assert retvalues[1]=='Anne','%s is not "Anne"'%repr(retvalues[1])
|
|
# assert retvalues[2]=='DodsworthAnne','%s is not "DodsworthAnne"'%repr(retvalues[2])
|
|
#
|
|
# try:
|
|
# crsr.execute("DROP PROCEDURE, DeleteMeOnlyForTesting")
|
|
# self.conn.commit()
|
|
# except: #Make sure it is empty
|
|
# pass
|
|
|
|
|
|
class TestADOwithPostgres(CommonDBTests):
|
|
def setUp(self):
|
|
self.conn = config.dbPostgresConnect(
|
|
*config.connStrPostgres[0], **config.connStrPostgres[1]
|
|
)
|
|
self.conn.timeout = 30 # turn timeout back up
|
|
self.engine = "PostgreSQL"
|
|
self.db = config.dbPostgresConnect
|
|
self.remote = config.connStrPostgres[2]
|
|
|
|
def tearDown(self):
|
|
try:
|
|
self.conn.rollback()
|
|
except:
|
|
pass
|
|
try:
|
|
self.conn.close()
|
|
except:
|
|
pass
|
|
self.conn = None
|
|
|
|
def getConnection(self):
|
|
return self.conn
|
|
|
|
def getAnotherConnection(self, addkeys=None):
|
|
keys = dict(config.connStrPostgres[1])
|
|
if addkeys:
|
|
keys.update(addkeys)
|
|
return config.dbPostgresConnect(*config.connStrPostgres[0], **keys)
|
|
|
|
def testOkConnect(self):
|
|
c = self.db(*config.connStrPostgres[0], **config.connStrPostgres[1])
|
|
assert c != None
|
|
|
|
# def testStoredProcedure(self):
|
|
# crsr=self.conn.cursor()
|
|
# spdef= """
|
|
# CREATE OR REPLACE FUNCTION DeleteMeOnlyForTesting (text, text)
|
|
# RETURNS text AS $funk$
|
|
# BEGIN
|
|
# RETURN $1 || $2;
|
|
# END;
|
|
# $funk$
|
|
# LANGUAGE SQL;
|
|
# """
|
|
#
|
|
# crsr.execute(spdef)
|
|
# retvalues = crsr.callproc('DeleteMeOnlyForTesting',('Dodsworth','Anne',' '))
|
|
# ### print 'return value (pg)=',repr(crsr.returnValue) ###
|
|
# assert retvalues[0]=='Dodsworth', '%s is not "Dodsworth"'%repr(retvalues[0])
|
|
# assert retvalues[1]=='Anne','%s is not "Anne"'%repr(retvalues[1])
|
|
# assert retvalues[2]=='Dodsworth Anne','%s is not "Dodsworth Anne"'%repr(retvalues[2])
|
|
# self.conn.rollback()
|
|
# try:
|
|
# crsr.execute("DROP PROCEDURE, DeleteMeOnlyForTesting")
|
|
# self.conn.commit()
|
|
# except: #Make sure it is empty
|
|
# pass
|
|
|
|
|
|
class TimeConverterInterfaceTest(unittest.TestCase):
|
|
def testIDate(self):
|
|
assert self.tc.Date(1990, 2, 2)
|
|
|
|
def testITime(self):
|
|
assert self.tc.Time(13, 2, 2)
|
|
|
|
def testITimestamp(self):
|
|
assert self.tc.Timestamp(1990, 2, 2, 13, 2, 1)
|
|
|
|
def testIDateObjectFromCOMDate(self):
|
|
assert self.tc.DateObjectFromCOMDate(37435.7604282)
|
|
|
|
def testICOMDate(self):
|
|
assert hasattr(self.tc, "COMDate")
|
|
|
|
def testExactDate(self):
|
|
d = self.tc.Date(1994, 11, 15)
|
|
comDate = self.tc.COMDate(d)
|
|
correct = 34653.0
|
|
assert comDate == correct, comDate
|
|
|
|
def testExactTimestamp(self):
|
|
d = self.tc.Timestamp(1994, 11, 15, 12, 0, 0)
|
|
comDate = self.tc.COMDate(d)
|
|
correct = 34653.5
|
|
self.assertEqual(comDate, correct)
|
|
|
|
d = self.tc.Timestamp(2003, 5, 6, 14, 15, 17)
|
|
comDate = self.tc.COMDate(d)
|
|
correct = 37747.593946759262
|
|
self.assertEqual(comDate, correct)
|
|
|
|
def testIsoFormat(self):
|
|
d = self.tc.Timestamp(1994, 11, 15, 12, 3, 10)
|
|
iso = self.tc.DateObjectToIsoFormatString(d)
|
|
self.assertEqual(str(iso[:19]), "1994-11-15 12:03:10")
|
|
|
|
dt = self.tc.Date(2003, 5, 2)
|
|
iso = self.tc.DateObjectToIsoFormatString(dt)
|
|
self.assertEqual(str(iso[:10]), "2003-05-02")
|
|
|
|
|
|
if config.doMxDateTimeTest:
|
|
import mx.DateTime
|
|
|
|
|
|
class TestMXDateTimeConverter(TimeConverterInterfaceTest):
|
|
def setUp(self):
|
|
self.tc = api.mxDateTimeConverter()
|
|
|
|
def testCOMDate(self):
|
|
t = mx.DateTime.DateTime(2002, 6, 28, 18, 15, 2)
|
|
cmd = self.tc.COMDate(t)
|
|
assert cmd == t.COMDate()
|
|
|
|
def testDateObjectFromCOMDate(self):
|
|
cmd = self.tc.DateObjectFromCOMDate(37435.7604282)
|
|
t = mx.DateTime.DateTime(2002, 6, 28, 18, 15, 0)
|
|
t2 = mx.DateTime.DateTime(2002, 6, 28, 18, 15, 2)
|
|
assert t2 > cmd > t
|
|
|
|
def testDate(self):
|
|
assert mx.DateTime.Date(1980, 11, 4) == self.tc.Date(1980, 11, 4)
|
|
|
|
def testTime(self):
|
|
assert mx.DateTime.Time(13, 11, 4) == self.tc.Time(13, 11, 4)
|
|
|
|
def testTimestamp(self):
|
|
t = mx.DateTime.DateTime(2002, 6, 28, 18, 15, 1)
|
|
obj = self.tc.Timestamp(2002, 6, 28, 18, 15, 1)
|
|
assert t == obj
|
|
|
|
|
|
import time
|
|
|
|
|
|
class TestPythonTimeConverter(TimeConverterInterfaceTest):
|
|
def setUp(self):
|
|
self.tc = api.pythonTimeConverter()
|
|
|
|
def testCOMDate(self):
|
|
mk = time.mktime((2002, 6, 28, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
|
|
t = time.localtime(mk)
|
|
# Fri, 28 Jun 2002 18:15:01 +0000
|
|
cmd = self.tc.COMDate(t)
|
|
assert abs(cmd - 37435.7604282) < 1.0 / 24, "%f more than an hour wrong" % cmd
|
|
|
|
def testDateObjectFromCOMDate(self):
|
|
cmd = self.tc.DateObjectFromCOMDate(37435.7604282)
|
|
t1 = time.gmtime(
|
|
time.mktime((2002, 6, 28, 0, 14, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
|
|
)
|
|
# there are errors in the implementation of gmtime which we ignore
|
|
t2 = time.gmtime(
|
|
time.mktime((2002, 6, 29, 12, 14, 2, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
|
|
)
|
|
assert t1 < cmd < t2, '"%s" should be about 2002-6-28 12:15:01' % repr(cmd)
|
|
|
|
def testDate(self):
|
|
t1 = time.mktime((2002, 6, 28, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 30, 0))
|
|
t2 = time.mktime((2002, 6, 30, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, 0))
|
|
obj = self.tc.Date(2002, 6, 29)
|
|
assert t1 < time.mktime(obj) < t2, obj
|
|
|
|
def testTime(self):
|
|
self.assertEqual(
|
|
self.tc.Time(18, 15, 2), time.gmtime(18 * 60 * 60 + 15 * 60 + 2)
|
|
)
|
|
|
|
def testTimestamp(self):
|
|
t1 = time.localtime(
|
|
time.mktime((2002, 6, 28, 18, 14, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
|
|
)
|
|
t2 = time.localtime(
|
|
time.mktime((2002, 6, 28, 18, 16, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
|
|
)
|
|
obj = self.tc.Timestamp(2002, 6, 28, 18, 15, 2)
|
|
assert t1 < obj < t2, obj
|
|
|
|
|
|
class TestPythonDateTimeConverter(TimeConverterInterfaceTest):
|
|
def setUp(self):
|
|
self.tc = api.pythonDateTimeConverter()
|
|
|
|
def testCOMDate(self):
|
|
t = datetime.datetime(2002, 6, 28, 18, 15, 1)
|
|
# Fri, 28 Jun 2002 18:15:01 +0000
|
|
cmd = self.tc.COMDate(t)
|
|
assert abs(cmd - 37435.7604282) < 1.0 / 24, "more than an hour wrong"
|
|
|
|
def testDateObjectFromCOMDate(self):
|
|
cmd = self.tc.DateObjectFromCOMDate(37435.7604282)
|
|
t1 = datetime.datetime(2002, 6, 28, 18, 14, 1)
|
|
t2 = datetime.datetime(2002, 6, 28, 18, 16, 1)
|
|
assert t1 < cmd < t2, cmd
|
|
|
|
tx = datetime.datetime(
|
|
2002, 6, 28, 18, 14, 1, 900000
|
|
) # testing that microseconds don't become milliseconds
|
|
c1 = self.tc.DateObjectFromCOMDate(self.tc.COMDate(tx))
|
|
assert t1 < c1 < t2, c1
|
|
|
|
def testDate(self):
|
|
t1 = datetime.date(2002, 6, 28)
|
|
t2 = datetime.date(2002, 6, 30)
|
|
obj = self.tc.Date(2002, 6, 29)
|
|
assert t1 < obj < t2, obj
|
|
|
|
def testTime(self):
|
|
self.assertEqual(self.tc.Time(18, 15, 2).isoformat()[:8], "18:15:02")
|
|
|
|
def testTimestamp(self):
|
|
t1 = datetime.datetime(2002, 6, 28, 18, 14, 1)
|
|
t2 = datetime.datetime(2002, 6, 28, 18, 16, 1)
|
|
obj = self.tc.Timestamp(2002, 6, 28, 18, 15, 2)
|
|
assert t1 < obj < t2, obj
|
|
|
|
|
|
suites = []
|
|
suites.append(unittest.makeSuite(TestPythonDateTimeConverter, "test"))
|
|
if config.doMxDateTimeTest:
|
|
suites.append(unittest.makeSuite(TestMXDateTimeConverter, "test"))
|
|
if config.doTimeTest:
|
|
suites.append(unittest.makeSuite(TestPythonTimeConverter, "test"))
|
|
|
|
if config.doAccessTest:
|
|
suites.append(unittest.makeSuite(TestADOwithAccessDB, "test"))
|
|
if config.doSqlServerTest:
|
|
suites.append(unittest.makeSuite(TestADOwithSQLServer, "test"))
|
|
if config.doMySqlTest:
|
|
suites.append(unittest.makeSuite(TestADOwithMySql, "test"))
|
|
if config.doPostgresTest:
|
|
suites.append(unittest.makeSuite(TestADOwithPostgres, "test"))
|
|
|
|
|
|
class cleanup_manager(object):
|
|
def __enter__(self):
|
|
pass
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
config.cleanup(config.testfolder, config.mdb_name)
|
|
|
|
|
|
suite = unittest.TestSuite(suites)
|
|
if __name__ == "__main__":
|
|
mysuite = copy.deepcopy(suite)
|
|
with cleanup_manager():
|
|
defaultDateConverter = adodbapi.dateconverter
|
|
print(__doc__)
|
|
print("Default Date Converter is %s" % (defaultDateConverter,))
|
|
dateconverter = defaultDateConverter
|
|
tag = "datetime"
|
|
unittest.TextTestRunner().run(mysuite)
|
|
|
|
if config.iterateOverTimeTests:
|
|
for test, dateconverter, tag in (
|
|
(config.doTimeTest, api.pythonTimeConverter, "pythontime"),
|
|
(config.doMxDateTimeTest, api.mxDateTimeConverter, "mx"),
|
|
):
|
|
if test:
|
|
mysuite = copy.deepcopy(
|
|
suite
|
|
) # work around a side effect of unittest.TextTestRunner
|
|
adodbapi.adodbapi.dateconverter = dateconverter()
|
|
print("Changed dateconverter to ")
|
|
print(adodbapi.adodbapi.dateconverter)
|
|
unittest.TextTestRunner().run(mysuite)
|