Skip to content

PERF: Optimize fetchone, fetchmany(1) and fetchval paths - #829

Open
Jahnvi Thakkar (jahnvi480) wants to merge 7 commits into
mainfrom
jahnvi/candidate-a-fetchone-column-count
Open

Jahnvi Thakkar (jahnvi480) wants to merge 7 commits into
mainfrom
jahnvi/candidate-a-fetchone-column-count

Conversation

@jahnvi480

@jahnvi480 Jahnvi Thakkar (jahnvi480) commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#48364

Not applicable; the work item above is the single reference.


Summary

  • Consolidate fetchone(), fetchmany(1) and inherited fetchval()/iterator optimization work in this existing PR.
  • Retain generation-scoped full column-count caching, separate from prefix SQLGetData metadata. Direct column-count calls remain uncached; all nine original count-cache regressions remain.
  • Share native single-row fetching and reuse successful unbinds only within a valid generation. Invalidate before binding, including Arrow/partial binds, and during cleanup. The marker does not certify row-array attributes.
  • Route only all-numeric fetchmany(1) results through SQLFetchScroll and SQLGetData, preserving eager count/name validation and row-array configuration/cleanup. Mixed INT/NVARCHAR and other types retain their existing native paths.
  • Use direct one-row wrapping only for exact built-in integer size 1, one returned row, canonical Row/factory, and no converter/UUID work. Preserve larger-request tails, substituted factories, integer subclasses and actual fetchone() overrides.
  • Keep full-row construction and all-column converter callbacks for fetchval(). Replace Python single-row phase context managers with equivalent paired start/stop instrumentation.
  • Add numeric parity, EOF, override/factory, diagnostics, generation-change, mixed-API and fault-recovery regressions, plus attribution documentation.

Qualification status: source review accepted; source-only controls passed (17 methods, 144 fake-boundary variants and eight 10,000-row operation-count controls). These use fake native boundaries/textual checks and do not establish native correctness or latency.

Formatting: python -m pre_commit run black-check --all-files --hook-stage pre-push passed, using isolated pre-commit 4.5.1 and the repository-pinned Black 26.5.1 hook. Functional isolated commit/push hooks ran the same full check successfully. Black removed one extra blank line in the new test file; all 17 source controls passed again after formatting. git diff --cached --check also passed before commit.

Native qualification and performance testing remain pending. No combined native build/import, live SQL test or benchmark has run locally. Earlier original-head CI/profiler results do not qualify this combined change. No speedup is claimed. Fresh-main, original-PR, combined and pyodbc comparisons require a separately reviewed runtime plan; normal CI for this update is expected to provide broader regression coverage.

Normal CI update: ADO build 180509 on 4c43081a built Ubuntu SQL2025 Release with profiling enabled, then reported 11 failed, 5,696 passed, 132 skipped and 42 deselected. All nine original count-cache cases and both new generation-change cases passed. Ten failures used unprefixed native profiler keys; the partial-bind case incorrectly excluded the existing RuntimeError contract. Commit ac6e9880 corrects only those test expectations and counter-name documentation; production source is unchanged. All 19 source-only controls and the pinned full Black check passed before pushing the correction. A fresh native CI run is pending; the failed run is not presented as successful qualification.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 09:00
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

PR Performance Report

✅ No regression detected

No consistent slowdowns detected across all 2 environments.

0 IMPROVEMENTS 0 SLOWDOWNS 2/2 ENVIRONMENTS

Coverage: 2 of 2 environments completed. Advisory result; does not block merging.

Performance diagnostics

Phase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed.

Unix / SQL Server 2022

Row-by-row fetching: ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.000 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (1000 -> 1 calls).
Repeated positional queries: py::execute::cpp_call +0.112 ms; ddbc::FetchOne_wrap +0.107 ms; ddbc::SQLExecute_wrap +0.101 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
Repeated named-parameter queries: py::fetchone::cpp_call +0.042 ms; ddbc::FetchOne_wrap +0.024 ms; ddbc::SQLRowCount_wrap +0.004 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
10,000 scalar values / fetchval() (debug disabled): ddbc::SQLGetData_wrap +0.101 ms; ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.002 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (10000 -> 1 calls).

Unix / SQL Server 2025

Row-by-row fetching: ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.001 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (1000 -> 1 calls).
Repeated positional queries: py::fetchone::cpp_call +0.030 ms; ddbc::FetchOne_wrap +0.017 ms; py::execute::post_execute +0.010 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
Repeated named-parameter queries: py::fetchone::cpp_call +0.024 ms; ddbc::FetchOne_wrap +0.015 ms; ddbc::BindParameters +0.012 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
10,000 scalar values / fetchval() (debug disabled): ddbc::SQLGetData_wrap +0.006 ms; ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.003 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (10000 -> 1 calls).

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 10.676 ms 11.180 ms +5.3% no signal
SELECT queries 1.100 ms 1.085 ms -0.5% no signal
Row insertion 34.894 ms 35.490 ms +1.7% no signal
Executemany inserts 160.223 ms 157.454 ms -0.1% no signal
Fetch-all queries 121.506 ms 121.246 ms -0.3% no signal
Row-by-row fetching 14.894 ms 13.052 ms -12.4% no signal
Batched row fetching 119.422 ms 119.739 ms -1.4% no signal
Transaction commit and rollback 115.869 ms 116.739 ms +0.8% no signal
Arrow row fetching 95.098 ms 95.418 ms -1.1% no signal
100,000-row insertion 461.326 ms 475.363 ms +1.8% no signal
Row fetching in batches of 100 122.460 ms 121.899 ms -0.5% no signal
Row fetching in batches of 10,000 144.462 ms 139.705 ms -3.9% no signal
Repeated positional queries 34.501 ms 34.143 ms +0.6% no signal
Repeated named-parameter queries 37.344 ms 36.808 ms +0.5% no signal
Legacy 100,000-row insertion 364.044 ms 368.070 ms +3.3% no signal
Insertion with explicit input sizes 507.348 ms 511.514 ms +0.8% no signal
Joined aggregation queries 181.555 ms 181.288 ms +0.9% no signal
Large joined-result fetching 191.211 ms 195.874 ms +1.9% no signal
1.2-million-row fetching 3513.058 ms 3486.117 ms -0.6% no signal
Common table expression queries 5.801 ms 5.556 ms -3.9% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.347 ms 1.435 ms +8.8% no signal
10,000 scalar values / fetchval() (debug disabled) 107.412 ms 95.766 ms -10.9% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 96.810 ms 97.096 ms +0.1% no signal
SELECT queries 1.064 ms 1.089 ms +2.2% no signal
Row insertion 34.697 ms 34.440 ms -0.7% no signal
Executemany inserts 150.572 ms 153.296 ms +1.8% no signal
Fetch-all queries 121.506 ms 121.231 ms -0.2% no signal
Row-by-row fetching 14.776 ms 13.145 ms -10.5% no signal
Batched row fetching 118.185 ms 117.953 ms -0.2% no signal
Transaction commit and rollback 113.800 ms 116.088 ms +1.8% no signal
Arrow row fetching 93.301 ms 95.790 ms +4.1% no signal
100,000-row insertion 444.240 ms 457.861 ms +2.7% no signal
Row fetching in batches of 100 123.626 ms 122.990 ms -2.4% no signal
Row fetching in batches of 10,000 138.892 ms 139.016 ms -0.3% no signal
Repeated positional queries 34.000 ms 33.734 ms -2.4% no signal
Repeated named-parameter queries 36.248 ms 35.791 ms -1.6% no signal
Legacy 100,000-row insertion 361.869 ms 351.495 ms -5.0% no signal
Insertion with explicit input sizes 545.572 ms 483.933 ms -6.8% no signal
Joined aggregation queries 162.185 ms 162.390 ms +0.1% no signal
Large joined-result fetching 185.443 ms 185.418 ms -1.0% no signal
1.2-million-row fetching 3562.422 ms 3542.734 ms +0.9% no signal
Common table expression queries 5.134 ms 5.142 ms -1.3% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.472 ms 1.508 ms +3.1% no signal
10,000 scalar values / fetchval() (debug disabled) 107.075 ms 94.591 ms -12.1% no signal
Build and measurement details

ADO build 180515

PR head: ac6e9880cd0118afa7f66cee3c45c9db91ef2fed
Base: 4c4195d4bb57c947cdab76334175bac4e023a828
Measured merge: 8193555a8038f4e5b0ab82472776d1f6565e0795

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4295.3; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5005.3; 5 paired comparisons and 1 warmup.

