Permalink
Browse files

Added SQL Server 2008 date/time extensions support. Now caches connec…

…tion info.

The connection info is in preparation for querying even more info per connection to implement
Cursor.call

Fixed the setup.py file so it doesn't build a debug library all the time.  You can add --assert
and --trace to turn those on instead of modifying pyodbc.h.

Also added a license file (feature request).
  • Loading branch information...
mkleehammer committed Oct 13, 2008
1 parent bbb51c1 commit 04d8111b80fb00cae4c2677b1bb1ae8e20fec02c
Showing with 393 additions and 165 deletions.
  1. +9 −0 LICENSE.txt
  2. +1 −1 MANIFEST.in
  3. +23 −7 setup.py
  4. +172 −0 src/cnxninfo.cpp
  5. +38 −0 src/cnxninfo.h
  6. +14 −40 src/connection.cpp
  7. +9 −11 src/connection.h
  8. +2 −0 src/cursor.cpp
  9. +26 −0 src/dbspecific.h
  10. +26 −5 src/getdata.cpp
  11. +3 −1 src/params.cpp
  12. +1 −0 src/pyodbc.h
  13. +5 −1 src/pyodbcmodule.cpp
  14. +2 −0 src/wrapper.h
  15. +62 −99 tests/sqlservertests.py
