[Python-modules-commits] [pyodbc] 01/05: New upstream version 4.0.14

Laurent Bigonville bigon at moszumanska.debian.org
Tue Mar 14 14:09:24 UTC 2017


This is an automated email from the git hooks/post-receive script.

bigon pushed a commit to branch master
in repository pyodbc.

commit 20ef3e73423653112268f3440e4a4f2d7c0919e1
Author: Laurent Bigonville <bigon at bigon.be>
Date:   Tue Mar 14 14:53:48 2017 +0100

    New upstream version 4.0.14
---
 MANIFEST.in                 |    5 +-
 PKG-INFO                    |    5 +-
 README.md                   |   20 +
 pyodbc.egg-info/PKG-INFO    |    5 +-
 pyodbc.egg-info/SOURCES.txt |   50 +-
 setup.cfg                   |    6 +
 setup.py                    |   87 +--
 src/cnxninfo.cpp            |  108 ++-
 src/cnxninfo.h              |    2 +-
 src/connection.cpp          |  608 ++++++++++++++---
 src/connection.h            |   46 +-
 src/cursor.cpp              |  342 +++++-----
 src/cursor.h                |   15 +-
 src/dbspecific.h            |   27 +-
 src/getdata.cpp             |  845 ++++++++++++-----------
 src/getdata.h               |    2 +
 src/params.cpp              |  397 ++++++-----
 src/pyodbc.h                |   22 +-
 src/pyodbccompat.cpp        |   20 +-
 src/pyodbccompat.h          |   27 +-
 src/pyodbcdbg.cpp           |   80 +++
 src/pyodbcmodule.cpp        |  265 ++++++--
 src/pyodbcmodule.h          |    8 +
 src/row.cpp                 |   13 +-
 src/row.h                   |    2 +
 src/sqlwchar.cpp            |  220 ------
 src/sqlwchar.h              |  102 +--
 src/textenc.cpp             |  157 +++++
 src/textenc.h               |   69 ++
 src/wrapper.h               |   29 +-
 tests2/accesstests.py       |  657 ++++++++++++++++++
 tests2/dbapi20.py           |  850 +++++++++++++++++++++++
 tests2/dbapitests.py        |   43 ++
 tests2/empty.accdb          |  Bin 0 -> 311296 bytes
 tests2/empty.mdb            |  Bin 0 -> 188416 bytes
 tests2/exceltests.py        |  140 ++++
 tests2/freetdstests.py      | 1274 ++++++++++++++++++++++++++++++++++
 tests2/informixtests.py     | 1273 ++++++++++++++++++++++++++++++++++
 tests2/mysqltests.py        |  725 ++++++++++++++++++++
 tests2/pgtests.py           |  537 +++++++++++++++
 tests2/pgtests.pyc          |  Bin 0 -> 20699 bytes
 tests2/sqlite.db            |  Bin 0 -> 2048 bytes
 tests2/sqlitetests.py       |  757 +++++++++++++++++++++
 tests2/sqlservertests.py    | 1574 +++++++++++++++++++++++++++++++++++++++++++
 tests2/test.py              |   41 ++
 tests2/test.xls             |  Bin 0 -> 17920 bytes
 tests2/testbase.py          |   25 +
 tests2/testutils.py         |  113 ++++
 tests2/testutils.pyc        |  Bin 0 -> 4531 bytes
 tests3/accesstests.py       |  626 +++++++++++++++++
 tests3/dbapi20.py           |  850 +++++++++++++++++++++++
 tests3/dbapitests.py        |   43 ++
 tests3/exceltests.py        |  140 ++++
 tests3/informixtests.py     | 1262 ++++++++++++++++++++++++++++++++++
 tests3/mysqltests.py        |  732 ++++++++++++++++++++
 tests3/pgtests.py           |  529 +++++++++++++++
 tests3/sqlitetests.py       |  708 +++++++++++++++++++
 tests3/sqlservertests.py    | 1399 ++++++++++++++++++++++++++++++++++++++
 tests3/test.py              |   17 +
 tests3/testbase.py          |   25 +
 tests3/testutils.py         |  106 +++
 tests3/testutils.pyc        |  Bin 0 -> 3968 bytes
 62 files changed, 16662 insertions(+), 1368 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index ebb6238..ea7fb2b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,7 +1,8 @@
 include src/*.h
 include src/*.cpp
-include tests/*
-include README.rst
+include tests2/*
+include tests3/*
+include README.*
 include LICENSE.txt
 
 # Include this file, needed for bdist_rpm
diff --git a/PKG-INFO b/PKG-INFO
index 0f78a41..3e29a8f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: pyodbc
-Version: 3.0.10
+Version: 4.0.14
 Summary: DB API Module for ODBC
-Home-page: http://code.google.com/p/pyodbc
+Home-page: https://github.com/mkleehammer/pyodbc
 Author: Michael Kleehammer
 Author-email: michael at kleehammer.com
 License: MIT
-Download-URL: http://code.google.com/p/pyodbc/downloads/list
 Description: A Python DB API 2 module for ODBC. This project provides an up-to-date, convenient interface to ODBC using native data types like datetime and decimal.
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b6bf634
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# pyodbc
+
+[![Build Status](https://travis-ci.org/mkleehammer/pyodbc.svg?branch=master)](https://travis-ci.org/mkleehammer/pyodbc)
+[![Windows Status](https://ci.appveyor.com/api/projects/status/github/mkleehammer/pyodbc?branch=master&svg=true)](https://ci.appveyor.com/project/mkleehammer/pyodbc)
+
+pyodbc is an open source Python module that makes accessing ODBC databases simple.  It
+implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249) specification but is
+packed with even more Pythonic convenience.
+
+The easiest way to install is to use pip:
+
+    pip install pyodbc
+    
+Precompiled binary wheels are provided for most Python versions on Windows and macOS.  On other
+operating systems this will build from source.
+
+[Documentation](https://github.com/mkleehammer/pyodbc/wiki)
+
+[Release Notes](https://github.com/mkleehammer/pyodbc/releases)
+
diff --git a/pyodbc.egg-info/PKG-INFO b/pyodbc.egg-info/PKG-INFO
index 0f78a41..3e29a8f 100644
--- a/pyodbc.egg-info/PKG-INFO
+++ b/pyodbc.egg-info/PKG-INFO
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: pyodbc
-Version: 3.0.10
+Version: 4.0.14
 Summary: DB API Module for ODBC
-Home-page: http://code.google.com/p/pyodbc
+Home-page: https://github.com/mkleehammer/pyodbc
 Author: Michael Kleehammer
 Author-email: michael at kleehammer.com
 License: MIT
-Download-URL: http://code.google.com/p/pyodbc/downloads/list
 Description: A Python DB API 2 module for ODBC. This project provides an up-to-date, convenient interface to ODBC using native data types like datetime and decimal.
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
diff --git a/pyodbc.egg-info/SOURCES.txt b/pyodbc.egg-info/SOURCES.txt
index 5858f17..65016a1 100644
--- a/pyodbc.egg-info/SOURCES.txt
+++ b/pyodbc.egg-info/SOURCES.txt
@@ -1,19 +1,8 @@
 LICENSE.txt
 MANIFEST.in
+README.md
 setup.cfg
 setup.py
-/Users/mkleehammer/dev/pyodbc/src/buffer.cpp
-/Users/mkleehammer/dev/pyodbc/src/cnxninfo.cpp
-/Users/mkleehammer/dev/pyodbc/src/connection.cpp
-/Users/mkleehammer/dev/pyodbc/src/cursor.cpp
-/Users/mkleehammer/dev/pyodbc/src/errors.cpp
-/Users/mkleehammer/dev/pyodbc/src/getdata.cpp
-/Users/mkleehammer/dev/pyodbc/src/params.cpp
-/Users/mkleehammer/dev/pyodbc/src/pyodbccompat.cpp
-/Users/mkleehammer/dev/pyodbc/src/pyodbcdbg.cpp
-/Users/mkleehammer/dev/pyodbc/src/pyodbcmodule.cpp
-/Users/mkleehammer/dev/pyodbc/src/row.cpp
-/Users/mkleehammer/dev/pyodbc/src/sqlwchar.cpp
 pyodbc.egg-info/PKG-INFO
 pyodbc.egg-info/SOURCES.txt
 pyodbc.egg-info/dependency_links.txt
@@ -42,6 +31,39 @@ src/pyodbcmodule.h
 src/resource.h
 src/row.cpp
 src/row.h
-src/sqlwchar.cpp
 src/sqlwchar.h
-src/wrapper.h
\ No newline at end of file
+src/textenc.cpp
+src/textenc.h
+src/wrapper.h
+tests2/accesstests.py
+tests2/dbapi20.py
+tests2/dbapitests.py
+tests2/empty.accdb
+tests2/empty.mdb
+tests2/exceltests.py
+tests2/freetdstests.py
+tests2/informixtests.py
+tests2/mysqltests.py
+tests2/pgtests.py
+tests2/pgtests.pyc
+tests2/sqlite.db
+tests2/sqlitetests.py
+tests2/sqlservertests.py
+tests2/test.py
+tests2/test.xls
+tests2/testbase.py
+tests2/testutils.py
+tests2/testutils.pyc
+tests3/accesstests.py
+tests3/dbapi20.py
+tests3/dbapitests.py
+tests3/exceltests.py
+tests3/informixtests.py
+tests3/mysqltests.py
+tests3/pgtests.py
+tests3/sqlitetests.py
+tests3/sqlservertests.py
+tests3/test.py
+tests3/testbase.py
+tests3/testutils.py
+tests3/testutils.pyc
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 374087c..2b59e66 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,12 @@
 [build_ext]
 include_dirs = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/usr/include
 
+[sqlservertests]
+connection-string = DRIVER={FreeTDS};Server=10.0.2.15;Database=test;UID=test;PWD=test;
+
+[pgtests]
+connection-string = DSN=psqlODBC
+
 [egg_info]
 tag_build = 
 tag_date = 0
diff --git a/setup.py b/setup.py
index 5940f70..2844bad 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 
 import sys, os, re, platform
-from os.path import exists, abspath, dirname, join, isdir
+from os.path import exists, abspath, dirname, join, isdir, relpath
 
 try:
     # Allow use of setuptools so eggs can be built.
@@ -38,7 +38,7 @@ class VersionCommand(Command):
     def run(self):
         version_str, version = get_version()
         sys.stdout.write(version_str + '\n')
-    
+
 
 class TagsCommand(Command):
 
@@ -58,7 +58,7 @@ class TagsCommand(Command):
         files = [ join('src', f) for f in os.listdir('src') if f.endswith(('.h', '.cpp')) ]
         cmd = 'etags %s' % ' '.join(files)
         return os.system(cmd)
-    
+
 
 
 def main():
@@ -67,7 +67,7 @@ def main():
 
     settings = get_compiler_settings(version_str)
 
-    files = [ abspath(join('src', f)) for f in os.listdir('src') if f.endswith('.cpp') ]
+    files = [ relpath(join('src', f)) for f in os.listdir('src') if f.endswith('.cpp') ]
 
     if exists('MANIFEST'):
         os.remove('MANIFEST')
@@ -79,10 +79,10 @@ def main():
 
         'long_description': ('A Python DB API 2 module for ODBC. This project provides an up-to-date, '
                             'convenient interface to ODBC using native data types like datetime and decimal.'),
-        
+
         'maintainer':       "Michael Kleehammer",
         'maintainer_email': "michael at kleehammer.com",
-        
+
         'ext_modules': [Extension('pyodbc', files, **settings)],
 
         'license': 'MIT',
@@ -99,12 +99,11 @@ def main():
                        'Topic :: Database',
                        ],
 
-        'url': 'http://code.google.com/p/pyodbc',
-        'download_url': 'http://code.google.com/p/pyodbc/downloads/list',
+        'url': 'https://github.com/mkleehammer/pyodbc',
         'cmdclass': { 'version' : VersionCommand,
                      'tags'    : TagsCommand }
         }
-    
+
     if sys.hexversion >= 0x02060000:
         kwargs['options'] = {
             'bdist_wininst': {'user_access_control' : 'auto'}
@@ -115,11 +114,11 @@ def main():
 
 def get_compiler_settings(version_str):
 
-    settings = { 
+    settings = {
         'extra_compile_args' : [],
         'libraries': [],
         'include_dirs': [],
-        'define_macros' : [ ('PYODBC_VERSION', version_str) ] 
+        'define_macros' : [ ('PYODBC_VERSION', version_str) ]
     }
 
     # This isn't the best or right way to do this, but I don't see how someone is supposed to sanely subclass the build
@@ -131,15 +130,12 @@ def get_compiler_settings(version_str):
         except ValueError:
             pass
 
-    from array import array
-    UNICODE_WIDTH = array('u').itemsize
-    settings['define_macros'].append(('PYODBC_UNICODE_WIDTH', str(UNICODE_WIDTH)))
-
     if os.name == 'nt':
         settings['extra_compile_args'].extend([
             '/Wall',
-            '/wd4668',
-            '/wd4820',
+            '/wd4514',          # unreference inline function removed
+            '/wd4820',          # padding after struct member
+            '/wd4668',          # is not defined as a preprocessor macro
             '/wd4711', # function selected for automatic inline expansion
             '/wd4100', # unreferenced formal parameter
             '/wd4127', # "conditional expression is constant" testing compilation constants
@@ -173,12 +169,18 @@ def get_compiler_settings(version_str):
         # For now target 10.7 to eliminate the warnings.
         settings['define_macros'].append( ('MAC_OS_X_VERSION_10_7',) )
 
+        # Add directories for MacPorts and Homebrew.
+        dirs = ['/usr/local/include', '/opt/local/include','~/homebrew/include']
+        settings['include_dirs'].extend(dir for dir in dirs if isdir(dir))
+
     else:
         # Other posix-like: Linux, Solaris, etc.
 
         # Python functions take a lot of 'char *' that really should be const.  gcc complains about this *a lot*
         settings['extra_compile_args'].append('-Wno-write-strings')
 
+        from array import array
+        UNICODE_WIDTH = array('u').itemsize
         if UNICODE_WIDTH == 4:
             # This makes UnixODBC use UCS-4 instead of UCS-2, which works better with sizeof(wchar_t)==4.
             # Thanks to Marc-Antoine Parent
@@ -190,32 +192,6 @@ def get_compiler_settings(version_str):
     return settings
 
 
-def add_to_path():
-    """
-    Prepends the build directory to the path so pyodbcconf can be imported without installing it.
-    """
-    # Now run the utility
-  
-    import imp
-    library_exts  = [ t[0] for t in imp.get_suffixes() if t[-1] == imp.C_EXTENSION ]
-    library_names = [ 'pyodbcconf%s' % ext for ext in library_exts ]
-     
-    # Only go into directories that match our version number. 
-     
-    dir_suffix = '-%s.%s' % (sys.version_info[0], sys.version_info[1])
-     
-    build = join(dirname(abspath(__file__)), 'build')
-
-    for top, dirs, files in os.walk(build):
-        dirs = [ d for d in dirs if d.endswith(dir_suffix) ]
-        for name in library_names:
-            if name in files:
-                sys.path.insert(0, top)
-                return
-  
-    raise SystemExit('Did not find pyodbcconf')
-
-
 def get_version():
     """
     Returns the version of the product as (description, [major,minor,micro,beta]).
@@ -225,7 +201,7 @@ def get_version():
       1. If in a git repository, use the latest tag (git describe).
       2. If in an unzipped source directory (from setup.py sdist),
          read the version from the PKG-INFO file.
-      3. Use 3.0.0.0 and complain a lot.
+      3. Use 4.0.0.0 and complain a lot.
     """
     # My goal is to (1) provide accurate tags for official releases but (2) not have to manage tags for every test
     # release.
@@ -253,11 +229,11 @@ def get_version():
         name, numbers = _get_version_git()
 
     if not numbers:
-        _print('WARNING: Unable to determine version.  Using 3.0.0.0')
-        name, numbers = '3.0.0-unsupported', [3,0,0,0]
+        _print('WARNING: Unable to determine version.  Using 4.0.0.0')
+        name, numbers = '4.0.0-unsupported', [4,0,0,0]
 
     return name, numbers
-            
+
 
 def _get_version_pkginfo():
     filename = join(dirname(abspath(__file__)), 'PKG-INFO')
@@ -275,7 +251,7 @@ def _get_version_pkginfo():
 
 
 def _get_version_git():
-    n, result = getoutput('git describe --tags --match 3.*')
+    n, result = getoutput("git describe --tags --match [0-9]*")
     if n:
         _print('WARNING: git describe failed with: %s %s' % (n, result))
         return None, None
@@ -292,10 +268,15 @@ def _get_version_git():
         numbers[-2] += 1
         name = '%s.%s.%sb%d' % tuple(numbers)
 
-    n, result = getoutput('git branch --color=never')
-    branch = re.search(r'\* (\S+)', result).group(1)
-    if branch != 'master' and not re.match('^v\d+$', branch):
-        name = name + '+' + branch.replace('-', '')
+    n, result = getoutput('git rev-parse --abbrev-ref HEAD')
+
+    if result == 'HEAD':
+        # We are not on a branch, so use the last revision instead
+        n, result = getoutput('git rev-parse --short HEAD')
+        name = name + '+commit' + result
+    else:
+        if result != 'master' and not re.match('^v\d+$', result):
+            name = name + '+' + result.replace('-', '')
 
     return name, numbers
 
diff --git a/src/cnxninfo.cpp b/src/cnxninfo.cpp
index b336e82..9e09c30 100644
--- a/src/cnxninfo.cpp
+++ b/src/cnxninfo.cpp
@@ -1,4 +1,3 @@
-
 // There is a bunch of information we want from connections which requires calls to SQLGetInfo when we first connect.
 // However, this isn't something we really want to do for every connection, so we cache it by the hash of the
 // connection string.  When we create a new connection, we copy the values into the connection structure.
@@ -6,19 +5,19 @@
 // We hash the connection string since it may contain sensitive information we wouldn't want exposed in a core dump.
 
 #include "pyodbc.h"
+#include "wrapper.h"
+#include "textenc.h"
 #include "cnxninfo.h"
 #include "connection.h"
-#include "wrapper.h"
 
 // Maps from a Python string of the SHA1 hash to a CnxnInfo object.
 //
 static PyObject* map_hash_to_info;
 
-static PyObject* hashlib;       // The hashlib module if Python 2.5+
-static PyObject* sha;           // The sha module if Python 2.4
+static PyObject* hashlib;       // The hashlib module
 static PyObject* update;        // The string 'update', used in GetHash.
 
-void CnxnInfo_init()
+bool CnxnInfo_init()
 {
     // Called during startup to give us a chance to import the hash code.  If we can't find it, we'll print a warning
     // to the console and not cache anything.
@@ -29,12 +28,15 @@ void CnxnInfo_init()
     map_hash_to_info = PyDict_New();
 
     update = PyString_FromString("update");
+    if (!map_hash_to_info || !update)
+        return false;
 
     hashlib = PyImport_ImportModule("hashlib");
+
     if (!hashlib)
-    {
-        sha = PyImport_ImportModule("sha");
-    }
+        return false;
+
+    return true;
 }
 
 static PyObject* GetHash(PyObject* p)
@@ -46,30 +48,38 @@ static PyObject* GetHash(PyObject* p)
     p = bytes.Get();
 #endif
 
-    if (hashlib)
-    {
-        Object hash(PyObject_CallMethod(hashlib, "new", "s", "sha1"));
-        if (!hash.IsValid())
-            return 0;
+    Object hash(PyObject_CallMethod(hashlib, "new", "s", "sha1"));
+    if (!hash.IsValid())
+        return 0;
 
-        PyObject_CallMethodObjArgs(hash, update, p, 0);
-        return PyObject_CallMethod(hash, "hexdigest", 0);
-    }
+    PyObject_CallMethodObjArgs(hash, update, p, 0);
+    return PyObject_CallMethod(hash, "hexdigest", 0);
+}
 
-    if (sha)
-    {
-        Object hash(PyObject_CallMethod(sha, "new", 0));
-        if (!hash.IsValid())
-            return 0;
+inline void GetColumnSize(Connection* cnxn, SQLSMALLINT sqltype, int* psize)
+{
+    // For some reason I can't seem to reuse the HSTMT multiple times in a row here.  Until I
+    // figure it out I'll simply allocate a new one each time.
+    HSTMT hstmt;
+    if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, cnxn->hdbc, &hstmt)))
+        return;
 
-        PyObject_CallMethodObjArgs(hash, update, p, 0);
-        return PyObject_CallMethod(hash, "hexdigest", 0);
+    SQLINTEGER columnsize;
+
+    if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, sqltype)) &&
+        SQL_SUCCEEDED(SQLFetch(hstmt)) &&
+        SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
+    {
+        // I believe some drivers are returning negative numbers for "unlimited" text fields,
+        // such as FileMaker.  Ignore anything that seems too small.
+        if (columnsize >= 255)
+            *psize = (int)columnsize;
     }
 
-    return 0;
+    SQLFreeStmt(hstmt, SQL_CLOSE);
+    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
 }
 
-
 static PyObject* CnxnInfo_New(Connection* cnxn)
 {
 #ifdef _MSC_VER
@@ -81,15 +91,20 @@ static PyObject* CnxnInfo_New(Connection* cnxn)
     Object info((PyObject*)p);
 
     // set defaults
-    p->odbc_major             = 3;
-    p->odbc_minor             = 50;
+    p->odbc_major             = 0;
+    p->odbc_minor             = 0;
     p->supports_describeparam = false;
     p->datetime_precision     = 19; // default: "yyyy-mm-dd hh:mm:ss"
     p->need_long_data_len     = false;
 
-    // WARNING: The GIL lock is released for the *entire* function here.  Do not touch any objects, call Python APIs,
-    // etc.  We are simply making ODBC calls and setting atomic values (ints & chars).  Also, make sure the lock gets
-    // released -- do not add an early exit.
+    p->varchar_maxlength  = 1 * 1024 * 1024 * 1024;
+    p->wvarchar_maxlength = 1 * 1024 * 1024 * 1024;
+    p->binary_maxlength   = 1 * 1024 * 1024 * 1024;
+
+    // WARNING: The GIL lock is released for the *entire* function here.  Do not
+    // touch any objects, call Python APIs, etc.  We are simply making ODBC
+    // calls and setting atomic values (ints & chars).  Also, make sure the lock
+    // gets released -- do not add an early exit.
 
     SQLRETURN ret;
     Py_BEGIN_ALLOW_THREADS
@@ -115,38 +130,13 @@ static PyObject* CnxnInfo_New(Connection* cnxn)
     if (SQL_SUCCEEDED(SQLGetInfo(cnxn->hdbc, SQL_NEED_LONG_DATA_LEN, szYN, _countof(szYN), &cch)))
         p->need_long_data_len = (szYN[0] == 'Y');
 
-    // These defaults are tiny, but are necessary for Access.
-    p->varchar_maxlength = 255;
-    p->wvarchar_maxlength = 255;
-    p->binary_maxlength  = 510;
-
-    HSTMT hstmt = 0;
-    if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, cnxn->hdbc, &hstmt)))
-    {
-        SQLINTEGER columnsize;
-        if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_TYPE_TIMESTAMP)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
-            if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
-                p->datetime_precision = (int)columnsize;
-
-        if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_VARCHAR)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
-            if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
-                p->varchar_maxlength = (int)columnsize;
-
-        if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_WVARCHAR)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
-            if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
-                p->wvarchar_maxlength = (int)columnsize;
-
-        if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_BINARY)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
-            if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
-                p->binary_maxlength = (int)columnsize;
-
-        SQLFreeStmt(hstmt, SQL_CLOSE);
-    }
+    GetColumnSize(cnxn, SQL_VARCHAR, &p->varchar_maxlength);
+    GetColumnSize(cnxn, SQL_WVARCHAR, &p->wvarchar_maxlength);
+    GetColumnSize(cnxn, SQL_VARBINARY, &p->binary_maxlength);
+    GetColumnSize(cnxn, SQL_TYPE_TIMESTAMP, &p->datetime_precision);
 
     Py_END_ALLOW_THREADS
 
-    // WARNING: Released the lock now.
-
     return info.Detach();
 }
 
diff --git a/src/cnxninfo.h b/src/cnxninfo.h
index c5e78c0..b380208 100644
--- a/src/cnxninfo.h
+++ b/src/cnxninfo.h
@@ -37,7 +37,7 @@ struct CnxnInfo
     int binary_maxlength;
 };
 
-void CnxnInfo_init();
+bool CnxnInfo_init();
 
 // Looks-up or creates a CnxnInfo object for the given connection string.  The connection string can be a Unicode or
 // String object.
diff --git a/src/connection.cpp b/src/connection.cpp
index b1e18f9..f92c07c 100644
--- a/src/connection.cpp
+++ b/src/connection.cpp
@@ -1,23 +1,28 @@
-
 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so.
-//  
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 #include "pyodbc.h"
+#include "wrapper.h"
+#include "textenc.h"
 #include "connection.h"
 #include "cursor.h"
 #include "pyodbcmodule.h"
 #include "errors.h"
-#include "wrapper.h"
 #include "cnxninfo.h"
 #include "sqlwchar.h"
 
+#if PY_MAJOR_VERSION < 3
+static bool IsStringType(PyObject* t) { return (void*)t == (void*)&PyString_Type; }
+static bool IsUnicodeType(PyObject* t) { return (void*)t == (void*)&PyUnicode_Type; }
+#endif
+
 static char connection_doc[] =
     "Connection objects manage connections to the database.\n"
     "\n"
@@ -26,7 +31,7 @@ static char connection_doc[] =
 static Connection* Connection_Validate(PyObject* self)
 {
     Connection* cnxn;
-    
+
     if (self == 0 || !Connection_Check(self))
     {
         PyErr_SetString(PyExc_TypeError, "Connection object required");
@@ -44,7 +49,8 @@ static Connection* Connection_Validate(PyObject* self)
     return cnxn;
 }
 
-static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeout)
+static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeout,
+                    Object& encoding)
 {
     // This should have been checked by the global connect function.
     I(PyString_Check(pConnectString) || PyUnicode_Check(pConnectString));
@@ -72,7 +78,7 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou
     if (timeout > 0)
     {
         Py_BEGIN_ALLOW_THREADS
-        ret = SQLSetConnectAttr(hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)timeout, SQL_IS_UINTEGER);
+        ret = SQLSetConnectAttr(hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)(uintptr_t)timeout, SQL_IS_UINTEGER);
         Py_END_ALLOW_THREADS
         if (!SQL_SUCCEEDED(ret))
             RaiseErrorFromHandle("SQLSetConnectAttr(SQL_ATTR_LOGIN_TIMEOUT)", hdbc, SQL_NULL_HANDLE);
@@ -80,56 +86,24 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou
 
     if (!fAnsi)
     {
-        SQLWChar connectString(pConnectString);
+        // I want to call the W version when possible since the driver can use it as an
+        // indication that we can handle Unicode.  We are going to use the same unicode ending
+        // as we do for binding parameters.
+
+        SQLWChar wchar(pConnectString, SQL_C_WCHAR, encoding, "utf-16le");
+        if (!wchar)
+            return false;
+
         Py_BEGIN_ALLOW_THREADS
-        ret = SQLDriverConnectW(hdbc, 0, connectString.get(), (SQLSMALLINT)connectString.size(), 0, 0, 0, SQL_DRIVER_NOPROMPT);
+        ret = SQLDriverConnectW(hdbc, 0, (SQLWCHAR*)wchar.value(), (SQLSMALLINT)wchar.charlen(), 0, 0, 0, SQL_DRIVER_NOPROMPT);
         Py_END_ALLOW_THREADS
         if (SQL_SUCCEEDED(ret))
             return true;
-
-        // The Unicode function failed.  If the error is that the driver doesn't have a Unicode version (IM001), continue
-        // to the ANSI version.
-        //
-        // I've commented this out since a number of common drivers are returning different errors.  The MySQL 5
-        // driver, for example, returns IM002 "Data source name not found...".
-        //
-        // PyObject* error = GetErrorFromHandle("SQLDriverConnectW", hdbc, SQL_NULL_HANDLE);
-        // if (!HasSqlState(error, "IM001"))
-        // {
-        //     RaiseErrorFromException(error);
-        //     return false;
-        // }
-        // Py_XDECREF(error);
-    }
-        
-    SQLCHAR szConnect[cchMax];
-    if (PyUnicode_Check(pConnectString))
-    {
-        Py_UNICODE* p = PyUnicode_AS_UNICODE(pConnectString);
-        for (Py_ssize_t i = 0, c = PyUnicode_GET_SIZE(pConnectString); i <= c; i++)
-        {
-            if (p[i] > 0xFF)
-            {
-                PyErr_SetString(PyExc_TypeError, "A Unicode connection string was supplied but the driver does "
-                                "not have a Unicode connect function");
-                return false;
-            }
-            szConnect[i] = (SQLCHAR)p[i];
-        }
-    }
-    else
-    {
-#if PY_MAJOR_VERSION < 3
-        const char* p = PyString_AS_STRING(pConnectString);
-        memcpy(szConnect, p, (size_t)(PyString_GET_SIZE(pConnectString) + 1));
-#else
-        PyErr_SetString(PyExc_TypeError, "Connection strings must be Unicode");
-        return false;
-#endif
     }
 
+    SQLWChar ch(pConnectString, SQL_C_CHAR, encoding, "utf-8");
     Py_BEGIN_ALLOW_THREADS
-    ret = SQLDriverConnect(hdbc, 0, szConnect, SQL_NTS, 0, 0, 0, SQL_DRIVER_NOPROMPT);
+    ret = SQLDriverConnect(hdbc, 0, (SQLCHAR*)ch.value(), (SQLSMALLINT)ch.charlen(), 0, 0, 0, SQL_DRIVER_NOPROMPT);
     Py_END_ALLOW_THREADS
     if (SQL_SUCCEEDED(ret))
         return true;
@@ -140,16 +114,14 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou
 }
 
 
-PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly)
+PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, long timeout, bool fReadOnly,
+                         PyObject* attrs_before, Object& encoding)
 {
     // pConnectString
     //   A string or unicode object.  (This must be checked by the caller.)
     //
     // fAnsi
     //   If true, do not attempt a Unicode connection.
-    //
-    // fUnicodeResults
-    //   If true, return strings in rows as unicode objects.
 
     //
     // Allocate HDBC and connect
@@ -163,7 +135,44 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
     if (!SQL_SUCCEEDED(ret))
         return RaiseErrorFromHandle("SQLAllocHandle", SQL_NULL_HANDLE, SQL_NULL_HANDLE);
 
-    if (!Connect(pConnectString, hdbc, fAnsi, timeout))
+    //
+    // Attributes that must be set before connecting.
+    //
+
+    if (attrs_before)
+    {
+        Py_ssize_t pos = 0;
+        PyObject* key = 0;
+        PyObject* value = 0;
+        while (PyDict_Next(attrs_before, &pos, &key, &value))
+        {
+            int ikey = 0, ivalue = 0;
+#if PY_MAJOR_VERSION < 3
+            if (PyInt_Check(key))
+                ikey = (int)PyInt_AsLong(key);
+            if (PyInt_Check(value))
+                ivalue = (int)PyInt_AsLong(value);
+#endif
+            if (PyLong_Check(key))
+                ikey = (int)PyLong_AsLong(key);
+            if (PyLong_Check(value))
+                ivalue = (int)PyLong_AsLong(value);
+
+            Py_BEGIN_ALLOW_THREADS
+            ret = SQLSetConnectAttr(hdbc, ikey, (SQLPOINTER)(uintptr_t)ivalue, SQL_IS_INTEGER);
+            Py_END_ALLOW_THREADS
+            if (!SQL_SUCCEEDED(ret))
+            {
+                RaiseErrorFromHandle("SQLSetConnectAttr", hdbc, SQL_NULL_HANDLE);
+                Py_BEGIN_ALLOW_THREADS
+                SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
+                Py_END_ALLOW_THREADS
+                return 0;
+            }
+        }
+    }
+
+    if (!Connect(pConnectString, hdbc, fAnsi, timeout, encoding))
     {
         // Connect has already set an exception.
         Py_BEGIN_ALLOW_THREADS
@@ -173,7 +182,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
     }
 
     //
-    // Connected, so allocate the Connection object. 
+    // Connected, so allocate the Connection object.
     //
 
     // Set all variables to something valid, so we don't crash in dealloc if this function fails.
@@ -194,16 +203,59 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
         return 0;
     }
 
-    cnxn->hdbc            = hdbc;
-    cnxn->nAutoCommit     = fAutoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
-    cnxn->searchescape    = 0;
-    cnxn->timeout         = 0;
+    cnxn->hdbc         = hdbc;
+    cnxn->nAutoCommit  = fAutoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
+    cnxn->searchescape = 0;
+    cnxn->maxwrite     = 0;
+    cnxn->timeout      = 0;
+    cnxn->conv_count   = 0;
+    cnxn->conv_types   = 0;
+    cnxn->conv_funcs   = 0;
+
+    // This is an inefficient default, but should work all the time.  When we are offered
+    // single-byte text we don't actually know what the encoding is.  For example, with SQL
+    // Server the encoding is based on the database's collation.  We ask the driver / DB to
+    // convert to SQL_C_WCHAR and use the ODBC default of UTF-16LE.
+    cnxn->sqlchar_enc.optenc = OPTENC_UTF16LE;
+    cnxn->sqlchar_enc.name   = _strdup("utf-16le");
+    cnxn->sqlchar_enc.ctype  = SQL_C_WCHAR;
+
+    cnxn->sqlwchar_enc.optenc = OPTENC_UTF16LE;
+    cnxn->sqlwchar_enc.name   = _strdup("utf-16le");
+    cnxn->sqlwchar_enc.ctype  = SQL_C_WCHAR;
+
+    cnxn->metadata_enc.optenc = OPTENC_UTF16LE;
+    cnxn->metadata_enc.name   = _strdup("utf-16le");
+    cnxn->metadata_enc.ctype  = SQL_C_WCHAR;
+
+    // Note: I attempted to use UTF-8 here too since it can hold any type, but SQL Server fails
+    // with a data truncation error if we send something encoded in 2 bytes to a column with 1
+    // character.  I don't know if this is a bug in SQL Server's driver or if I'm missing
+    // something, so we'll stay with the default ODBC conversions.
+    cnxn->unicode_enc.optenc = OPTENC_UTF16LE;
+    cnxn->unicode_enc.name   = _strdup("utf-16le");
+    cnxn->unicode_enc.ctype  = SQL_C_WCHAR;
+
+#if PY_MAJOR_VERSION < 3
+    cnxn->str_enc.optenc = OPTENC_UTF8;
+    cnxn->str_enc.name   = _strdup("utf-8");
+    cnxn->str_enc.ctype  = SQL_C_CHAR;
+
+    cnxn->sqlchar_enc.to  = TO_UNICODE;
+    cnxn->sqlwchar_enc.to = TO_UNICODE;
+    cnxn->metadata_enc.to = TO_UNICODE;
+#endif
+
+    if (!cnxn->sqlchar_enc.name || !cnxn->sqlwchar_enc.name || !cnxn->metadata_enc.name || !cnxn->unicode_enc.name
 #if PY_MAJOR_VERSION < 3
-    cnxn->unicode_results = fUnicodeResults;
+        || !cnxn->str_enc.name
 #endif
-    cnxn->conv_count      = 0;
-    cnxn->conv_types      = 0;
-    cnxn->conv_funcs      = 0;
+        )
+    {
+        PyErr_NoMemory();
+        Py_DECREF(cnxn);
+        return 0;
+    }
 
     //
     // Initialize autocommit mode.
@@ -215,7 +267,6 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
 
     if (fAutoCommit == false)
     {
-        SQLRETURN ret;
         Py_BEGIN_ALLOW_THREADS
         ret = SQLSetConnectAttr(cnxn->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)cnxn->nAutoCommit, SQL_IS_UINTEGER);
         Py_END_ALLOW_THREADS
@@ -227,10 +278,9 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
             return 0;
         }
     }
-    
+
     if (fReadOnly)
     {
-        SQLRETURN ret;
         Py_BEGIN_ALLOW_THREADS
         ret = SQLSetConnectAttr(cnxn->hdbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER)SQL_MODE_READ_ONLY, 0);
         Py_END_ALLOW_THREADS
@@ -262,10 +312,10 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
     cnxn->odbc_minor             = p->odbc_minor;
     cnxn->supports_describeparam = p->supports_describeparam;
     cnxn->datetime_precision     = p->datetime_precision;
+    cnxn->need_long_data_len     = p->need_long_data_len;
     cnxn->varchar_maxlength      = p->varchar_maxlength;
     cnxn->wvarchar_maxlength     = p->wvarchar_maxlength;
     cnxn->binary_maxlength       = p->binary_maxlength;
-    cnxn->need_long_data_len     = p->need_long_data_len;
 
     return reinterpret_cast<PyObject*>(cnxn);
 }
@@ -276,7 +326,7 @@ static void _clear_conv(Connection* cnxn)
     {
         pyodbc_free(cnxn->conv_types);
         cnxn->conv_types = 0;
-        
+
         for (int i = 0; i < cnxn->conv_count; i++)
             Py_XDECREF(cnxn->conv_funcs[i]);
         pyodbc_free(cnxn->conv_funcs);
@@ -286,6 +336,34 @@ static void _clear_conv(Connection* cnxn)
     }
 }
 
+static char set_attr_doc[] =
+    "set_attr(attr_id, value) -> None\n\n"
+    "Calls SQLSetConnectAttr with the given values.\n\n"
+    "attr_id\n"
+    "  The attribute id (integer) to set.  These are ODBC or driver constants.\n\n"
+    "value\n"
+    "  An integer value.\n\n"
+    "At this time, only integer values are supported and are always passed as SQLUINTEGER.";
+
+static PyObject* Connection_set_attr(PyObject* self, PyObject* args)
+{
+    int id;
+    int value;
+    if (!PyArg_ParseTuple(args, "ii", &id, &value))
+        return 0;
+
+    Connection* cnxn = (Connection*)self;
+
+    SQLRETURN ret;
+    Py_BEGIN_ALLOW_THREADS
+    ret = SQLSetConnectAttr(cnxn->hdbc, id, (SQLPOINTER)(intptr_t)value, SQL_IS_INTEGER);
+    Py_END_ALLOW_THREADS
+
+    if (!SQL_SUCCEEDED(ret))
+        return RaiseErrorFromHandle("SQLSetConnectAttr", cnxn->hdbc, SQL_NULL_HANDLE);
+    Py_RETURN_NONE;
+}
+
 static char conv_clear_doc[] =
     "clear_output_converters() --> None\n\n"
     "Remove all output converter functions.";
@@ -293,7 +371,7 @@ static char conv_clear_doc[] =
 static PyObject* Connection_conv_clear(PyObject* self, PyObject* args)
 {
     UNUSED(args);
-    
+
     Connection* cnxn = (Connection*)self;
     _clear_conv(cnxn);
     Py_RETURN_NONE;
@@ -308,24 +386,35 @@ static int Connection_clear(PyObject* self)
 
     if (cnxn->hdbc != SQL_NULL_HANDLE)
     {
-        // REVIEW: Release threads? (But make sure you zero out hdbc *first*!
-
         TRACE("cnxn.clear cnxn=%p hdbc=%d\n", cnxn, cnxn->hdbc);
 
+        HDBC hdbc = cnxn->hdbc;
... 19024 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/pyodbc.git



More information about the Python-modules-commits mailing list