Wie der Treiber Anweisungen übersetzt
graph TD
A["SQL-Anweisung der Anwendung (Access, ADO.NET, VBA)"] --> B{"Lässt sie sich als OData-Anfrage ausdrücken?"}
B -->|ja| C["OData-Anfrage an den Dienst: $select, $filter, $orderby, $top, $skip, $count, $apply, POST/PATCH/DELETE"]
B -->|nein| D{"Nennt die Verbindung oder der Dienst eine Adresse für eigene Anweisungen?"}
D -->|ja| E["Unverändert an die Datenbank (Durchreiche, mit gebundenen Werten)"]
D -->|nein| F["Abgewiesen mit SQLSTATE 42000 und einer Meldung, die den Grund nennt"]Der Treiber rechnet nichts selbst. Jede Anweisung wird entweder in eine OData-Anfrage übersetzt, an die Datenbank durchgereicht oder abgewiesen – nie lädt er eine ganze Tabelle, um lokal zu filtern, zu gruppieren oder zu verbinden. Das erklärt, warum eine Abfrage in Access gegen den einen Dienst läuft und gegen einen anderen mit „ODBC-Aufruf fehlgeschlagen“ endet.
Wichtige Funktionen
- Lesen, Filtern, Sortieren und Zählen auf dem Dienst. Bedingungen, Sortierung, Spaltenauswahl und
COUNT(*)gehen als Abfrageoptionen an den Dienst; nur die gefundenen Zeilen reisen. - Bearbeiten wie an einer Tabelle von SQL Server.
INSERT,UPDATEundDELETEaus Datenblatt und Formular werden zuPOST,PATCHundDELETE, mit Schreibkonflikt-Erkennung überIf-Match. - Summen und Gruppen ohne Durchreiche, wenn der Dienst Gruppieren ankündigt.
- Blättern mit
OFFSET … FETCHwie in SQL Server. - Lange Bedingungen im Rumpf statt in der Adresse.
- Alles andere unverändert an die Datenbank, wenn die Verbindung das anbietet – sonst eine klare Ablehnung statt eines falschen Ergebnisses.
Grundkonzepte
Übersetzt
| Die Anwendung schickt | Der Treiber fragt den Dienst |
|---|---|
SELECT a, b FROM t WHERE x = ? ORDER BY a | GET t?$select=a,b&$filter=x eq …&$orderby=a |
… WHERE x LIKE '%Keller%' | $filter=contains(x,'Keller') (ebenso x% → startswith, %x → endswith) |
… WHERE Nr IN (?, ?, …) | Oder-Kette Nr eq … or Nr eq … |
SELECT COUNT(*) FROM t WHERE … | GET t/$count?$filter=… – ohne Zeilen zu laden |
… 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=…, nur wenn der Dienst $apply ankündigt |
SELECT … WHERE Schlüssel = ? | $filter=Schlüssel eq … – gelesen wird immer über die Menge, nie über die Schlüsseladresse |
INSERT INTO t … | POST t |
UPDATE t SET … WHERE Schlüssel = ? AND Zeilenversion = ? | PATCH t(…) mit If-Match |
DELETE FROM t WHERE Schlüssel = ? AND Zeilenversion = ? | DELETE t(…) mit If-Match |
UPDATE/DELETE einer Tabelle ohne Schlüssel über alle alten Werte | gebundene Aktionen UpdateByOriginal/DeleteByOriginal, wenn der Dienst sie anbietet |
Eine Adresse, die länger als MAXURLLENGTH würde, geht als POST …/$query mit den Optionen im
Rumpf. Die vollständigen Regeln stehen unter
Unterstütztes SQL: Gruppieren und Zusammenfassen
und Unterstütztes SQL: Seitenweise lesen mit OFFSET/FETCH.
Durchgereicht
Was sich nicht übersetzen lässt – ein Verbund zweier Tabellen, eine Unterabfrage, eine Änderung
vieler Zeilen, eine Schemaanweisung, Rechnen zwischen Aggregaten –, schickt der Treiber unverändert an
die Adresse für eigene Anweisungen. Gebundene Werte bleiben Werte: Aus den Platzhaltern ? werden
benannte Parameter. Die Spalten des Ergebnisses beschreibt der Dienst, der Treiber rät keine Typen.
Die Adresse nennt entweder die Verbindung (PASSTHROUGH) oder der Dienst in seinem Schemadokument.
Ob die Anweisung ausgeführt werden darf, entscheidet der Dienst, nicht der Treiber; der Treiber
prüft sie nicht und stuft sie nicht ein. Warum, steht unter
Sicherheitsmodell und Vertrauensgrenzen;
die Einrichtung unter Pass-Through-Abfragen einrichten.
Eine Ausnahme: Anweisungen über Tabellen mit dem Präfix MSys – die Systemtabellen der Anwendung,
etwa die Abfrage von Access nach MSysConf bei jeder Verbindung – weist der Treiber selbst ab. Sie
gibt es im Dienst nicht, und Access erwartet genau diese Antwort.
Abgewiesen
Ohne Adresse für eigene Anweisungen weist der Treiber alles Nicht-Übersetzbare mit SQLSTATE 42000
ab. Access zeigt das als Fehler 3146 „ODBC-Aufruf fehlgeschlagen“ und rechnet die Abfrage dann nicht
selbst – auch nicht für Summen und Verbünde, die es über eine lokale Tabelle selbst könnte. Siehe
„ODBC-Aufruf fehlgeschlagen“ bei Summen oder Verbünden.
Ebenso abgewiesen statt angenähert – und mit einer Adresse ebenfalls durchgereicht: LIKE mit _ oder einer Zeichenklasse ([a-c]), Funktionen
außerhalb von SUM, MIN, MAX, AVG, COUNT in einer Gruppierung, Ändern oder Löschen einer
Tabelle ohne Zeilenversion über die alten Werte aller Spalten, wenn der Dienst weder eine
Nebenläufigkeitseigenschaft noch die Aktionen für Tabellen ohne Schlüssel für sie deklariert.
Der Grund ist immer derselbe: Eine stillschweigend vereinfachte Anweisung lieferte ein anderes
Ergebnis, das wie die Antwort aussähe.
Transaktionen
Jede Anweisung ist eine eigene Anfrage an den Dienst. Mehrere davon klammert der Treiber nur, wenn der
Dienst Transaktionen anbietet: Er nennt dann in $metadata die Adresse Store.Transactions (der
Datendienst Wkl.Data.Gateway tut das).
| Dienst | SQL_TXN_CAPABLE | Autocommit aus (BeginTrans, BeginTransaction) |
|---|---|---|
mit Store.Transactions | SQL_TC_ALL, Isolation READ COMMITTED | Mit der ersten Anfrage öffnet der Treiber eine Transaktion auf dem Dienst; jede weitere Anfrage der Verbindung – Lesen, Schreiben, eigene Anweisungen – läuft in ihr und sieht ihre eigenen, noch nicht gespeicherten Änderungen. SQLEndTran speichert oder verwirft sie; Trennen verwirft sie |
| ohne | SQL_TC_NONE | HYC00, statt einer scheinbar geöffneten Transaktion |
Access und DAO entscheiden allein nach SQL_TXN_CAPABLE, ob sie BeginTrans an den Treiber
weitergeben. Ohne Transaktionen des Dienstes behalten sie die Transaktion für sich: Jede Änderung
ist sofort auf dem Dienst gespeichert, und ein Rollback nimmt dort nichts zurück – ohne Fehler.
Mit ihnen verhält sich VBA-Code mit BeginTrans/Rollback wie über den Treiber von SQL Server:
Ändern, Anlegen und Aktualisierungsabfragen nimmt Rollback zurück, CommitTrans speichert sie, und
der Fehler einer Anweisung lässt die Transaktion bestehen.
Unterschiede zum Treiber von SQL Server: Die Transaktion hält auf dem Dienst eine eigene Verbindung;
nutzt die Anwendung sie eine Zeit lang nicht, nimmt der Dienst sie zurück, und CommitTrans meldet
dann 40001 – nichts davon ist gespeichert. Wie lange der Dienst wartet, legt er selbst fest; beim
Datendienst Wkl.Data.Gateway sind es in der Vorgabe zwei Minuten.
Vor dem Einsatz prüfen, ob Transaktionen wirken
Verlässt sich eine Anwendung auf Rollback – etwa ein Vorgang, der Kopf und Positionen zusammen
anlegt –, prüfen Sie mit dem Dienst, an dem sie laufen soll:
- Fragen Sie die Fähigkeit ab: in DAO
CurrentDb.OpenRecordset("<verknüpfte Tabelle>").Transactions(mussTruesein), in ODBCSQLGetInfo(SQL_TXN_CAPABLE)(mussSQL_TC_ALLsein). - Probieren Sie es an einer Testzeile aus:
BeginTrans, die Zeile ändern,Rollback, die Zeile neu lesen. Sie muss den alten Wert tragen.
Steht in Schritt 1 False, nimmt Rollback auf dem Dienst nichts zurück, ohne Fehler. Ein Vorgang,
der mehrere Sätze nur gemeinsam speichern darf, gehört dann in eine Prozedur der Datenbank, die er
über die Durchreiche aufruft, oder er braucht einen Dienst mit Transaktionen.
Was bei Fehlern geschieht
| Lage | Ergebnis | Was die Anwendung tun muss |
|---|---|---|
| Eine Anweisung in der Transaktion scheitert (etwa ein verletzter Schlüssel) | Die Anweisung meldet den Fehler; die Transaktion bleibt offen, die übrigen Änderungen darin bleiben bestehen – wie über den Treiber von SQL Server | selbst entscheiden: Rollback oder die übrigen mit CommitTrans speichern |
| Der Dienst hat die Transaktion zurückgenommen (Leerlauf, unbrauchbar gewordene Transaktion) | CommitTrans meldet 40001; nichts ist gespeichert | den Vorgang neu beginnen |
Die Verbindung bricht während CommitTrans ab | 08S01. Der Treiber wiederholt einen Abschluss nicht: ob gespeichert wurde, ist unbekannt | die betroffenen Sätze neu lesen; kam der Abschluss beim Dienst nicht an, nimmt der Dienst die Transaktion nach seiner Leerlaufzeit zurück |
Die Verbindung bricht während Rollback ab | 08S01; nichts wird gespeichert, weil der Dienst die offene Transaktion spätestens nach seiner Leerlaufzeit zurücknimmt | nichts; die Sätze sind nach Ablauf der Leerlaufzeit wieder frei |
| Der Dienst bietet keine Transaktionen an | ADO.NET: HYC00 bei BeginTransaction; DAO/Access: jede Änderung ist sofort gespeichert, Rollback bleibt wirkungslos | siehe Prüfung oben |
Meldungen der Datenbank
Lehnt die Datenbank eine Anweisung ab und gibt der Dienst ihre Meldungen mit (error.details mit
Meldungsnummer als code, so der Datendienst Wkl.Data.Gateway), meldet der Treiber sie wie der Treiber von
SQL Server: einen Diagnosesatz je Meldung, die Meldungsnummer als native Fehlernummer und den
SQLSTATE, den der Dienst dazu nennt (23000 für einen verletzten Schlüssel, Bezug oder
Pflichtwert, 22001 für einen zu langen Wert, 01000 für einen begleitenden Hinweis).
| Anwendung | sieht |
|---|---|
| Access, Meldungsfenster | den Text der ersten Meldung, dahinter ihre Nummer: … (#547) |
| VBA/DAO | DBEngine.Errors(0).Number = Meldungsnummer, .Description = Text; der letzte Eintrag ist wie immer der Fehler von Access (3146) |
| ADO.NET | OdbcException.Errors[i].NativeError und .SQLState |
Code, der an einer direkten Verbindung auf die Nummer verzweigt
(If DBEngine.Errors(0).Number = 2627 Then), läuft damit unverändert. Den Text gibt der Dienst nur
weiter, wenn er nichts nennt, was der Aufrufer nicht sehen darf; sonst steht dort ein Text des
Dienstes, Nummer und SQLSTATE stimmen trotzdem. Ohne details meldet der Treiber einen einzigen
Satz mit dem HTTP-Status als Nummer.
Warum so: erwogene Alternativen
Die Aufteilung in übersetzen, durchreichen und abweisen ist eine Entwurfsentscheidung. Drei Alternativen hätten Richtigkeit, Kosten oder Sicherheit wesentlich anders verteilt:
| Alternative | Was sie gebracht hätte | Warum sie verworfen ist |
|---|---|---|
| Lokal rechnen: Der Treiber lädt die Zeilen und filtert, gruppiert oder verbindet selbst | Jede Abfrage liefe, auch gegen einen Dienst ohne Gruppieren und ohne Durchreiche | Die Kosten wachsen mit der Tabelle, nicht mit dem Ergebnis, und bleiben unsichtbar, bis die Tabelle groß ist. Außerdem rechnete der Treiber mit seinen eigenen Regeln für Sortierfolge, NULL und Rundung, nicht mit denen der Datenbank – das Ergebnis wiche ohne Hinweis ab |
| Nur durchreichen: Jede Anweisung geht unverändert an die Datenbank | Volle SQL-Sprache der Datenbank, keine Übersetzungsregeln | Der Treiber wäre an einen Dienst gebunden, der eine solche Adresse anbietet, und an dessen SQL-Dialekt; jeder Aufrufer bräuchte das Recht, eigene Anweisungen auszuführen. Eine durchgereichte Anweisung wird zudem ganz gelesen, ohne Folgeseiten |
| Nur übersetzen, alles andere abweisen | Kein Weg an den Regeln des Dienstes vorbei | Access rechnet nach einer Abweisung nicht selbst, auch wenn SQLGetInfo die Fähigkeit verneint. Verbünde und Summen über verknüpfte Tabellen scheiterten dann immer |
Die gewählte Form verbindet die beiden letzten: Was sich als OData-Anfrage ausdrücken lässt, läuft
gegen jeden OData-v4-Dienst und unter dessen Regeln je Tabelle; nur der Rest braucht die Adresse für
eigene Anweisungen. Ihr Preis: Eine Abfrage, die keiner der beiden Wege trägt, scheitert sichtbar,
statt langsam oder ungenau zu laufen. Ebenso verworfen ist das Annähern einzelner Bestandteile, etwa
LIKE 'a_c' als contains; der Grund steht unter Abgewiesen.
Verwandte Themen
- Sicherheitsmodell und Vertrauensgrenzen – wer eine durchgereichte Anweisung erlaubt