Fix: Exception chaining for nested stored procedure errors (#2115)#2886
Fix: Exception chaining for nested stored procedure errors (#2115)#2886
Conversation
|
/azp run |
1 similar comment
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
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.
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
…intermediate ResultSets (e.g., nested stored procedures), ensuring outer exceptions are not missed.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
David-Engel
left a comment
There was a problem hiding this comment.
Approved from a general functionality/API perspective.
|
/azp run |
|
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. |
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:
Example:
-- P2 (inner procedure) raises error and returns-- P1 (outer procedure) calls P2, then raises its own errorEXEC P1Expected: 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:
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
Part1: #2251
Fixes #2115