Skip to content

FIX: Update rowcount in nextset() - #864

Merged
Jahnvi Thakkar (jahnvi480) merged 3 commits into
microsoft:mainfrom
phelipelui:fix/nextset-rowcount
Oct 28, 2026
Merged

Jahnvi Thakkar (jahnvi480) merged 3 commits into
microsoft:mainfrom
phelipelui:fix/nextset-rowcount

Conversation

@phelipelui

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

GitHub Issue: #863


Summary

Cursor.nextset() calls SQLMoreResults and refreshes description and messages, but it leaves rowcount at the value execute() set for the first statement. In a multi-statement batch, the affected-row count of every later INSERT/UPDATE/DELETE was lost. This hits hardest with T-SQL control flow: a WHILE loop sends one result per INSERT, so a tool that sums rowcount over nextset() gets a stale number.

Change (mssql_python/cursor.py, nextset()). After the describe step, rowcount is set the same way execute() sets it:

  • -1 when the new result has columns; fetch*() then updates it as it already does;
  • DDBCSQLRowCount(self.hstmt) otherwise.

The SQL_NO_DATA path (nextset() returning False) is unchanged. This matches pyodbc, which calls SQLRowCount in Cursor_nextset after SQLMoreResults.

Tests (tests/test_004_cursor.py):

  • test_nextset_updates_rowcount runs a batch of INSERT (1 row), INSERT (2 rows), SELECT, UPDATE (3 rows), and DELETE (0 rows). It checks rowcount after each nextset(): 1, 2, -1, 3, 0.
  • test_nextset_rowcount_in_loop_batch runs a WHILE loop whose iterations insert 1, 2 and 3 rows. It expects per-result counts [1, 2, 3]. The counts differ on purpose, so a stale rowcount cannot reach the right total by accident.

CHANGELOG: entry under Unreleased → Fixed.

Verification. I ran against SQL Server 2022 (16.0.4305.5) on Python 3.12, Debian 12, with the native bindings from the published 1.16.0 wheel, whose cursor.py is identical to main before this change.

tests/test_004_cursor.py Without the fix With the fix
New tests 2 failed (assert 1 == 2, [1, 1, 1] == [1, 2, 3]) 2 passed
Whole file 2 failed, 544 passed, 4 skipped 546 passed, 4 skipped

The only failures without the fix are the two new tests, and no other test changes result. Black 26.5.1, the version pinned in .pre-commit-config.yaml, reports both files unchanged.

Out of scope: AsyncCursor.nextset() goes through py-core and was not changed or checked.

🤖 Generated with Claude Code

nextset() moved to the next result with SQLMoreResults and refreshed
description and messages, but left rowcount with the value execute() set
for the first statement. The affected-row count of later statements in a
batch (e.g. each INSERT of a T-SQL WHILE loop) could not be read.

nextset() now sets rowcount like execute(): -1 for a result set, the
SQLRowCount of the statement otherwise. This matches pyodbc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 24, 2026 00:59
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions

github-actions Bot commented Oct 24, 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.

