Skip to content

Commit 94b8a69

Browse files
committed
OPTIMIZATION #2: Direct Python C API for numeric types
Problem: - All numeric conversions used pybind11 wrappers with overhead: * Type detection, wrapper object creation, bounds checking * ~20-40 CPU cycles overhead per cell Solution: - Use direct Python C API calls: * PyLong_FromLong/PyLong_FromLongLong for integers * PyFloat_FromDouble for floats * PyBool_FromLong for booleans * PyList_SET_ITEM macro (no bounds check - list pre-sized) Changes: - SQL_INTEGER, SQL_SMALLINT, SQL_BIGINT, SQL_TINYINT → PyLong_* - SQL_BIT → PyBool_FromLong - SQL_REAL, SQL_DOUBLE, SQL_FLOAT → PyFloat_FromDouble - Added explicit NULL handling for each type Impact: - Eliminates pybind11 wrapper overhead for simple numeric types - Direct array access via PyList_SET_ITEM macro - Affects 7 common numeric SQL types
1 parent c7d1aa3 commit 94b8a69

File tree

1 file changed

+56
-7
lines changed

1 file changed

+56
-7
lines changed

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3318,23 +3318,58 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
33183318
break;
33193319
}
33203320
case SQL_INTEGER: {
3321-
row[col - 1] = buffers.intBuffers[col - 1][i];
3321+
// OPTIMIZATION #2: Direct Python C API for integers
3322+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3323+
Py_INCREF(Py_None);
3324+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3325+
} else {
3326+
PyObject* pyInt = PyLong_FromLong(buffers.intBuffers[col - 1][i]);
3327+
PyList_SET_ITEM(row.ptr(), col - 1, pyInt);
3328+
}
33223329
break;
33233330
}
33243331
case SQL_SMALLINT: {
3325-
row[col - 1] = buffers.smallIntBuffers[col - 1][i];
3332+
// OPTIMIZATION #2: Direct Python C API for smallint
3333+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3334+
Py_INCREF(Py_None);
3335+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3336+
} else {
3337+
PyObject* pyInt = PyLong_FromLong(buffers.smallIntBuffers[col - 1][i]);
3338+
PyList_SET_ITEM(row.ptr(), col - 1, pyInt);
3339+
}
33263340
break;
33273341
}
33283342
case SQL_TINYINT: {
3329-
row[col - 1] = buffers.charBuffers[col - 1][i];
3343+
// OPTIMIZATION #2: Direct Python C API for tinyint
3344+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3345+
Py_INCREF(Py_None);
3346+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3347+
} else {
3348+
PyObject* pyInt = PyLong_FromLong(buffers.charBuffers[col - 1][i]);
3349+
PyList_SET_ITEM(row.ptr(), col - 1, pyInt);
3350+
}
33303351
break;
33313352
}
33323353
case SQL_BIT: {
3333-
row[col - 1] = static_cast<bool>(buffers.charBuffers[col - 1][i]);
3354+
// OPTIMIZATION #2: Direct Python C API for bit/boolean
3355+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3356+
Py_INCREF(Py_None);
3357+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3358+
} else {
3359+
PyObject* pyBool = PyBool_FromLong(buffers.charBuffers[col - 1][i]);
3360+
PyList_SET_ITEM(row.ptr(), col - 1, pyBool);
3361+
}
33343362
break;
33353363
}
33363364
case SQL_REAL: {
3337-
row[col - 1] = buffers.realBuffers[col - 1][i];
3365+
// OPTIMIZATION #2: Direct Python C API for real/float
3366+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3367+
Py_INCREF(Py_None);
3368+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3369+
} else {
3370+
PyObject* pyFloat = PyFloat_FromDouble(buffers.realBuffers[col - 1][i]);
3371+
PyList_SET_ITEM(row.ptr(), col - 1, pyFloat);
3372+
}
33383373
break;
33393374
}
33403375
case SQL_DECIMAL:
@@ -3356,7 +3391,14 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
33563391
}
33573392
case SQL_DOUBLE:
33583393
case SQL_FLOAT: {
3359-
row[col - 1] = buffers.doubleBuffers[col - 1][i];
3394+
// OPTIMIZATION #2: Direct Python C API for double/float
3395+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3396+
Py_INCREF(Py_None);
3397+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3398+
} else {
3399+
PyObject* pyFloat = PyFloat_FromDouble(buffers.doubleBuffers[col - 1][i]);
3400+
PyList_SET_ITEM(row.ptr(), col - 1, pyFloat);
3401+
}
33603402
break;
33613403
}
33623404
case SQL_TIMESTAMP:
@@ -3369,7 +3411,14 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
33693411
break;
33703412
}
33713413
case SQL_BIGINT: {
3372-
row[col - 1] = buffers.bigIntBuffers[col - 1][i];
3414+
// OPTIMIZATION #2: Direct Python C API for bigint
3415+
if (buffers.indicators[col - 1][i] == SQL_NULL_DATA) {
3416+
Py_INCREF(Py_None);
3417+
PyList_SET_ITEM(row.ptr(), col - 1, Py_None);
3418+
} else {
3419+
PyObject* pyInt = PyLong_FromLongLong(buffers.bigIntBuffers[col - 1][i]);
3420+
PyList_SET_ITEM(row.ptr(), col - 1, pyInt);
3421+
}
33733422
break;
33743423
}
33753424
case SQL_TYPE_DATE: {

0 commit comments

Comments
 (0)