How the driver classifies and reports responses
graph TD
A["Response from the service or dropped connection"] --> B{"HTTP status"}
B -->|"2xx"| C{"Shape matches the request?"}
C -->|"yes"| D["Result: rows, count, or affected records"]
C -->|"no"| X["08S01: response unusable"]
B -->|"401, 403 with challenge"| H["Ask for code or identity, send the request again once"]
B -->|"4xx with database messages"| E["one diagnostic record per message with number and SQLSTATE"]
B -->|"other 4xx, 5xx, 504"| F["one diagnostic record with the HTTP status as the number"]
A -->|"connection dropped"| W["retry where harmless; otherwise 08S01"]Figure: The driver decides first by the HTTP status, then by the shape that's to be expected for the request made. The content and shape of a success are described in How an OData service becomes a relational ODBC model.
An HTTP response isn't automatically a result in the ODBC sense: it can carry data, a rejection by the service, database messages, or a shape that the driver can't make use of. This page specifies, for every class of response, which return value the application receives, which diagnostic records (SQLSTATE, native error number, text) are created, and what state the statement and connection are in afterward.
Key features
- Success only with a matching shape: a
200with unreadable or unexpected content isn't a success. - Database messages as over a direct connection: number, SQLSTATE, and text per message.
- No invented results: a response that broke off or was truncated by the service isn't reported as complete.
Basic concepts
Classes of responses
| Class | Recognized by | Return to the application | Diagnostic record | State afterward |
|---|---|---|---|---|
| Data | 2xx, collection with value (reading) or columns (pass-through) | SQL_SUCCESS; rows through SQLFetch | none | cursor open |
| Change executed | 2xx on POST, PATCH, DELETE | SQL_SUCCESS, SQLRowCount = 1 (actions for tables without a key: the number stated) | none | – |
| Change hits no record | 404 or 412 on PATCH/DELETE | SQLRowCount = 0 – from this, Access shows its write conflict | none | – |
| Second factor required | 401 with WWW-Authenticate: Wkl-Totp | after the code is entered, the result of the repeated request; without a dialog SQL_ERROR | 28000 with the service's message | – |
| Identity to choose | 403 with X-Wkl-Roles | after the choice, the result of the repeated request; without a dialog SQL_ERROR | 28000 | – |
| Sign-in refused | other 401 or 403 | SQL_ERROR | 28000, native number = HTTP status | – |
| Database messages | 4xx with error.details whose code is a number | SQL_ERROR | one record per message, at most 20: SQLSTATE from sqlState (if missing: 42000), native number = message number, text of the message | an open transaction stays open |
| Other rejection | 4xx without such messages | SQL_ERROR | 42000, native number = HTTP status, text from error.message or the first 200 characters of the body | – |
| Result truncated | "truncated": true in the pass-through response: the service's row limit took effect | SQL_SUCCESS for the rows supplied, then SQL_ERROR on the next SQLFetch | HY000 “The service cut the result off after N rows (the service's row limit); it is incomplete.” – the flag comes after the rows, so the driver can't know it any earlier | the rows supplied remain supplied; the statement ends |
| Redirect | 3xx with Location | when reading at the same origin: the result of the target address; otherwise SQL_ERROR | 08S01 with the target; the driver follows no redirect to another origin and doesn't repeat a redirected change | – |
| Timeout | 504 or the driver's own time limit | SQL_ERROR | HYT00; not retried, because the service may still be working | result of a change unknown |
| Service failure | 5xx except 504 | SQL_ERROR | 08S01, native number = HTTP status | – |
| Transaction rolled back | 409 on commit | SQL_ERROR from SQLEndTran | 40001 | nothing from the transaction saved |
| Connection dropped | no response | after a retry (see below), the result; otherwise SQL_ERROR | 08S01 | – |
| Certificate not trusted | TLS error | SQL_ERROR, without a retry | 08S01 with the reason, for example “UntrustedRoot” | – |
| Response unusable | 2xx with invalid JSON, without value or columns, with a value that doesn't match the announced type, or a stream that ends midway | SQL_ERROR | 08S01 with a message that names the defect | the statement ends with the error; rows already supplied remain supplied |
| Error in the driver | any other exception | SQL_ERROR | HY000 “Unerwarteter Fehler: …” (Unexpected error: …); the complete exception is in the trace | – |
A response that has one of these shapes but carries one field more than expected counts as matching: the driver skips unknown fields and annotations. That keeps it compatible with later versions of a service, but has a consequence that's listed under Limits.
Why 08S01 for an unusable response. ODBC has no SQLSTATE of its own for “the peer isn't
responding according to the protocol”. 08S01 (“Communication link failure”) tells the application
what to do: the statement itself isn't wrong, the peer is; a retry with the same statement can
succeed once the service responds correctly again. The message distinguishes this case from a
network failure.
Warnings
ODBC reports a warning as SQL_SUCCESS_WITH_INFO with a diagnostic record of class 01. This driver
generates warnings only itself:
| SQLSTATE | Occasion |
|---|---|
01004 | a value was longer than the application's buffer and was returned truncated |
01S02 | the application requested a cursor that the driver doesn't have; it continues forward-only without locks |
Informational messages from the database on success don't reach the application. If the database
sends a message with a successful statement (in SQL Server, for example PRINT or RAISERROR with
severity up to 10), the application receives SQL_SUCCESS_WITH_INFO with this message through the
SQL Server driver. Through this driver, SQL_SUCCESS arrives without it: the data service
Wkl.Data.Gateway passes messages on only with a rejection, and the driver reads none in a
successful response. An accompanying informational message with a rejection (SQLSTATE 01000), by
contrast, appears as a further diagnostic record of the error.
Retry after a dropped connection
If the connection drops without a response, the driver retries after 1 and after 3 seconds: reads
always, changes and custom statements read as a whole with the same Idempotency-Key. Not retried are an
exceeded time limit, the commit of a transaction, and a streamed custom statement; with RETRY=OFF,
nothing at all. When this is safe for which service is described under
Commitments per kind of service.
Language of the messages
The text of a diagnostic record follows the display language of the Windows user the application
runs as: German, Slovenian, or else English; so do the driver's dialogs. What counts is Windows, not
a language that an application such as Office sets for itself. The SQLSTATE and the native error
number are the same in every language – an application that evaluates errors checks those, not the
text. Messages of the service and of the database are passed on unchanged, in the language they were
written in there. The trace's own lines stay German; a diagnostic record appears there (DIAG) as
the application received it. An application that reads messages narrow (SQLGetDiagRecA) receives
them in its ANSI code page; Slovenian letters (č, š, ž) exist only in code page 1250 (Central
European), the ANSI code page of a Slovenian Windows.
Limits
| Limit | Consequence |
|---|---|
| Only the first result set of a pass-through statement arrives row by row | further sets (results) are held in memory after the end of the first one; output values (outputs) are in the buffers as soon as SQLMoreResults reports SQL_NO_DATA, and right after execution for a statement without a result set |
| Informational messages from the database on success aren't passed on | no SQL_SUCCESS_WITH_INFO |
Related topics
- "The service cut the result off after N rows" – what to do
- "The service redirects to …" – what to do
- How the driver translates statements: Database messages – how Access, DAO, and ADO.NET show the records
- Turn on tracing – find the message behind an error