Permalink
Browse files

Reworked Unicode; changed version format; more Py_ssize_t

Reworked Unicode support, properly differentiating between SQLWCHAR on the different platforms.
This should fix a lot of the OS/X problems and perhaps Linux UCS4 build problems.

Version format now includes the branch name if not 'master' or v<version>, allowing custom
builds to be identified, such as v2unicode-2.1.8-beta03.

Also tested with 64-bit Windows builds, so more Py_ssize_t warnings were found and corrected.

Created TRACE macro to replace the #ifdefs sprinkled through the code.
  • Loading branch information...
1 parent fc71271 commit 691b6ba73a641823fe6fb1f9f83c49596834d0f7 @mkleehammer committed Aug 26, 2010
Showing with 370 additions and 128 deletions.
  1. +10 −8 setup.py
  2. +9 −0 src/cnxninfo.cpp
  3. +2 −0 src/cnxninfo.h
  4. +10 −14 src/connection.cpp
  5. +1 −0 src/connection.h
  6. +32 −27 src/cursor.cpp
  7. +2 −3 src/errors.cpp
  8. +32 −26 src/getdata.cpp
  9. +64 −28 src/params.cpp
  10. +10 −4 src/pyodbc.h
  11. +12 −0 src/pyodbcdbg.cpp
  12. +12 −16 src/pyodbcmodule.cpp
  13. +1 −1 src/row.cpp
  14. +1 −1 src/row.h
  15. +118 −0 src/sqlwchar.cpp
  16. +54 −0 src/sqlwchar.h
View
18 setup.py
@@ -42,12 +42,8 @@ def main():
extra_link_args = None
if os.name == 'nt':
- # if not '--compiler=mingw32' in sys.argv:
- # # Windows native
- # files.append(join('src', 'pyodbc.rc'))
- # extra_compile_args = ['/W4']
libraries.append('odbc32')
-
+ # extra_compile_args = ['/W4']
# extra_compile_args = ['/W4', '/Zi', '/Od']
# extra_link_args = ['/DEBUG']
@@ -69,7 +65,7 @@ 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)]
+ 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
# command.
@@ -81,7 +77,7 @@ def main():
try:
sys.argv.remove('--trace')
- macros.append(('TRACE_ALL', 1))
+ macros.append(('PYODBC_TRACE', 1))
except ValueError:
pass
@@ -195,7 +191,13 @@ def _get_version_git():
if numbers[-1] != OFFICIAL_BUILD:
# This is a beta of the next micro release, so increment the micro number to reflect this.
numbers[-2] += 1
- name = '%s.%s.%s-beta%s' % tuple(numbers)
+ name = '%s.%s.%s-beta%02d' % tuple(numbers)
+
+ n, result = getoutput('git branch')
+ branch = re.search(r'\* (\w+)', result).group(1)
+ if branch != 'master' and not re.match('^v\d+$', branch):
+ name = branch + '-' + name
+
return name, numbers
View
9 src/cnxninfo.cpp
@@ -106,6 +106,7 @@ static PyObject* CnxnInfo_New(Connection* cnxn)
// These defaults are tiny, but are necessary for Access.
p->varchar_maxlength = 255;
+ p->wvarchar_maxlength = 255;
p->binary_maxlength = 510;
HSTMT hstmt = 0;
@@ -128,6 +129,14 @@ static PyObject* CnxnInfo_New(Connection* cnxn)
SQLFreeStmt(hstmt, SQL_CLOSE);
}
+ 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;
+
+ SQLFreeStmt(hstmt, SQL_CLOSE);
+ }
+
if (SQL_SUCCEEDED(SQLGetTypeInfo(hstmt, SQL_BINARY)) && SQL_SUCCEEDED(SQLFetch(hstmt)))
{
if (SQL_SUCCEEDED(SQLGetData(hstmt, 3, SQL_INTEGER, &columnsize, sizeof(columnsize), 0)))
View
2 src/cnxninfo.h
@@ -27,7 +27,9 @@ struct CnxnInfo
bool supports_describeparam;
int datetime_precision;
+ // These are from SQLGetTypeInfo.column_size, so the char ones are in characters, not bytes.
int varchar_maxlength;
+ int wvarchar_maxlength;
int binary_maxlength;
};
View
24 src/connection.cpp
@@ -75,14 +75,14 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi)
if (PyUnicode_Check(pConnectString))
{
Py_UNICODE* p = PyUnicode_AS_UNICODE(pConnectString);
- for (int i = 0, c = PyUnicode_GET_SIZE(pConnectString); i <= c; i++)
- szConnectW[i] = (wchar_t)p[i];
+ for (Py_ssize_t i = 0, c = PyUnicode_GET_SIZE(pConnectString); i <= c; i++)
+ szConnectW[i] = (SQLWCHAR)p[i];
}
else
{
const char* p = PyString_AS_STRING(pConnectString);
- for (int i = 0, c = PyString_GET_SIZE(pConnectString); i <= c; i++)
- szConnectW[i] = (wchar_t)p[i];
+ for (Py_ssize_t i = 0, c = PyString_GET_SIZE(pConnectString); i <= c; i++)
+ szConnectW[i] = (SQLWCHAR)p[i];
}
Py_BEGIN_ALLOW_THREADS
@@ -107,7 +107,7 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi)
if (PyUnicode_Check(pConnectString))
{
Py_UNICODE* p = PyUnicode_AS_UNICODE(pConnectString);
- for (int i = 0, c = PyUnicode_GET_SIZE(pConnectString); i <= c; i++)
+ for (Py_ssize_t i = 0, c = PyUnicode_GET_SIZE(pConnectString); i <= c; i++)
{
if (p[i] > 0xFF)
{
@@ -213,9 +213,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
}
}
-#ifdef TRACE_ALL
- printf("cnxn.new cnxn=%p hdbc=%d\n", cnxn, cnxn->hdbc);
-#endif
+ TRACE("cnxn.new cnxn=%p hdbc=%d\n", cnxn, cnxn->hdbc);
//
// Gather connection-level information we'll need later.
@@ -235,6 +233,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
cnxn->supports_describeparam = p->supports_describeparam;
cnxn->datetime_precision = p->datetime_precision;
cnxn->varchar_maxlength = p->varchar_maxlength;
+ cnxn->wvarchar_maxlength = p->wvarchar_maxlength;
cnxn->binary_maxlength = p->binary_maxlength;
return reinterpret_cast<PyObject*>(cnxn);
@@ -251,9 +250,8 @@ Connection_clear(Connection* cnxn)
{
// REVIEW: Release threads? (But make sure you zero out hdbc *first*!
-#ifdef TRACE_ALL
- printf("cnxn.clear cnxn=%p hdbc=%d\n", cnxn, cnxn->hdbc);
-#endif
+ TRACE("cnxn.clear cnxn=%p hdbc=%d\n", cnxn, cnxn->hdbc);
+
Py_BEGIN_ALLOW_THREADS
if (cnxn->nAutoCommit == SQL_AUTOCOMMIT_OFF)
SQLEndTran(SQL_HANDLE_DBC, cnxn->hdbc, SQL_ROLLBACK);
@@ -571,9 +569,7 @@ Connection_endtrans(PyObject* self, PyObject* args, SQLSMALLINT type)
if (!cnxn)
return 0;
-#ifdef TRACE_ALL
- printf("%s: cnxn=%p hdbc=%d\n", (type == SQL_COMMIT) ? "commit" : "rollback", cnxn, cnxn->hdbc);
-#endif
+ TRACE("%s: cnxn=%p hdbc=%d\n", (type == SQL_COMMIT) ? "commit" : "rollback", cnxn, cnxn->hdbc);
SQLRETURN ret;
Py_BEGIN_ALLOW_THREADS
View
1 src/connection.h
@@ -49,6 +49,7 @@ struct Connection
// These are copied from cnxn info for performance and convenience.
int varchar_maxlength;
+ int wvarchar_maxlength;
int binary_maxlength;
};
View
59 src/cursor.cpp
@@ -23,6 +23,7 @@
#include "errors.h"
#include "getdata.h"
#include "dbspecific.h"
+#include "sqlwchar.h"
enum
{
@@ -267,9 +268,8 @@ create_name_map(Cursor* cur, SQLSMALLINT field_count, bool lower)
goto done;
}
-#ifdef TRACE_ALL
- printf("Col %d: type=%d colsize=%d\n", (i+1), (int)nDataType, (int)nColSize);
-#endif
+ TRACE("Col %d: type=%d colsize=%d\n", (i+1), (int)nDataType, (int)nColSize);
+
if (lower)
_strlwr((char*)name);
@@ -695,15 +695,17 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
while (ret == SQL_NEED_DATA)
{
- // We have bound a `buffer` object using SQL_DATA_AT_EXEC, so ODBC is asking us for the data now. We gave the
- // buffer pointer to ODBC in SQLBindParameter -- SQLParamData below gives the pointer back to us.
+ // We have bound a PyObject* using SQL_LEN_DATA_AT_EXEC, so ODBC is asking us for the data now. We gave the
+ // PyObject pointer to ODBC in SQLBindParameter -- SQLParamData below gives the pointer back to us.
szLastFunction = "SQLParamData";
PyObject* pParam;
Py_BEGIN_ALLOW_THREADS
ret = SQLParamData(cur->hstmt, (SQLPOINTER*)&pParam);
Py_END_ALLOW_THREADS
+ TRACE("SQLParamData() --> %d\n", ret);
+
if (ret == SQL_NEED_DATA)
{
szLastFunction = "SQLPutData";
@@ -718,22 +720,27 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
while (it.Next(pb, cb))
{
Py_BEGIN_ALLOW_THREADS
- SQLPutData(cur->hstmt, pb, cb);
+ ret = SQLPutData(cur->hstmt, pb, cb);
Py_END_ALLOW_THREADS
+ if (!SQL_SUCCEEDED(ret))
+ return RaiseErrorFromHandle("SQLPutData", cur->cnxn->hdbc, cur->hstmt);
}
}
else if (PyUnicode_Check(pParam))
{
- // REVIEW: This will fail if PyUnicode != wchar_t?
- Py_UNICODE* p = PyUnicode_AS_UNICODE(pParam);
- SQLLEN offset = 0;
- SQLLEN cb = (SQLLEN)PyUnicode_GET_SIZE(pParam);
- while (offset < cb)
+ SQLWChar wchar(pParam); // Will convert to SQLWCHAR if necessary.
+
+ Py_ssize_t offset = 0; // in characters
+ Py_ssize_t length = wchar.size(); // in characters
+
+ while (offset < length)
{
- SQLLEN remaining = min(cur->cnxn->varchar_maxlength, cb - offset);
+ SQLLEN remaining = min(cur->cnxn->varchar_maxlength, length - offset);
Py_BEGIN_ALLOW_THREADS
- SQLPutData(cur->hstmt, &p[offset], remaining * 2);
+ ret = SQLPutData(cur->hstmt, (SQLPOINTER)wchar[offset], remaining * sizeof(SQLWCHAR));
Py_END_ALLOW_THREADS
+ if (!SQL_SUCCEEDED(ret))
+ return RaiseErrorFromHandle("SQLPutData", cur->cnxn->hdbc, cur->hstmt);
offset += remaining;
}
}
@@ -745,12 +752,17 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
while (offset < cb)
{
SQLLEN remaining = min(cur->cnxn->varchar_maxlength, cb - offset);
+ TRACE("SQLPutData [%d] (%d) %s\n", offset, remaining, &p[offset]);
Py_BEGIN_ALLOW_THREADS
- SQLPutData(cur->hstmt, (SQLPOINTER)&p[offset], remaining);
+ ret = SQLPutData(cur->hstmt, (SQLPOINTER)&p[offset], remaining);
Py_END_ALLOW_THREADS
+ if (!SQL_SUCCEEDED(ret))
+ return RaiseErrorFromHandle("SQLPutData", cur->cnxn->hdbc, cur->hstmt);
offset += remaining;
}
}
+
+ ret = SQL_NEED_DATA;
}
}
@@ -771,12 +783,12 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
Py_BEGIN_ALLOW_THREADS
ret = SQLRowCount(cur->hstmt, &cRows);
Py_END_ALLOW_THREADS
+ if (!SQL_SUCCEEDED(ret))
+ return RaiseErrorFromHandle("SQLRowCount", cur->cnxn->hdbc, cur->hstmt);
cur->rowcount = (int)cRows;
-#ifdef TRACE_ALL
- printf("SQLRowCount: %d\n", cRows);
-#endif
+ TRACE("SQLRowCount: %d\n", cRows);
SQLSMALLINT cCols = 0;
Py_BEGIN_ALLOW_THREADS
@@ -790,9 +802,7 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
return RaiseErrorFromHandle("SQLNumResultCols", cur->cnxn->hdbc, cur->hstmt);
}
-#ifdef TRACE_ALL
- printf("SQLNumResultCols: %d\n", cCols);
-#endif
+ TRACE("SQLNumResultCols: %d\n", cCols);
if (cur->cnxn->hdbc == SQL_NULL_HANDLE)
{
@@ -946,7 +956,7 @@ Cursor_fetch(Cursor* cur)
// exception is set and zero is returned. (To differentiate between the last two, use PyErr_Occurred.)
SQLRETURN ret = 0;
- int field_count, i;
+ Py_ssize_t field_count, i;
PyObject** apValues;
Py_BEGIN_ALLOW_THREADS
@@ -1624,9 +1634,6 @@ Cursor_nextset(PyObject* self, PyObject* args)
if (ret == SQL_NO_DATA)
{
- //#ifdef TRACE_ALL
- //printf("Cursor_nextset: SQL_NO_DATA\r\n");
- //#endif
free_results(cur, FREE_STATEMENT);
Py_RETURN_FALSE;
}
@@ -2124,9 +2131,7 @@ Cursor_New(Connection* cnxn)
}
}
-#ifdef TRACE_ALL
- printf("cursor.new cnxn=%p hdbc=%d cursor=%p hstmt=%d\n", (Connection*)cur->cnxn, ((Connection*)cur->cnxn)->hdbc, cur, cur->hstmt);
-#endif
+ TRACE("cursor.new cnxn=%p hdbc=%d cursor=%p hstmt=%d\n", (Connection*)cur->cnxn, ((Connection*)cur->cnxn)->hdbc, cur, cur->hstmt);
}
return cur;
View
5 src/errors.cpp
@@ -171,9 +171,8 @@ PyObject* RaiseErrorFromHandle(const char* szFunction, HDBC hdbc, HSTMT hstmt)
PyObject* GetErrorFromHandle(const char* szFunction, HDBC hdbc, HSTMT hstmt)
{
-#ifdef TRACE_ALL
- printf("In RaiseError!\n");
-#endif
+ TRACE("In RaiseError(%s)!\n", szFunction);
+
// Creates and returns an exception from ODBC error information.
//
// ODBC can generate a chain of errors which we concatenate into one error message. We use the SQLSTATE from the
View
58 src/getdata.cpp
@@ -8,6 +8,7 @@
#include "connection.h"
#include "errors.h"
#include "dbspecific.h"
+#include "sqlwchar.h"
void GetData_init()
{
@@ -24,30 +25,32 @@ class DataBuffer
//
// 1) Binary, which is a simple array of 8-bit bytes.
// 2) ANSI text, which is an array of chars with a NULL terminator.
- // 3) Unicode text, which is an array of wchar_ts with a NULL terminator.
+ // 3) Unicode text, which is an array of SQLWCHARs with a NULL terminator.
//
- // When dealing with Unicode, there are two widths we have to be aware of: (1) wchar_t and (2) Py_UNICODE. If
+ // When dealing with Unicode, there are two widths we have to be aware of: (1) SQLWCHAR and (2) Py_UNICODE. If
// these are the same we can use a PyUnicode object so we don't have to allocate our own buffer and then the
// Unicode object. If they are not the same (e.g. OS/X where wchar_t-->4 Py_UNICODE-->2) then we need to maintain
- // our own buffer and pass it to the PyUnicode object later.
+ // our own buffer and pass it to the PyUnicode object later. Many Linux distros are now using UCS4, so Py_UNICODE
+ // will be larger than SQLWCHAR.
//
// To reduce heap fragmentation, we perform the initial read into an array on the stack since we don't know the
- // length of the data. If the data doesn't fit, this class then allocates new memory.
+ // length of the data. If the data doesn't fit, this class then allocates new memory. If the first read gives us
+ // the length, then we create a Python object of the right size and read into its memory.
private:
SQLSMALLINT dataType;
char* buffer;
Py_ssize_t bufferSize; // How big is the buffer.
- int bytesUsed; // How many elements have been read into the buffer?
+ int bytesUsed; // How many elements have been read into the buffer?
PyObject* bufferOwner; // If possible, we bind into a PyString or PyUnicode object.
int element_size; // How wide is each character: ASCII/ANSI -> 1, Unicode -> 2 or 4, binary -> 1
bool usingStack; // Is buffer pointing to the initial stack buffer?
public:
- int null_size; // How much room to add for null terminator: binary -> 0, other -> same as a element_size
+ int null_size; // How much room, in bytes, to add for null terminator: binary -> 0, other -> same as a element_size
DataBuffer(SQLSMALLINT dataType, char* stackBuffer, SQLLEN stackBufferSize)
{
@@ -56,7 +59,7 @@ class DataBuffer
this->dataType = dataType;
- element_size = (dataType == SQL_C_WCHAR) ? sizeof(wchar_t) : sizeof(char);
+ element_size = (dataType == SQL_C_WCHAR) ? sizeof(SQLWCHAR) : sizeof(char);
null_size = (dataType == SQL_C_BINARY) ? 0 : element_size;
buffer = stackBuffer;
@@ -98,11 +101,14 @@ class DataBuffer
void AddUsed(SQLLEN cbRead)
{
I(cbRead <= GetRemaining());
- bytesUsed += cbRead;
+ bytesUsed += (int)cbRead;
}
bool AllocateMore(SQLLEN cbAdd)
{
+ // cbAdd
+ // The number of bytes (cb --> count of bytes) to add.
+
if (cbAdd == 0)
return true;
@@ -120,23 +126,23 @@ class DataBuffer
bufferOwner = PyString_FromStringAndSize(0, newSize);
buffer = bufferOwner ? PyString_AS_STRING(bufferOwner) : 0;
}
- else if (sizeof(wchar_t) == Py_UNICODE_SIZE)
+ else if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE)
{
// Allocate directly into a Unicode object.
bufferOwner = PyUnicode_FromUnicode(0, newSize / element_size);
buffer = bufferOwner ? (char*)PyUnicode_AsUnicode(bufferOwner) : 0;
}
else
{
- // We're Unicode, but wchar_t and Py_UNICODE don't match, so maintain our own wchar_t buffer.
+ // We're Unicode, but SQLWCHAR and Py_UNICODE don't match, so maintain our own SQLWCHAR buffer.
buffer = (char*)malloc(newSize);
}
- usingStack = false;
-
if (buffer == 0)
return false;
+ usingStack = false;
+
memcpy(buffer, stackBuffer, bufferSize);
bufferSize = newSize;
return true;
@@ -179,10 +185,10 @@ class DataBuffer
if (dataType == SQL_C_CHAR || dataType == SQL_C_BINARY)
return PyString_FromStringAndSize(buffer, bytesUsed);
- if (sizeof(wchar_t) == Py_UNICODE_SIZE)
+ if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE)
return PyUnicode_FromUnicode((const Py_UNICODE*)buffer, bytesUsed / element_size);
- return PyUnicode_FromWideChar((const wchar_t*)buffer, bytesUsed / element_size);
+ return PyUnicode_FromSQLWCHAR((const SQLWCHAR*)buffer, bytesUsed / element_size);
}
if (PyString_CheckExact(bufferOwner))
@@ -205,8 +211,8 @@ class DataBuffer
return tmp;
}
- // We have allocated our own wchar_t buffer and must now copy it to a Unicode object.
- PyObject* result = PyUnicode_FromWideChar((const wchar_t*)buffer, bytesUsed / element_size);
+ // We have allocated our own SQLWCHAR buffer and must now copy it to a Unicode object.
+ PyObject* result = PyUnicode_FromSQLWCHAR((const SQLWCHAR*)buffer, bytesUsed / element_size);
if (result == 0)
return false;
free(buffer);
@@ -216,14 +222,14 @@ class DataBuffer
};
static PyObject*
-GetDataString(Cursor* cur, int iCol)
+GetDataString(Cursor* cur, Py_ssize_t iCol)
{
// Returns a String or Unicode object for character and binary data.
// NULL terminator notes:
//
// * pinfo->column_size, from SQLDescribeCol, does not include a NULL terminator. For example, column_size for a
- // char(10) column would be 10. (Also, when dealing with wchar_t, it is the number of *characters*, not bytes.)
+ // char(10) column would be 10. (Also, when dealing with SQLWCHAR, it is the number of *characters*, not bytes.)
//
// * When passing a length to PyString_FromStringAndSize and similar Unicode functions, do not add the NULL
// terminator -- it will be added automatically. See objects/stringobject.c
@@ -361,7 +367,7 @@ GetDataBuffer(Cursor* cur, Py_ssize_t iCol)
}
static PyObject*
-GetDataDecimal(Cursor* cur, int iCol)
+GetDataDecimal(Cursor* cur, Py_ssize_t iCol)
{
// The SQL_NUMERIC_STRUCT support is hopeless (SQL Server ignores scale on input parameters and output columns), so
// we'll rely on the Decimal's string parsing. Unfortunately, the Decimal author does not pay attention to the
@@ -397,7 +403,7 @@ GetDataDecimal(Cursor* cur, int iCol)
//
// Note: cbFetched does not include the NULL terminator.
- for (int i = cbFetched - 1; i >=0; i--)
+ for (int i = (int)(cbFetched - 1); i >=0; i--)
{
if (sz[i] == chGroupSeparator || sz[i] == '$' || sz[i] == chCurrencySymbol)
{
@@ -414,7 +420,7 @@ GetDataDecimal(Cursor* cur, int iCol)
}
static PyObject*
-GetDataBit(Cursor* cur, int iCol)
+GetDataBit(Cursor* cur, Py_ssize_t iCol)
{
SQLCHAR ch;
SQLLEN cbFetched;
@@ -437,7 +443,7 @@ GetDataBit(Cursor* cur, int iCol)
}
static PyObject*
-GetDataLong(Cursor* cur, int iCol)
+GetDataLong(Cursor* cur, Py_ssize_t iCol)
{
ColumnInfo* pinfo = &cur->colinfos[iCol];
@@ -463,7 +469,7 @@ GetDataLong(Cursor* cur, int iCol)
}
static PyObject*
-GetDataLongLong(Cursor* cur, int iCol)
+GetDataLongLong(Cursor* cur, Py_ssize_t iCol)
{
ColumnInfo* pinfo = &cur->colinfos[iCol];
@@ -490,7 +496,7 @@ GetDataLongLong(Cursor* cur, int iCol)
}
static PyObject*
-GetDataDouble(Cursor* cur, int iCol)
+GetDataDouble(Cursor* cur, Py_ssize_t iCol)
{
double value;
SQLLEN cbFetched = 0;
@@ -509,7 +515,7 @@ GetDataDouble(Cursor* cur, int iCol)
}
static PyObject*
-GetSqlServerTime(Cursor* cur, int iCol)
+GetSqlServerTime(Cursor* cur, Py_ssize_t iCol)
{
SQL_SS_TIME2_STRUCT value;
@@ -530,7 +536,7 @@ GetSqlServerTime(Cursor* cur, int iCol)
}
static PyObject*
-GetDataTimestamp(Cursor* cur, int iCol)
+GetDataTimestamp(Cursor* cur, Py_ssize_t iCol)
{
TIMESTAMP_STRUCT value;
View
92 src/params.cpp
@@ -8,15 +8,16 @@
#include "wrapper.h"
#include "errors.h"
#include "dbspecific.h"
+#include "sqlwchar.h"
inline Connection* GetConnection(Cursor* cursor)
{
return (Connection*)cursor->cnxn;
}
-static int GetParamBufferSize(PyObject* param, Py_ssize_t iParam);
-static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam);
-static SQLSMALLINT GetParamType(Cursor* cur, int iParam);
+static Py_ssize_t GetParamBufferSize(Cursor* cur, PyObject* param, Py_ssize_t iParam);
+static bool BindParam(Cursor* cur, Py_ssize_t iParam, PyObject* param, byte** ppbParam);
+static SQLSMALLINT GetParamType(Cursor* cur, Py_ssize_t iParam);
static Py_ssize_t GetDecimalColumnSize(PyObject *p)
{
@@ -167,8 +168,9 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
}
else
{
+ SQLWChar sql(pSql);
Py_BEGIN_ALLOW_THREADS
- ret = SQLPrepareW(cur->hstmt, (SQLWCHAR*)PyUnicode_AsUnicode(pSql), SQL_NTS);
+ ret = SQLPrepareW(cur->hstmt, sql, SQL_NTS);
if (SQL_SUCCEEDED(ret))
{
szErrorFunc = "SQLNumParams";
@@ -211,19 +213,21 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
// we will bind directly into its memory. (We only use a vector so its destructor will free the memory.)
// We'll set aside one SQLLEN for each column to be used as the StrLen_or_IndPtr.
- int cb = 0;
+ Py_ssize_t cbParams = 0;
for (Py_ssize_t i = 0; i < cParams; i++)
{
- int cbT = GetParamBufferSize(params[i], i + 1) + sizeof(SQLLEN); // +1 to map to ODBC one-based index
+ Py_ssize_t cbT = GetParamBufferSize(cur, params[i], i + 1) + sizeof(SQLLEN); // +1 to map to ODBC one-based index
if (cbT < 0)
return 0;
- cb += cbT;
+ TRACE("* size(%d): %d\n", (i + 1), cbT);
+
+ cbParams += cbT;
}
- cur->paramdata = reinterpret_cast<byte*>(malloc(cb));
+ cur->paramdata = reinterpret_cast<byte*>(malloc(cbParams));
if (cur->paramdata == 0)
{
PyErr_NoMemory();
@@ -250,18 +254,29 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
for (Py_ssize_t i = 0; i < cParams; i++)
{
+ #ifdef PYODBC_TRACE
+ byte* pbBefore = pbParam;
+ #endif
+
if (!BindParam(cur, i + 1, params[i], &pbParam))
{
free(cur->paramdata);
cur->paramdata = 0;
return false;
}
+
+ #ifdef PYODBC_TRACE
+ TRACE("* used(%d): %d\n", (i + 1), (pbParam - pbBefore));
+ #endif
}
+ // We didn't use exactly the amount of memory allocated? GetParamBufferSize and BindParam are not in sync!
+ I(pbParam == cur->paramdata + cbParams);
+
return true;
}
-static SQLSMALLINT GetParamType(Cursor* cur, int iParam)
+static SQLSMALLINT GetParamType(Cursor* cur, Py_ssize_t iParam)
{
// Returns the ODBC type of the of given parameter.
//
@@ -312,21 +327,40 @@ static SQLSMALLINT GetParamType(Cursor* cur, int iParam)
}
-static int GetParamBufferSize(PyObject* param, Py_ssize_t iParam)
+static Py_ssize_t GetParamBufferSize(Cursor* cur, PyObject* param, Py_ssize_t iParam)
{
// Returns the size in bytes needed to hold the parameter in a format for binding, used to allocate the parameter
// buffer. (The value is not passed to ODBC. Values passed to ODBC are in BindParam.)
//
// If we can bind directly into the Python object (e.g., using PyString_AsString), zero is returned since no extra
- // memory is required. If the data will be provided at execution time (e.g. SQL_DATA_AT_EXEC), zero is returned
+ // memory is required. If the data will be provided at execution time (e.g. SQL_LEN_DATA_AT_EXEC), zero is returned
// since the parameter value is not stored at all. If the data type is not recognized, -1 is returned.
if (param == Py_None)
return 0;
- if (PyString_Check(param) || PyUnicode_Check(param))
+ if (PyString_Check(param))
return 0;
+ if (PyUnicode_Check(param))
+ {
+ if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE)
+ {
+ // We can bind directly into the parameter itself.
+ return 0;
+ }
+
+ if (PyUnicode_GET_SIZE(param) > cur->cnxn->wvarchar_maxlength)
+ {
+ // This is too long to pass, so we'll use SQLPutData to pass in chunks. We'll need to store the pointer to
+ // the object.
+ return 0;
+ }
+
+ // We are going to need to copy this buffer to convert from PyUNICODE to SQLWCHAR.
+ return PyUnicode_GET_SIZE(param) * sizeof(SQLWCHAR);
+ }
+
if (param == Py_True || param == Py_False)
return 1;
@@ -345,7 +379,7 @@ static int GetParamBufferSize(PyObject* param, Py_ssize_t iParam)
if (PyBuffer_Check(param))
{
// If the buffer has a single segment, we can bind directly to it, so we need 0 bytes. Otherwise, we'll use
- // SQL_DATA_AT_EXEC, so we still need 0 bytes.
+ // SQL_LEN_DATA_AT_EXEC, so we still need 0 bytes.
return 0;
}
@@ -363,14 +397,15 @@ static int GetParamBufferSize(PyObject* param, Py_ssize_t iParam)
return -1;
}
-#ifdef TRACE_ALL
#define _MAKESTR(n) case n: return #n
static const char* SqlTypeName(SQLSMALLINT n)
{
switch (n)
{
_MAKESTR(SQL_UNKNOWN_TYPE);
_MAKESTR(SQL_CHAR);
+ _MAKESTR(SQL_VARCHAR);
+ _MAKESTR(SQL_LONGVARCHAR);
_MAKESTR(SQL_NUMERIC);
_MAKESTR(SQL_DECIMAL);
_MAKESTR(SQL_INTEGER);
@@ -379,12 +414,17 @@ static const char* SqlTypeName(SQLSMALLINT n)
_MAKESTR(SQL_REAL);
_MAKESTR(SQL_DOUBLE);
_MAKESTR(SQL_DATETIME);
- _MAKESTR(SQL_VARCHAR);
+ _MAKESTR(SQL_WCHAR);
+ _MAKESTR(SQL_WVARCHAR);
+ _MAKESTR(SQL_WLONGVARCHAR);
_MAKESTR(SQL_TYPE_DATE);
_MAKESTR(SQL_TYPE_TIME);
_MAKESTR(SQL_TYPE_TIMESTAMP);
_MAKESTR(SQL_SS_TIME2);
_MAKESTR(SQL_SS_XML);
+ _MAKESTR(SQL_BINARY);
+ _MAKESTR(SQL_VARBINARY);
+ _MAKESTR(SQL_LONGVARBINARY);
}
return "unknown";
}
@@ -394,6 +434,7 @@ static const char* CTypeName(SQLSMALLINT n)
switch (n)
{
_MAKESTR(SQL_C_CHAR);
+ _MAKESTR(SQL_C_WCHAR);
_MAKESTR(SQL_C_LONG);
_MAKESTR(SQL_C_SHORT);
_MAKESTR(SQL_C_FLOAT);
@@ -435,9 +476,7 @@ static const char* CTypeName(SQLSMALLINT n)
return "unknown";
}
-#endif
-
-static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
+static bool BindParam(Cursor* cur, Py_ssize_t iParam, PyObject* param, byte** ppbParam)
{
// Called to bind a single parameter.
//
@@ -502,8 +541,8 @@ static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
}
else if (PyString_Check(param))
{
- char* pch = PyString_AS_STRING(param);
- int len = PyString_GET_SIZE(param);
+ char* pch = PyString_AS_STRING(param);
+ Py_ssize_t len = PyString_GET_SIZE(param);
if (len <= cur->cnxn->varchar_maxlength)
{
@@ -527,7 +566,7 @@ static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
else if (PyUnicode_Check(param))
{
Py_UNICODE* pch = PyUnicode_AsUnicode(param);
- int len = PyUnicode_GET_SIZE(param);
+ Py_ssize_t len = PyUnicode_GET_SIZE(param);
if (len <= cur->cnxn->wvarchar_maxlength)
{
@@ -673,8 +712,8 @@ static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
if (!str)
return false;
- char* pch = PyString_AS_STRING(str.Get());
- int len = PyString_GET_SIZE(str.Get());
+ char* pch = PyString_AS_STRING(str.Get());
+ Py_ssize_t len = PyString_GET_SIZE(str.Get());
*pcbValue = (SQLLEN)len;
@@ -699,7 +738,7 @@ static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
else if (PyBuffer_Check(param))
{
const char* pb;
- int cb = PyBuffer_GetMemory(param, &pb);
+ Py_ssize_t cb = PyBuffer_GetMemory(param, &pb);
if (cb != -1 && cb <= cur->cnxn->binary_maxlength)
{
@@ -734,10 +773,7 @@ static bool BindParam(Cursor* cur, int iParam, PyObject* param, byte** ppbParam)
return false;
}
- #ifdef TRACE_ALL
- printf("BIND: param=%d fCType=%d (%s) fSqlType=%d (%s) cbColDef=%d DecimalDigits=%d cbValueMax=%d *pcb=%d\n", iParam,
- fCType, CTypeName(fCType), fSqlType, SqlTypeName(fSqlType), cbColDef, decimalDigits, cbValueMax, pcbValue ? *pcbValue : 0);
- #endif
+ TRACE("BIND: param=%d fCType=%d (%s) fSqlType=%d (%s) cbColDef=%d DecimalDigits=%d cbValueMax=%d *pcb=%d\n", iParam, fCType, CTypeName(fCType), fSqlType, SqlTypeName(fSqlType), cbColDef, decimalDigits, cbValueMax, pcbValue ? *pcbValue : 0);
SQLRETURN ret = -1;
Py_BEGIN_ALLOW_THREADS
View
14 src/pyodbc.h
@@ -113,13 +113,12 @@ inline void _strlwr(char* name)
}
#endif
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
// Building an actual debug version of Python is so much of a pain that it never happens. I'm providing release-build
// versions of assertions.
-// REVIEW: Put these into the setup script command line (or setup.cfg)
-// #define PYODBC_ASSERT 1
-// #define TRACE_ALL 1
-
#ifdef PYODBC_ASSERT
#ifdef _MSC_VER
#include <crtdbg.h>
@@ -139,4 +138,11 @@ inline void _strlwr(char* name)
#define N(expr)
#endif
+#ifdef PYODBC_TRACE
+void __cdecl DebugTrace(const char* szFmt, ...);
+#else
+inline void __cdecl DebugTrace(const char* szFmt, ...) { UNUSED(szFmt); }
+#endif
+#define TRACE DebugTrace
+
#endif // pyodbc_h
View
12 src/pyodbcdbg.cpp
@@ -0,0 +1,12 @@
+
+#include "pyodbc.h"
+
+#ifdef PYODBC_TRACE
+void DebugTrace(const char* szFmt, ...)
+{
+ va_list marker;
+ va_start(marker, szFmt);
+ vprintf(szFmt, marker);
+ va_end(marker);
+}
+#endif
View
28 src/pyodbcmodule.cpp
@@ -32,15 +32,19 @@ _typeobject* OurTimeType = 0;
PyObject* pModule = 0;
static char module_doc[] =
- "A DB API 2.0 module for ODBC databases.\n"
+ "A database module for accessing databases via ODBC.\n"
"\n"
"This module conforms to the DB API 2.0 specification while providing\n"
"non-standard convenience features. Only standard Python data types are used\n"
"so additional DLLs are not required.\n"
"\n"
"Static Variables:\n\n"
"version\n"
- " The module version string in the format major.minor.revision\n"
+ " The module version string. Official builds will have a version in the format\n"
+ " `major.minor.revision`, such as 2.1.7. Beta versions will have -beta appended,\n"
+ " such as 2.1.8-beta03. (This would be a build before the official 2.1.8 release.)\n"
+ " Some special test builds will have a test name (the git branch name) prepended,\n"
+ " such as fixissue90-2.1.8-beta03.\n"
"\n"
"apilevel\n"
" The string constant '2.0' indicating this module supports DB API level 2.0.\n"
@@ -50,6 +54,7 @@ static char module_doc[] =
" This can be changed any time and affects queries executed after the change.\n"
" The default is False. This can be useful when database columns have\n"
" inconsistent capitalization.\n"
+ "\n"
"pooling\n"
" A Boolean indicating whether connection pooling is enabled. This is a\n"
" global (HENV) setting, so it can only be modified before the first\n"
@@ -276,7 +281,7 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs)
int fAnsi = 0; // force ansi
int fUnicodeResults = 0;
- int size = args ? PyTuple_Size(args) : 0;
+ Py_ssize_t size = args ? PyTuple_Size(args) : 0;
if (size > 1)
{
@@ -879,17 +884,8 @@ initpyodbc()
if (!CreateExceptions())
return;
- // The 'build' version number is a beta identifier. For example, if it is 7, then we are on beta7 of the
- // (major,minor.micro) version. On Windows, we poke these values into the DLL's version resource, so when we make
- // an official build (which come *after* the betas), we set the BUILD to 9999 so installers will know that it
- // should replace any installed betas. However, we obviously don't want to see these.
-
- PyObject* pVersion;
- if (PYODBC_BUILD == 9999)
- pVersion = PyString_FromFormat("%d.%d.%d", PYODBC_MAJOR, PYODBC_MINOR, PYODBC_MICRO);
- else
- pVersion = PyString_FromFormat("%d.%d.%d-beta%d", PYODBC_MAJOR, PYODBC_MINOR, PYODBC_MICRO, PYODBC_BUILD);
- PyModule_AddObject(pModule, "version", pVersion);
+ const char* szVersion = TOSTRING(PYODBC_VERSION);
+ PyModule_AddStringConstant(pModule, "version", szVersion);
PyModule_AddIntConstant(pModule, "threadsafety", 1);
PyModule_AddStringConstant(pModule, "apilevel", "2.0");
@@ -950,7 +946,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts)
// Creates a connection string from an optional existing connection string plus a dictionary of keyword value
// pairs. The keywords must be String objects and the values must be Unicode objects.
- int length = 0;
+ Py_ssize_t length = 0;
if (existing)
length = PyUnicode_GET_SIZE(existing) + 1; // + 1 to add a trailing
@@ -968,7 +964,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts)
return 0;
Py_UNICODE* buffer = PyUnicode_AS_UNICODE(result);
- int offset = 0;
+ Py_ssize_t offset = 0;
if (existing)
{
View
2 src/row.cpp
@@ -39,7 +39,7 @@ struct Row
#define Row_CheckExact(op) ((op)->ob_type == &RowType)
void
-FreeRowValues(int cValues, PyObject** apValues)
+FreeRowValues(Py_ssize_t cValues, PyObject** apValues)
{
if (apValues)
{
View
2 src/row.h
@@ -28,7 +28,7 @@ Row* Row_New(PyObject* description, PyObject* map_name_to_index, Py_ssize_t cVal
*
* apValues: The array of values. This can be NULL.
*/
-void FreeRowValues(int cValues, PyObject** apValues);
+void FreeRowValues(Py_ssize_t cValues, PyObject** apValues);
extern PyTypeObject RowType;
#define Row_Check(op) PyObject_TypeCheck(op, &RowType)
View
118 src/sqlwchar.cpp
@@ -0,0 +1,118 @@
+
+#include "pyodbc.h"
+#include "sqlwchar.h"
+#include "wrapper.h"
+
+// We could eliminate a lot of trouble if we had a define for the size of a SQLWCHAR. Unfortunately, I can't think of
+// a way to do this with the preprocessor. Python's setup.cfg files aren't really making it easy either. For now
+// we'll use C code and revisit this later.
+
+SQLWChar::SQLWChar(PyObject* o)
+{
+ // Converts from a Python Unicode string.
+
+ pch = 0;
+ len = 0;
+ owns_memory = false;
+
+ Convert(o);
+}
+
+void SQLWChar::Free()
+{
+ if (pch && owns_memory)
+ free(pch);
+ pch = 0;
+ len = 0;
+ owns_memory = false;
+}
+
+bool SQLWChar::Convert(PyObject* o)
+{
+ Free();
+
+ if (!PyUnicode_Check(o))
+ {
+ PyErr_SetString(PyExc_TypeError, "Unicode required");
+ return false;
+ }
+
+ Py_UNICODE* pU = (SQLWCHAR*)PyUnicode_AS_UNICODE(o);
+ Py_ssize_t lenT = PyUnicode_GET_SIZE(o);
+
+ if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE)
+ {
+ // The ideal case - SQLWCHAR and Py_UNICODE are the same, so we point into the Unicode object.
+
+ pch = (SQLWCHAR*)pU;
+ len = lenT;
+ owns_memory = false;
+ return true;
+ }
+
+ SQLWCHAR* pchT = (SQLWCHAR*)malloc(sizeof(SQLWCHAR) * (lenT + 1));
+ if (pchT == 0)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+
+ if (!sqlwchar_copy(pchT, pU, lenT))
+ {
+ free(pchT);
+ return false;
+ }
+
+ pch = pchT;
+ len = lenT;
+ owns_memory = true;
+ return true;
+}
+
+
+bool sqlwchar_copy(SQLWCHAR* pdest, const Py_UNICODE* psrc, Py_ssize_t len)
+{
+ for (int i = 0; i <= len; i++) // ('<=' to include the NULL)
+ {
+ pdest[i] = (SQLWCHAR)psrc[i];
+ if ((Py_UNICODE)pdest[i] < psrc[i])
+ {
+ PyErr_Format(PyExc_ValueError, "Cannot convert from Unicode %zd to SQLWCHAR. Value is too large.", (Py_ssize_t)psrc[i]);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+PyObject* PyUnicode_FromSQLWCHAR(const SQLWCHAR* sz, Py_ssize_t cch)
+{
+ if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE)
+ return PyUnicode_FromUnicode(sz, cch);
+
+#if HAVE_WCHAR_H
+ if (sizeof(wchar_t) == sizeof(SQLWCHAR))
+ {
+ // Python provides a function to map from wchar_t to Unicode. Since wchar_t and SQLWCHAR are the same size, we can
+ // use it.
+ return PyUnicode_FromWideChar((const wchar_t*)sz, cch);
+ }
+#endif
+
+ Object result(PyUnicode_FromUnicode(0, cch));
+ if (!result)
+ return 0;
+
+ Py_UNICODE* pch = PyUnicode_AS_UNICODE(result.Get());
+ for (Py_ssize_t i = 0; i < cch; i++)
+ {
+ pch[i] = (Py_UNICODE)sz[i];
+ if ((SQLWCHAR)pch[i] != sz[i])
+ {
+ PyErr_Format(PyExc_ValueError, "Cannot convert from SQLWCHAR %zd to Unicode. Value is too large.", (Py_ssize_t)pch[i]);
+ return 0;
+ }
+ }
+
+ return result.Detach();
+}
View
54 src/sqlwchar.h
@@ -0,0 +1,54 @@
+
+#ifndef _PYODBCSQLWCHAR_H
+#define _PYODBCSQLWCHAR_H
+
+class SQLWChar
+{
+private:
+ SQLWCHAR* pch;
+ Py_ssize_t len;
+ bool owns_memory;
+
+public:
+ SQLWChar()
+ {
+ pch = 0;
+ len = 0;
+ owns_memory = false;
+ }
+
+ SQLWChar(PyObject* o);
+
+ bool Convert(PyObject* o);
+
+ void Free();
+
+ ~SQLWChar()
+ {
+ Free();
+ }
+
+ operator SQLWCHAR*() { return pch; }
+ operator const SQLWCHAR*() const { return pch; }
+ operator bool() const { return pch != 0; }
+ Py_ssize_t size() const { return len; }
+
+ SQLWCHAR* operator[] (Py_ssize_t i)
+ {
+ I(i <= len); // we'll allow access to the NULL?
+ return &pch[i];
+ }
+
+ const SQLWCHAR* operator[] (Py_ssize_t i) const
+ {
+ I(i <= len); // we'll allow access to the NULL?
+ return &pch[i];
+ }
+};
+
+PyObject* PyUnicode_FromSQLWCHAR(const SQLWCHAR* sz, Py_ssize_t cch);
+
+bool sqlwchar_copy(SQLWCHAR* pdest, const Py_UNICODE* psrc, Py_ssize_t len);
+
+
+#endif // _PYODBCSQLWCHAR_H

0 comments on commit 691b6ba

Please sign in to comment.