Skip to content

FIX: record correct UTF-16 code-unit length for every string parameter during native detection - #759

Draft
Gaurav Sharma (bewithgaurav) wants to merge 10 commits into
mainfrom
bewithgaurav/insertmany-utf16len
Draft

FIX: record correct UTF-16 code-unit length for every string parameter during native detection#759
Gaurav Sharma (bewithgaurav) wants to merge 10 commits into
mainfrom
bewithgaurav/insertmany-utf16len

Conversation

@bewithgaurav

@bewithgaurav Gaurav Sharma (bewithgaurav) commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Work Item / Issue Reference

GitHub Issue: #500


Summary

During native parameter detection, ParamInfo.utf16Len records how many UTF-16 code units a string parameter occupies, which is exactly what the wide-character binder writes for that value. Before this change, only the long-string data-at-execution path set the field; inline strings, time values normalized through isoformat, and setinputsizes-formatted strings all left it at 0.

That gap is a latent correctness bug in the parameter metadata: any consumer that trusts utf16Len to size a wide-character buffer would read 0 for the common cases and undersize the allocation. The current production binder does not read the field yet, so there is no user-visible failure today; this change closes the gap defensively so the metadata is correct at every site a string binds wide, before anything starts depending on it.

The length is computed after any in-place normalization (time and Decimal formatting), and supplementary Unicode characters above U+FFFF are counted as the two code units of their surrogate pair. The field is exposed read-only, and a DetectParamTypesForTesting hook lets the detection contract be asserted without a live SQL Server.

This is a metadata correctness fix only. It is not a performance change: arena allocation and cached parameter bindings are out of scope and land in separate pull requests.


No-Regression Check

utf16Len is computed inside the hot detection path, so these local timings confirm the added work does not regress detection rather than claim any speedup. macOS arm64, Python 3.13, release builds (-O3 -DNDEBUG), 2,000 parameters per call, median of 270 timed samples after 30 warmups. Both builds include the same test hook, so the numbers cover list copying and result marshalling, not native detection alone.

Batch Before After
INT / VARCHAR 320.6 us 316.5 us
INT / VARCHAR / TIME / DECIMAL 785.3 us 795.9 us

Both deltas sit inside run-to-run spread, so this is a wash. These are preliminary local observations, not a formal guarantee; profiler-based analysis is tracked separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
reject invalid Decimal subclass formatting before replacing the parameter so native execution preserves the Python format contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reconcile #742 (bind Decimal as SQL_NUMERIC regardless of value, GH-740) with the native setinputsizes migration:
- param_detect.hpp: drop the automatic MONEY/SMALLMONEY VARCHAR shortcut; every finite Decimal binds SQL_NUMERIC natively. FormatDecimalParam stays for the setinputsizes DECIMAL override only.
- cursor.py: _create_parameter_types_list forwards decimal_as_numeric to _map_sql_type; the parameterless else-branch keeps DDBCSQLExecDirect and drops the deleted DDBCSQLExecuteLegacy block (GH-740 fix now happens in native detection).
- test_023: keep both suites; narrow test_decimal_format_must_return_string to the setinputsizes override, the only path that still formats Decimals after GH-740.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
native detection left utf16Len at 0 for inline strings, time->isoformat strings, and setinputsizes-formatted strings; only the DAE branch set it. a later change will size a per-batch wide-char arena from utf16Len, where a 0 would undersize the slice and corrupt the heap. compute it (surrogate pairs counted as two units) after any in-place normalization at every site a string binds wide. no behaviour change: nothing reads utf16Len yet. expose it read-only and add a DetectParamTypesForTesting hook so the contract is unit-tested without a live server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Sep 7, 2026
# Conflicts:
#	mssql_python/cursor.py
#	mssql_python/pybind/ddbc_bindings.cpp
#	mssql_python/pybind/param_detect.hpp
@bewithgaurav Gaurav Sharma (bewithgaurav) changed the title PERF: track correct UTF-16 code-unit lengths for every string parameter FIX: record correct UTF-16 code-unit length for every string parameter during native detection Sep 8, 2026
@bewithgaurav
Gaurav Sharma (bewithgaurav) changed the base branch from bewithgaurav/native-setinputsizes to main September 8, 2026 18:52
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.

3 participants