Skip to content

Commit

Permalink
Fix decimal read bug. Fix SQLGetTypeInfo param. Better guess of Curso…
Browse files Browse the repository at this point in the history
…r.description types.

Fixes #126.

Because drivers are so bad about binary decimals pyodbc reads them as text.  I forgot to apply
the configured encodings to these.

This seems like a lot of minor changes at once.  I fixed the gettypeinfo bug and found the
decimal crash during testing.  Unfortunately it took forever to actually find the cause and I
touched a lot of files.

Tweaked the warnings in the VC builds.

Removed Python 2.4 support code in CnxnInfo.

Watch for errors during initialization of CnxnInfo.

Fix typo SQL_CHAR to SQL_C_CHAR when connecting via ANSI.

Reorganized encoding conditionals while reviewing code.  A little duplication between 2.7 and
3.x seems better than condensed but complicated code.
  • Loading branch information
mkleehammer committed Jan 19, 2017
1 parent c5359c5 commit 6d9f00a
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 259 deletions.
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ def get_compiler_settings(version_str):
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
Expand Down
39 changes: 13 additions & 26 deletions src/cnxninfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
//
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.
Expand All @@ -28,12 +27,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)
Expand All @@ -45,27 +47,12 @@ static PyObject* GetHash(PyObject* p)
p = bytes.Get();
#endif

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);
}
Object hash(PyObject_CallMethod(hashlib, "new", "s", "sha1"));
if (!hash.IsValid())
return 0;

return 0;
PyObject_CallMethodObjArgs(hash, update, p, 0);
return PyObject_CallMethod(hash, "hexdigest", 0);
}


Expand Down
2 changes: 1 addition & 1 deletion src/cnxninfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
52 changes: 27 additions & 25 deletions src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou

if (!fAnsi)
{
// I want to call the W version when possible since the driver uses it
// as an indication that we can handle Unicode. We are going to use the
// same unicode ending as we do for binding parameters.
// 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)
Expand All @@ -100,7 +100,7 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou
return true;
}

SQLWChar ch(pConnectString, SQL_CHAR, encoding, "utf-8");
SQLWChar ch(pConnectString, SQL_C_CHAR, encoding, "utf-8");
Py_BEGIN_ALLOW_THREADS
ret = SQLDriverConnect(hdbc, 0, (SQLCHAR*)ch.value(), (SQLSMALLINT)ch.charlen(), 0, 0, 0, SQL_DRIVER_NOPROMPT);
Py_END_ALLOW_THREADS
Expand Down Expand Up @@ -346,7 +346,7 @@ static PyObject* Connection_set_attr(PyObject* self, PyObject* args)

SQLRETURN ret;
Py_BEGIN_ALLOW_THREADS
ret = SQLSetConnectAttr(cnxn->hdbc, id, (SQLPOINTER)(uintptr_t)value, SQL_IS_INTEGER);
ret = SQLSetConnectAttr(cnxn->hdbc, id, (SQLPOINTER)(intptr_t)value, SQL_IS_INTEGER);
Py_END_ALLOW_THREADS

if (!SQL_SUCCEEDED(ret))
Expand Down Expand Up @@ -376,19 +376,17 @@ 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;
cnxn->hdbc = SQL_NULL_HANDLE;
Py_BEGIN_ALLOW_THREADS
if (cnxn->nAutoCommit == SQL_AUTOCOMMIT_OFF)
SQLEndTran(SQL_HANDLE_DBC, cnxn->hdbc, SQL_ROLLBACK);
SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_ROLLBACK);

SQLDisconnect(cnxn->hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, cnxn->hdbc);
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
Py_END_ALLOW_THREADS

cnxn->hdbc = SQL_NULL_HANDLE;
}

Py_XDECREF(cnxn->searchescape);
Expand Down Expand Up @@ -649,7 +647,7 @@ static PyObject* Connection_getinfo(PyObject* self, PyObject* args)
}

if (i == _countof(aInfoTypes))
return RaiseErrorV(0, ProgrammingError, "Invalid getinfo value: %d", infotype);
return RaiseErrorV(0, ProgrammingError, "Unsupported getinfo value: %d", infotype);

char szBuffer[0x1000];
SQLSMALLINT cch = 0;
Expand Down Expand Up @@ -1029,7 +1027,7 @@ static void NormalizeCodecName(const char* src, char* dest, size_t cbDest)
// (Same as _Py_normalize_encoding which is not public.) It also wraps the value with
// pipes so we can search with it.
//
// utf_8 --> |utf-8|
// UTF_8 --> |utf-8|
//
// This is an internal function - it will truncate so you should use a buffer bigger than
// anything you expect to search for.
Expand All @@ -1041,8 +1039,6 @@ static void NormalizeCodecName(const char* src, char* dest, size_t cbDest)

while (*src && pch < pchLast)
{
// TODO: Py_ISUPPEr and Py_TOLOWER aren't found when linking on Windows
// Python 3.5.
if (isupper(*src))
{
*pch++ = (char)tolower(*src++);
Expand Down Expand Up @@ -1079,20 +1075,28 @@ static bool SetTextEncCommon(TextEnc& enc, const char* encoding, int ctype, bool
NormalizeCodecName(encoding, lower, sizeof(lower));

#if PY_MAJOR_VERSION < 3
// Give a better error message for 'raw' than "not a registered codec". It is never
// registered.
if (strcmp(lower, "|raw|") == 0 && !allow_raw)
if (strcmp(lower, "|raw|") == 0)
{
PyErr_Format(PyExc_ValueError, "Raw codec is only allowed for str / SQL_CHAR");
if (!allow_raw)
{
// Give a better error message for 'raw' than "not a registered codec". It is never
// registered.
PyErr_Format(PyExc_ValueError, "Raw codec is only allowed for str / SQL_CHAR");
return false;
}
}
else if (!PyCodec_KnownEncoding(encoding))
{
PyErr_Format(PyExc_ValueError, "not a registered codec: '%s'", encoding);
return false;
}
#endif

if (!PyCodec_KnownEncoding(encoding) && (!allow_raw || strcmp(lower, "|raw|") != 0))
#else
if (!PyCodec_KnownEncoding(encoding))
{
PyErr_Format(PyExc_ValueError, "not a registered codec: '%s'", encoding);
return false;
}
#endif

if (ctype != 0 && ctype != SQL_WCHAR && ctype != SQL_CHAR)
{
Expand Down Expand Up @@ -1230,8 +1234,6 @@ static PyObject* Connection_setdecoding(PyObject* self, PyObject* args, PyObject

if (toObj)
{
// Type objects are classes so they should be the same object. (I'm not
// sure if there is a proper way to do this in Python.)
if (IsUnicodeType(toObj))
to = TO_UNICODE;
else if (IsStringType(toObj))
Expand Down
Loading

0 comments on commit 6d9f00a

Please sign in to comment.