[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
+
+[](https://travis-ci.org/mkleehammer/pyodbc)
+[](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