Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dcb60f2
Add C++ DetectParamTypes + SQLExecuteFast pipeline
bewithgaurav Apr 29, 2026
14ccf08
Fix DAE handling, MONEY range, and TypeError for unknown types
bewithgaurav Apr 29, 2026
8ab074c
Fix MSVC warnings-as-errors: unused param and catch variable
bewithgaurav Apr 30, 2026
00c85cc
Guard memcpy with null/length check for DevSkim DS121708
bewithgaurav Apr 30, 2026
1c1111e
Merge branch 'main' into bewithgaurav/insertmany-perf-detect-types
jahnvi480 May 6, 2026
bad8acf
STYLE: Fix black formatting in cursor.py
bewithgaurav May 7, 2026
c5a827f
Address PR review: encoding key, subclass support, GIL, exec_rc, curs…
bewithgaurav May 7, 2026
a38ce4e
Address second PR review: refcount leak, geometry+DAE, NaN, parity test
bewithgaurav May 7, 2026
a0e031b
Merge branch 'main' into bewithgaurav/insertmany-perf-detect-types
bewithgaurav Jun 24, 2026
fbd1a07
Merge branch 'main' into bewithgaurav/insertmany-perf-detect-types
bewithgaurav Jul 6, 2026
ba65f46
FIX: Address rubber-duck review findings in fast path
bewithgaurav Jul 1, 2026
bd205cc
Merge remote-tracking branch 'origin/main' into bewithgaurav/insertma…
bewithgaurav Jul 14, 2026
dc1460b
STYLE: Fix black formatting in test_023_fast_path_parity
bewithgaurav Jul 14, 2026
5cf016e
PERF: Convert DetectParamTypes to raw CPython API
bewithgaurav Jul 14, 2026
8e8d622
FIX: Use memcpy_s on MSVC for CodeQL DS121708 compliance
bewithgaurav Jul 14, 2026
cbd2f00
REFACTOR: Rename DDBCSQLExecuteFast → DDBCSQLExecute, old → DDBCSQLEx…
bewithgaurav Jul 14, 2026
fa0e866
REFACTOR: Convert PythonObjectCache + ParamInfo::dataPtr to raw CPython
bewithgaurav Jul 14, 2026
3d73824
FIX: Add ParamInfo copy constructor for pybind11 legacy path compatib…
bewithgaurav Jul 14, 2026
98eb254
FIX: Use explicit byte length for string binding instead of SQL_NTS
bewithgaurav Jul 14, 2026
4ee2db9
TEST: Add edge-case tests and LCOV exclusions for untestable paths
bewithgaurav Jul 14, 2026
8941070
FIX: Address review comments — PyList_SetItem checks and bytearray DA…
bewithgaurav Jul 14, 2026
432b522
FIX: Harden Decimal subclass safety, RAII param cleanup, cache init leak
bewithgaurav Jul 14, 2026
fd06b80
FIX: Revert RAII ParamResetGuard — it wiped ODBC diagnostics on error
bewithgaurav Jul 15, 2026
48ba475
REFACTOR: Extract helpers — PyObjGuard, stream_dae_chunks, get_cached…
bewithgaurav Jul 15, 2026
114d76b
FIX: Suppress DevSkim DS121708 for bounded memcpy on non-MSVC
bewithgaurav Jul 15, 2026
3499dbd
DOC: Add algorithm comments to build_numeric_data and Decimal detection
bewithgaurav Jul 15, 2026
d164a54
REFACTOR: Replace PyObjGuard with PyPtr (Pattern 0 from perf proposal)
bewithgaurav Jul 15, 2026
95b3b10
Merge branch 'main' into bewithgaurav/insertmany-perf-detect-types
bewithgaurav Jul 15, 2026
eb59911
Merge branch 'main' of https://github.com/microsoft/mssql-python into…
bewithgaurav Jul 17, 2026
b5054c0
REFACTOR: Extract PythonObjectCache to header, extend PyPtr, fix doub…
bewithgaurav Jul 17, 2026
f74148f
REFACTOR: Rename PythonObjectCache → PyTypeCache, remove unused imports
bewithgaurav Jul 17, 2026
527be8f
REFACTOR: use pybind11's py::object for refcount RAII instead of cust…
bewithgaurav Jul 30, 2026
a2eb3a7
PERF: build NUMERIC mantissa with fixed-width limbs instead of Python…
bewithgaurav Jul 30, 2026
2342353
REFACTOR: hold ParamInfo::dataPtr in py::object instead of a hand-rol…
Copilot Jul 31, 2026
2a8f18a
REFACTOR: add a borrow() counterpart to steal() and use both at every…
Copilot Jul 31, 2026
d69ee60
REFACTOR: move steal/borrow into py_ref.hpp
Copilot Aug 1, 2026
f5e3a62
REFACTOR: extract parameter type detection into param_detect.hpp
Copilot Aug 1, 2026
2ecda91
REFACTOR: drop fast_path naming, mark the legacy execute path for rem…
Copilot Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 65 additions & 33 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,14 @@ def _create_parameter_types_list( # pylint: disable=too-many-arguments,too-many
) -> Tuple[int, int, int, int, bool]:
"""
Maps parameter types for the given parameter.

Python-side type detection. The standard execute() path no longer calls this —
DetectParamTypes does the same job in C++ without crossing the pybind11
boundary per parameter. Two callers remain: the legacy execute() branch used
when setinputsizes overrides are active, and executemany(). The former goes
away once setinputsizes is handled natively; the latter needs its own
native columnwise detection before this can be deleted outright.

Args:
parameter: parameter to bind.
Returns:
Expand Down Expand Up @@ -1487,11 +1495,6 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
# Getting encoding setting
encoding_settings = self._get_encoding_settings()

# Apply timeout if set (non-zero)
logger.debug("execute: Creating parameter type list")
param_info = ddbc_bindings.ParamInfo
parameters_type = []

# Validate that inputsizes matches parameter count if both are present
if parameters and self._inputsizes:
if len(self._inputsizes) != len(parameters):
Expand All @@ -1503,11 +1506,6 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
Warning,
)

if parameters:
for i, param in enumerate(parameters):
paraminfo = self._create_parameter_types_list(param, param_info, parameters, i)
parameters_type.append(paraminfo)

# Prepare caching: skip SQLPrepare when re-executing the same SQL
# with parameters. The HSTMT is reused via _soft_reset_cursor, so the
# server-side plan from the previous SQLPrepare is still valid.
Expand All @@ -1516,30 +1514,64 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
self.is_stmt_prepared = [False]
effective_use_prepare = use_prepare and not same_sql

if logger.isEnabledFor(logging.DEBUG):
for i, param in enumerate(parameters):
logger.debug(
"""Parameter number: %s, Parameter: %s,
Param Python Type: %s, ParamInfo: %s, %s, %s, %s, %s""",
i + 1,
param,
str(type(param)),
parameters_type[i].paramSQLType,
parameters_type[i].paramCType,
parameters_type[i].columnSize,
parameters_type[i].decimalDigits,
parameters_type[i].inputOutputType,
)

ret = ddbc_bindings.DDBCSQLExecute(
self.hstmt,
operation,
parameters,
parameters_type,
self.is_stmt_prepared,
effective_use_prepare,
encoding_settings,
# Standard path: when no inputsizes override, type detection + bind + execute
# all happen in C++ via DDBCSQLExecute. ParamInfo never crosses the pybind11
# boundary. This is the path ~99% of calls take.
use_standard_execute = parameters and not (
self._inputsizes and any(s is not None for s in self._inputsizes)
)

if use_standard_execute:
ret = ddbc_bindings.DDBCSQLExecute(
self.hstmt,
operation,
parameters,
self.is_stmt_prepared,
effective_use_prepare,
encoding_settings,
)
else:
# LEGACY PATH — slated for removal in a future optimization round.
#
# Kept only for setinputsizes() callers, where the user's explicit type
# overrides have to be honoured instead of C++ detecting types itself.
# Type detection happens in Python here, so every parameter round-trips
# through pybind11 as a ParamInfo object, which is what makes it slow.
# Once setinputsizes overrides are handled natively, this branch and
# DDBCSQLExecuteLegacy both go away.
parameters_type = []
if parameters:
param_info = ddbc_bindings.ParamInfo
for i, param in enumerate(parameters):
paraminfo = self._create_parameter_types_list(param, param_info, parameters, i)
parameters_type.append(paraminfo)

if logger.isEnabledFor(logging.DEBUG):
for i, param in enumerate(parameters):
logger.debug(
"""Parameter number: %s, Parameter: %s,
Param Python Type: %s, ParamInfo: %s, %s, %s, %s, %s""",
i + 1,
param,
str(type(param)),
parameters_type[i].paramSQLType,
parameters_type[i].paramCType,
parameters_type[i].columnSize,
parameters_type[i].decimalDigits,
parameters_type[i].inputOutputType,
)

# Legacy binding: accepts the pre-built ParamInfo list from Python.
# Goes away with the branch above.
ret = ddbc_bindings.DDBCSQLExecuteLegacy(
self.hstmt,
operation,
parameters,
parameters_type,
self.is_stmt_prepared,
effective_use_prepare,
encoding_settings,
)
# Check return code
try:

Expand Down
Loading
Loading