You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
FIx: Add opt-in batch error continuation for sqlsrv and PDO next-result APIs (#1600)
* Fix#1599: Remove SQLCancel from core_sqlsrv_next_result catch block
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
* Implement opt-in batch error continuation with legacy default
Add optional batch error continuation controls for both sqlsrv and PDO
next-result APIs, preserving legacy fail-on-error behavior by default
while allowing users to opt-in to continue-after-error semantics.
Changes:
- Add batch_error_continue boolean flag to shared connection struct
- Initialize flag to false to preserve backward-compatible behavior
- Expose BatchErrorContinue option for sqlsrv_connect()
- Expose SQLSRV_ATTR_BATCH_ERROR_CONTINUE attribute for PDO
- Gate both sqlsrv_next_result() and PDOStatement::nextRowset() on flag
- Updated tests to validate both default and opt-in behaviors
Design:
- Default (batch_error_continue=false): sqlsrv_next_result/nextRowset
return false on mid-batch SQL errors, matching pre-fix semantics
- Opt-in (batch_error_continue=true): continue past mid-batch errors
while reporting them via sqlsrv_errors()/errorInfo()
Impact:
- Existing applications unaffected; no behavior changes without opt-in
- Users must explicitly set the flag to enable new behavior
- Full backward compatibility maintained
* Harden opt-in batch error continuation without breaking defaults
- Split non-throwing next-result behavior into explicit modes
(silent internal drain vs opt-in user-facing diagnostics)
- Preserve default re-execute flush semantics (no surfaced diagnostics)
- Gate sqlsrv_next_result/PDO nextRowset reporting strictly on opt-in
- Guard zend_clear_exception behind pending-exception check
- Reduce test flakiness by asserting diagnostic presence instead of fixed SQLSTATE
- Add coverage for default silent re-execute and PDO constructor opt-in path
- Clarify connection-scope behavior in code and changelog (issue #1599)
* Fix batch error tests: use RAISERROR instead of SELECT 1/0
SELECT 1/0 with ANSI_WARNINGS ON (the SQL Server default) produces
SQL_SUCCESS_WITH_INFO (a NULL result set + warning), not SQL_ERROR.
This caused the tests to fail across all CI environments because
nextRowset() returned true (valid result set) instead of the expected
false (error).
RAISERROR('...', 16, 1) always produces SQL_ERROR from SQLMoreResults
regardless of ANSI_WARNINGS or ARITHABORT settings, making it a
reliable cross-environment trigger for the mid-batch error path that
this feature is designed to handle.
* Fix PDO flush loops to use silent drain when batch_error_continue is enabled
The PDO execute and param_hook flush loops used throw_on_errors=true
(the default), which means a mid-batch SQL_ERROR from a prior partial
navigation would throw, call SQLCancel, and fail the re-execute.
The sqlsrv driver already handled this correctly with silent drain
(throw_on_errors=false). Apply the same conditional logic to PDO:
when batch_error_continue is enabled, flush silently; otherwise
preserve the existing throwing behavior.
Also adds PDO re-execute tests (Tests 6 and 7) to validate that
re-executing after partial batch navigation works correctly for both
opt-in and default connections.
* Fix PDO 'Invalid cursor state' after SQL_ERROR in opt-in batch continuation
After SQLMoreResults returns SQL_ERROR for a non-result-producing statement
(e.g. RAISERROR), there is no active ODBC cursor. The PDO nextRowset code
unconditionally called SQLNumResultCols/SQLRowCount after core_sqlsrv_next_result,
which failed with 'Invalid cursor state' (SQLSTATE 24000).
Fix:
1. In core_sqlsrv_next_result: when report_errors=true and r==SQL_ERROR,
clean up the previous result set and return early WITHOUT creating a
new result set object. The batch remains navigable.
2. In pdo_sqlsrv_stmt_next_rowset: check current_results != NULL before
calling SQLNumResultCols/SQLRowCount. On error markers (NULL results),
set column_count=0 and row_count=0.
3. In test: suppress ERRMODE_WARNING output with @ operator so expected
output stays deterministic across environments.
* Address review nits: extract free_current_results() helper, remove dead assignments
1. Extract the ~12-line current_results teardown block into a
free_current_results() helper method on sqlsrv_stmt. Used in the
destructor, new_result_set(), and the SQL_ERROR opt-in path.
2. Remove dead stmt->column_count = 0 / stmt->row_count = 0 assignments
that were immediately overwritten by clean_up_results_metadata()
(which sets them to ACTIVE_NUM_COLS_INVALID / ACTIVE_NUM_ROWS_INVALID).
0 commit comments