Skip to content

FIX: Preserve leading BOM-like characters in bounded text fetches - #793

Open
Jahnvi Thakkar (jahnvi480) wants to merge 2 commits into
mainfrom
jahnvi/fix-fetch-bounded-text
Open

Jahnvi Thakkar (jahnvi480) wants to merge 2 commits into
mainfrom
jahnvi/fix-fetch-bounded-text

Conversation

@jahnvi480

@jahnvi480 Jahnvi Thakkar (jahnvi480) commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

ADO Bug: AB#48253
GitHub Issue: #794


Summary

This pull request improves the handling of bounded text columns fetched as UTF-16, ensuring that leading U+FEFF and U+FFFE code points are preserved as payload rather than being treated as byte-order markers (BOMs). This change affects row-wise fetching methods and batch decoding on Linux/macOS, and is covered by new regression tests. The implementation introduces a new utility for consistent UTF-16 decoding and updates the codebase to use it.

Bounded text payload handling:

  • Bounded UTF-16 text fetched from the database now preserves leading U+FEFF and U+FFFE as payload, fixing previous behavior where these were sometimes interpreted as BOMs and removed. This affects fetchone(), fetchmany(), and fetchall() results, including when bounded columns are fetched alongside a MAX column.
  • Added comprehensive regression tests in tests/test_017_fetch_bounded_text.py to verify correct handling of BOMs, surrogate pairs, and payload fidelity for bounded text columns.

Decoding implementation changes:

  • Introduced a new fetch_text.hpp utility with FetchText::decode_utf16_native and FetchText::from_utf16_native for correct, platform-consistent UTF-16 decoding that treats leading BOM-like code points as payload.
  • Updated all relevant code paths in ddbc_bindings.cpp and ddbc_bindings.h to use the new decoding utility instead of direct calls to PyUnicode_DecodeUTF16, ensuring consistent handling across the codebase.
  • Included the new utility header in affected files.

Decode bounded ODBC wide-text payloads strictly in native byte order without consuming leading payload characters. Add regression coverage and document the intentional correctness change and unchanged MAX limitations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 12:28

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.

🔵 Needs a closer look

The native-endian selection has a correctness defect on big-endian builds, and cross-platform validation remains pending.

Pull request overview

Preserves leading BOM-like characters in bounded text fetches through strict native-endian decoding.

Changes:

  • Added shared UTF-16 decoding helper.
  • Updated bounded text conversion paths.
  • Added regression tests and changelog documentation.
File summaries
File Summary
tests/test_017_fetch_bounded_text.py Adds bounded text regression coverage.
mssql_python/pybind/fetch_text.hpp Adds strict UTF-16 decoding helper.
mssql_python/pybind/ddbc_bindings.cpp Uses the helper for bounded conversions.
CHANGELOG.md Documents BOM-like character preservation.
Review details

Suppressed comments (1)

mssql_python/pybind/fetch_text.hpp:14

  • PY_LITTLE_ENDIAN is an endian tag value, not a boolean predicate (CPython defines it as a nonzero constant such as 1234). Consequently this expression selects -1 on both little- and big-endian builds, so the helper would decode every buffer as little-endian on a big-endian target. Compare PY_BYTE_ORDER with PY_LITTLE_ENDIAN (or use an equivalent compile-time endianness check) before choosing the decoder byte order.
    int byteorder = PY_LITTLE_ENDIAN ? -1 : 1;
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Share nonthrowing native-endian decoding with the two Unix batch processors while retaining their existing fallback behavior and the checked row-wise wrapper. Add platform-aware malformed NVARCHAR batch controls.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 17, 2026 12:46

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.

🔵 Needs a closer look

Batch decoder changes conflict with the stated scope and require correction or updated scope and validation.

Review details

Suppressed comments (2)

mssql_python/pybind/ddbc_bindings.h:598

  • This hunk also changes the batch ProcessChar decoder from BOM-autodetecting mode to explicit native-endian mode, so it changes fetchmany()/fetchall() behavior outside the stated scope of replacing only the two bounded SQLGetData_wrap expressions while leaving batch conversion unchanged. Either keep the batch path unchanged or update the PR description and validation/scope claims to explicitly include this behavior change.
            PyObject* pyStr =
                FetchText::decode_utf16_native(reinterpret_cast<const char*>(wcharData),
                                              numCharsInData * sizeof(SQLWCHAR));

mssql_python/pybind/ddbc_bindings.h:712

  • This hunk likewise changes the batch ProcessWChar decoder, even though the PR description says batch conversion remains unchanged and only the two bounded SQLGetData_wrap expressions are being replaced. This changes bounded fetchmany()/fetchall() semantics and should either be reverted here or explicitly added to the documented scope and cross-platform validation plan.
        SQLWCHAR* wcharData = &buffers.wcharBuffers[col - 1][rowIdx * colInfo->fetchBufferSize];
        PyObject* pyStr = FetchText::decode_utf16_native(
            reinterpret_cast<const char*>(wcharData), numCharsInData * sizeof(SQLWCHAR));
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

83%


📈 Total Lines Covered: 8455 out of 10100
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/pybind/ddbc_bindings.cpp (100%)
  • mssql_python/pybind/ddbc_bindings.h (100%)
  • mssql_python/pybind/fetch_text.hpp (100%)

Summary

  • Total: 18 lines
  • Missing: 0 lines
  • Coverage: 100%

📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 64.1%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.row.py: 77.6%
mssql_python.pybind.ddbc_bindings.cpp: 77.7%
mssql_python.pybind.connection.connection_pool.cpp: 81.8%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%
mssql_python.pybind.py_type_cache.hpp: 91.6%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: medium Moderate update size

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants