Hello community, here is the log from the commit of package python-pyodbc for openSUSE:Factory checked in at 2019-08-13 13:23:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyodbc (Old) and /work/SRC/openSUSE:Factory/.python-pyodbc.new.9556 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyodbc" Tue Aug 13 13:23:37 2019 rev:5 rq:722652 version:4.0.27 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyodbc/python-pyodbc.changes 2019-03-11 11:17:57.397284479 +0100 +++ /work/SRC/openSUSE:Factory/.python-pyodbc.new.9556/python-pyodbc.changes 2019-08-13 13:23:38.853380757 +0200 @@ -1,0 +2,13 @@ +Mon Aug 5 18:35:48 UTC 2019 - Dirk Hartmann <[email protected]> + +- Update to version 4.0.27: + * Use int instead of bigint when possible (based on size of data) + to work with drivers that don't support bigint at all. + * Support SQL Server datetime2 precision. Previously more data + was passed than the column precision causing an error. + * Make Informix unit tests work again. + * Correct encoding error on big-endian machines for connection + errors. Default to native UTF16 instead of UTF16-LE. + * Fix MySQL unit tests. + +------------------------------------------------------------------- Old: ---- pyodbc-4.0.26.tar.gz New: ---- pyodbc-4.0.27.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyodbc.spec ++++++ --- /var/tmp/diff_new_pack.2E6cz4/_old 2019-08-13 13:23:39.285380643 +0200 +++ /var/tmp/diff_new_pack.2E6cz4/_new 2019-08-13 13:23:39.289380642 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-pyodbc -Version: 4.0.26 +Version: 4.0.27 Release: 0 Summary: Python ODBC API License: MIT ++++++ pyodbc-4.0.26.tar.gz -> pyodbc-4.0.27.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/PKG-INFO new/pyodbc-4.0.27/PKG-INFO --- old/pyodbc-4.0.26/PKG-INFO 2019-02-23 20:17:56.000000000 +0100 +++ new/pyodbc-4.0.27/PKG-INFO 2019-07-31 05:14:55.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyodbc -Version: 4.0.26 +Version: 4.0.27 Summary: DB API Module for ODBC Home-page: https://github.com/mkleehammer/pyodbc Author: Michael Kleehammer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/pyodbc.egg-info/PKG-INFO new/pyodbc-4.0.27/pyodbc.egg-info/PKG-INFO --- old/pyodbc-4.0.26/pyodbc.egg-info/PKG-INFO 2019-02-23 20:17:55.000000000 +0100 +++ new/pyodbc-4.0.27/pyodbc.egg-info/PKG-INFO 2019-07-31 05:14:54.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyodbc -Version: 4.0.26 +Version: 4.0.27 Summary: DB API Module for ODBC Home-page: https://github.com/mkleehammer/pyodbc Author: Michael Kleehammer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/pyodbc.egg-info/SOURCES.txt new/pyodbc-4.0.27/pyodbc.egg-info/SOURCES.txt --- old/pyodbc-4.0.26/pyodbc.egg-info/SOURCES.txt 2019-02-23 20:17:56.000000000 +0100 +++ new/pyodbc-4.0.27/pyodbc.egg-info/SOURCES.txt 2019-07-31 05:14:55.000000000 +0200 @@ -55,6 +55,8 @@ tests3/accesstests.py tests3/dbapi20.py tests3/dbapitests.py +tests3/empty.accdb +tests3/empty.mdb tests3/exceltests.py tests3/informixtests.py tests3/mysqltests.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/src/errors.cpp new/pyodbc-4.0.27/src/errors.cpp --- old/pyodbc-4.0.26/src/errors.cpp 2019-01-30 05:14:36.000000000 +0100 +++ new/pyodbc-4.0.27/src/errors.cpp 2019-07-31 05:14:16.000000000 +0200 @@ -289,9 +289,9 @@ // Not always NULL terminated (MS Access) sqlstateT[5] = 0; - // For now, default to UTF-16LE if this is not in the context of a connection. + // For now, default to UTF-16 if this is not in the context of a connection. // Note that this will not work if the DM is using a different wide encoding (e.g. UTF-32). - const char *unicode_enc = conn ? conn->metadata_enc.name : "utf-16-le"; + const char *unicode_enc = conn ? conn->metadata_enc.name : ENCSTR_UTF16NE; Object msgStr(PyUnicode_Decode((char*)szMsg, cchMsg * sizeof(ODBCCHAR), unicode_enc, "strict")); if (cchMsg != 0 && msgStr.Get()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/src/params.cpp new/pyodbc-4.0.27/src/params.cpp --- old/pyodbc-4.0.26/src/params.cpp 2019-02-23 19:31:35.000000000 +0100 +++ new/pyodbc-4.0.27/src/params.cpp 2019-07-31 05:14:16.000000000 +0200 @@ -20,6 +20,7 @@ #include "row.h" #include <datetime.h> + inline Connection* GetConnection(Cursor* cursor) { return (Connection*)cursor->cnxn; @@ -400,7 +401,12 @@ pts->hour = PyDateTime_DATE_GET_HOUR(cell); pts->minute = PyDateTime_DATE_GET_MINUTE(cell); pts->second = PyDateTime_DATE_GET_SECOND(cell); - pts->fraction = PyDateTime_DATE_GET_MICROSECOND(cell) * 1000; + + // Truncate the fraction according to precision + long fast_pow10[] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; + SQLUINTEGER milliseconds = PyDateTime_DATE_GET_MICROSECOND(cell) * 1000; + pts->fraction = milliseconds - (milliseconds % fast_pow10[9 - pi->DecimalDigits]); + *outbuf += sizeof(SQL_TIMESTAMP_STRUCT); ind = sizeof(SQL_TIMESTAMP_STRUCT); } @@ -634,7 +640,7 @@ Py_ssize_t cb = PyBytes_GET_SIZE(param); info.ValueType = SQL_C_BINARY; - info.ColumnSize = isTVP?0:(SQLUINTEGER)max(cb, 1); + info.ColumnSize = isTVP ? 0 : (SQLUINTEGER)max(cb, 1); SQLLEN maxlength = cur->cnxn->GetMaxLength(info.ValueType); @@ -670,7 +676,7 @@ Py_ssize_t cch = PyString_GET_SIZE(param); - info.ColumnSize = isTVP?0:(SQLUINTEGER)max(cch, 1); + info.ColumnSize = isTVP ? 0 : (SQLUINTEGER)max(cch, 1); Object encoded; @@ -740,20 +746,20 @@ } Py_ssize_t cb = PyBytes_GET_SIZE(encoded); - + int denom = 1; - - if(enc.optenc == OPTENC_UTF16) + + if (enc.optenc == OPTENC_UTF16) { denom = 2; } - else if(enc.optenc == OPTENC_UTF32) + else if (enc.optenc == OPTENC_UTF32) { denom = 4; } - - info.ColumnSize = isTVP?0:(SQLUINTEGER)max(cb / denom, 1); - + + info.ColumnSize = isTVP ? 0 : (SQLUINTEGER)max(cb / denom, 1); + info.pObject = encoded.Detach(); SQLLEN maxlength = cur->cnxn->GetMaxLength(enc.ctype); @@ -851,85 +857,67 @@ return true; } + +inline bool NeedsBigInt(PyObject* p) +{ + // NOTE: Smallest 32-bit int should be -214748368 but the MS compiler v.1900 AMD64 + // says that (10 < -2147483648). Perhaps I miscalculated the minimum? + long long ll = PyLong_AsLongLong(p); + return ll < -2147483647 || ll > 2147483647; +} + #if PY_MAJOR_VERSION < 3 static bool GetIntInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, bool isTVP) { - if(isTVP) + if (isTVP || NeedsBigInt(param)) { - PyErr_Clear(); info.Data.i64 = (INT64)PyLong_AsLongLong(param); - if (!PyErr_Occurred()) - { - info.ValueType = SQL_C_SBIGINT; - info.ParameterType = SQL_BIGINT; - info.ParameterValuePtr = &info.Data.i64; - info.StrLen_or_Ind = 8; - - return true; - } - - return false; + + info.ValueType = SQL_C_SBIGINT; + info.ParameterType = SQL_BIGINT; + info.ParameterValuePtr = &info.Data.i64; + info.StrLen_or_Ind = 8; + } + else + { + info.Data.l = PyLong_AsLong(param); + + info.ValueType = SQL_C_LONG; + info.ParameterType = SQL_INTEGER; + info.ParameterValuePtr = &info.Data.l; + info.StrLen_or_Ind = 4; } - info.Data.i64 = (INT64)PyLong_AsLongLong(param); - -#if LONG_BIT == 64 - info.ValueType = SQL_C_SBIGINT; - // info.ValueType = SQL_C_LONG; - info.ParameterType = SQL_BIGINT; - info.StrLen_or_Ind = 8; -#elif LONG_BIT == 32 - info.ValueType = SQL_C_LONG; - info.ParameterType = SQL_INTEGER; - info.StrLen_or_Ind = 4; -#else - #error Unexpected LONG_BIT value -#endif - info.ParameterValuePtr = &info.Data.l; return true; } #endif static bool GetLongInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, bool isTVP) { - // Try to use integer when possible. BIGINT is not always supported and is a "special - // case" for some drivers. + // Since some drivers like Access don't support BIGINT, we use INTEGER when possible. + // Unfortunately this may mean that we end up with two execution plans for the same SQL. + // We could use SQLDescribeParam but that's kind of expensive. - // REVIEW: C & C++ now have constants for max sizes, but I'm not sure if they will be - // available on all platforms Python supports right now. It would be more performant when - // a lot of 64-bit values are used since we could avoid the AsLong call. - - // It's not clear what the maximum SQL_INTEGER should be. The Microsoft documentation says - // it is a 'long int', but some drivers run into trouble at high values. We'll use - // SQL_INTEGER as an optimization for smaller values and rely on BIGINT - - INT64 val = (INT64)PyLong_AsLongLong(param); - - if (!PyErr_Occurred() && !isTVP && (val >= -2147483648 && val <= 2147483647)) + if (isTVP || NeedsBigInt(param)) { - info.Data.l = PyLong_AsLong(param); - if (!PyErr_Occurred()) - { - info.ValueType = SQL_C_LONG; - info.ParameterType = SQL_INTEGER; - info.ParameterValuePtr = &info.Data.l; - info.StrLen_or_Ind = 4; - } + info.Data.i64 = (INT64)PyLong_AsLongLong(param); + + info.ValueType = SQL_C_SBIGINT; + info.ParameterType = SQL_BIGINT; + info.ParameterValuePtr = &info.Data.i64; + info.StrLen_or_Ind = 8; } else { - PyErr_Clear(); - info.Data.i64 = (INT64)PyLong_AsLongLong(param); - if (!PyErr_Occurred()) - { - info.ValueType = SQL_C_SBIGINT; - info.ParameterType = SQL_BIGINT; - info.ParameterValuePtr = &info.Data.i64; - info.StrLen_or_Ind = 8; - } + info.Data.l = PyLong_AsLong(param); + + info.ValueType = SQL_C_LONG; + info.ParameterType = SQL_INTEGER; + info.ParameterValuePtr = &info.Data.l; + info.StrLen_or_Ind = 4; } - return !PyErr_Occurred(); + return true; } static bool GetFloatInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info) @@ -1153,7 +1141,7 @@ Py_ssize_t cb = PyByteArray_Size(param); SQLLEN maxlength = cur->cnxn->GetMaxLength(info.ValueType); - + if (maxlength == 0 || cb <= maxlength || isTVP) { info.ParameterType = SQL_VARBINARY; @@ -1299,40 +1287,40 @@ static bool getObjectValue(PyObject *pObject, long& nValue) { - if (pObject == NULL) - return false; + if (pObject == NULL) + return false; #if PY_MAJOR_VERSION < 3 - if (PyInt_Check(pObject)) - { - nValue = PyInt_AS_LONG(pObject); - return true; - } + if (PyInt_Check(pObject)) + { + nValue = PyInt_AS_LONG(pObject); + return true; + } #endif - if (PyLong_Check(pObject)) - { - nValue = PyLong_AsLong(pObject); - return true; - } + if (PyLong_Check(pObject)) + { + nValue = PyLong_AsLong(pObject); + return true; + } - return false; + return false; } static long getSequenceValue(PyObject *pSequence, Py_ssize_t nIndex, long nDefault, bool &bChanged) { - PyObject *obj; - long v = nDefault; + PyObject *obj; + long v = nDefault; - obj = PySequence_GetItem(pSequence, nIndex); - if (obj != NULL) - { - if (getObjectValue(obj, v)) - bChanged = true; - } - Py_CLEAR(obj); + obj = PySequence_GetItem(pSequence, nIndex); + if (obj != NULL) + { + if (getObjectValue(obj, v)) + bChanged = true; + } + Py_CLEAR(obj); - return v; + return v; } /** @@ -1344,47 +1332,47 @@ */ static bool UpdateParamInfo(Cursor* pCursor, Py_ssize_t nIndex, ParamInfo *pInfo) { - if (pCursor->inputsizes == NULL || nIndex >= PySequence_Length(pCursor->inputsizes)) - return false; + if (pCursor->inputsizes == NULL || nIndex >= PySequence_Length(pCursor->inputsizes)) + return false; + + PyObject *desc = PySequence_GetItem(pCursor->inputsizes, nIndex); + if (desc == NULL) + return false; - PyObject *desc = PySequence_GetItem(pCursor->inputsizes, nIndex); - if (desc == NULL) - return false; - - bool rc = false; - long v; - bool clearError = true; - - // If the error was already set before we entered here, it is not from us, so we leave it alone. - if (PyErr_Occurred()) - clearError = false; - - // integer - sets colsize - // type object - sets sqltype (mapping between Python and SQL types is not 1:1 so it may not always work) - // Consider: sequence of (colsize, sqltype, scale) - if (getObjectValue(desc, v)) - { - pInfo->ColumnSize = (SQLULEN)v; - rc = true; - } - else if (PySequence_Check(desc)) - { - pInfo->ParameterType = (SQLSMALLINT)getSequenceValue(desc, 0, (long)pInfo->ParameterType, rc); - pInfo->ColumnSize = (SQLUINTEGER)getSequenceValue(desc, 1, (long)pInfo->ColumnSize, rc); - pInfo->DecimalDigits = (SQLSMALLINT)getSequenceValue(desc, 2, (long)pInfo->ColumnSize, rc); - } - - Py_CLEAR(desc); - - // If the user didn't provide the full array (in case he gave us an array), the above code would - // set an internal error on the cursor object, as we try to read three values from an array - // which may not have as many. This is ok, because we don't really care if the array is not completly - // specified, so we clear the error in case it comes from this. If the error was already present before that - // we keep it, so the user can handle it. - if (clearError) - PyErr_Clear(); + bool rc = false; + long v; + bool clearError = true; + + // If the error was already set before we entered here, it is not from us, so we leave it alone. + if (PyErr_Occurred()) + clearError = false; + + // integer - sets colsize + // type object - sets sqltype (mapping between Python and SQL types is not 1:1 so it may not always work) + // Consider: sequence of (colsize, sqltype, scale) + if (getObjectValue(desc, v)) + { + pInfo->ColumnSize = (SQLULEN)v; + rc = true; + } + else if (PySequence_Check(desc)) + { + pInfo->ParameterType = (SQLSMALLINT)getSequenceValue(desc, 0, (long)pInfo->ParameterType, rc); + pInfo->ColumnSize = (SQLUINTEGER)getSequenceValue(desc, 1, (long)pInfo->ColumnSize, rc); + pInfo->DecimalDigits = (SQLSMALLINT)getSequenceValue(desc, 2, (long)pInfo->ColumnSize, rc); + } + + Py_CLEAR(desc); + + // If the user didn't provide the full array (in case he gave us an array), the above code would + // set an internal error on the cursor object, as we try to read three values from an array + // which may not have as many. This is ok, because we don't really care if the array is not completly + // specified, so we clear the error in case it comes from this. If the error was already present before that + // we keep it, so the user can handle it. + if (clearError) + PyErr_Clear(); - return rc; + return rc; } bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) @@ -1395,13 +1383,13 @@ if (UpdateParamInfo(cur, index, &info)) { - // Reload in case it has changed. - colsize = info.ColumnSize; - sqltype = info.ParameterType; - scale = info.DecimalDigits; + // Reload in case it has changed. + colsize = info.ColumnSize; + sqltype = info.ParameterType; + scale = info.DecimalDigits; } - TRACE("BIND: param=%ld ValueType=%d (%s) ParameterType=%d (%s) ColumnSize=%ld DecimalDigits=%d BufferLength=%ld *pcb=%ld\n", + TRACE("BIND: param=%ld ValueType=%d (%s) ParameterType=%d (%s) ColumnSize=%ld DecimalDigits=%d BufferLength=%ld *pcb=%ld\n", (index+1), info.ValueType, CTypeName(info.ValueType), sqltype, SqlTypeName(sqltype), colsize, scale, info.BufferLength, info.StrLen_or_Ind); @@ -1451,7 +1439,7 @@ err = 1; break; } - if(ncols && ncols != PySequence_Size(row)) + if (ncols && ncols != PySequence_Size(row)) { RaiseErrorV(0, ProgrammingError, "A TVP's rows must all be the same size."); err = 1; @@ -1699,11 +1687,11 @@ } memset(cur->paramInfos, 0, sizeof(ParamInfo) * cur->paramcount); - // Describe each parameter (SQL type) in preparation for allocation of paramset array + // Describe each parameter (SQL type) in preparation for allocation of paramset array for (Py_ssize_t i = 0; i < cur->paramcount; i++) { - SQLSMALLINT nullable; - if(!SQL_SUCCEEDED(SQLDescribeParam(cur->hstmt, i + 1, &(cur->paramInfos[i].ParameterType), + SQLSMALLINT nullable; + if (!SQL_SUCCEEDED(SQLDescribeParam(cur->hstmt, i + 1, &(cur->paramInfos[i].ParameterType), &cur->paramInfos[i].ColumnSize, &cur->paramInfos[i].DecimalDigits, &nullable))) { @@ -1713,11 +1701,11 @@ cur->paramInfos[i].DecimalDigits = 0; } - // This supports overriding of input sizes via setinputsizes - // See issue 380 - // The logic is duplicated from BindParameter - UpdateParamInfo(cur, i, &cur->paramInfos[i]); - } + // This supports overriding of input sizes via setinputsizes + // See issue 380 + // The logic is duplicated from BindParameter + UpdateParamInfo(cur, i, &cur->paramInfos[i]); + } PyObject *rowseq = PySequence_Fast(paramArrayObj, "Parameter array must be a sequence."); if (!rowseq) @@ -2030,7 +2018,7 @@ Py_XDECREF(rowseq); FreeParameterData(cur); - return ret; + return ret; } static bool GetParamType(Cursor* cur, Py_ssize_t index, SQLSMALLINT& type) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests2/accesstests.py new/pyodbc-4.0.27/tests2/accesstests.py --- old/pyodbc-4.0.26/tests2/accesstests.py 2019-01-30 05:14:36.000000000 +0100 +++ new/pyodbc-4.0.27/tests2/accesstests.py 2019-07-31 05:14:16.000000000 +0200 @@ -9,25 +9,25 @@ installed into the Python directories. You must run python setup.py build before running the tests. -To run, pass the filename of an Access database on the command line: +To run, pass the file EXTENSION of an Access database on the command line: - accesstests test.accdb + accesstests accdb -An empty Access 2000 database (empty.mdb) and an empty Access 2007 database -(empty.accdb), are provided. +An empty Access 2000 database (empty.mdb) or an empty Access 2007 database +(empty.accdb), are automatically created for the tests. To run a single test, use the -t option: - accesstests test.accdb -t unicode_null + accesstests -t unicode_null accdb If you want to report an error, it would be helpful to include the driver information by using the verbose flag and redirecting the output to a file: - accesstests test.accdb -v >& results.txt + accesstests -v accdb >& results.txt You can pass the verbose flag twice for more verbose output: - accesstests test.accdb -vv + accesstests -vv accdb """ # Access SQL data types: http://msdn2.microsoft.com/en-us/library/bb208866.aspx @@ -36,7 +36,8 @@ import unittest from decimal import Decimal from datetime import datetime, date, time -from os.path import abspath +from os.path import abspath, dirname, join +import shutil from testutils import * CNXNSTRING = None @@ -77,6 +78,11 @@ self.cnxn = pyodbc.connect(CNXNSTRING) self.cursor = self.cnxn.cursor() + # https://docs.microsoft.com/en-us/sql/odbc/microsoft/desktop-database-driver-performance-issues?view=sql-server-2017 + # + # As of the 4.0 drivers, you have to send as Unicode? + self.cnxn.setencoding(str, encoding='utf-16le') + for i in range(3): try: self.cursor.execute("drop table t%d" % i) @@ -428,10 +434,7 @@ self.assertEqual(False, result) def test_guid(self): - # REVIEW: Python doesn't (yet) have a UUID type so the value is returned as a string. Access, however, only - # really supports Unicode. For now, we'll have to live with this difference. All strings in Python 3.x will - # be Unicode -- pyodbc 3.x will have different defaults. - value = "de2ac9c6-8676-4b0b-b8a6-217a8580cbee" + value = u"de2ac9c6-8676-4b0b-b8a6-217a8580cbee" self.cursor.execute("create table t1(g1 uniqueidentifier)") self.cursor.execute("insert into t1 values (?)", value) v = self.cursor.execute("select * from t1").fetchone()[0] @@ -620,32 +623,36 @@ def main(): - from optparse import OptionParser - parser = OptionParser(usage=usage) - parser.add_option("-v", "--verbose", default=0, action="count", help="Increment test verbosity (can be used multiple times)") - parser.add_option("-d", "--debug", action="store_true", default=False, help="Print debugging items") - parser.add_option("-t", "--test", help="Run only the named test") - - (options, args) = parser.parse_args() - - if len(args) != 1: - parser.error('dbfile argument required') - - if args[0].endswith('.accdb'): - driver = 'Microsoft Access Driver (*.mdb, *.accdb)' - else: - driver = 'Microsoft Access Driver (*.mdb)' + from argparse import ArgumentParser + parser = ArgumentParser(usage=usage) + parser.add_argument("-v", "--verbose", default=0, action="count", help="Increment test verbosity (can be used multiple times)") + parser.add_argument("-d", "--debug", action="store_true", default=False, help="Print debugging items") + parser.add_argument("-t", "--test", help="Run only the named test") + parser.add_argument('type', choices=['accdb', 'mdb'], help='Which type of file to test') + + args = parser.parse_args() + + DRIVERS = { + 'accdb': 'Microsoft Access Driver (*.mdb, *.accdb)', + 'mdb': 'Microsoft Access Driver (*.mdb)' + } + + here = dirname(abspath(__file__)) + src = join(here, 'empty.' + args.type) + dest = join(here, 'test.' + args.type) + shutil.copy(src, dest) global CNXNSTRING - CNXNSTRING = 'DRIVER={%s};DBQ=%s;ExtendedAnsiSQL=1' % (driver, abspath(args[0])) + CNXNSTRING = 'DRIVER={%s};DBQ=%s;ExtendedAnsiSQL=1' % (DRIVERS[args.type], dest) + print(CNXNSTRING) cnxn = pyodbc.connect(CNXNSTRING) print_library_info(cnxn) cnxn.close() - suite = load_tests(AccessTestCase, options.test) + suite = load_tests(AccessTestCase, args.test) - testRunner = unittest.TextTestRunner(verbosity=options.verbose) + testRunner = unittest.TextTestRunner(verbosity=args.verbose) result = testRunner.run(suite) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests2/mysqltests.py new/pyodbc-4.0.27/tests2/mysqltests.py --- old/pyodbc-4.0.26/tests2/mysqltests.py 2019-01-30 05:14:36.000000000 +0100 +++ new/pyodbc-4.0.27/tests2/mysqltests.py 2019-07-31 05:14:16.000000000 +0200 @@ -4,14 +4,18 @@ usage = """\ usage: %prog [options] connection_string -Unit tests for MySQL. To use, pass a connection string as the parameter. The tests will create and drop tables t1 and -t2 as necessary. The default installation of mysql allows you to connect locally with no password and already contains -a 'test' database, so you can probably use the following. (Update the driver name as appropriate.) - - ./mysqltests DRIVER={MySQL};DATABASE=test +Unit tests for MySQL. To use, pass a connection string as the parameter. +The tests will create and drop tables t1 and t2 as necessary. These tests use the pyodbc library from the build directory, not the version installed in your Python directories. You must run `python setup.py build` before running these tests. + +You can also put the connection string into a tmp/setup.cfg file like so: + + [mysqltests] + connection-string=DRIVER=MySQL ODBC 8.0 Unicode Driver;charset=utf8mb4;SERVER=localhost;DATABASE=pyodbc;UID=root;PWD=rootpw + +Note: Include charset=utf8mb4 in the connection string so the high-Unicode tests won't fail. """ import sys, os, re @@ -689,9 +693,9 @@ # # http://www.fileformat.info/info/unicode/char/1f31c/index.htm - v = "x \U0001F31C z" + v = u"x \U0001F31C z" - self.cursor.execute("create table t1(s varchar(100))") + self.cursor.execute("CREATE TABLE t1(s varchar(100)) DEFAULT CHARSET=utf8mb4") self.cursor.execute("insert into t1 values (?)", v) result = self.cursor.execute("select s from t1").fetchone()[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests2/sqlservertests.py new/pyodbc-4.0.27/tests2/sqlservertests.py --- old/pyodbc-4.0.26/tests2/sqlservertests.py 2019-02-23 19:31:35.000000000 +0100 +++ new/pyodbc-4.0.27/tests2/sqlservertests.py 2019-07-31 05:14:16.000000000 +0200 @@ -458,6 +458,18 @@ self.cursor.executemany(sql, params) self.assertEqual(self.cursor.execute("SELECT txt FROM #issue295").fetchval(), v) + def test_fast_executemany_to_datetime2(self): + if self.handle_known_issues_for('freetds', print_reminder=True): + warn('FREETDS_KNOWN_ISSUE - test_fast_executemany_to_datetime2: test cancelled.') + return + v = datetime(2019, 3, 12, 10, 0, 0, 123456) + self.cursor.execute("CREATE TABLE ##issue540 (dt2 DATETIME2(2))") + sql = "INSERT INTO ##issue540 (dt2) VALUES (?)" + params = [(v,)] + self.cursor.fast_executemany = True + self.cursor.executemany(sql, params) + self.assertEqual(self.cursor.execute("SELECT CAST(dt2 AS VARCHAR) FROM ##issue540").fetchval(), '2019-03-12 10:00:00.12') + # # binary # diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests2/testutils.py new/pyodbc-4.0.27/tests2/testutils.py --- old/pyodbc-4.0.26/tests2/testutils.py 2017-08-12 22:24:51.000000000 +0200 +++ new/pyodbc-4.0.27/tests2/testutils.py 2019-07-31 05:14:16.000000000 +0200 @@ -50,9 +50,13 @@ cursor = cnxn.cursor() for typename in ['VARCHAR', 'WVARCHAR', 'BINARY']: t = getattr(pyodbc, 'SQL_' + typename) - cursor.getTypeInfo(t) - row = cursor.fetchone() - print('Max %s = %s' % (typename, row and row[2] or '(not supported)')) + try: + cursor.getTypeInfo(t) + except pyodbc.Error as e: + print('Max %s = (not supported)' % (typename, )) + else: + row = cursor.fetchone() + print('Max %s = %s' % (typename, row and row[2] or '(not supported)')) if platform.system() == 'Windows': print(' %s' % ' '.join([s for s in platform.win32_ver() if s])) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests3/accesstests.py new/pyodbc-4.0.27/tests3/accesstests.py --- old/pyodbc-4.0.26/tests3/accesstests.py 2019-01-30 05:14:36.000000000 +0100 +++ new/pyodbc-4.0.27/tests3/accesstests.py 2019-07-31 05:14:16.000000000 +0200 @@ -9,25 +9,25 @@ installed into the Python directories. You must run python setup.py build before running the tests. -To run, pass the filename of an Access database on the command line: +To run, pass the file EXTENSION of an Access database on the command line: - accesstests test.accdb + accesstests accdb -An empty Access 2000 database (empty.mdb) and an empty Access 2007 database -(empty.accdb), are provided. +An empty Access 2000 database (empty.mdb) or an empty Access 2007 database +(empty.accdb), are automatically created for the tests. To run a single test, use the -t option: - accesstests test.accdb -t unicode_null + accesstests -t unicode_null accdb If you want to report an error, it would be helpful to include the driver information by using the verbose flag and redirecting the output to a file: - accesstests test.accdb -v >& results.txt + accesstests -v accdb >& results.txt You can pass the verbose flag twice for more verbose output: - accesstests test.accdb -vv + accesstests -vv accdb """ # Access SQL data types: http://msdn2.microsoft.com/en-us/library/bb208866.aspx @@ -36,7 +36,8 @@ import unittest from decimal import Decimal from datetime import datetime, date, time -from os.path import abspath +from os.path import abspath, dirname, join +import shutil from testutils import * CNXNSTRING = None @@ -394,10 +395,7 @@ self.assertEqual(False, result) def test_guid(self): - # REVIEW: Python doesn't (yet) have a UUID type so the value is returned as a string. Access, however, only - # really supports Unicode. For now, we'll have to live with this difference. All strings in Python 3.x will - # be Unicode -- pyodbc 3.x will have different defaults. - value = "de2ac9c6-8676-4b0b-b8a6-217a8580cbee" + value = u"de2ac9c6-8676-4b0b-b8a6-217a8580cbee" self.cursor.execute("create table t1(g1 uniqueidentifier)") self.cursor.execute("insert into t1 values (?)", value) v = self.cursor.execute("select * from t1").fetchone()[0] @@ -586,35 +584,36 @@ def main(): - from optparse import OptionParser - parser = OptionParser(usage=usage) - parser.add_option("-v", "--verbose", default=0, action="count", help="Increment test verbosity (can be used multiple times)") - parser.add_option("-d", "--debug", action="store_true", default=False, help="Print debugging items") - parser.add_option("-t", "--test", help="Run only the named test") - - (options, args) = parser.parse_args() - - if len(args) != 1: - parser.error('dbfile argument required') - - if args[0].endswith('.accdb'): - driver = 'Microsoft Access Driver (*.mdb, *.accdb)' - else: - driver = 'Microsoft Access Driver (*.mdb, *.accdb)' - # driver = 'Microsoft Access Driver (*.mdb)' + from argparse import ArgumentParser + parser = ArgumentParser(usage=usage) + parser.add_argument("-v", "--verbose", default=0, action="count", help="Increment test verbosity (can be used multiple times)") + parser.add_argument("-d", "--debug", action="store_true", default=False, help="Print debugging items") + parser.add_argument("-t", "--test", help="Run only the named test") + parser.add_argument('type', choices=['accdb', 'mdb'], help='Which type of file to test') + + args = parser.parse_args() + + DRIVERS = { + 'accdb': 'Microsoft Access Driver (*.mdb, *.accdb)', + 'mdb': 'Microsoft Access Driver (*.mdb)' + } + + here = dirname(abspath(__file__)) + src = join(here, 'empty.' + args.type) + dest = join(here, 'test.' + args.type) + shutil.copy(src, dest) global CNXNSTRING - CNXNSTRING = 'DRIVER={%s};DBQ=%s;ExtendedAnsiSQL=1' % (driver, abspath(args[0])) - + CNXNSTRING = 'DRIVER={%s};DBQ=%s;ExtendedAnsiSQL=1' % (DRIVERS[args.type], dest) print(CNXNSTRING) cnxn = pyodbc.connect(CNXNSTRING) print_library_info(cnxn) cnxn.close() - suite = load_tests(AccessTestCase, options.test) + suite = load_tests(AccessTestCase, args.test) - testRunner = unittest.TextTestRunner(verbosity=options.verbose) + testRunner = unittest.TextTestRunner(verbosity=args.verbose) result = testRunner.run(suite) Binary files old/pyodbc-4.0.26/tests3/empty.accdb and new/pyodbc-4.0.27/tests3/empty.accdb differ Binary files old/pyodbc-4.0.26/tests3/empty.mdb and new/pyodbc-4.0.27/tests3/empty.mdb differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests3/mysqltests.py new/pyodbc-4.0.27/tests3/mysqltests.py --- old/pyodbc-4.0.26/tests3/mysqltests.py 2019-01-30 05:14:36.000000000 +0100 +++ new/pyodbc-4.0.27/tests3/mysqltests.py 2019-07-31 05:14:16.000000000 +0200 @@ -4,11 +4,8 @@ usage = """\ usage: %prog [options] connection_string -Unit tests for MySQL. To use, pass a connection string as the parameter. The tests will create and drop tables t1 and -t2 as necessary. The default installation of mysql allows you to connect locally with no password and already contains -a 'test' database, so you can probably use the following. (Update the driver name as appropriate.) - - ./mysqltests DRIVER={MySQL};DATABASE=test +Unit tests for MySQL. To use, pass a connection string as the parameter. +The tests will create and drop tables t1 and t2 as necessary. These tests use the pyodbc library from the build directory, not the version installed in your Python directories. You must run `python setup.py build` before running these tests. @@ -16,7 +13,9 @@ You can also put the connection string into a tmp/setup.cfg file like so: [mysqltests] - connection-string=DRIVER={MySQL};SERVER=localhost;UID=uid;PWD=pwd;DATABASE=db + connection-string=DRIVER=MySQL ODBC 8.0 Unicode Driver;charset=utf8mb4;SERVER=localhost;DATABASE=pyodbc;UID=root;PWD=rootpw + +Note: Include charset=utf8mb4 in the connection string so the high-Unicode tests won't fail. """ import sys, os, re @@ -722,7 +721,7 @@ v = "x \U0001F31C z" - self.cursor.execute("create table t1(s varchar(100))") + self.cursor.execute("CREATE TABLE t1(s varchar(100)) DEFAULT CHARSET=utf8mb4") self.cursor.execute("insert into t1 values (?)", v) result = self.cursor.execute("select s from t1").fetchone()[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyodbc-4.0.26/tests3/sqlservertests.py new/pyodbc-4.0.27/tests3/sqlservertests.py --- old/pyodbc-4.0.26/tests3/sqlservertests.py 2019-02-23 19:55:23.000000000 +0100 +++ new/pyodbc-4.0.27/tests3/sqlservertests.py 2019-07-31 05:14:16.000000000 +0200 @@ -450,6 +450,18 @@ self.cursor.executemany(sql, params) self.assertEqual(self.cursor.execute("SELECT txt FROM #issue295").fetchval(), v) + def test_fast_executemany_to_datetime2(self): + if self.handle_known_issues_for('freetds', print_reminder=True): + warn('FREETDS_KNOWN_ISSUE - test_fast_executemany_to_datetime2: test cancelled.') + return + v = datetime(2019, 3, 12, 10, 0, 0, 123456) + self.cursor.execute("CREATE TABLE ##issue540 (dt2 DATETIME2(2))") + sql = "INSERT INTO ##issue540 (dt2) VALUES (?)" + params = [(v,)] + self.cursor.fast_executemany = True + self.cursor.executemany(sql, params) + self.assertEqual(self.cursor.execute("SELECT CAST(dt2 AS VARCHAR) FROM ##issue540").fetchval(), '2019-03-12 10:00:00.12') + # # binary #
