Permalink
Browse files

Issue 91: decimal fix; Completely reworked parameters; added leakcheck

Fixed issue 91 - handling of decimals was incorrect (off by 1).  Added lots of SQL Server unit
tests.

To properly fix this, reworked and simplified parameter binding.  Instead of separating the
memory requirement and the actual memory preparation, it is now performed in 1 step and stored
in the new ParamInfo structures.

I added a lot of optional malloc calls (not usually used), so I added a PYODBC_LEAK_CHECK
parameter which causes pyodbc_malloc and pyodbc_free to track allocations.  This is not thread
safe and is intended only for troubleshooting.
  • Loading branch information...
mkleehammer committed Sep 5, 2010
1 parent 83bd0ff commit 523aed0ec8ad5b4b948623fabf644fc90b1a15a8
Showing with 645 additions and 636 deletions.
  1. +6 −0 setup.py
  2. +8 −8 src/connection.cpp
  3. +5 −5 src/cursor.cpp
  4. +37 −1 src/cursor.h
  5. +3 −3 src/getdata.cpp
  6. +444 −607 src/params.cpp
  7. +10 −0 src/pyodbc.h
  8. +89 −0 src/pyodbcdbg.cpp
  9. +14 −0 src/pyodbcmodule.cpp
  10. +4 −7 src/row.cpp
  11. +18 −3 src/sqlwchar.cpp
  12. +6 −0 src/sqlwchar.h
  13. +1 −2 tests/sqlservertests.py
