Skip to content

Commit

Permalink
Added SQL Server 2008 date/time extensions support. Now caches connec…
Browse files Browse the repository at this point in the history
…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 04d8111
Show file tree
Hide file tree
Showing 15 changed files with 393 additions and 165 deletions.
9 changes: 9 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include src\*.h
include src\*.cpp
include tests\*
include README.txt
include *.txt
prune setup.cfg

include web\*
Expand Down
30 changes: 23 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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')

Expand All @@ -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
) ],
Expand All @@ -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]).
Expand Down
172 changes: 172 additions & 0 deletions src/cnxninfo.cpp
Original file line number Diff line number Diff line change
@@ -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
};
38 changes: 38 additions & 0 deletions src/cnxninfo.h
Original file line number Diff line number Diff line change
@@ -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
54 changes: 14 additions & 40 deletions src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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);
}
Expand Down
Loading

0 comments on commit 04d8111

Please sign in to comment.