Skip to content

Fix regression: missing intermediate update counts in multi-statement PreparedStatement execution#2941

Open
Ananya2 wants to merge 6 commits into
mainfrom
user/anagarg/issue#2940
Open

Fix regression: missing intermediate update counts in multi-statement PreparedStatement execution#2941
Ananya2 wants to merge 6 commits into
mainfrom
user/anagarg/issue#2940

Conversation

@Ananya2
Copy link
Copy Markdown
Contributor

@Ananya2 Ananya2 commented Apr 30, 2026

Background

PR #2554 fixed an issue where Statement.execute() for INSERT was not consuming the explicit TDS_DONE token containing the update count. This caused getUpdateCount() and getMoreResults() to report incorrect results. The fix consumed INSERT DONEINPROC tokens so the driver would surface only the final DONEPROC update count.

Problem

Compound SQL statements (e.g., DELETE; INSERT; INSERT; UPDATE; INSERT; SELECT) executed via PreparedStatement.execute() That fix introduced a regression for PreparedStatement executing compound SQL (e.g., DELETE; INSERT; INSERT; UPDATE; INSERT; SELECT). Because sp_prepexec wraps the entire batch in a procedure context, all DONE tokens appear as DONEINPROC, not just trigger noise. The unconditional consumption discards legitimate intermediate update counts.

Expected (driver 12.8):

update count: 3  (deleted 3 rows)
update count: 1  (inserted 1 row)
update count: 1  (inserted 1 row)
update count: 2  (updated 2 rows)
update count: 1  (inserted 1 row)
has result: true (resultset for select)

Actual (driver 13.4.0):

 update count: 3, has result :false
 update count: 2, has result :false  (messed up from here)
 update count: 1, has result :false
 update count: -1, has result true

Solution

For PreparedStatement, the fix is: don't consume INSERT DONEINPROC tokens, they are real results, not trigger noise. This alone restores correct compound SQL behavior.

However, that would regress the trigger-filtering behavior that PR #2817 fixed (where lastUpdateCount=true means the app doesn't want intermediate trigger counts). So the override uses connection.useLastUpdateCount() as the gate:

lastUpdateCount=true (default) → consume intermediate INSERT counts (preserves trigger filtering from #2817 )
lastUpdateCount=false → retain all counts (app explicitly opted in to seeing everything)
One exception: when generated keys are requested (bRequestedGeneratedKeys), always consume, the appended SCOPE_IDENTITY() query means intermediate INSERT counts must be skipped to reach the identity result.

if (bRequestedGeneratedKeys) {
    return true;  // always consume to reach SCOPE_IDENTITY() result
}
return connection.useLastUpdateCount();  // respect the connection property

Testing

  • Added testCompoundPreparedStatementWithLastUpdateCountFalse in StatementTest.java
  • All existing regression tests pass successfully with the current fix

Fixes #2940

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 regression in PreparedStatement.execute() result traversal for compound SQL batches by preventing legitimate intermediate INSERT update counts from being discarded when lastUpdateCount=false, while preserving trigger-noise filtering and generated-keys behavior.

Changes:

  • Adds an overridable hook to control whether intermediate INSERT DONEINPROC tokens are consumed during execute() processing.
  • Implements PreparedStatement-specific behavior gated by lastUpdateCount and generated-keys requests.
  • Adds a regression test covering compound PreparedStatement execution with lastUpdateCount=false.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java Adds shouldConsumeInsertDoneToken() hook and uses it to decide whether to skip intermediate INSERT DONEINPROC counts.
src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java Overrides the hook to respect lastUpdateCount and always consume when generated keys are requested.
src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java Adds regression test for compound PreparedStatement execution with lastUpdateCount=false.

Comment thread src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.82%. Comparing base (eb01159) to head (cd605f4).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2941      +/-   ##
============================================
- Coverage     60.96%   60.82%   -0.14%     
+ Complexity     5011     4976      -35     
============================================
  Files           151      151              
  Lines         35221    35226       +5     
  Branches       5900     5901       +1     
============================================
- Hits          21471    21427      -44     
- Misses        10925    10931       +6     
- Partials       2825     2868      +43     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Ananya2 Ananya2 added this to the 13.5.1 milestone May 5, 2026
@machavan machavan removed this from the 13.5.1 milestone May 11, 2026
@machavan machavan added this to the 13.5.1 milestone May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Regression: Concatenated select SQL Statement returns null in JDBC Driver 13.4.1

3 participants