No affected phases or call-count changes were recorded.

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 10.859 ms 10.787 ms -0.4% no signal
SELECT queries 1.294 ms 1.091 ms -15.0% no signal
Row insertion 31.498 ms 31.482 ms +0.2% no signal
Executemany inserts 136.656 ms 138.094 ms +0.3% no signal
Fetch-all queries 116.997 ms 117.861 ms +0.8% no signal
Row-by-row fetching 12.695 ms 12.548 ms -0.9% no signal
Batched row fetching 110.367 ms 110.806 ms +0.4% no signal
Transaction commit and rollback 100.852 ms 99.766 ms -1.1% no signal
Arrow row fetching 90.241 ms 87.839 ms -3.1% no signal
100,000-row insertion 404.615 ms 400.005 ms -0.6% no signal
Row fetching in batches of 100 106.997 ms 107.648 ms +0.2% no signal
Row fetching in batches of 10,000 130.018 ms 118.466 ms -8.1% no signal
Repeated positional queries 30.134 ms 30.072 ms -0.3% no signal
Repeated named-parameter queries 32.281 ms 32.805 ms +1.6% no signal
Legacy 100,000-row insertion 308.605 ms 311.153 ms +1.4% no signal
Insertion with explicit input sizes 435.290 ms 440.170 ms +1.1% no signal
Joined aggregation queries 185.291 ms 183.791 ms -0.5% no signal
Large joined-result fetching 183.204 ms 178.662 ms -2.0% no signal
1.2-million-row fetching 3460.605 ms 3478.392 ms +0.5% no signal
Common table expression queries 5.761 ms 5.701 ms -0.9% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.259 ms 1.313 ms +2.8% no signal
10,000 scalar values / fetchval() (debug disabled) 100.813 ms 100.513 ms -0.9% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 96.190 ms 96.277 ms +0.6% no signal
SELECT queries 1.249 ms 1.120 ms -2.6% no signal
Row insertion 32.216 ms 32.544 ms +0.9% no signal
Executemany inserts 131.874 ms 131.960 ms +0.1% no signal
Fetch-all queries 120.463 ms 121.775 ms +1.5% no signal
Row-by-row fetching 12.743 ms 12.686 ms -1.1% no signal
Batched row fetching 110.042 ms 112.625 ms +0.3% no signal
Transaction commit and rollback 104.451 ms 104.000 ms -0.6% no signal
Arrow row fetching 90.493 ms 88.964 ms -0.5% no signal
100,000-row insertion 396.350 ms 424.020 ms +3.3% no signal
Row fetching in batches of 100 107.127 ms 107.389 ms +0.1% no signal
Row fetching in batches of 10,000 136.806 ms 126.252 ms -5.9% no signal
Repeated positional queries 30.412 ms 30.494 ms +1.1% no signal
Repeated named-parameter queries 32.954 ms 32.978 ms +0.1% no signal
Legacy 100,000-row insertion 313.275 ms 334.676 ms +5.9% no signal
Insertion with explicit input sizes 438.347 ms 475.596 ms +5.0% no signal
Joined aggregation queries 164.407 ms 164.757 ms +1.4% no signal
Large joined-result fetching 196.313 ms 198.652 ms +1.2% no signal
1.2-million-row fetching 3466.679 ms 3485.073 ms +1.0% no signal
Common table expression queries 5.405 ms 5.406 ms -1.5% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.360 ms 1.375 ms +0.7% no signal
10,000 scalar values / fetchval() (debug disabled) 100.412 ms 100.075 ms -0.1% no signal
Build and measurement details

ADO build 186626

PR head: 683a85f216bd64c13f13fe4a274d5bec1ad451ef
Base: 21187de03cb86e82a19c6363e5243d7b18daacc4
Measured merge: c3c77a76a0a3bd521c997e6a906175562cf16c18

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4305.5; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5035.5; 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.

🔵 Needs a closer look

Reported integration results use published-wheel bindings; SQL Server validation against the current native build still needs human confirmation.

No open issues 🎉

What changed in this PR

Addresses #863 by refreshing the synchronous cursor’s rowcount when nextset() advances, aligning it with execute() behavior.

Changes:

  • Reports affected-row counts for non-query results and resets rowcount to -1 for result sets.
  • Adds regression tests for mixed-statement batches and loops with differing insert counts.
  • Documents the fix in the changelog; asynchronous cursors remain unchanged.
File Description
tests/​test_004_cursor.py Tests per-result row counts in mixed batches and loops.
mssql_python/​cursor.py Refreshes rowcount after advancing to a new result.
CHANGELOG.md Records the fix for #863.

Review effort: Balanced

@phelipelui

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Copilot AI balanced review requested due to automatic review settings October 27, 2026 19: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.

🟢 Approval recommended

No active review issues are recorded for the synchronous cursor’s per-result row-count update.

No open issues 🎉

Review effort: Balanced

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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.

LGTM!

Copilot AI balanced review requested due to automatic review settings October 28, 2026 07:17
@jahnvi480

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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.

🟢 Approval recommended

No active review issues were identified for the synchronous cursor row-count update.

No open issues 🎉

Review effort: Balanced

@jahnvi480
Jahnvi Thakkar (jahnvi480) merged commit 2c6a268 into microsoft:main Oct 28, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants