FIx: Do not call SQLCancel in core_sqlsrv_next_result after SQLMoreResults error#1600
FIx: Do not call SQLCancel in core_sqlsrv_next_result after SQLMoreResults error#1600jahnvi480 wants to merge 5 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #1600 +/- ##
==========================================
- Coverage 85.76% 85.76% -0.01%
==========================================
Files 23 23
Lines 7207 7214 +7
==========================================
+ Hits 6181 6187 +6
- Misses 1026 1027 +1
🚀 New features to boost your workflow:
|
e7c13ef to
0b73262
Compare
When SQLMoreResults returns SQL_ERROR for a mid-batch statement failure
(e.g. divide-by-zero with XACT_ABORT OFF), the statement handle remains
valid. The old code called SQLCancel() in the catch block, which aborted
the entire remaining batch.
This change:
1. Removes SQLCancel from the catch block so the handle stays alive.
2. Adds a throw_on_errors parameter (default true) to
core_sqlsrv_next_result to scope the non-throwing behaviour:
- throw_on_errors=true (flush loops, closeCursor, param binding):
uses the throwing core::SQLMoreResults wrapper — SQL_ERROR exits
the loop via exception. Same behaviour as before.
- throw_on_errors=false (sqlsrv_next_result / PDO::nextRowset):
calls SQLMoreResults directly, reports the error through the
normal error handler, clears any pending PDO exception, and
falls through to new_result_set(). The batch remains navigable.
3. Tests both extensions (ERRMODE_WARNING and ERRMODE_EXCEPTION for PDO)
and verifies re-execute after a batch error (flush loop).
Fixes #1599
0b73262 to
a2d3fcc
Compare
…ing APIs When SQLMoreResults returns SQL_ERROR for a mid-batch statement failure (e.g. divide-by-zero with XACT_ABORT OFF), the ODBC spec says it should return SQL_SUCCESS_WITH_INFO when the batch is not aborted and the failed statement is not the last one. The Microsoft ODBC driver incorrectly returns SQL_ERROR, but the statement handle remains valid. The old code called SQLCancel() in the catch block, which aborted the entire remaining batch, making subsequent result sets unreachable. This change adds a throw_on_errors parameter (default true) to core_sqlsrv_next_result: - throw_on_errors=true (default): used by internal flush loops (closeCursor, re-execute, param binding). Uses the throwing core::SQLMoreResults wrapper and calls SQLCancel in the catch block. Same behavior as before. - throw_on_errors=false: used only by sqlsrv_next_result() and PDO::nextRowset(). Calls SQLMoreResults directly, reports the error through the normal error handler, clears any pending PDO exception, and falls through to new_result_set(). The batch remains navigable. SQLCancel is NOT called in the catch block for this path. Tests cover both extensions (ERRMODE_WARNING and ERRMODE_EXCEPTION for PDO), re-execute after a batch error (flush loop), and error visibility via sqlsrv_errors() / errorInfo(). Fixes #1599
647cc77 to
6f911c2
Compare
Impact Analysis of the current changesThere are 6 call sites for
Compatibility & Behavioral Concerns1. Breaking change:
|
| Before | After | |
|---|---|---|
| Return value on mid-batch error | false |
true |
Error available via sqlsrv_errors() |
Yes | Yes |
| Can reach subsequent result sets | No | Yes |
Applications that check sqlsrv_next_result() === false to detect batch errors will silently miss the error. This is a user-visible behavior change with no opt-out mechanism.
2. Breaking change: PDO::nextRowset() exception behavior (HIGH)
-
ERRMODE_EXCEPTION: Previously threwPDOException. Now the PR explicitly callszend_clear_exception()to suppress it and returns success. Existingcatch (PDOException $e)blocks aroundnextRowset()will stop firing. Error info is still available viaerrorInfo(), but code structured around exception-based error handling will silently skip errors. -
ERRMODE_WARNING/ERRMODE_SILENT: Previously returned an error indicator; now returns success. Same concern as the SQLSRV driver.
3. SQLCancel removed from shared catch block — affects all callers (MEDIUM-HIGH)
The catch block is shared between throw_on_errors=true (internal flush loops, closeCursor, param binding) and throw_on_errors=false (user-facing). Removing SQLCancel from the catch block affects the internal paths too:
- When
throw_on_errors=true, acore::SQLMoreResults()SQL_ERROR still throwsCoreExceptioninto this catch block. - Previously,
SQLCancelensured the ODBC handle was cleaned up. Now the exception propagates without canceling the statement. - For error scenarios unrelated to mid-batch failures (network errors, connection drops, OOM), not calling
SQLCancelcould leave the ODBC handle in an indeterminate state.
Recommendation: The SQLCancel removal should be conditional — only skip it when throw_on_errors=false. Something like:
catch( core::CoreException& e ) {
if( throw_on_errors ) {
SQLCancel( stmt->handle() );
}
throw e;
}4. zend_clear_exception() is overly broad (MEDIUM)
zend_clear_exception() clears any pending Zend exception, not just the one raised by the ODBC error handler. If another exception was already pending (e.g., from user code or a prior operation), this would silently consume it. A more targeted approach would check whether the pending exception matches the expected type before clearing.
5. new_result_set() called after SQL_ERROR (LOW-MEDIUM)
When SQLMoreResults returns SQL_ERROR, the code falls through to stmt->new_result_set(), which resets internal metadata and may call SQLNumResultCols/SQLRowCount. Whether the ODBC handle is in a valid state for these calls after SQL_ERROR depends on the driver implementation.
6. No opt-out to retain old behavior (HIGH)
Applications that relied on the batch-aborting behavior as a safety mechanism — stopping processing after any statement error — have no way to restore the old behavior. A connection or statement attribute to control this would provide a safer migration path.
Summary of Recommendations
- Make
SQLCancelremoval conditional onthrow_on_errors— the internal flush/closeCursor paths should still callSQLCancelon error to properly clean up the ODBC handle. - Narrow
zend_clear_exception()— check the pending exception type or only clear if it was raised by this specific error handler invocation. - Document the behavior change in release notes — callers checking
sqlsrv_next_result() === falseor catchingPDOExceptionfromnextRowset()need to update. - Add a connection attribute (e.g.,
SQLSRV_ATTR_BATCH_ERROR_CONTINUE) to let users opt in to new behavior, preserving backward compatibility.
Github issue #1599
This pull request improves how the SQLSRV and PDO_SQLSRV drivers handle errors during multi-statement batches, specifically when a non-fatal error (such as divide-by-zero) occurs mid-batch with
XACT_ABORT OFF. Previously, the driver would callSQLCancel()on any error, which destroyed the remaining result sets. Now, errors are reported but the batch remains navigable, allowing users to continue to subsequent result sets. The changes also ensure that error information is available without interrupting batch navigation, and add comprehensive tests for these scenarios.Error handling and batch navigation improvements:
core_sqlsrv_next_resultto distinguish between internal and user-facing error handling: for user-facing calls (likesqlsrv_next_resultandPDO::nextRowset), SQL errors no longer triggerSQLCancel(), allowing continued navigation through remaining result sets after a mid-batch error. Errors are reported via standard error handlers, and any pending Zend exceptions are cleared to avoid aborting navigation.pdo_stmt.cpp,stmt.cpp) to support the new error handling logic by passing additional parameters tocore_sqlsrv_next_result.Test coverage:
pdo_batch_error_continue.phptto test thatPDO::nextRowset()reports errors but allows continued navigation after a mid-batch error, for bothERRMODE_WARNINGandERRMODE_EXCEPTIONmodes.sqlsrv_batch_error_continue.phptto verify thatsqlsrv_next_result()behaves correctly after a mid-batch error, that error info is available, and that re-execution works after such errors.Dependency and include updates:
zend_exceptions.hincore_stmt.cppto enable Zend exception management for the improved error handling logic.