View
@@ -81,6 +81,12 @@ def main():
except ValueError:
pass
+ try:
+ sys.argv.remove('--leak-check')
+ macros.append(('PYODBC_LEAK_CHECK', 1))
+ except ValueError:
+ pass
+
if exists('MANIFEST'):
os.remove('MANIFEST')
View
@@ -243,12 +243,12 @@ static void _clear_conv(Connection* cnxn)
{
if (cnxn->conv_count != 0)
{
- free(cnxn->conv_types);
+ pyodbc_free(cnxn->conv_types);
cnxn->conv_types = 0;
for (int i = 0; i < cnxn->conv_count; i++)
Py_XDECREF(cnxn->conv_funcs[i]);
- free(cnxn->conv_funcs);
+ pyodbc_free(cnxn->conv_funcs);
cnxn->conv_funcs = 0;
cnxn->conv_count = 0;
@@ -793,15 +793,15 @@ static bool _add_converter(Connection* cnxn, int sqltype, PyObject* func)
PyObject** oldfuncs = cnxn->conv_funcs;
int newcount = oldcount + 1;
- SQLSMALLINT* newtypes = (SQLSMALLINT*)malloc(sizeof(SQLSMALLINT) * newcount);
- PyObject** newfuncs = (PyObject**)malloc(sizeof(PyObject*) * newcount);
+ SQLSMALLINT* newtypes = (SQLSMALLINT*)pyodbc_malloc(sizeof(SQLSMALLINT) * newcount);
+ PyObject** newfuncs = (PyObject**)pyodbc_malloc(sizeof(PyObject*) * newcount);
if (newtypes == 0 || newfuncs == 0)
{
if (newtypes)
- free(newtypes);
+ pyodbc_free(newtypes);
if (newfuncs)
- free(newfuncs);
+ pyodbc_free(newfuncs);
PyErr_NoMemory();
return false;
}
@@ -820,8 +820,8 @@ static bool _add_converter(Connection* cnxn, int sqltype, PyObject* func)
memcpy(&newtypes[1], oldtypes, sizeof(int) * oldcount);
memcpy(&newfuncs[1], oldfuncs, sizeof(PyObject*) * oldcount);
- free(oldtypes);
- free(oldfuncs);
+ pyodbc_free(oldtypes);
+ pyodbc_free(oldfuncs);
}
return true;
View
@@ -377,7 +377,7 @@ free_results(Cursor* self, free_results_type free_statement)
if (self->colinfos)
{
- free(self->colinfos);
+ pyodbc_free(self->colinfos);
self->colinfos = 0;
}
@@ -589,7 +589,7 @@ PrepareResults(Cursor* cur, int cCols)
int i;
I(cur->colinfos == 0);
- cur->colinfos = (ColumnInfo*)malloc(sizeof(ColumnInfo) * cCols);
+ cur->colinfos = (ColumnInfo*)pyodbc_malloc(sizeof(ColumnInfo) * cCols);
if (cur->colinfos == 0)
{
PyErr_NoMemory();
@@ -600,7 +600,7 @@ PrepareResults(Cursor* cur, int cCols)
{
if (!InitColumnInfo(cur, (SQLSMALLINT)(i + 1), &cur->colinfos[i]))
{
- free(cur->colinfos);
+ pyodbc_free(cur->colinfos);
cur->colinfos = 0;
return false;
}
@@ -981,7 +981,7 @@ Cursor_fetch(Cursor* cur)
field_count = PyTuple_GET_SIZE(cur->description);
- apValues = (PyObject**)malloc(sizeof(PyObject*) * field_count);
+ apValues = (PyObject**)pyodbc_malloc(sizeof(PyObject*) * field_count);
if (apValues == 0)
return PyErr_NoMemory();
@@ -2100,7 +2100,7 @@ Cursor_New(Connection* cnxn)
cur->pPreparedSQL = 0;
cur->paramcount = 0;
cur->paramtypes = 0;
- cur->paramdata = 0;
+ cur->paramInfos = 0;
cur->colinfos = 0;
cur->arraysize = 1;
cur->rowcount = -1;
View
@@ -34,6 +34,42 @@ struct ColumnInfo
bool is_unsigned;
};
+struct ParamInfo
+{
+ // The following correspond to the SQLBindParameter parameters.
+ SQLSMALLINT ValueType;
+ SQLSMALLINT ParameterType;
+ SQLULEN ColumnSize;
+ SQLSMALLINT DecimalDigits;
+
+ // If it is possible to bind into the parameter itself (ANSI string), this points into the Python object and must
+ // not be modified or freed. Otherwise, this is memory allocated with 'malloc' specifically for this parameter and
+ // should be freed after the call.
+ SQLPOINTER ParameterValuePtr;
+
+ SQLLEN BufferLength;
+ SQLLEN StrLen_or_Ind;
+
+ // Optional Python object if we converted from the original parameter to one (e.g. Decimal to String). If
+ // non-zero, ParameterValuePtr will point into this and allocated will be zero.
+ PyObject* temp;
+
+ // The amount of memory allocated (bytes) if binding into the original parameter or a temporary Python object was
+ // not posssible. Otherwise zero.
+ Py_ssize_t allocated;
+
+ // Optional data. If used, ParameterValuePtr will point into this.
+ union
+ {
+ unsigned char ch;
+ long l;
+ INT64 i64;
+ double dbl;
+ TIMESTAMP_STRUCT timestamp;
+ DATE_STRUCT date;
+ TIME_STRUCT time;
+ } Data;
+};
struct Cursor
{
@@ -67,7 +103,7 @@ struct Cursor
//
// Even if the same SQL statement is executed twice, the parameter bindings are redone from scratch since we try to
// bind into the Python objects directly.
- byte* paramdata;
+ ParamInfo* paramInfos;
//
// Result Information
View
@@ -79,7 +79,7 @@ class DataBuffer
}
else
{
- free(buffer);
+ pyodbc_free(buffer);
}
}
}
@@ -135,7 +135,7 @@ class DataBuffer
else
{
// We're Unicode, but SQLWCHAR and Py_UNICODE don't match, so maintain our own SQLWCHAR buffer.
- buffer = (char*)malloc(newSize);
+ buffer = (char*)pyodbc_malloc(newSize);
}
if (buffer == 0)
@@ -215,7 +215,7 @@ class DataBuffer
PyObject* result = PyUnicode_FromSQLWCHAR((const SQLWCHAR*)buffer, bytesUsed / element_size);
if (result == 0)
return false;
- free(buffer);
+ pyodbc_free(buffer);
buffer = 0;
return result;
}
Oops, something went wrong.

0 comments on commit 523aed0

Please sign in to comment.