Farbschema: Hell
Sie befinden sich hier: start » en » Documentation » Data integration and data access » WKL OData ODBC Driver » OData ODBC driver concepts » How the driver translates statements

How the driver translates statements

graph TD
    A["SQL statement of the application (Access, ADO.NET, VBA)"] --> B{"Can it be expressed as an OData request?"}
    B -->|yes| C["OData request to the service: $select, $filter, $orderby, $top, $skip, $count, $apply, POST/PATCH/DELETE"]
    B -->|no| D{"Does the connection or the service name an address for custom statements?"}
    D -->|yes| E["Unchanged to the database (pass-through, with bound values)"]
    D -->|no| F["Rejected with SQLSTATE 42000 and a message that names the reason"]

The driver computes nothing itself. Every statement is either translated into an OData request, passed through to the database, or rejected – it never loads a whole table to filter, group, or join locally. That explains why a query in Access runs against one service and ends with „ODBC–call failed“ against another.

Key features

  • Reading, filtering, sorting, and counting on the service. Conditions, sorting, column selection, and COUNT(*) go to the service as query options; only the rows found travel.
  • Editing as with a SQL Server table. INSERT, UPDATE, and DELETE from datasheets and forms become POST, PATCH, and DELETE, with write conflict detection through If-Match.
  • Totals and groups without pass-through, if the service announces grouping.
  • Paging with OFFSET … FETCH as in SQL Server.
  • Long conditions in the body instead of the address.
  • Everything else unchanged to the database, if the connection offers that – otherwise a clear rejection instead of a wrong result.

Basic concepts

Translated

The application sendsThe driver asks the service
SELECT a, b FROM t WHERE x = ? ORDER BY aGET t?$select=a,b&$filter=x eq …&$orderby=a
… WHERE x LIKE '%Keller%'$filter=contains(x,'Keller') (likewise x% → startswith, %x → endswith)
… WHERE Nr IN (?, ?, …)or-chain Nr eq … or Nr eq …
SELECT COUNT(*) FROM t WHERE …GET t/$count?$filter=… – without loading rows
… ORDER BY a OFFSET 500 ROWS FETCH NEXT 200 ROWS ONLY$orderby=a&$skip=500&$top=200
SELECT g, SUM(x) FROM t GROUP BY g HAVING …$apply=groupby((g),aggregate(x with sum as …))&$filter=…, only if the service announces $apply
SELECT … WHERE Key = ?$filter=Key eq …—reading always goes through the set, never through the key address
INSERT INTO t …POST t
UPDATE t SET … WHERE Key = ? AND RowVersion = ?PATCH t(…) with If-Match
DELETE FROM t WHERE Key = ? AND RowVersion = ?DELETE t(…) with If-Match
UPDATE/DELETE of a table without a key over all old valuesbound actions UpdateByOriginal/DeleteByOriginal, if the service offers them

An address that would become longer than MAXURLLENGTH goes as POST …/$query with the options in the body. The complete rules are under Supported SQL: Grouping and summarizing and Supported SQL: Paging with OFFSET/FETCH.

Passed through

What can't be translated – a join of two tables, a subquery, a change to many rows, a schema statement, calculating between aggregates – the driver sends unchanged to the address for own statements. Bound values remain values: the ? placeholders become named parameters. The service describes the columns of the result; the driver doesn't guess types.

The address is named either by the connection (PASSTHROUGH) or by the service in its schema document. Whether the statement may be executed is decided by the service, not by the driver; the driver doesn't check or classify it. The reason is under Security model and trust boundaries; the setup under Set up pass-through queries.

One exception: statements over tables with the prefix MSys – the application's system tables, for example the Access query for MSysConf on every connection – are rejected by the driver itself. They don't exist in the service, and Access expects exactly this answer.

Rejected

Without an address for custom statements, the driver rejects everything that can't be translated with SQLSTATE 42000. Access shows this as error 3146 „ODBC–call failed“ and then doesn't compute the query itself – not even for totals and joins that it could compute itself over a local table. See "ODBC--call failed" for totals or joins.

Likewise rejected instead of approximated – and, with an address, likewise passed through: LIKE with _ or a character class ([a-c]), functions other than SUM, MIN, MAX, AVG, COUNT in a grouping, changing or deleting a table without a row version over the old values of all columns if the service declares neither a concurrency property nor the actions for tables without a key for it. The reason is always the same: a silently simplified statement would return a different result that would look like the answer.

Transactions

Every statement is a separate request to the service. The driver brackets several of them only if the service offers transactions: it then names the address Store.Transactions in $metadata (the data service Wkl.Data.Gateway does this).

ServiceSQL_TXN_CAPABLEAutocommit off (BeginTrans, BeginTransaction)
with Store.TransactionsSQL_TC_ALL, isolation READ COMMITTEDWith the first request, the driver opens a transaction on the service; every further request of the connection – reading, writing, custom statements – runs in it and sees its own changes that aren't saved yet. SQLEndTran saves or discards them; disconnecting discards them
withoutSQL_TC_NONEHYC00, instead of an apparently opened transaction