View
@@ -0,0 +1,9 @@
+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.
View
@@ -1,7 +1,7 @@
include src\*.h
include src\*.cpp
include tests\*
-include README.txt
+include *.txt
prune setup.cfg
include web\*
View
@@ -1,19 +1,19 @@
#!/usr/bin/python
import sys, os, re
-from distutils.core import setup, Command
+from distutils.core import setup
from distutils.extension import Extension
from distutils.errors import *
from os.path import exists, abspath, dirname, join, isdir
+
OFFICIAL_BUILD = 9999
def main():
version_str, version = get_version()
- files = [ 'pyodbcmodule.cpp', 'cursor.cpp', 'row.cpp', 'connection.cpp', 'buffer.cpp', 'params.cpp', 'errors.cpp', 'getdata.cpp' ]
- files = [ abspath(join('src', f)) for f in files ]
+ files = [ abspath(join('src', f)) for f in os.listdir('src') if f.endswith('.cpp') ]
libraries = []
extra_compile_args = None
@@ -25,9 +25,8 @@ def main():
libraries.append('odbc32')
extra_compile_args = [ '/W4' ]
- # Add debugging symbols
- extra_compile_args = [ '/W4', '/Zi', '/Od' ]
- extra_link_args = [ '/DEBUG' ]
+ # extra_compile_args = [ '/W4', '/Zi', '/Od' ]
+ # extra_link_args = [ '/DEBUG' ]
elif os.environ.get("OS", '').lower().startswith('windows'):
# Windows Cygwin (posix on windows)
@@ -43,6 +42,22 @@ def main():
# What is the proper way to detect iODBC, MyODBC, unixODBC, etc.?
libraries.append('odbc')
+ macros = [ ('PYODBC_%s' % name, value) for name,value in zip(['MAJOR', 'MINOR', 'MICRO', 'BUILD'], version) ]
+
+ # 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
+ # command.
+ try:
+ sys.argv.remove('--assert')
+ macros.append(('PYODBC_ASSERT', 1))
+ except ValueError:
+ pass
+
+ try:
+ sys.argv.remove('--trace')
+ macros.append(('TRACE_ALL', 1))
+ except ValueError:
+ pass
+
if exists('MANIFEST'):
os.remove('MANIFEST')
@@ -58,7 +73,7 @@ def main():
ext_modules = [ Extension('pyodbc', files,
libraries=libraries,
- define_macros = [ ('PYODBC_%s' % name, value) for name,value in zip(['MAJOR', 'MINOR', 'MICRO', 'BUILD'], version) ],
+ define_macros = macros,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args
) ],
@@ -77,6 +92,7 @@ def main():
download_url = 'http://github.com/pyodbc/pyodbc/tree/master')
+
def get_version():
"""
Returns the version of the product as (description, [major,minor,micro,beta]).
View
@@ -0,0 +1,172 @@
+
+// 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.
+//
+// We hash the connection string since it may contain sensitive information we wouldn't want exposed in a core dump.
+
+#include "pyodbc.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* update; // The string 'update', used in GetHash.
+
+void 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.
+
+ // First try hashlib which was added in 2.5. 2.6 complains using warnings which we don't want affecting the
+ // caller.
+
+ map_hash_to_info = PyDict_New();
+
+ update = PyString_FromString("update");
+
+ // hashlib = PyImport_ImportModule("hashlib");
+ if (!hashlib)
+ {
+ sha = PyImport_ImportModule("sha");
+ }
+
+ printf("hashlib=%p sha=%p\n", hashlib, sha);
+}
+
+static PyObject* GetHash(PyObject* p)
+{
+ if (hashlib)
+ {
+ 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);
+ }
+
+ if (sha)
+ {
+ Object hash(PyObject_CallMethod(sha, "new", 0));
+ if (!hash.IsValid())
+ return 0;
+
+ PyObject_CallMethodObjArgs(hash, update, p, 0);
+ return PyObject_CallMethod(hash, "hexdigest", 0);
+ }
+
+ return 0;
+}
+
+
+static PyObject* CnxnInfo_New(Connection* cnxn)
+{
+ CnxnInfo* p = PyObject_NEW(CnxnInfo, &CnxnInfoType);
+ if (!p)
+ return 0;
+ Object info((PyObject*)p);
+
+ printf("********************************************************************************\n");
+
+ // set defaults
+ p->odbc_major = 3;
+ p->odbc_minor = 50;
+ p->supports_describeparam = false;
+ p->datetime_precision = 19; // default: "yyyy-mm-dd hh:mm:ss"
+
+ char szVer[20];
+ SQLSMALLINT cch = 0;
+ if (SQL_SUCCEEDED(SQLGetInfo(cnxn->hdbc, SQL_DRIVER_ODBC_VER, szVer, _countof(szVer), &cch)))
+ {
+ char* dot = strchr(szVer, '.');
+ if (dot)
+ {
+ *dot = '\0';
+ p->odbc_major=(char)atoi(szVer);
+ p->odbc_minor=(char)atoi(dot + 1);
+ }
+ }
+
+ char szYN[2];
+ if (SQL_SUCCEEDED(SQLGetInfo(cnxn->hdbc, SQL_DESCRIBE_PARAMETER, szYN, _countof(szYN), &cch)))
+ {
+ p->supports_describeparam = szYN[0] == 'Y';
+ }
+
+ // What is the datetime precision? This unfortunately requires a cursor (HSTMT).
+
+ HSTMT hstmt = 0;
+ if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, cnxn->hdbc, &hstmt)))
+ {
+ if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_TYPE_TIMESTAMP)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
+ {
+ SQLINTEGER columnsize;
+ if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
+ {
+ p->datetime_precision = columnsize;
+ }
+ }
+
+ SQLFreeStmt(hstmt, SQL_CLOSE);
+ }
+
+ return info.Detach();
+}
+
+
+PyObject* GetConnectionInfo(PyObject* pConnectionString, Connection* cnxn)
+{
+ // Looks-up or creates a CnxnInfo object for the given connection string. The connection string can be a Unicode
+ // or String object.
+
+ Object hash(GetHash(pConnectionString));
+
+ if (hash.IsValid())
+ {
+ PyObject* info = PyDict_GetItem(map_hash_to_info, hash);
+
+ if (info)
+ {
+ Py_INCREF(info);
+ return info;
+ }
+ }
+
+ PyObject* info = CnxnInfo_New(cnxn);
+ if (info != 0 && hash.IsValid())
+ PyDict_SetItem(map_hash_to_info, hash, info);
+
+ return info;
+}
+
+
+PyTypeObject CnxnInfoType =
+{
+ PyObject_HEAD_INIT(0)
+ 0, // ob_size
+ "pyodbc.CnxnInfo", // tp_name
+ sizeof(CnxnInfo), // tp_basicsize
+ 0, // tp_itemsize
+ 0, // destructor tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+};
View
@@ -0,0 +1,38 @@
+
+// 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.
+
+#ifndef CNXNINFO_H
+#define CNXNINFO_H
+
+struct Connection;
+extern PyTypeObject CnxnInfoType;
+
+struct CnxnInfo
+{
+ PyObject_HEAD
+
+ // The description of these fields is in the connection structure.
+
+ char odbc_major;
+ char odbc_minor;
+
+ bool supports_describeparam;
+ int datetime_precision;
+};
+
+void CnxnInfo_init();
+
+// Looks-up or creates a CnxnInfo object for the given connection string. The connection string can be a Unicode or
+// String object.
+
+PyObject* GetConnectionInfo(PyObject* pConnectionString, Connection* cnxn);
+
+#endif // CNXNINFO_H
View
@@ -14,6 +14,8 @@
#include "cursor.h"
#include "pyodbcmodule.h"
#include "errors.h"
+#include "wrapper.h"
+#include "cnxninfo.h"
static char connection_doc[] =
"Connection objects manage connections to the database.\n"
@@ -171,13 +173,9 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi)
return 0;
}
- cnxn->hdbc = hdbc;
- cnxn->searchescape = 0;
- cnxn->odbc_major = 3;
- cnxn->odbc_minor = 50;
- cnxn->nAutoCommit = fAutoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
- cnxn->supports_describeparam = false;
- cnxn->datetime_precision = 19; // default: "yyyy-mm-dd hh:mm:ss"
+ cnxn->hdbc = hdbc;
+ cnxn->nAutoCommit = fAutoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
+ cnxn->searchescape = 0;
//
// Initialize autocommit mode.
@@ -202,43 +200,19 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi)
// Gather connection-level information we'll need later.
//
- // FUTURE: Measure performance here. Consider caching by connection string if necessary.
+ Object info(GetConnectionInfo(pConnectString, cnxn));
- char szVer[20];
- SQLSMALLINT cch = 0;
- if (SQL_SUCCEEDED(SQLGetInfo(cnxn->hdbc, SQL_DRIVER_ODBC_VER, szVer, _countof(szVer), &cch)))
+ if (!info.IsValid())
{
- char* dot = strchr(szVer, '.');
- if (dot)
- {
- *dot = '\0';
- cnxn->odbc_major=(char)atoi(szVer);
- cnxn->odbc_minor=(char)atoi(dot + 1);
- }
- }
-
- char szYN[2];
- if (SQL_SUCCEEDED(SQLGetInfo(cnxn->hdbc, SQL_DESCRIBE_PARAMETER, szYN, _countof(szYN), &cch)))
- {
- cnxn->supports_describeparam = szYN[0] == 'Y';
+ Py_DECREF(cnxn);
+ return 0;
}
- // What is the datetime precision? This unfortunately requires a cursor (HSTMT).
-
- HSTMT hstmt = 0;
- if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, cnxn->hdbc, &hstmt)))
- {
- if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_TYPE_TIMESTAMP)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
- {
- SQLINTEGER columnsize;
- if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
- {
- cnxn->datetime_precision = columnsize;
- }
- }
-
- SQLFreeStmt(hstmt, SQL_CLOSE);
- }
+ CnxnInfo* p = (CnxnInfo*)info.Get();
+ cnxn->odbc_major = p->odbc_major;
+ cnxn->odbc_minor = p->odbc_minor;
+ cnxn->supports_describeparam = p->supports_describeparam;
+ cnxn->datetime_precision = p->datetime_precision;
return reinterpret_cast<PyObject*>(cnxn);
}
Oops, something went wrong.

0 comments on commit 04d8111

Please sign in to comment.