Skip to content

Commit ef07d52

Browse files
maltesanderclaude
andcommitted
docs: state the real HY008 contract on all 32 entry points
Every one claimed the state could not arise, on one of two false grounds: that "the Backend trait is synchronous", which says nothing about another thread cancelling, or that it was (DM)-handled, which the spec contradicts -- the HY008 row carries no (DM) marker on any of these pages. Each now names which of the row's two clauses applies, in one of three shapes: the call reclassifies (18), it is connection-level and has no token to observe (6), or it makes no fallible backend call for a cancellation to be reported through (8). SQLDescribeCol and SQLColAttribute get their own wording: they do reach a fallible call, but its map_err replaces any error with 07009, so a cancellation is indistinguishable from a bad column number by the time core sees it. Classifying each function rather than pattern-matching turned up one the design missed: SQLDescribeParam makes a fallible backend call and has a token in reach, so it is wired here rather than documented as exempt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3711a3f commit ef07d52

11 files changed

Lines changed: 243 additions & 42 deletions

File tree

AGENTS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,26 @@ mechanism:
610610
cross-thread branch, so a merely-idle statement's data-at-execution state
611611
is occasionally left uncleared where it strictly could have been —
612612
harmless, and explicitly spec-legal.
613+
- **A cancelled call reports `HY008`, and the token is minted per execution.**
614+
`Backend::cancel` signals; `Backend::is_cancelled` observes. They are a pair —
615+
a backend implementing the first and not the second still cancels the work,
616+
but the application sees whatever SQLSTATE the driver's error mapping
617+
produced instead of "operation canceled". Core asks `is_cancelled` **only
618+
after a backend call returned an error**: the spec permits a cancelled
619+
execution to finish anyway ("it is possible for the execution to succeed and
620+
return SQL_SUCCESS while the cancel is also successful"), so `Ok` is never
621+
reclassified. The single implementation is `crate::cancel`.
622+
623+
`mint_cancel_token` builds a **new** token at every statement-producing call,
624+
and the cursor-consuming calls read that execution's token rather than
625+
minting one. An earlier revision created one token per statement and never
626+
replaced it, which left a cancelled statement permanently unusable —
627+
`Backend::cancel` marks the token, and the next execution reused it. The spec
628+
requires the opposite ("After the statement has been canceled, the
629+
application can call SQLExecute or SQLExecDirect again"), and the outcome
630+
that rule was protecting against is itself spec-mandated: "a call to
631+
SQLCancel when no processing is being done on the statement ... has is [sic]
632+
no effect at all."
613633
- **Every lock in the crate is imported from `src/sync.rs`**, never directly
614634
from `std::sync`, so that building a test with `--cfg loom` swaps every one
615635
of them for loom's instrumented equivalent. A lock imported around that

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,19 @@ Everything a driver has to change for the catalog rework, in one place.
797797

798798
### Fixed
799799

800+
- The 32 `HY008` doc comments across `src/ffi/` claimed the state could not
801+
arise, on one of two false grounds: that "the `Backend` trait is synchronous"
802+
— which says nothing about another thread cancelling — or that it was
803+
`(DM)`-handled, which the spec contradicts, since its `HY008` row carries no
804+
`(DM)` marker on any of these pages. Each now states which of the row's two
805+
clauses applies and why, in one of three shapes: the call reclassifies, it is
806+
connection-level and has no token to observe, or it makes no fallible backend
807+
call for a cancellation to be reported through.
808+
809+
- `SQLDescribeParam` now returns `HY008` on a cross-thread cancel.
810+
`Backend::describe_param` is a fallible backend call, so a backend answering
811+
it over the wire could be cancelled mid-lookup and reported `HY000`.
812+
800813
- `SQLAllocHandle` returned a bare `SQL_ERROR` with no diagnostic for
801814
`SQL_HANDLE_DESC` and `SQL_HANDLE_DBC_INFO_TOKEN`. It now posts `HYC00`, which
802815
this function's diagnostics table lists un-annotated for exactly that case.

src/ffi/connect.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ use crate::utf16::{utf16_to_string, write_utf16};
5252
/// - 28000: Invalid authorization specification — may be returned by the backend via `B::connect`.
5353
/// - HY000: General error — returned for any backend error with no specific SQLSTATE.
5454
/// - HY001: Memory allocation failure — not returned here (Rust panics on alloc failure).
55-
/// - HY008: Operation cancelled (async) — (driver-manager-handled; not returned here);
56-
/// the `Backend` trait has no async connection path.
55+
/// - HY008: Operation canceled; not returned here. Cancelling a connection-level call needs
56+
/// `SQLCancelHandle` on a connection handle, which this driver does not export, so no cancel
57+
/// token exists for this call to observe — `SQLCancel` takes a statement handle and cannot
58+
/// reach one. The asynchronous clause is likewise inapplicable: core never returns
59+
/// `SQL_STILL_EXECUTING`.
5760
/// - HY009: Invalid use of null pointer — returned when `in_connection_string` is null.
5861
/// - HY010: Function sequence error (async in progress) — (driver-manager-handled; not returned here).
5962
/// - HY013: Memory management error — not returned here (Rust panics on alloc failure).
@@ -207,8 +210,11 @@ pub unsafe fn sql_driver_connect_w<B: Backend>(
207210
/// - 28000: Invalid authorization specification — may be returned by the backend via `B::connect`.
208211
/// - HY000: General error — returned for any backend error with no specific SQLSTATE.
209212
/// - HY001: Memory allocation failure — (driver-manager-handled; not returned here).
210-
/// - HY008: Operation cancelled (async) — (driver-manager-handled; not returned here);
211-
/// the `Backend` trait has no async connection path.
213+
/// - HY008: Operation canceled; not returned here. Cancelling a connection-level call needs
214+
/// `SQLCancelHandle` on a connection handle, which this driver does not export, so no cancel
215+
/// token exists for this call to observe — `SQLCancel` takes a statement handle and cannot
216+
/// reach one. The asynchronous clause is likewise inapplicable: core never returns
217+
/// `SQL_STILL_EXECUTING`.
212218
/// - HY010: Function sequence error (async in progress) — (driver-manager-handled; not returned here).
213219
/// - HY013: Memory management error — not returned here (Rust panics on alloc failure).
214220
/// - HY090: Invalid string or buffer length — returned when `name_length1`, `name_length2`, or
@@ -370,8 +376,11 @@ pub unsafe fn sql_connect_w<B: Backend>(
370376
/// - 28000: Invalid authorization specification — may be returned by the backend via `B::connect`.
371377
/// - HY000: General error — returned for any backend error with no specific SQLSTATE.
372378
/// - HY001: Memory allocation failure — not returned here (Rust panics on alloc failure).
373-
/// - HY008: Operation cancelled (async) — (driver-manager-handled; not returned here);
374-
/// the `Backend` trait has no async connection path.
379+
/// - HY008: Operation canceled; not returned here. Cancelling a connection-level call needs
380+
/// `SQLCancelHandle` on a connection handle, which this driver does not export, so no cancel
381+
/// token exists for this call to observe — `SQLCancel` takes a statement handle and cannot
382+
/// reach one. The asynchronous clause is likewise inapplicable: core never returns
383+
/// `SQL_STILL_EXECUTING`.
375384
/// - HY009: Invalid use of null pointer — returned when `in_connection_string` is null.
376385
/// - HY010: Function sequence error (async in progress) — (driver-manager-handled; not returned here).
377386
/// - HY013: Memory management error — not returned here (Rust panics on alloc failure).
@@ -683,7 +692,11 @@ fn read_dsn_keys(dsn: &str) -> Vec<(String, String)> {
683692
/// which is not currently tracked. Deferred.
684693
/// - HY000: General error — returned for any backend `disconnect` error with no specific SQLSTATE.
685694
/// - HY001: Memory allocation failure — not returned here (Rust panics on alloc failure).
686-
/// - HY008: Operation cancelled (async) — not returned here (the `Backend` trait is synchronous).
695+
/// - HY008: Operation canceled; not returned here. Cancelling a connection-level call needs
696+
/// `SQLCancelHandle` on a connection handle, which this driver does not export, so no cancel
697+
/// token exists for this call to observe — `SQLCancel` takes a statement handle and cannot
698+
/// reach one. The asynchronous clause is likewise inapplicable: core never returns
699+
/// `SQL_STILL_EXECUTING`.
687700
/// - HY010: Function sequence error (async in progress) — (driver-manager-handled; not returned here).
688701
/// - HY013: Memory management error — not returned here (Rust panics on alloc failure).
689702
/// - HY117: Connection suspended due to unknown transaction state — (driver-manager-handled; not returned here).

src/ffi/connect_attr.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ fn validate_txn_isolation<B: Backend>(
155155
/// - HY000 General error: returned for unexpected internal errors.
156156
/// - HY001 Memory allocation error: not returned; Rust panics on allocation
157157
/// failure, which is caught by `panic_safe` and converted to `SQL_ERROR`/HY000.
158-
/// - HY008 Operation canceled: not returned; the `Backend` trait is synchronous.
158+
/// - HY008: Operation canceled; not returned here. Cancelling a connection-level call needs
159+
/// `SQLCancelHandle` on a connection handle, which this driver does not export, so no cancel
160+
/// token exists for this call to observe — `SQLCancel` takes a statement handle and cannot
161+
/// reach one. The asynchronous clause is likewise inapplicable: core never returns
162+
/// `SQL_STILL_EXECUTING`.
159163
/// - HY009 Invalid use of null pointer: HY009 is not applicable here:
160164
/// `SQL_ATTR_CURRENT_CATALOG` is the only string attribute handled, and null
161165
/// means "clear the catalog" (a valid operation).

src/ffi/cursor.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ static CURSOR_NAME_COUNTER: AtomicU32 = AtomicU32::new(1);
4444
/// - HY000 (general error): returned via `OdbcError::general` for unexpected failures.
4545
/// - HY001 (memory allocation error): not applicable; Rust allocation panics are caught by
4646
/// `panic_safe`.
47-
/// - HY008 (operation canceled): (driver-manager-handled; not returned here)
47+
/// - HY008: Operation canceled; not returned here. This call makes no fallible backend call —
48+
/// `StatementBackend::column_count` returns a plain `i16` — so there is no error for a
49+
/// cancellation to be reported through. The asynchronous clause is inapplicable: core never
50+
/// returns `SQL_STILL_EXECUTING`.
4851
/// - HY010 (function sequence error): returned with SQLSTATE `HY010` when no result set is
4952
/// available (statement not yet executed). The `(DM)` variants (async in progress, etc.) are
5053
/// driver-manager-handled; not returned here.
@@ -208,7 +211,10 @@ pub unsafe fn sql_row_count<B: Backend>(
208211
/// - HY000 (general error): returned via `OdbcError::general` for unexpected failures.
209212
/// - HY001 (memory allocation error): not applicable; Rust allocation panics are caught by
210213
/// `panic_safe`.
211-
/// - HY008 (operation canceled): (driver-manager-handled; not returned here)
214+
/// - HY008: Operation canceled; not returned here. This call makes no fallible backend call —
215+
/// `SQLMoreResults` is a core stub that reports `SQL_NO_DATA` without asking the backend — so
216+
/// there is no error for a cancellation to be reported through. The asynchronous clause is
217+
/// inapplicable: core never returns `SQL_STILL_EXECUTING`.
212218
/// - HY010 (function sequence error): (driver-manager-handled; not returned here)
213219
/// - HY013 (memory management error): not applicable; Rust memory access cannot fail silently.
214220
/// - HY117 (connection suspended): (driver-manager-handled; not returned here)
@@ -684,7 +690,10 @@ pub unsafe fn sql_set_cursor_name_w<B: Backend>(
684690
/// - HY000 (general error): returned via `OdbcError::general` for unexpected failures.
685691
/// - HY001 (memory allocation error): not applicable; Rust allocation panics are caught by
686692
/// `panic_safe`.
687-
/// - HY008 (operation canceled): (driver-manager-handled; not returned here)
693+
/// - HY008: Operation canceled; not returned here. This call makes no fallible backend call —
694+
/// `SQLBulkOperations` reports `HYC00` without asking the backend — so there is no error for a
695+
/// cancellation to be reported through. The asynchronous clause is inapplicable: core never
696+
/// returns `SQL_STILL_EXECUTING`.
688697
/// - HY010 (function sequence error): (driver-manager-handled; not returned here)
689698
/// - HY011 (attribute cannot be set now): not applicable.
690699
/// - HY013 (memory management error): not applicable; Rust memory access cannot fail silently.
@@ -786,7 +795,10 @@ pub unsafe fn sql_bulk_operations<B: Backend>(
786795
/// - HY000 (general error): returned via `OdbcError::general` for unexpected failures.
787796
/// - HY001 (memory allocation error): not applicable; Rust allocation panics are caught by
788797
/// `panic_safe`.
789-
/// - HY008 (operation canceled): (driver-manager-handled; not returned here)
798+
/// - HY008: Operation canceled; not returned here. This call makes no fallible backend call —
799+
/// `SQLSetPos` reports `HYC00` without asking the backend — so there is no error for a
800+
/// cancellation to be reported through. The asynchronous clause is inapplicable: core never
801+
/// returns `SQL_STILL_EXECUTING`.
790802
/// - HY010 (function sequence error): (driver-manager-handled; not returned here)
791803
/// - HY011 (attribute cannot be set now): not applicable.
792804
/// - HY013 (memory management error): not applicable; Rust memory access cannot fail silently.

src/ffi/execute.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ use crate::utf16::utf16_to_string;
7070
/// - 44000: WITH CHECK OPTION violation — propagated from backend.
7171
/// - HY000: General error — propagated from backend.
7272
/// - HY001: Memory allocation error — propagated from backend.
73-
/// - HY008: Operation canceled — (driver-manager-handled; not returned here).
73+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
74+
/// function called again — is not applicable: core implements no asynchronous execution and
75+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
76+
/// statement "from a different thread in a multithread application", **is returned by this
77+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
78+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
79+
/// the backend's own SQLSTATE.
7480
/// - HY009: Invalid use of null pointer — fails if `StatementText` is null (checked here).
7581
/// - HY010: Function sequence error — (DM cases for async/NEED_DATA: driver-manager-handled; not
7682
/// returned here); fails if the connection is not open (checked here).
@@ -270,7 +276,13 @@ pub unsafe fn sql_exec_direct_w<B: Backend>(
270276
/// - 42S22: Column not found — propagated from backend.
271277
/// - HY000: General error — propagated from backend.
272278
/// - HY001: Memory allocation error — propagated from backend.
273-
/// - HY008: Operation canceled — (driver-manager-handled; not returned here).
279+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
280+
/// function called again — is not applicable: core implements no asynchronous execution and
281+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
282+
/// statement "from a different thread in a multithread application", **is returned by this
283+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
284+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
285+
/// the backend's own SQLSTATE.
274286
/// - HY009: Invalid use of null pointer — fails if `StatementText` is null (checked here).
275287
/// - HY010: Function sequence error — (DM cases for async/NEED_DATA: driver-manager-handled; not
276288
/// returned here); fails if the connection is not open (checked here).
@@ -433,7 +445,13 @@ pub unsafe fn sql_prepare_w<B: Backend>(
433445
/// - 44000: WITH CHECK OPTION violation — propagated from backend.
434446
/// - HY000: General error — propagated from backend.
435447
/// - HY001: Memory allocation error — propagated from backend.
436-
/// - HY008: Operation canceled — (driver-manager-handled; not returned here).
448+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
449+
/// function called again — is not applicable: core implements no asynchronous execution and
450+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
451+
/// statement "from a different thread in a multithread application", **is returned by this
452+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
453+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
454+
/// the backend's own SQLSTATE.
437455
/// - HY010: Function sequence error — (DM cases for async/NEED_DATA: driver-manager-handled; not
438456
/// returned here); fails if no SQL has been prepared (checked here); fails if
439457
/// the connection is not open (checked here).

src/ffi/fetch.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ unsafe fn report_rows_fetched_only<B: Backend>(stmt: &StatementHandle<B>, count:
133133
/// - 40003 (statement completion unknown): propagated from the backend.
134134
/// - HY000 (general error): propagated from the backend.
135135
/// - HY001 (memory allocation error): not returned; Rust panics on allocation failure.
136-
/// - HY008 (operation canceled): not applicable; the `Backend` trait is synchronous.
136+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
137+
/// function called again — is not applicable: core implements no asynchronous execution and
138+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
139+
/// statement "from a different thread in a multithread application", **is returned by this
140+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
141+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
142+
/// the backend's own SQLSTATE.
137143
/// - HY010 (function sequence error): returned when `stmt.statement` is `None`, i.e. no
138144
/// result set is open. (DM) variants (async context, `SQLExtendedFetch` mixing) are
139145
/// driver-manager-handled; not returned here.
@@ -347,7 +353,13 @@ pub unsafe fn sql_fetch<B: Backend>(statement_handle: *mut c_void) -> SqlReturn
347353
/// - 40001, 40003: delegated to `sql_fetch`.
348354
/// - HY000 (general error): propagated from the backend.
349355
/// - HY001 (memory allocation error): not returned; Rust panics on allocation failure.
350-
/// - HY008 (operation canceled): not applicable; the `Backend` trait is synchronous.
356+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
357+
/// function called again — is not applicable: core implements no asynchronous execution and
358+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
359+
/// statement "from a different thread in a multithread application", **is returned by this
360+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
361+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
362+
/// the backend's own SQLSTATE.
351363
/// - HY010 (function sequence error): delegated to `sql_fetch` for `SQL_FETCH_NEXT`. (DM)
352364
/// variants are driver-manager-handled; not returned here.
353365
/// - HY013 (memory management error): not returned.
@@ -502,7 +514,13 @@ pub unsafe fn sql_fetch_scroll<B: Backend>(
502514
/// - HY003 (program type out of range): returned both when `target_type` is not a recognized C
503515
/// data type, and via `write_column_value`'s numeric-pivot catch-all for a `CDataType` with no
504516
/// numeric arm. (DM) variants (column 0 with wrong bookmark type) are driver-manager-handled.
505-
/// - HY008 (operation canceled): not applicable; the `Backend` trait is synchronous.
517+
/// - HY008: Operation canceled. The row's first clause — asynchronous processing, then the
518+
/// function called again — is not applicable: core implements no asynchronous execution and
519+
/// never returns `SQL_STILL_EXECUTING`. The second clause, `SQLCancel` called on the
520+
/// statement "from a different thread in a multithread application", **is returned by this
521+
/// driver**: the row carries no `(DM)` marker, and when a backend call fails with
522+
/// `Backend::is_cancelled` reporting its token signalled, core reports `HY008` in place of
523+
/// the backend's own SQLSTATE.
506524
/// - HY009 (invalid use of null pointer): not checked; `target_value_ptr` null is not
507525
/// validated. (DM) — driver-manager-handled.
508526
/// - HY010 (function sequence error): driver-manager-handled; not returned here.

0 commit comments

Comments
 (0)