Access and DAO decide solely by SQL_TXN_CAPABLE whether to pass BeginTrans on to the driver. Without transactions from the service, they keep the transaction to themselves: every change is saved on the service immediately, and a Rollback takes nothing back there – without an error. With them, VBA code with BeginTrans/Rollback behaves as it does through the SQL Server driver: Rollback takes back changes, additions, and update queries, CommitTrans saves them, and an error in one statement leaves the transaction in place.

Differences from the SQL Server driver: the transaction holds a connection of its own on the service; if the application doesn't use it for a while, the service rolls it back, and CommitTrans then reports 40001 – none of it is saved. How long the service waits is up to the service; for the data service Wkl.Data.Gateway, the default is two minutes.

Check before use whether transactions take effect

If an application relies on Rollback – for example an operation that creates a header and its line items together – check with the service it's going to run against:

  1. Query the capability: in DAO CurrentDb.OpenRecordset("<verknüpfte Tabelle>").Transactions (must be True), in ODBC SQLGetInfo(SQL_TXN_CAPABLE) (must be SQL_TC_ALL).
  2. Try it on a test row: BeginTrans, change the row, Rollback, read the row again. It must carry the old value.

If step 1 shows False, Rollback takes nothing back on the service, without an error. An operation that may save several records only together then belongs in a database procedure that it calls through pass-through, or it needs a service with transactions.

What happens on errors

SituationResultWhat the application must do
A statement in the transaction fails (for example a violated key)The statement reports the error; the transaction stays open, and the other changes in it remain – as through the SQL Server driverdecide for itself: Rollback, or save the rest with CommitTrans
The service has rolled back the transaction (idle time, transaction that has become unusable)CommitTrans reports 40001; nothing is savedstart the operation again
The connection breaks during CommitTrans08S01. The driver doesn't repeat a commit: whether anything was saved is unknownread the affected records again; if the commit didn't reach the service, the service rolls back the transaction after its idle time
The connection breaks during Rollback08S01; nothing is saved, because the service rolls back the open transaction after its idle time at the latestnothing; the records are free again once the idle time has elapsed
The service doesn't offer transactionsADO.NET: HYC00 on BeginTransaction; DAO/Access: every change is saved immediately, Rollback has no effectsee the check above

Database messages

If the database rejects a statement and the service passes on its messages (error.details with the message number as code, as the data service Wkl.Data.Gateway does), the driver reports them as the SQL Server driver does: one diagnostic record per message, the message number as the native error number, and the SQLSTATE that the service names for it (23000 for a violated key, reference, or required value, 22001 for a value that's too long, 01000 for an accompanying informational message).

Applicationsees
Access, message boxthe text of the first message, followed by its number: … (#547)
VBA/DAODBEngine.Errors(0).Number = message number, .Description = text; the last entry is, as always, the Access error (3146)
ADO.NETOdbcException.Errors[i].NativeError and .SQLState

Code that branches on the number over a direct connection (If DBEngine.Errors(0).Number = 2627 Then) therefore runs unchanged. The service passes the text on only if it names nothing the caller isn't allowed to see; otherwise a text of the service appears there, and the number and SQLSTATE are still correct. Without details, the driver reports a single record with the HTTP status as the number.

Why it works this way: alternatives considered

The division into translating, passing through, and rejecting is a design decision. Three alternatives would have distributed correctness, cost, or security very differently:

AlternativeWhat it would have broughtWhy it was rejected
Compute locally: the driver loads the rows and filters, groups, or joins itselfEvery query would run, even against a service without grouping and without pass-throughThe cost grows with the table, not with the result, and stays invisible until the table is large. Moreover, the driver would compute with its own rules for collation, NULL, and rounding, not with those of the database – the result would differ without notice
Pass through only: every statement goes to the database unchangedThe database's full SQL language, no translation rulesThe driver would be tied to a service that offers such an address, and to its SQL dialect; every caller would need the right to execute custom statements. A passed-through statement is also read in full, without next pages
Translate only, reject everything elseNo way around the service's rulesAccess doesn't compute itself after a rejection, even if SQLGetInfo denies the capability. Joins and totals over linked tables would then always fail

The chosen form combines the last two: whatever can be expressed as an OData request runs against any OData v4 service and under its per-table rules; only the rest needs the address for own statements. Its price: a query that neither route can carry fails visibly instead of running slowly or inaccurately. Approximating individual parts, for example LIKE 'a_c' as contains, is rejected as well; the reason is under Rejected.

en/wiki/data-conectivity/odata-odbc/concepts/how-the-driver-translates-statements.txt · Zuletzt geändert: von Wolfgang van der Stille