Skip to content

Fix: Exception chaining for nested stored procedure errors (#2115)#2886

Merged
Ananya2 merged 10 commits intomainfrom
user/anagarg/issue#2115
Feb 26, 2026
Merged

Fix: Exception chaining for nested stored procedure errors (#2115)#2886
Ananya2 merged 10 commits intomainfrom
user/anagarg/issue#2115

Conversation

@Ananya2
Copy link
Copy Markdown
Contributor

@Ananya2 Ananya2 commented Jan 28, 2026

Problem

When nested stored procedures raise multiple errors using RAISERROR, the JDBC driver only surfaces the first exception.
Although SQL Server sends all errors in the TDS stream, the driver stops parsing early and:

  • SQLException.getNextException() returns null
  • Only the first error is visible to applications
  • Behavior diverges from SSMS and SQL Server semantics
    Example:
    -- P2 (inner procedure) raises error and returns
    -- P1 (outer procedure) calls P2, then raises its own error
    EXEC P1
    Expected: 2 exceptions
    Actual: only the inner exception is visible

Root Cause

The JDBC driver stops parsing the TDS stream early when it encounters a DONEINPROC token with the error flag set. This causes:

  • Parsing to terminate before subsequent error tokens are read
  • The driver to throw the first exception immediately
  • Remaining errors to be left unread in the stream

Modifying the TDS parsing state machine to continue parsing was explored, but doing so breaks existing driver behavior, especially when mixed with SELECT result sets inside stored procedures.

Solution

Instead of modifying the core TDS parsing behavior, this PR introduces lazy exception chaining by overriding getNextException() in SQLServerException to resume TDS parsing via Statement.getMoreResults().

With this approach, the driver continues parsing the TDS stream only when the application explicitly requests the next exception. When invoked, getNextException() safely resumes parsing, reads the next error token already sent by SQL Server, throws it internally, and chains it to the original exception in the correct order.

This ensures that all errors from nested stored procedures, including 3-4 or more levels, are reliably surfaced, while preserving backward compatibility and existing driver behavior.

Testing

  • testExceptionChaining – Validates exception chaining with 2 nested stored procedures
  • testExceptionChainingWith4NestedProcs – Validates chaining across 4 nested stored procedures
  • testExceptionChainingWithGetMoreResults – Verifies exception retrieval using a manual getMoreResults() loop
  • testExceptionChainingWith3NestedProcs– Tests exception chaining via getNextException() with 3 nested stored procedures.
  • testExceptionChainingWithDifferentErrorTypes – Verifies exception chaining across 3 nested stored procedures when each procedure throws a different SQL error type (division by zero and distinct RAISERRORs), ensuring chaining works beyond RAISERROR-only scenarios.
  • testExceptionChainingWithClosedStatement – Verifies that getNextException() returns null when the statement is closed after an exception is thrown, instead of throwing a “statement is closed” error that would mask the original exception.

Part1: #2251

Fixes #2115

@Ananya2 Ananya2 self-assigned this Jan 28, 2026
@github-project-automation github-project-automation Bot moved this to In progress in MSSQL JDBC Jan 28, 2026
@Ananya2
Copy link
Copy Markdown
Contributor Author

Ananya2 commented Jan 28, 2026

/azp run

1 similar comment
@Ananya2
Copy link
Copy Markdown
Contributor Author

Ananya2 commented Jan 28, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

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

This PR fixes exception chaining for nested stored procedures in the JDBC driver (issue #2115). When nested stored procedures raise multiple errors, SQL Server sends all errors in the TDS stream, but the driver previously stopped parsing early and only surfaced the first exception. The fix implements lazy exception chaining by overriding getNextException() to resume TDS parsing via getMoreResults().

Changes:

  • Introduced lazy exception chaining mechanism in SQLServerException that triggers TDS parsing on-demand when getNextException() is called
  • Modified makeFromDatabaseError to accept and store a statement reference for lazy chaining
  • Added comprehensive test coverage for 2, 3, and 4 levels of nested stored procedures

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

File Description
SQLServerException.java Added sourceStatement field, overridden getNextException() to lazily fetch exceptions via getMoreResults(), and extended makeFromDatabaseError signature
SQLServerStatement.java Updated onInfo() to pass statement reference to makeFromDatabaseError
StatementTest.java Added TCExceptionChaining nested test class with 4 comprehensive tests for exception chaining scenarios
Comments suppressed due to low confidence (1)

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java:378

  • The chained exceptions created within makeFromDatabaseError (lines 367-378) for the errorChain do not receive the sourceStatement reference. This means that if there are pre-existing chained exceptions from the errorChain, and then additional exceptions are discovered via lazy chaining through getNextException(), the lazy-loaded exceptions will not be able to traverse past the pre-existing chain.

Consider whether the chained exceptions from errorChain should also receive the sourceStatement reference, or document this limitation.

        // Add any extra messages to the SQLException error chain
        List<SQLServerError> errorChain = sqlServerError.getErrorChain();
        if (errorChain != null) {
            for (SQLServerError srvError : errorChain) {
                String state2 = generateStateCode(con, srvError.getErrorNumber(), srvError.getErrorState());

                SQLServerException chainException = new SQLServerException(obj,
                        SQLServerException.checkAndAppendClientConnId(srvError.getErrorMessage(), con), state2,
                        srvError, bStack);
                chainException.setDriverErrorCode(DRIVER_ERROR_FROM_DATABASE);

                theException.setNextException(chainException);
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

codecov Bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 84.61538% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.67%. Comparing base (a41b142) to head (43efe9f).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...m/microsoft/sqlserver/jdbc/SQLServerException.java 84.00% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2886      +/-   ##
============================================
- Coverage     60.78%   60.67%   -0.12%     
+ Complexity     4925     4908      -17     
============================================
  Files           151      151              
  Lines         34936    34961      +25     
  Branches       5837     5843       +6     
============================================
- Hits          21236    21211      -25     
- Misses        10885    10918      +33     
- Partials       2815     2832      +17     

☔ 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.

Comment thread src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java Outdated
machavan
machavan previously approved these changes Feb 4, 2026
muskan124947
muskan124947 previously approved these changes Feb 4, 2026
…intermediate ResultSets (e.g., nested stored procedures), ensuring outer exceptions are not missed.
@Ananya2 Ananya2 dismissed stale reviews from muskan124947 and machavan via 8cb010c February 13, 2026 08:14
muskan124947
muskan124947 previously approved these changes Feb 17, 2026
Comment thread src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java Outdated
@Ananya2
Copy link
Copy Markdown
Contributor Author

Ananya2 commented Feb 20, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

David-Engel
David-Engel previously approved these changes Feb 25, 2026
Copy link
Copy Markdown
Collaborator

@David-Engel David-Engel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved from a general functionality/API perspective.

@Ananya2
Copy link
Copy Markdown
Contributor Author

Ananya2 commented Feb 25, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@Ananya2 Ananya2 merged commit c197d66 into main Feb 26, 2026
18 of 19 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Closed/Merged PRs in MSSQL JDBC Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Closed/Merged PRs

Development

Successfully merging this pull request may close these issues.

Exception chaining doesn't work in JDBC driver

6 participants