A consistent change requires more than 20% median paired movement, at least 1 ms between the median runtimes, and at least 80% of pairs exceeding the relative threshold in the same direction. A slowdown without enough pair agreement is reported as inconsistent.

The displayed change is the median of paired before-and-after ratios. It is not recalculated from the two displayed median runtimes.

Both revisions use profiling-enabled builds on the same agent and database, with alternating order and discarded warmups. Results are diagnostic and do not represent production-wheel latency.

Raw samples and logs are attached to the ADO run as profiler-* artifacts.

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.

Copilot review overview

🔵 Needs a closer look

The native hot-path change still requires live correctness and performance validation, as acknowledged by the draft description.

Review effort: Balanced
Findings: None

What changed in this PR

Introduces an experimental native cache to avoid repeated column-count queries during fetchone.

Changes:

  • Caches full column counts by metadata generation.
  • Reuses cached counts while preserving invalidation and error handling.
  • Adds nine subprocess-isolated integration scenarios.
File Description
mssql_python/​pybind/​ddbc_bindings.cpp Uses the cached count in FetchOne_wrap.
mssql_python/​pybind/​result_metadata.hpp Stores and invalidates full column counts.
tests/​test_fetch_settings_cache.py Tests reuse, invalidation, failures, and mixed fetching.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Use a separate connection for the cross-handle assertion without requiring MARS. Preserve all fetch and native call-count assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:13

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.

Copilot review overview

🔵 Needs a closer look

The native hot-path change remains an explicitly unvalidated draft with correctness tests and performance measurements still pending.

Review effort: Balanced
Findings: None

Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:16

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.

Copilot review overview

🔵 Needs a closer look

The native cache behavior and performance impact remain unvalidated by runtime execution.

Review effort: Balanced
Findings: None

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

98%


🎯 Overall Coverage

85%


📈 Total Lines Covered: 9536 out of 11173
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/cursor.py (100%)
  • mssql_python/pybind/ddbc_bindings.cpp (98.4%): Missing lines 5428
  • mssql_python/pybind/result_metadata.hpp (100%)

Summary

  • Total: 93 lines
  • Missing: 1 line
  • Coverage: 98%

mssql_python/pybind/ddbc_bindings.cpp

Lines 5424-5432

  5424     FetchStateGuard fetchStateGuard(StatementHandle, messages);
  5425 
  5426     if (!hasLobColumns && fetchSize > 0) {
  5427         ret = SQLBindColums(StatementHandle, buffers, columnNames, numCols, fetchSize, charCtype,
! 5428                             messages);
  5429         if (!SQL_SUCCEEDED(ret)) {
  5430             LOG("Error when binding columns");
  5431             return ret;
  5432         }


📋 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: 62.6%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 80.5%
mssql_python.pybind.connection.connection_pool.cpp: 82.3%
mssql_python.pybind.connection.connection.cpp: 83.1%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%
mssql_python.pybind.fetch_temporal.hpp: 92.1%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Copilot AI balanced review requested due to automatic review settings October 5, 2026 06:03

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.

Copilot review overview

🔵 Needs a closer look

Native correctness tests and uninstrumented Release performance measurements remain unrun.

Review effort: Balanced
Findings: None

@jahnvi480
Jahnvi Thakkar (jahnvi480) marked this pull request as ready for review October 5, 2026 07:56
Copilot AI balanced review requested due to automatic review settings October 5, 2026 07:56

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.

Copilot review overview

🔵 Needs a closer look

The native hot-path changes and fault-injection tests remain unexecuted, with no Release-OFF performance result available.

Review effort: Balanced
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 10:54
@github-actions github-actions Bot added pr-size: large Substantial code update and removed pr-size: medium Moderate update size labels Oct 5, 2026
@jahnvi480 Jahnvi Thakkar (jahnvi480) changed the title PERF: Cache full column counts for fetchone PERF: Optimize fetchone, fetchmany(1) and fetchval paths Oct 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.

Copilot review overview

🔵 Needs a closer look

The native ODBC hot-path and cleanup changes still lack completed native runtime and performance qualification.

Review effort: Balanced
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 11:08

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.

Copilot review overview

🔵 Needs a closer look

Native ODBC correctness and uninstrumented performance qualification remain pending.

Review effort: Balanced
Findings: None

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

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants