Skip to content

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

Merged
Ananya Garg (Ananya2) merged 10 commits into
mainfrom
user/anagarg/issue#2115
Feb 26, 2026
Merged

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

Conversation

@Ananya2

@Ananya2 Ananya Garg (Ananya2) commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor Author

/azp run

1 similar comment
@Ananya2

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 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.

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

codecov Bot commented Jan 28, 2026

Copy link
Copy Markdown

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
…intermediate ResultSets (e.g., nested stored procedures), ensuring outer exceptions are not missed.
Comment thread src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java Outdated
@Ananya2

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@David-Engel David Engel (David-Engel) left a comment

Copy link
Copy Markdown
Collaborator

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

/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
Ananya Garg (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
Ananya Garg (Ananya2) added a commit that referenced this pull request Jun 25, 2026
…ation (#2971)

* Fix #2968: prevent infinite recursion in SQLServerError bean serialization

SQLServerError.getSQLServerMessage() returns 'this', creating a self-referential JavaBeans property that causes Jackson, XMLEncoder, DataWeave, and similar bean-introspection serializers to recurse indefinitely when serializing a SQLServerException (regression surfaced by #2886 chaining changes in 13.4.0).

Annotate the getter with @java.beans.Transient so Introspector excludes it as a bean property. The method remains public and the identity contract (err.getSQLServerMessage() == err) is preserved.

* Add regression tests for #2968 (bean introspection / identity contract)

* fixed test failures

* Addressed review comments

---------

Co-authored-by: Ananya Garg <ananya20.garg@gmail.com>
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