Skip to content

FIX: Initialize numeric array indicators in executemany - #702

Merged
gargsaumya merged 5 commits into
mainfrom
saumya/gh-670
Aug 6, 2026
Merged

FIX: Initialize numeric array indicators in executemany#702
gargsaumya merged 5 commits into
mainfrom
saumya/gh-670

Conversation

@gargsaumya

@gargsaumya gargsaumya commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

GitHub Issue: #670


Summary

  • Initialize every fixed-width numeric parameter indicator before ODBC array execution.
  • Prevent late NULL values from leaving earlier indicator slots uninitialized.
  • Add regression coverage for TINYINT, SMALLINT, INT, and FLOAT batches with late NULLs.

Root cause

The affected numeric binding paths allocated the ODBC indicator array only after encountering the first NULL. Earlier rows then contained uninitialized indicators, which could be interpreted as data-at-execution markers and cause a batch to insert zero rows without an exception.

Validation

  • Native extension build succeeded on Windows x64 with Python 3.13.
  • Reproduction improved from 209/300 anomalous batches to 0/300.
  • Focused cursor tests: 2 passed.
  • Black check and git diff whitespace check passed.

Copilot AI lite review requested due to automatic review settings August 5, 2026 07:43
@github-actions github-actions Bot added the pr-size: small Minimal code update label Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a native executemany() array-binding correctness bug where fixed-width numeric parameters could leave earlier ODBC indicator slots uninitialized if a None (NULL) appeared later in the batch, leading to sporadic “0 rows inserted” outcomes with no exception.

Changes:

  • Initialize/allocate strLenOrIndArray upfront for fixed-width numeric array bindings (e.g., SQL_C_LONG, SQL_C_DOUBLE, SQL_C_TINYINT, SQL_C_SHORT) and explicitly set each slot to 0 or SQL_NULL_DATA.
  • Add a regression test that repeatedly inserts batches containing “late” NULLs across multiple numeric types (TINYINT/SMALLINT/INT/FLOAT) and asserts both rowcount and round-tripped values.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/test_004_cursor.py Adds regression coverage for executemany() numeric batches where NULLs occur late in the parameter arrays.
mssql_python/pybind/ddbc_bindings.cpp Ensures indicator arrays for fixed-width numeric parameter arrays are always allocated and fully initialized before SQLBindParameter/SQLExecuteMany.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

62%


🎯 Overall Coverage

82%


📈 Total Lines Covered: 7351 out of 8961
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/pybind/ddbc_bindings.cpp (62.5%): Missing lines 2257,2264,2313

Summary

  • Total: 8 lines
  • Missing: 3 lines
  • Coverage: 62%

mssql_python/pybind/ddbc_bindings.cpp

Lines 2253-2261

  2253                     LOG("BindParameterArray: Binding SQL_C_DOUBLE array - "
  2254                         "param_index=%d, count=%zu",
  2255                         paramIndex, paramSetSize);
  2256                     double* dataArray = AllocateParamBufferArray<double>(tempBuffers, paramSetSize);
! 2257                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2258                     for (size_t i = 0; i < paramSetSize; ++i) {
  2259                         if (columnValues[i].is_none()) {
  2260                             dataArray[i] = 0;
  2261                             strLenOrIndArray[i] = SQL_NULL_DATA;

Lines 2260-2268

  2260                             dataArray[i] = 0;
  2261                             strLenOrIndArray[i] = SQL_NULL_DATA;
  2262                         } else {
  2263                             dataArray[i] = columnValues[i].cast<double>();
! 2264                             strLenOrIndArray[i] = 0;
  2265                         }
  2266                     }
  2267                     LOG("BindParameterArray: SQL_C_DOUBLE bound - "
  2268                         "param_index=%d",

Lines 2309-2317

  2309                         "array - param_index=%d, count=%zu",
  2310                         paramIndex, paramSetSize);
  2311                     unsigned char* dataArray =
  2312                         AllocateParamBufferArray<unsigned char>(tempBuffers, paramSetSize);
! 2313                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2314                     for (size_t i = 0; i < paramSetSize; ++i) {
  2315                         if (columnValues[i].is_none()) {
  2316                             dataArray[i] = 0;
  2317                             strLenOrIndArray[i] = SQL_NULL_DATA;


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.3%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.4%
mssql_python.pybind.connection.connection.cpp: 83.7%
mssql_python.logging.py: 85.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

@subrata-ms Subrata (subrata-ms) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Only concern is the code coverage showing pretty less. Probably you need to modify the existing test to cover the new AllocateParamBufferArray allocation call.

@gargsaumya
gargsaumya merged commit e0eb9fd into main Aug 6, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: small Minimal code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants