Skip to content

Commit

Permalink
Restored fast_executemany
Browse files Browse the repository at this point in the history
The only Py_Unicode call that generated an error wasn't even used.  Nothing really ported.
  • Loading branch information
mkleehammer committed Aug 27, 2023
1 parent 7daa723 commit c5bc231
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 30 deletions.
14 changes: 7 additions & 7 deletions src/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1063,13 +1063,13 @@ static PyObject* Cursor_executemany(PyObject* self, PyObject* args)
PyErr_SetString(ProgrammingError, "The second parameter to executemany must not be empty.");
return 0;
}
// if (cursor->fastexecmany)
// {
// free_results(cursor, FREE_STATEMENT | KEEP_PREPARED);
// if (!ExecuteMulti(cursor, pSql, param_seq))
// return 0;
// }
// else
if (cursor->fastexecmany)
{
free_results(cursor, FREE_STATEMENT | KEEP_PREPARED);
if (!ExecuteMulti(cursor, pSql, param_seq))
return 0;
}
else
{
for (Py_ssize_t i = 0; i < c; i++)
{
Expand Down
38 changes: 25 additions & 13 deletions src/params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,22 @@ inline Connection* GetConnection(Cursor* cursor)
return (Connection*)cursor->cnxn;
}

/*
struct DAEParam
{
PyObject *cell;
SQLLEN maxlen;
};

// Detects and sets the appropriate C type to use for binding the specified Python object.
// Also sets the buffer length to use.
// Returns false if unsuccessful.

static int DetectCType(PyObject *cell, ParamInfo *pi)
{
// Detects and sets the appropriate C type to use for binding the specified Python object.
// Also sets the buffer length to use. Returns false if unsuccessful.
//
// We're setting the pi ParameterType and BufferLength. These are based on the Python
// value if not None or binary. For those, the *existing* ParameterType is used. This
// could be from a previous row or could have been initialized from SQLDescribeParam.

PyObject* cls = 0;
if (PyBool_Check(cell))
{
Expand Down Expand Up @@ -263,6 +267,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
if (pi->ValueType != SQL_C_BINARY)
return false;
Py_ssize_t len = PyBytes_GET_SIZE(cell);

if (!pi->ColumnSize) // DAE
{
DAEParam *pParam = (DAEParam*)*outbuf;
Expand All @@ -289,10 +294,6 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
if (pi->ValueType != SQL_C_WCHAR)
return false;

Py_ssize_t len = PyUnicode_GET_SIZE(cell);
// Same size Different size
// DAE DAE only Convert + DAE
// non-DAE Copy Convert + Copy
const TextEnc& enc = cur->cnxn->unicode_enc;
Object encoded(PyCodec_Encode(cell, enc.name, "strict"));
if (!encoded)
Expand All @@ -305,7 +306,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
return false;
}

len = PyBytes_GET_SIZE(encoded);
Py_ssize_t len = PyBytes_GET_SIZE(encoded);
if (!pi->ColumnSize)
{
// DAE
Expand Down Expand Up @@ -489,6 +490,11 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
}
else if (cell == Py_None || cell == null_binary)
{
// REVIEW: Theoretically we could eliminate the initial call to SQLDescribeParam for
// all columns if we had a special value for "unknown" and called SQLDescribeParam only
// here when we hit it. Even then, only if we don't already have previous Python
// objects!

*outbuf += pi->BufferLength;
ind = SQL_NULL_DATA;
}
Expand All @@ -502,7 +508,6 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
return true;
}

*/

static bool GetParamType(Cursor* cur, Py_ssize_t iParam, SQLSMALLINT& type);

Expand Down Expand Up @@ -1445,7 +1450,7 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
return true;
}

/*

bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
{
bool ret = true;
Expand All @@ -1457,10 +1462,12 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
if (!(cur->paramInfos = (ParamInfo*)PyMem_Malloc(sizeof(ParamInfo) * cur->paramcount)))
{
PyErr_NoMemory();
return 0;
return false;
}
memset(cur->paramInfos, 0, sizeof(ParamInfo) * cur->paramcount);

// Wouldn't hurt to free threads here? Or is this fast enough because it is local?

// Describe each parameter (SQL type) in preparation for allocation of paramset array
for (Py_ssize_t i = 0; i < cur->paramcount; i++)
{
Expand Down Expand Up @@ -1498,6 +1505,7 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
{
// Scan current row to determine C types
PyObject *currow = *rowptr++;
// REVIEW: This check is not needed - PySequence_Fast below is sufficient.
if (!PyTuple_Check(currow) && !PyList_Check(currow) && !Row_Check(currow))
{
RaiseErrorV(0, PyExc_TypeError, "Params must be in a list, tuple, or Row");
Expand All @@ -1519,8 +1527,12 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
}
PyObject **cells = PySequence_Fast_ITEMS(colseq);

// REVIEW: We need a better description of what is going on here. Why is it OK to pass
// a fake bindptr to SQLBindParameter.

// Start at a non-zero offset to prevent null pointer detection.
char *bindptr = (char*)16;

Py_ssize_t i = 0;
for (; i < cur->paramcount; i++)
{
Expand Down Expand Up @@ -1769,7 +1781,7 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
FreeParameterData(cur);
return ret;
}
*/


static bool GetParamType(Cursor* cur, Py_ssize_t index, SQLSMALLINT& type)
{
Expand Down
2 changes: 1 addition & 1 deletion src/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ bool Params_init();
struct Cursor;

bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first);
/* bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj); */
bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj);
bool GetParameterInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info, bool isTVP);
void FreeParameterData(Cursor* cur);
void FreeParameterInfo(Cursor* cur);
Expand Down
18 changes: 9 additions & 9 deletions tests/sqlserver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,18 +850,18 @@ def test_executemany_one(cursor):
assert param[1] == row[1]


# def test_executemany_dae_0(cursor):
# """
# DAE for 0-length value
# """
# cursor.execute("create table t1(a nvarchar(max))")
def test_executemany_dae_0(cursor):
"""
DAE for 0-length value
"""
cursor.execute("create table t1(a nvarchar(max))")

# cursor.fast_executemany = True
# cursor.executemany("insert into t1(a) values(?)", [['']])
cursor.fast_executemany = True
cursor.executemany("insert into t1(a) values(?)", [['']])

# assert cursor.execute("select a from t1").fetchone()[0] == ''
assert cursor.execute("select a from t1").fetchone()[0] == ''

# cursor.fast_executemany = False
cursor.fast_executemany = False


def test_executemany_failure(cursor):
Expand Down

0 comments on commit c5bc231

Please sign in to comment.