Skip to content
Browse files

Add support for parameter array binding.

  • Loading branch information...
1 parent 7555f7d commit 956201ed27853c6445a03745bb0b99c3333262b5 @gbegen committed Apr 30, 2012
Showing with 551 additions and 249 deletions.
  1. +2 −1 src/connection.cpp
  2. +3 −1 src/connection.h
  3. +67 −31 src/cursor.cpp
  4. +20 −28 src/cursor.h
  5. +435 −185 src/params.cpp
  6. +2 −1 src/params.h
  7. +21 −2 src/pyodbcmodule.cpp
  8. +1 −0 src/pyodbcmodule.h
View
3 src/connection.cpp
@@ -140,7 +140,7 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeou
}
-PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly)
+PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly, bool fParameterArrayBinding)
{
// pConnectString
// A string or unicode object. (This must be checked by the caller.)
@@ -202,6 +202,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi,
cnxn->conv_count = 0;
cnxn->conv_types = 0;
cnxn->conv_funcs = 0;
+ cnxn->useParameterArrayBinding = fParameterArrayBinding;
//
// Initialize autocommit mode.
View
4 src/connection.h
@@ -62,6 +62,8 @@ struct Connection
int conv_count; // how many items are in conv_types and conv_funcs.
SQLSMALLINT* conv_types; // array of SQL_TYPEs to convert
PyObject** conv_funcs; // array of Python functions
+
+ bool useParameterArrayBinding; // allow executemany to use parameter array binding
};
#define Connection_Check(op) PyObject_TypeCheck(op, &ConnectionType)
@@ -71,6 +73,6 @@ struct Connection
* Used by the module's connect function to create new connection objects. If unable to connect to the database, an
* exception is set and zero is returned.
*/
-PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly);
+PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly, bool fParameterArrayBinding);
#endif
View
98 src/cursor.cpp
@@ -615,7 +615,7 @@ static bool PrepareResults(Cursor* cur, int cCols)
}
-static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first)
+static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, Py_ssize_t paramSetSize, Py_ssize_t paramsOffset)
{
// Internal function to execute SQL, called by .execute and .executemany.
//
@@ -626,37 +626,41 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
// Pointer to an optional sequence of parameters, and possibly the SQL statement (see skip_first):
// (SQL, param1, param2) or (param1, param2).
//
- // skip_first
- // If true, the first element in `params` is ignored. (It will be the SQL statement and `params` will be the
- // entire tuple passed to Cursor.execute.) Otherwise all of the params are used. (This case occurs when called
- // from Cursor.executemany, in which case the sequences do not contain the SQL statement.) Ignored if params is
- // zero.
+ // paramSetSize
+ // Number of parameter sets in params when called by .executemany. Set to 0 for .execute.
+ //
+ // paramsOffset
+ // Number of parameters to skip over. Set to 1 for .execute when the first parameter is the SQL statement.
+ // Otherwise, set to 0 to use all params. Ignored if params is zero.
- if (params)
+ if (params && paramSetSize == 0)
{
if (!PyTuple_Check(params) && !PyList_Check(params) && !Row_Check(params))
return RaiseErrorV(0, PyExc_TypeError, "Params must be in a list, tuple, or Row");
}
- // Normalize the parameter variables.
-
- int params_offset = skip_first ? 1 : 0;
- Py_ssize_t cParams = params == 0 ? 0 : PySequence_Length(params) - params_offset;
-
SQLRETURN ret = 0;
free_results(cur, FREE_STATEMENT | KEEP_PREPARED);
const char* szLastFunction = "";
- if (cParams > 0)
+ if (params)
{
// There are parameters, so we'll need to prepare the SQL statement and bind the parameters. (We need to
// prepare the statement because we can't bind a NULL (None) object without knowing the target datatype. There
// is no one data type that always maps to the others (no, not even varchar)).
- if (!PrepareAndBind(cur, pSql, params, skip_first))
- return 0;
+ if (paramSetSize > 0)
+ {
+ if (!PrepareAndBindArray(cur, pSql, params))
+ return 0;
+ }
+ else
+ {
+ if (!PrepareAndBind(cur, pSql, params, paramsOffset))
+ return 0;
+ }
szLastFunction = "SQLExecute";
Py_BEGIN_ALLOW_THREADS
@@ -709,13 +713,13 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
while (ret == SQL_NEED_DATA)
{
- // We have bound a PyObject* using SQL_LEN_DATA_AT_EXEC, so ODBC is asking us for the data now. We gave the
+ // 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;
+ PyObject** ppParam;
Py_BEGIN_ALLOW_THREADS
- ret = SQLParamData(cur->hstmt, (SQLPOINTER*)&pParam);
+ ret = SQLParamData(cur->hstmt, (SQLPOINTER*)&ppParam);
Py_END_ALLOW_THREADS
if (ret != SQL_NEED_DATA && ret != SQL_NO_DATA && !SQL_SUCCEEDED(ret))
@@ -725,6 +729,8 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
if (ret == SQL_NEED_DATA)
{
+ PyObject* pParam = *ppParam;
+
szLastFunction = "SQLPutData";
if (PyUnicode_Check(pParam))
{
@@ -875,6 +881,16 @@ inline bool IsSequence(PyObject* p)
return PyList_Check(p) || PyTuple_Check(p) || Row_Check(p);
}
+inline bool ClearArrayBindingNotSupportedError()
+{
+ PyObject *pError = PyErr_Occurred();
+ if (pError != 0 && PyErr_GivenExceptionMatches(pError, ArrayBindingNotSupportedError))
+ {
+ PyErr_Clear();
+ return true;
+ }
+ return false;
+}
static char execute_doc[] =
"C.execute(sql, [params]) --> Cursor\n"
@@ -914,25 +930,25 @@ PyObject* Cursor_execute(PyObject* self, PyObject* args)
// Figure out if there were parameters and how they were passed. Our optional parameter passing complicates this slightly.
- bool skip_first = false;
+ Py_ssize_t paramsOffset = 0;
PyObject *params = 0;
if (cParams == 1 && IsSequence(PyTuple_GET_ITEM(args, 1)))
{
// There is a single argument and it is a sequence, so we must treat it as a sequence of parameters. (This is
// the normal Cursor.execute behavior.)
- params = PyTuple_GET_ITEM(args, 1);
- skip_first = false;
+ params = PyTuple_GET_ITEM(args, 1);
+ paramsOffset = 0;
}
else if (cParams > 0)
{
- params = args;
- skip_first = true;
+ params = args;
+ paramsOffset = 1;
}
// Execute.
- return execute(cursor, pSql, params, skip_first);
+ return execute(cursor, pSql, params, 0, paramsOffset);
}
@@ -967,20 +983,40 @@ static PyObject* Cursor_executemany(PyObject* self, PyObject* args)
PyErr_SetString(ProgrammingError, "The second parameter to executemany must not be empty.");
return 0;
}
-
- for (Py_ssize_t i = 0; i < c; i++)
+
+ bool success = false;
+ if ( cursor->cnxn->useParameterArrayBinding )
{
- PyObject* params = PySequence_GetItem(param_seq, i);
- PyObject* result = execute(cursor, pSql, params, false);
- bool success = result != 0;
+ PyObject* result = execute(cursor, pSql, param_seq, c, false);
+ success = result != 0;
Py_XDECREF(result);
- Py_DECREF(params);
- if (!success)
+ result = 0;
+ if (!success && !ClearArrayBindingNotSupportedError())
{
cursor->rowcount = -1;
return 0;
}
}
+ if (!success)
+ {
+ for (Py_ssize_t i = 0; i < c; i++)
+ {
+ PyObject* params = PySequence_GetItem(param_seq, i);
+ PyObject* result = execute(cursor, pSql, params, 0, false);
+ success = result != 0;
+ Py_XDECREF(result);
+ Py_DECREF(params);
+ if (!success)
+ {
+ break;
+ }
+ }
+ }
+ if (!success)
+ {
+ cursor->rowcount = -1;
+ return 0;
+ }
cursor->rowcount = -1;
Py_RETURN_NONE;
View
48 src/cursor.h
@@ -36,53 +36,45 @@ struct ColumnInfo
struct ParamInfo
{
+ // Number of parameter sets for array binding. This will be set to 1 for non-array binding.
+ Py_ssize_t ParamSetSize;
+
// The following correspond to the SQLBindParameter parameters.
SQLSMALLINT ValueType;
SQLSMALLINT ParameterType;
SQLULEN ColumnSize;
SQLSMALLINT DecimalDigits;
+ SQLPOINTER ParameterValuePtr;
+ SQLLEN BufferLength;
+ SQLLEN* StrLen_or_IndPtr;
+
+ // Python objects this parameter is bound to.
+ PyObject** ParameterObjects;
- // The value pointer that will be bound. If `alloc` is true, this was allocated with malloc and must be freed.
- // Otherwise it is zero or points into memory owned by the original Python parameter.
- SQLPOINTER ParameterValuePtr;
-
- SQLLEN BufferLength;
- SQLLEN StrLen_or_Ind;
-
- // If true, the memory in ParameterValuePtr was allocated via malloc and must be freed.
- bool allocated;
-
- // The python object containing the parameter value. A reference to this object should be held until we have
- // finished using memory owned by it.
- PyObject* pParam;
-
- // 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;
+ // Default buffers. For non-array binding of many values, these buffers provide enough storage to avoid extra
+ // allocations. Pointer values above are initialized to point at these buffers.
+ SQLCHAR ParameterValueBuffer[64];
+ SQLLEN StrLen_or_IndBuffer[1];
+ PyObject* ParameterObjectBuffer[1];
+
+ // Flag to indicate that ParameterValuePtr points into data owned by the Python object.
+ bool IsParameterValueBorrowed;
};
struct Cursor
{
PyObject_HEAD
- // The Connection object (which is a PyObject) that created this cursor.
- Connection* cnxn;
+ // The Connection object (which is a PyObject) that created this cursor.
+ Connection* cnxn;
// Set to SQL_NULL_HANDLE when the cursor is closed.
HSTMT hstmt;
//
// SQL Parameters
//
-
+
// If non-zero, a pointer to the previously prepared SQL string, allowing us to skip the prepare and gathering of
// parameter data.
PyObject* pPreparedSQL;
View
620 src/params.cpp
@@ -9,6 +9,7 @@
#include "errors.h"
#include "dbspecific.h"
#include "sqlwchar.h"
+#include "row.h"
#include <datetime.h>
@@ -19,13 +20,55 @@ inline Connection* GetConnection(Cursor* cursor)
static bool GetParamType(Cursor* cur, Py_ssize_t iParam, SQLSMALLINT& type);
+static bool InitInfos(ParamInfo* a, Py_ssize_t count, Py_ssize_t paramSetSize)
+{
+ memset(a, 0, sizeof(ParamInfo) * count);
+ for (Py_ssize_t i = 0; i < count; i++)
+ {
+ a[i].ParamSetSize = paramSetSize;
+ if (paramSetSize > 1)
+ {
+ a[i].StrLen_or_IndPtr = (SQLLEN *)pyodbc_malloc(sizeof(SQLLEN) * a[i].ParamSetSize);
+ if (!a[i].StrLen_or_IndPtr)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+ memset(a[i].StrLen_or_IndPtr, 0, sizeof(SQLLEN) * a[i].ParamSetSize);
+
+ a[i].ParameterObjects = (PyObject**)pyodbc_malloc(sizeof(PyObject*) * a[i].ParamSetSize);
+ if (!a[i].ParameterObjects)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+ memset(a[i].ParameterObjects, 0, sizeof(PyObject*) * a[i].ParamSetSize);
+ }
+ else
+ {
+ a[i].ParameterValuePtr = a[i].ParameterValueBuffer;
+ a[i].StrLen_or_IndPtr = a[i].StrLen_or_IndBuffer;
+ a[i].ParameterObjects = a[i].ParameterObjectBuffer;
+ }
+ }
+ return true;
+}
+
static void FreeInfos(ParamInfo* a, Py_ssize_t count)
{
for (Py_ssize_t i = 0; i < count; i++)
{
- if (a[i].allocated)
+ if (a[i].ParameterObjects)
+ {
+ for (Py_ssize_t j = 0; j < a[i].ParamSetSize; j++)
+ Py_XDECREF(a[i].ParameterObjects[j]);
+ if(a[i].ParameterObjects != a[i].ParameterObjectBuffer)
+ pyodbc_free(a[i].ParameterObjects);
+ }
+ if (a[i].StrLen_or_IndPtr && a[i].StrLen_or_IndPtr != a[i].StrLen_or_IndBuffer)
+ pyodbc_free(a[i].StrLen_or_IndPtr);
+ if (!a[i].IsParameterValueBorrowed && a[i].ParameterValuePtr && a[i].ParameterValuePtr != a[i].ParameterValueBuffer)
pyodbc_free(a[i].ParameterValuePtr);
- Py_XDECREF(a[i].pParam);
}
pyodbc_free(a);
}
@@ -109,232 +152,340 @@ static const char* CTypeName(SQLSMALLINT n)
return "unknown";
}
-static bool GetNullInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info)
+static bool ExpandParameterValueBuffer(ParamInfo& info, SQLINTEGER bufferLength, SQLINTEGER maxLength)
+{
+ if (bufferLength <= info.BufferLength)
+ return true;
+
+ if (info.BufferLength >= maxLength)
+ return false;
+ SQLINTEGER newBufferLength = min(info.BufferLength * 2, maxLength);
+ SQLPOINTER newBuffer = pyodbc_malloc((size_t)(newBufferLength * info.ParamSetSize));
+ if (!newBuffer)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+
+ for (Py_ssize_t i = info.ParamSetSize; i > 0; i--)
+ memcpy((SQLCHAR*)newBuffer + newBufferLength * (i - 1), (SQLCHAR*)info.ParameterValuePtr + info.BufferLength * (i - 1), (size_t)info.BufferLength);
+
+ pyodbc_free(info.ParameterValuePtr);
+ info.ParameterValuePtr = newBuffer;
+ info.BufferLength = newBufferLength;
+
+ return true;
+}
+
+static bool UpdateInfo(ParamInfo& info, Py_ssize_t paramSetIndex, SQLSMALLINT valueType, SQLUINTEGER columnSize, SQLSMALLINT parameterType, SQLPOINTER buffer, SQLINTEGER bufferLength, SQLINTEGER maxLength, bool isBufferBorrowed, SQLLEN strLenOrInd)
+{
+ info.StrLen_or_IndPtr[paramSetIndex] = strLenOrInd;
+
+ if (info.ParameterType == SQL_UNKNOWN_TYPE)
+ {
+ info.ValueType = valueType;
+ info.ColumnSize = columnSize;
+ info.ParameterType = parameterType;
+ info.BufferLength = bufferLength;
+
+ if (info.ParamSetSize == 1 && isBufferBorrowed)
+ {
+ info.ParameterValuePtr = buffer;
+ info.IsParameterValueBorrowed = true;
+ }
+ else if (info.ParamSetSize == 1 && bufferLength <= sizeof(info.ParameterValueBuffer))
+ {
+ if (buffer)
+ memcpy(info.ParameterValuePtr, buffer, (size_t)bufferLength);
+ }
+ else
+ {
+ switch (info.ValueType)
+ {
+ case SQL_C_CHAR:
+ case SQL_C_WCHAR:
+ case SQL_C_BINARY:
+ if (buffer)
+ info.BufferLength = max(info.BufferLength, 64);
+ break;
+ }
+ info.ParameterValuePtr = pyodbc_malloc((size_t)(info.BufferLength * info.ParamSetSize));
+ if (!info.ParameterValuePtr)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+ memset(info.ParameterValuePtr, 0, (size_t)(info.BufferLength * info.ParamSetSize));
+ if (buffer)
+ memcpy(info.ParameterValuePtr, buffer, (size_t)bufferLength);
+ }
+
+ return true;
+ }
+
+ if (info.ValueType == valueType && info.ParameterType == parameterType)
+ {
+ info.ColumnSize = max(info.ColumnSize, columnSize);
+ if (!ExpandParameterValueBuffer(info, bufferLength, maxLength))
+ return false;
+ if (buffer)
+ memcpy((SQLCHAR*)info.ParameterValuePtr + info.BufferLength * paramSetIndex, buffer, (size_t)bufferLength);
+ return true;
+ }
+
+ if (!buffer)
+ {
+ // earlier non-null values have established the parameter information
+ return true;
+ }
+
+ // all earlier values must be null
+ for (Py_ssize_t i = 0; i < paramSetIndex; i++)
+ {
+ if ( info.StrLen_or_IndPtr[i] != SQL_NULL_DATA )
+ {
+ RaiseErrorV(0, ProgrammingError, "Incompatible types in same parameter to executemany.");
+ return false;
+ }
+ }
+
+ // free prior allocated parameter values
+ if (!info.IsParameterValueBorrowed && info.ParameterValuePtr && info.ParameterValuePtr != info.ParameterValueBuffer)
+ {
+ pyodbc_free(info.ParameterValuePtr);
+ info.ParameterValuePtr = 0;
+ }
+
+ // change parameter info
+ info.ValueType = valueType;
+ info.ColumnSize = columnSize;
+ info.ParameterType = parameterType;
+ info.BufferLength = bufferLength;
+ switch (info.ValueType)
+ {
+ case SQL_C_CHAR:
+ case SQL_C_WCHAR:
+ case SQL_C_BINARY:
+ info.BufferLength = max(info.BufferLength, 64);
+ break;
+ }
+
+ // allocate new parameter values
+ info.ParameterValuePtr = pyodbc_malloc((size_t)(info.BufferLength * info.ParamSetSize));
+ if (!info.ParameterValuePtr)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+ memset(info.ParameterValuePtr, 0, (size_t)(info.BufferLength * info.ParamSetSize));
+ if (buffer)
+ memcpy((SQLCHAR*)info.ParameterValuePtr + info.BufferLength * paramSetIndex, buffer, (size_t)bufferLength);
+
+ return true;
+}
+
+static bool GetNullInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- if (!GetParamType(cur, index, info.ParameterType))
+ SQLSMALLINT parameterType = info.ParameterType;
+ if (parameterType == SQL_UNKNOWN_TYPE)
+ {
+ if (!GetParamType(cur, index, parameterType))
+ return false;
+ }
+
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_DEFAULT, 1, parameterType, NULL, 1, 1, false, SQL_NULL_DATA))
return false;
- info.ValueType = SQL_C_DEFAULT;
- info.ColumnSize = 1;
- info.StrLen_or_Ind = SQL_NULL_DATA;
return true;
}
-static bool GetNullBinaryInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info)
+static bool GetNullBinaryInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.ValueType = SQL_C_BINARY;
- info.ParameterType = SQL_BINARY;
- info.ColumnSize = 1;
- info.ParameterValuePtr = 0;
- info.StrLen_or_Ind = SQL_NULL_DATA;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, 1, SQL_BINARY, NULL, 1, 1, false, SQL_NULL_DATA))
+ return false;
+
return true;
}
-static bool GetBytesInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetBytesInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
// In Python 2, a bytes object (ANSI string) is passed as varchar. In Python 3, it is passed as binary.
Py_ssize_t len = PyBytes_GET_SIZE(param);
-#if PY_MAJOR_VERSION >= 3
- info.ValueType = SQL_C_BINARY;
- info.ColumnSize = (SQLUINTEGER)max(len, 1);
+ SQLUINTEGER columnSize = (SQLUINTEGER)max(len, 1);
+#if PY_MAJOR_VERSION >= 3
if (len <= cur->cnxn->binary_maxlength)
{
- info.ParameterType = SQL_VARBINARY;
- info.StrLen_or_Ind = len;
- info.ParameterValuePtr = PyBytes_AS_STRING(param);
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, columnSize, SQL_VARBINARY, PyBytes_AS_STRING(param), len, true, len))
+ return false;
}
else
{
// Too long to pass all at once, so we'll provide the data at execute.
- info.ParameterType = SQL_LONGVARBINARY;
- info.StrLen_or_Ind = SQL_LEN_DATA_AT_EXEC((SQLLEN)len);
- info.ParameterValuePtr = param;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, columnSize, SQL_LONGVARBINARY, &param, sizeof(PyObject*), false, SQL_LEN_DATA_AT_EXEC((SQLLEN)len)))
+ return false;
}
-
#else
- info.ValueType = SQL_C_CHAR;
- info.ColumnSize = (SQLUINTEGER)max(len, 1);
-
if (len <= cur->cnxn->varchar_maxlength)
{
- info.ParameterType = SQL_VARCHAR;
- info.StrLen_or_Ind = len;
- info.ParameterValuePtr = PyBytes_AS_STRING(param);
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_CHAR, columnSize, SQL_VARCHAR, PyBytes_AS_STRING(param), len, cur->cnxn->varchar_maxlength, true, len))
+ return false;
}
else
{
// Too long to pass all at once, so we'll provide the data at execute.
- info.ParameterType = SQL_LONGVARCHAR;
- info.StrLen_or_Ind = SQL_LEN_DATA_AT_EXEC((SQLLEN)len);
- info.ParameterValuePtr = param;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_CHAR, columnSize, SQL_LONGVARCHAR, &param, sizeof(PyObject*), sizeof(PyObject*), false, SQL_LEN_DATA_AT_EXEC((SQLLEN)len)))
+ return false;
}
#endif
return true;
}
-static bool GetUnicodeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetUnicodeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- Py_UNICODE* pch = PyUnicode_AsUnicode(param);
Py_ssize_t len = PyUnicode_GET_SIZE(param);
- info.ValueType = SQL_C_WCHAR;
- info.ColumnSize = (SQLUINTEGER)max(len, 1);
+ SQLUINTEGER columnSize = (SQLUINTEGER)max(len, 1);
if (len <= cur->cnxn->wvarchar_maxlength)
{
- if (SQLWCHAR_SIZE == Py_UNICODE_SIZE)
+ Py_UNICODE* pch = PyUnicode_AsUnicode(param);
+ SQLPOINTER buffer = pch;
+ bool isBufferBorrowed = true;
+ if (len > 0 && SQLWCHAR_SIZE != Py_UNICODE_SIZE)
{
- info.ParameterValuePtr = pch;
+ buffer = SQLWCHAR_FromUnicode(pch, len);
+ isBufferBorrowed = false;
}
- else
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_WCHAR, columnSize, SQL_WVARCHAR, buffer, (SQLINTEGER)(len * sizeof(SQLWCHAR)), (SQLINTEGER)(cur->cnxn->wvarchar_maxlength * sizeof(SQLWCHAR)), isBufferBorrowed, (SQLLEN)(len * sizeof(SQLWCHAR))))
{
- // SQLWCHAR and Py_UNICODE are not the same size, so we need to allocate and copy a buffer.
- if (len > 0)
- {
- info.ParameterValuePtr = SQLWCHAR_FromUnicode(pch, len);
- if (info.ParameterValuePtr == 0)
- return false;
- info.allocated = true;
- }
- else
- {
- info.ParameterValuePtr = pch;
- }
+ if (!isBufferBorrowed)
+ pyodbc_free(buffer);
+ return false;
}
-
- info.ParameterType = SQL_WVARCHAR;
- info.StrLen_or_Ind = (SQLINTEGER)(len * sizeof(SQLWCHAR));
}
else
{
// Too long to pass all at once, so we'll provide the data at execute.
-
- info.ParameterType = SQL_WLONGVARCHAR;
- info.StrLen_or_Ind = SQL_LEN_DATA_AT_EXEC((SQLLEN)(len * sizeof(SQLWCHAR)));
- info.ParameterValuePtr = param;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_WCHAR, columnSize, SQL_WLONGVARCHAR, &param, sizeof(PyObject*), sizeof(PyObject*), false, SQL_LEN_DATA_AT_EXEC((SQLLEN)(len * sizeof(SQLWCHAR)))))
+ return false;
}
return true;
}
-static bool GetBooleanInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetBooleanInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.ValueType = SQL_C_BIT;
- info.ParameterType = SQL_BIT;
- info.StrLen_or_Ind = 1;
- info.Data.ch = (unsigned char)(param == Py_True ? 1 : 0);
- info.ParameterValuePtr = &info.Data.ch;
+ unsigned char bit = (unsigned char)(param == Py_True ? 1 : 0);
+
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BIT, sizeof(unsigned char), SQL_BIT, &bit, sizeof(bit), sizeof(bit), false, 1))
+ return false;
+
return true;
}
-static bool GetDateTimeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetDateTimeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.Data.timestamp.year = (SQLSMALLINT) PyDateTime_GET_YEAR(param);
- info.Data.timestamp.month = (SQLUSMALLINT)PyDateTime_GET_MONTH(param);
- info.Data.timestamp.day = (SQLUSMALLINT)PyDateTime_GET_DAY(param);
- info.Data.timestamp.hour = (SQLUSMALLINT)PyDateTime_DATE_GET_HOUR(param);
- info.Data.timestamp.minute = (SQLUSMALLINT)PyDateTime_DATE_GET_MINUTE(param);
- info.Data.timestamp.second = (SQLUSMALLINT)PyDateTime_DATE_GET_SECOND(param);
+ TIMESTAMP_STRUCT timestamp;
+ timestamp.year = (SQLSMALLINT) PyDateTime_GET_YEAR(param);
+ timestamp.month = (SQLUSMALLINT)PyDateTime_GET_MONTH(param);
+ timestamp.day = (SQLUSMALLINT)PyDateTime_GET_DAY(param);
+ timestamp.hour = (SQLUSMALLINT)PyDateTime_DATE_GET_HOUR(param);
+ timestamp.minute = (SQLUSMALLINT)PyDateTime_DATE_GET_MINUTE(param);
+ timestamp.second = (SQLUSMALLINT)PyDateTime_DATE_GET_SECOND(param);
// SQL Server chokes if the fraction has more data than the database supports. We expect other databases to be the
// same, so we reduce the value to what the database supports. http://support.microsoft.com/kb/263872
int precision = ((Connection*)cur->cnxn)->datetime_precision - 20; // (20 includes a separating period)
if (precision <= 0)
{
- info.Data.timestamp.fraction = 0;
+ timestamp.fraction = 0;
}
else
{
- info.Data.timestamp.fraction = (SQLUINTEGER)(PyDateTime_DATE_GET_MICROSECOND(param) * 1000); // 1000 == micro -> nano
+ timestamp.fraction = (SQLUINTEGER)(PyDateTime_DATE_GET_MICROSECOND(param) * 1000); // 1000 == micro -> nano
// (How many leading digits do we want to keep? With SQL Server 2005, this should be 3: 123000000)
int keep = (int)pow(10.0, 9-min(9, precision));
- info.Data.timestamp.fraction = info.Data.timestamp.fraction / keep * keep;
+ timestamp.fraction = timestamp.fraction / keep * keep;
info.DecimalDigits = (SQLSMALLINT)precision;
}
- info.ValueType = SQL_C_TIMESTAMP;
- info.ParameterType = SQL_TIMESTAMP;
- info.ColumnSize = (SQLUINTEGER)((Connection*)cur->cnxn)->datetime_precision;
- info.StrLen_or_Ind = sizeof(TIMESTAMP_STRUCT);
- info.ParameterValuePtr = &info.Data.timestamp;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_TIMESTAMP, (SQLUINTEGER)((Connection*)cur->cnxn)->datetime_precision, SQL_TIMESTAMP, &timestamp, sizeof(timestamp), sizeof(timestamp), false, sizeof(TIMESTAMP_STRUCT)))
+ return false;
+
return true;
}
-static bool GetDateInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetDateInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.Data.date.year = (SQLSMALLINT) PyDateTime_GET_YEAR(param);
- info.Data.date.month = (SQLUSMALLINT)PyDateTime_GET_MONTH(param);
- info.Data.date.day = (SQLUSMALLINT)PyDateTime_GET_DAY(param);
+ DATE_STRUCT date;
+ date.year = (SQLSMALLINT) PyDateTime_GET_YEAR(param);
+ date.month = (SQLUSMALLINT)PyDateTime_GET_MONTH(param);
+ date.day = (SQLUSMALLINT)PyDateTime_GET_DAY(param);
+
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_TYPE_DATE, 10, SQL_TYPE_DATE, &date, sizeof(date), sizeof(date), false, sizeof(DATE_STRUCT)))
+ return false;
- info.ValueType = SQL_C_TYPE_DATE;
- info.ParameterType = SQL_TYPE_DATE;
- info.ColumnSize = 10;
- info.ParameterValuePtr = &info.Data.date;
- info.StrLen_or_Ind = sizeof(DATE_STRUCT);
return true;
}
-static bool GetTimeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetTimeInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.Data.time.hour = (SQLUSMALLINT)PyDateTime_TIME_GET_HOUR(param);
- info.Data.time.minute = (SQLUSMALLINT)PyDateTime_TIME_GET_MINUTE(param);
- info.Data.time.second = (SQLUSMALLINT)PyDateTime_TIME_GET_SECOND(param);
+ TIME_STRUCT time;
+ time.hour = (SQLUSMALLINT)PyDateTime_TIME_GET_HOUR(param);
+ time.minute = (SQLUSMALLINT)PyDateTime_TIME_GET_MINUTE(param);
+ time.second = (SQLUSMALLINT)PyDateTime_TIME_GET_SECOND(param);
+
+ if(!UpdateInfo(info, paramSetIndex, SQL_C_TYPE_TIME, 8, SQL_TYPE_TIME, &time, sizeof(time), sizeof(time), false, sizeof(TIME_STRUCT)))
+ return false;
- info.ValueType = SQL_C_TYPE_TIME;
- info.ParameterType = SQL_TYPE_TIME;
- info.ColumnSize = 8;
- info.ParameterValuePtr = &info.Data.time;
- info.StrLen_or_Ind = sizeof(TIME_STRUCT);
return true;
}
#if PY_MAJOR_VERSION < 3
-static bool GetIntInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetIntInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.Data.l = PyInt_AsLong(param);
+ long l = PyInt_AsLong(param);
#if LONG_BIT == 64
- info.ValueType = SQL_C_SBIGINT;
- info.ParameterType = SQL_BIGINT;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_SBIGINT, sizeof(long), SQL_BIGINT, &l, sizeof(l), sizeof(l), false, 0))
+ return false;
#elif LONG_BIT == 32
- info.ValueType = SQL_C_LONG;
- info.ParameterType = SQL_INTEGER;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_LONG, sizeof(long), SQL_INTEGER, &l, sizeof(l), sizeof(l), false, 0))
+ return false;
#else
#error Unexpected LONG_BIT value
#endif
- info.ParameterValuePtr = &info.Data.l;
return true;
}
#endif
-static bool GetLongInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetLongInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
// TODO: Overflow?
- info.Data.i64 = (INT64)PyLong_AsLongLong(param);
+ INT64 i64 = (INT64)PyLong_AsLongLong(param);
+
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_SBIGINT, sizeof(INT64), SQL_BIGINT, &i64, sizeof(i64), sizeof(i64), false, 0))
+ return false;
- info.ValueType = SQL_C_SBIGINT;
- info.ParameterType = SQL_BIGINT;
- info.ParameterValuePtr = &info.Data.i64;
return true;
}
-static bool GetFloatInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetFloatInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
// TODO: Overflow?
- info.Data.dbl = PyFloat_AsDouble(param);
+ double dbl = PyFloat_AsDouble(param);
+
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_DOUBLE, 15, SQL_DOUBLE, &dbl, sizeof(dbl), sizeof(dbl), false, 0))
+ return false;
- info.ValueType = SQL_C_DOUBLE;
- info.ParameterType = SQL_DOUBLE;
- info.ParameterValuePtr = &info.Data.dbl;
- info.ColumnSize = 15;
return true;
}
@@ -412,7 +563,7 @@ static char* CreateDecimalString(long sign, PyObject* digits, long exp)
return pch;
}
-static bool GetDecimalInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetDecimalInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
// The NUMERIC structure never works right with SQL Server and probably a lot of other drivers. We'll bind as a
// string. Unfortunately, the Decimal class doesn't seem to have a way to force it to return a string without
@@ -428,159 +579,149 @@ static bool GetDecimalInfo(Cursor* cur, Py_ssize_t index, PyObject* param, Param
Py_ssize_t count = PyTuple_GET_SIZE(digits);
- info.ValueType = SQL_C_CHAR;
- info.ParameterType = SQL_NUMERIC;
+ SQLUINTEGER columnSize;
if (exp >= 0)
{
// (1 2 3) exp = 2 --> '12300'
- info.ColumnSize = (SQLUINTEGER)count + exp;
+ columnSize = (SQLUINTEGER)count + exp;
info.DecimalDigits = 0;
}
else if (-exp <= count)
{
// (1 2 3) exp = -2 --> 1.23 : prec = 3, scale = 2
- info.ColumnSize = (SQLUINTEGER)count;
+ columnSize = (SQLUINTEGER)count;
info.DecimalDigits = (SQLSMALLINT)-exp;
}
else
{
// (1 2 3) exp = -5 --> 0.00123 : prec = 5, scale = 5
- info.ColumnSize = (SQLUINTEGER)(count + (-exp));
+ columnSize = (SQLUINTEGER)(count + (-exp));
info.DecimalDigits = (SQLSMALLINT)info.ColumnSize;
}
- I(info.ColumnSize >= (SQLULEN)info.DecimalDigits);
+ I(columnSize >= (SQLULEN)info.DecimalDigits);
- info.ParameterValuePtr = CreateDecimalString(sign, digits, exp);
- if (!info.ParameterValuePtr)
+ char* buffer = CreateDecimalString(sign, digits, exp);
+ if (!buffer)
{
PyErr_NoMemory();
return false;
}
- info.allocated = true;
-
- info.StrLen_or_Ind = (SQLINTEGER)strlen((char*)info.ParameterValuePtr);
+ SQLINTEGER bufferLength = (SQLINTEGER)strlen(buffer);
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_CHAR, columnSize, SQL_NUMERIC, buffer, bufferLength, cur->cnxn->varchar_maxlength, false, bufferLength))
+ {
+ pyodbc_free(buffer);
+ return false;
+ }
+
+ pyodbc_free(buffer);
return true;
}
#if PY_MAJOR_VERSION < 3
-static bool GetBufferInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetBufferInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
- info.ValueType = SQL_C_BINARY;
-
const char* pb;
Py_ssize_t cb = PyBuffer_GetMemory(param, &pb);
if (cb != -1 && cb <= cur->cnxn->binary_maxlength)
{
// There is one segment, so we can bind directly into the buffer object.
- info.ParameterType = SQL_VARBINARY;
- info.ParameterValuePtr = (SQLPOINTER)pb;
- info.BufferLength = cb;
- info.ColumnSize = (SQLUINTEGER)max(cb, 1);
- info.StrLen_or_Ind = cb;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, (SQLUINTEGER)max(cb, 1), SQL_VARBINARY, (SQLPOINTER)pb, cb, cur->cnxn->binary_maxlength, true, cb))
+ return false;
}
else
{
// There are multiple segments, so we'll provide the data at execution time. Pass the PyObject pointer as
// the parameter value which will be pased back to us when the data is needed. (If we release threads, we
// need to up the refcount!)
- info.ParameterType = SQL_LONGVARBINARY;
- info.ParameterValuePtr = param;
- info.ColumnSize = (SQLUINTEGER)PyBuffer_Size(param);
- info.BufferLength = sizeof(PyObject*); // How big is ParameterValuePtr; ODBC copies it and gives it back in SQLParamData
- info.StrLen_or_Ind = SQL_LEN_DATA_AT_EXEC(PyBuffer_Size(param));
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, (SQLUINTEGER)PyBuffer_Size(param), SQL_LONGVARBINARY, &param, sizeof(PyObject*), sizeof(PyObject*), false, SQL_LEN_DATA_AT_EXEC(PyBuffer_Size(param))))
+ return false;
}
return true;
}
#endif
#if PY_VERSION_HEX >= 0x02060000
-static bool GetByteArrayInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetByteArrayInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetIndex)
{
info.ValueType = SQL_C_BINARY;
Py_ssize_t cb = PyByteArray_Size(param);
if (cb <= cur->cnxn->binary_maxlength)
{
- info.ParameterType = SQL_VARBINARY;
- info.ParameterValuePtr = (SQLPOINTER)PyByteArray_AsString(param);
- info.BufferLength = cb;
- info.ColumnSize = (SQLUINTEGER)max(cb, 1);
- info.StrLen_or_Ind = cb;
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, (SQLUINTEGER)max(cb, 1), SQL_VARBINARY, PyByteArray_AsString(param), cb, cur->cnxn->binary_maxlength, true, cb))
+ return false;
}
else
{
- info.ParameterType = SQL_LONGVARBINARY;
- info.ParameterValuePtr = param;
- info.ColumnSize = (SQLUINTEGER)cb;
- info.BufferLength = sizeof(PyObject*); // How big is ParameterValuePtr; ODBC copies it and gives it back in SQLParamData
- info.StrLen_or_Ind = SQL_LEN_DATA_AT_EXEC(cb);
+ if (!UpdateInfo(info, paramSetIndex, SQL_C_BINARY, (SQLUINTEGER)cb, SQL_LONGVARBINARY, &param, sizeof(PyObject*), sizeof(PyObject*), false, SQL_LEN_DATA_AT_EXEC(cb)))
+ return false;
}
return true;
}
#endif
-static bool GetParameterInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
+static bool GetParameterInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, Py_ssize_t paramSetSize, Py_ssize_t paramSetIndex)
{
// Determines the type of SQL parameter that will be used for this parameter based on the Python data type.
//
// Populates `info`.
// Hold a reference to param until info is freed, because info will often be holding data borrowed from param.
- info.pParam = param;
+ info.ParameterObjects[paramSetIndex] = param;
if (param == Py_None)
- return GetNullInfo(cur, index, info);
+ return GetNullInfo(cur, index, info, paramSetIndex);
if (param == null_binary)
- return GetNullBinaryInfo(cur, index, info);
+ return GetNullBinaryInfo(cur, index, info, paramSetIndex);
if (PyBytes_Check(param))
- return GetBytesInfo(cur, index, param, info);
+ return GetBytesInfo(cur, index, param, info, paramSetIndex);
if (PyUnicode_Check(param))
- return GetUnicodeInfo(cur, index, param, info);
+ return GetUnicodeInfo(cur, index, param, info, paramSetIndex);
if (PyBool_Check(param))
- return GetBooleanInfo(cur, index, param, info);
+ return GetBooleanInfo(cur, index, param, info, paramSetIndex);
if (PyDateTime_Check(param))
- return GetDateTimeInfo(cur, index, param, info);
+ return GetDateTimeInfo(cur, index, param, info, paramSetIndex);
if (PyDate_Check(param))
- return GetDateInfo(cur, index, param, info);
+ return GetDateInfo(cur, index, param, info, paramSetIndex);
if (PyTime_Check(param))
- return GetTimeInfo(cur, index, param, info);
+ return GetTimeInfo(cur, index, param, info, paramSetIndex);
if (PyLong_Check(param))
- return GetLongInfo(cur, index, param, info);
+ return GetLongInfo(cur, index, param, info, paramSetIndex);
if (PyFloat_Check(param))
- return GetFloatInfo(cur, index, param, info);
+ return GetFloatInfo(cur, index, param, info, paramSetIndex);
if (PyDecimal_Check(param))
- return GetDecimalInfo(cur, index, param, info);
+ return GetDecimalInfo(cur, index, param, info, paramSetIndex);
#if PY_VERSION_HEX >= 0x02060000
if (PyByteArray_Check(param))
- return GetByteArrayInfo(cur, index, param, info);
+ return GetByteArrayInfo(cur, index, param, info, paramSetIndex);
#endif
#if PY_MAJOR_VERSION < 3
if (PyInt_Check(param))
- return GetIntInfo(cur, index, param, info);
+ return GetIntInfo(cur, index, param, info, paramSetIndex);
if (PyBuffer_Check(param))
- return GetBufferInfo(cur, index, param, info);
+ return GetBufferInfo(cur, index, param, info, paramSetIndex);
#endif
RaiseErrorV("HY105", ProgrammingError, "Invalid parameter type. param-index=%zd param-type=%s", index, Py_TYPE(param)->tp_name);
@@ -591,11 +732,11 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info)
{
TRACE("BIND: param=%d ValueType=%d (%s) ParameterType=%d (%s) ColumnSize=%d DecimalDigits=%d BufferLength=%d *pcb=%d\n",
(index+1), info.ValueType, CTypeName(info.ValueType), info.ParameterType, SqlTypeName(info.ParameterType), info.ColumnSize,
- info.DecimalDigits, info.BufferLength, info.StrLen_or_Ind);
+ info.DecimalDigits, info.BufferLength, info.StrLen_or_IndPtr[0]);
SQLRETURN ret = -1;
Py_BEGIN_ALLOW_THREADS
- ret = SQLBindParameter(cur->hstmt, (SQLUSMALLINT)(index + 1), SQL_PARAM_INPUT, info.ValueType, info.ParameterType, info.ColumnSize, info.DecimalDigits, info.ParameterValuePtr, info.BufferLength, &info.StrLen_or_Ind);
+ ret = SQLBindParameter(cur->hstmt, (SQLUSMALLINT)(index + 1), SQL_PARAM_INPUT, info.ValueType, info.ParameterType, info.ColumnSize, info.DecimalDigits, info.ParameterValuePtr, info.BufferLength, info.StrLen_or_IndPtr);
Py_END_ALLOW_THREADS;
if (GetConnection(cur)->hdbc == SQL_NULL_HANDLE)
@@ -646,7 +787,7 @@ void FreeParameterInfo(Cursor* cur)
cur->paramcount = 0;
}
-bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool skip_first)
+static bool Prepare(Cursor* cur, PyObject* pSql, Py_ssize_t cParamSetSize)
{
#if PY_MAJOR_VERSION >= 3
if (!PyUnicode_Check(pSql))
@@ -657,24 +798,14 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
#endif
//
- // Normalize the parameter variables.
- //
-
- // Since we may replace parameters (we replace objects with Py_True/Py_False when writing to a bit/bool column),
- // allocate an array and use it instead of the original sequence
-
- int params_offset = skip_first ? 1 : 0;
- Py_ssize_t cParams = original_params == 0 ? 0 : PySequence_Length(original_params) - params_offset;
-
- //
// Prepare the SQL if necessary.
//
+ SQLRETURN ret = 0;
if (pSql != cur->pPreparedSQL)
{
FreeParameterInfo(cur);
- SQLRETURN ret = 0;
SQLSMALLINT cParamsT = 0;
const char* szErrorFunc = "SQLPrepare";
@@ -724,6 +855,42 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
Py_INCREF(cur->pPreparedSQL);
}
+ Py_BEGIN_ALLOW_THREADS
+ ret = SQLSetStmtAttr(cur->hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)cParamSetSize, SQL_IS_INTEGER);
+ Py_END_ALLOW_THREADS
+
+ if (cur->cnxn->hdbc == SQL_NULL_HANDLE)
+ {
+ // The connection was closed by another thread in the ALLOW_THREADS block above.
+ RaiseErrorV(0, ProgrammingError, "The cursor's connection was closed.");
+ return false;
+ }
+
+ if (!SQL_SUCCEEDED(ret))
+ {
+ RaiseErrorV(0, ArrayBindingNotSupportedError, "Binding parameters to arrays is not supported.");
+ return false;
+ }
+
+ return true;
+}
+
+bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, Py_ssize_t paramsOffset)
+{
+ if (!Prepare(cur, pSql, 1))
+ {
+ return false;
+ }
+
+ //
+ // Normalize the parameter variables.
+ //
+
+ // Since we may replace parameters (we replace objects with Py_True/Py_False when writing to a bit/bool column),
+ // allocate an array and use it instead of the original sequence
+
+ Py_ssize_t cParams = original_params == 0 ? 0 : PySequence_Length(original_params) - paramsOffset;
+
if (cParams != cur->paramcount)
{
RaiseErrorV(0, ProgrammingError, "The SQL contains %d parameter markers, but %d parameters were supplied",
@@ -737,7 +904,12 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
PyErr_NoMemory();
return 0;
}
- memset(cur->paramInfos, 0, sizeof(ParamInfo) * cParams);
+ if (!InitInfos(cur->paramInfos, cParams, 1))
+ {
+ FreeInfos(cur->paramInfos, cParams);
+ cur->paramInfos = 0;
+ return false;
+ }
// Since you can't call SQLDesribeParam *after* calling SQLBindParameter, we'll loop through all of the
// GetParameterInfos first, then bind.
@@ -747,8 +919,8 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
// PySequence_GetItem returns a *new* reference, which GetParameterInfo will take ownership of. It is stored
// in paramInfos and will be released in FreeInfos (which is always eventually called).
- PyObject* param = PySequence_GetItem(original_params, i + params_offset);
- if (!GetParameterInfo(cur, i, param, cur->paramInfos[i]))
+ PyObject* param = PySequence_GetItem(original_params, i + paramsOffset);
+ if (!GetParameterInfo(cur, i, param, cur->paramInfos[i], 1, 0))
{
FreeInfos(cur->paramInfos, cParams);
cur->paramInfos = 0;
@@ -769,6 +941,84 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
return true;
}
+bool PrepareAndBindArray(Cursor* cur, PyObject* pSql, PyObject* original_params)
+{
+ Py_ssize_t cItems = PySequence_Length(original_params);
+ if ( cItems == 0 )
+ {
+ RaiseErrorV(0, ProgrammingError, "Parameters must be a non-empty sequence");
+ return false;
+ }
+
+ if (!Prepare(cur, pSql, cItems))
+ {
+ return false;
+ }
+
+ cur->paramInfos = (ParamInfo*)pyodbc_malloc(sizeof(ParamInfo) * cur->paramcount);
+ if (cur->paramInfos == 0)
+ {
+ PyErr_NoMemory();
+ return false;
+ }
+ if (!InitInfos(cur->paramInfos, cur->paramcount, cItems))
+ {
+ FreeInfos(cur->paramInfos, cur->paramcount);
+ cur->paramInfos = 0;
+ return false;
+ }
+
+ // Since you can't call SQLDesribeParam *after* calling SQLBindParameter, we'll loop through all of the
+ // GetParameterInfos first, then bind.
+
+ for (Py_ssize_t paramSetIndex = 0; paramSetIndex < cItems; paramSetIndex++ )
+ {
+ PyObject *paramSet = PySequence_GetItem(original_params, paramSetIndex);
+
+ // TODO: check type of param set; allow it to be a non-sequence and treat it as a sequence of one item
+ if (PyList_Check(paramSet) || PyTuple_Check(paramSet) || Row_Check(paramSet))
+ {
+ Py_ssize_t paramCount = PySequence_Length(paramSet);
+ for (Py_ssize_t paramIndex = 0; paramIndex < cur->paramcount; paramIndex++)
+ {
+ PyObject *param;
+ if ( paramIndex <= paramCount )
+ {
+ param = PySequence_GetItem(paramSet, paramIndex);
+ }
+ else
+ {
+ param = Py_None;
+ Py_INCREF(param);
+ }
+
+ if (!GetParameterInfo(cur, paramIndex, param, cur->paramInfos[paramIndex], cItems, paramSetIndex))
+ {
+ Py_XDECREF(param);
+ Py_XDECREF(paramSet);
+ FreeInfos(cur->paramInfos, cur->paramcount);
+ cur->paramInfos = 0;
+ return false;
+ }
+ }
+ }
+
+ Py_XDECREF(paramSet);
+ }
+
+ for (Py_ssize_t i = 0; i < cur->paramcount; i++)
+ {
+ if (!BindParameter(cur, i, cur->paramInfos[i]))
+ {
+ FreeInfos(cur->paramInfos, cur->paramcount);
+ cur->paramInfos = 0;
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool GetParamType(Cursor* cur, Py_ssize_t index, SQLSMALLINT& type)
{
// Returns the ODBC type of the of given parameter.
View
3 src/params.h
@@ -6,7 +6,8 @@ bool Params_init();
struct Cursor;
-bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first);
+bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* params, Py_ssize_t paramsOffset);
+bool PrepareAndBindArray(Cursor* cur, PyObject* pSql, PyObject* params);
void FreeParameterData(Cursor* cur);
void FreeParameterInfo(Cursor* cur);
View
23 src/pyodbcmodule.cpp
@@ -78,6 +78,7 @@ PyObject* ProgrammingError;
PyObject* IntegrityError;
PyObject* DataError;
PyObject* NotSupportedError;
+PyObject* ArrayBindingNotSupportedError;
struct ExcInfo
{
@@ -122,7 +123,9 @@ static ExcInfo aExcInfos[] = {
MAKEEXCINFO(NotSupportedError, DatabaseError,
"Exception raised in case a method or database API was used which is not\n"
"supported by the database, e.g. requesting a .rollback() on a connection that\n"
- "does not support transaction or has transactions turned off.")
+ "does not support transaction or has transactions turned off."),
+ MAKEEXCINFO(ArrayBindingNotSupportedError, DatabaseError,
+ "Exception raised when database does not support array binding.")
};
@@ -253,6 +256,7 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs)
int fAnsi = 0; // force ansi
int fUnicodeResults = 0;
int fReadOnly = 0;
+ int fParameterArrayBinding = 0;
long timeout = 0;
Py_ssize_t size = args ? PyTuple_Size(args) : 0;
@@ -323,6 +327,11 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs)
fReadOnly = PyObject_IsTrue(value);
continue;
}
+ if (Text_EqualsI(key, "parameterarraybinding"))
+ {
+ fParameterArrayBinding = PyObject_IsTrue(value);
+ continue;
+ }
// Map DB API recommended names to ODBC names (e.g. user --> uid).
@@ -368,7 +377,7 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs)
return 0;
}
- return (PyObject*)Connection_New(pConnectString.Get(), fAutoCommit != 0, fAnsi != 0, fUnicodeResults != 0, timeout, fReadOnly != 0);
+ return (PyObject*)Connection_New(pConnectString.Get(), fAutoCommit != 0, fAnsi != 0, fUnicodeResults != 0, timeout, fReadOnly != 0, fParameterArrayBinding != 0);
}
@@ -500,6 +509,14 @@ static char connect_doc[] =
" drivers that return the wrong SQLSTATE (or if pyodbc is out of date and\n"
" should support other SQLSTATEs).\n"
" \n"
+ " parameterarraybinding\n"
+ " If False or zero, the default, executemany does not attempt to use ODBC\n"
+ " parameter array binding. If True or non-zero, executemany will attempt to\n"
+ " use parameter array binding. This is necessary for drivers that do not\n"
+ " handle parameter array binding correctly. Disabling parameter array binding\n"
+ " causes executemany to behave as it did in earlier versions of PyODBC,\n"
+ " as if execute was called multiple times.\n"
+ " \n"
" timeout\n"
" An integer login timeout in seconds, used to set the SQL_ATTR_LOGIN_TIMEOUT\n"
" attribute of the connection. The default is 0 which means the database's\n"
@@ -613,6 +630,7 @@ static void ErrorInit()
IntegrityError = 0;
DataError = 0;
NotSupportedError = 0;
+ ArrayBindingNotSupportedError = 0;
decimal_type = 0;
}
@@ -632,6 +650,7 @@ static void ErrorCleanup()
Py_XDECREF(IntegrityError);
Py_XDECREF(DataError);
Py_XDECREF(NotSupportedError);
+ Py_XDECREF(ArrayBindingNotSupportedError);
Py_XDECREF(decimal_type);
}
View
1 src/pyodbcmodule.h
@@ -24,6 +24,7 @@ extern PyObject* ProgrammingError;
extern PyObject* IntegrityError;
extern PyObject* DataError;
extern PyObject* NotSupportedError;
+extern PyObject* ArrayBindingNotSupportedError;
extern PyObject* null_binary;

0 comments on commit 956201e

Please sign in to comment.
Something went wrong with that request. Please try again.