Barvna shema: Riviera
Trenutno dejavna stran: start » de » Dokumentation » Datenintegration und Datenzugriff » WKL Database Gateway » Referenz » HTTP: eigener Vertrag (Referenz)

HTTP: eigener Vertrag (Referenz)

Der eigene Vertrag des Dienstes unter /api/v1/ – lesen, schreiben, Prozeduren aufrufen, eigene Anweisungen ausführen – für Programme, die nicht OData sprechen. Clients, die OData v4 sprechen (Access über den Treiber, Excel, Power BI), verwenden die OData-Oberfläche.

Syntax

MethodePfadBedeutung
GET/api/v1/cf/{alias}/{art}/{name}.jsonlesen; Art: tables, views oder functions
POST/api/v1/cf/{alias}/procedures/{name}.jsonJSON-Rumpf mit benannten Argumenten
POST/api/v1/cf/{alias}/sql.jsonRumpf: eigene Anweisung (freizuschalten)
GET/api/v1/cf/{alias}/tables/{name}.json?{schluessel}={wert}einen Satz lesen
POST/api/v1/cf/{alias}/tables/{name}.jsonanlegen; Rumpf: Felder; Antwort 201 + Satz
PATCH/api/v1/cf/{alias}/tables/{name}.json?{schluessel}={wert}ändern; Rumpf: Felder; Antwort 200 + Satz
DELETE/api/v1/cf/{alias}/tables/{name}.json?{schluessel}={wert}löschen; Antwort 204

Ohne Anmeldung, für Überwachung und Verteilung:

MethodePfadBedeutung
GET/health/liveProzess lebt (200, Text „Healthy“)
GET/health/ready200; „Healthy“ mit mindestens einem Alias, sonst „Degraded“ (auch dann 200)
GET/api/v1/health{"status":"ok","configuredAliases":<Zahl>}
GET/download/Installationspakete (siehe unten)

Mit Windows-Anmeldung, für den WKL Bootstrapper:

MethodePfadBedeutung
GET/schema-state/{alias}/{produkt}Schemastand einer versionierten Datenbank (siehe unten)

Parameter

Die Endung entscheidet die Darstellung: .json oder .xml, an jeder dieser Adressen und für Erfolg wie Fehler. Siehe Darstellungen.

Angemeldet wird mit jedem Verfahren, das die Konfiguration freischaltet: Dienstschlüssel in der Kopfzeile X-Api-Key, persönlicher Schlüssel (in derselben Kopfzeile oder als Kennwort von HTTP Basic), Windows-Konto (Negotiate) oder SQL-Anmeldung (HTTP Basic). Alle Verfahren, ihre Antworten und der zweite Faktor stehen in HTTP: Anmeldung; für diesen Vertrag gelten sie unverändert.

Schreiben in Tabellen

Schreiben ist so dynamisch wie Lesen: Methode, Tabelle, Schlüssel und Felder kommen aus der Anfrage, das SQL erzeugt der Dienst. Der Aufrufer schickt dabei kein SQL. Er bestimmt, welcher Satz (über den Schlüssel) und welche Werte (über die Felder), nie eine Anweisung, eine Bedingung oder eine Spalte, die der Katalog nicht kennt.

PATCH /api/v1/cf/fachdaten/tables/APPARAT.json?nummer=4711
X-Api-Key: …
If-Match: "AAAAAAAQgpE="
Content-Type: application/json
 
{ "benennung": "Kühler Nord", "preis": 12.50 }

Der Rumpf einer ändernden Anfrage trägt Content-Type: application/json, eine hochgeladene Datei application/octet-stream. Einen Rumpf ohne diese Angabe oder mit einem Typ, den eine Webseite ohne Rückfrage senden darf (text/plain, application/x-www-form-urlencoded, multipart/form-data), beantwortet der Dienst mit 400 und dem Code BAD_REQUEST, bevor er ihn liest. Ausgenommen sind die Abfrageoptionen eines Lesezugriffs in $query, die als text/plain kommen.

Grundsätze und Begründung: Schreiben und Schreibkonflikte.

RegelWarum
PATCH setzt nur die genannten FelderDie übrigen behalten ihren Wert; niemand setzt fünfzig Felder auf den Stand einer älteren Kopie zurück
PUT wird nicht angeboten (405)„Satz ersetzen“ müsste jede nicht genannte Spalte leeren
Der Schlüssel ist nicht änderbarDer Satz wanderte unter seiner eigenen Adresse weg
Identitäts-, berechnete und Versionsspalten sind nicht schreibbarDie setzt die Datenbank
Unbekannte oder doppelt geschriebene Felder werden abgewiesenWelches von zwei Schreibweisen gewönne, hinge an der Reihenfolge im Rumpf
Beim Anlegen müssen alle Pflichtfelder und der Schlüssel dastehenAußer ein Schlüssel, den die Datenbank vergibt – als Identität oder per Vorgabe (NEWSEQUENTIALID(), NEWID(), Sequenz); den vergebenen Wert liest die Anweisung beim Einfügen mit (OUTPUT … INTO, auch bei Tabellen mit Triggern), der gespeicherte Satz wird über ihn zurückgelesen

Wie Werte gebunden werden: Zeitpunkte, Zahlen und Namen.

AntwortBedeutung
201 / 200 + Satzangelegt / geändert; der Satz ist der gespeicherte Stand
204gelöscht
400Schlüssel unvollständig, Feld unbekannt oder nicht schreibbar, Wert passt nicht zum Typ
404kein Satz mit diesem Schlüssel
409Schlüssel beim Anlegen schon vergeben
412Zeilenversion veraltet
422von der Datenbank abgelehnt (Pflichtwert, Fremdschlüssel, Prüfbedingung); die Meldungen von SQL Server stehen in details (siehe „Meldungen von SQL Server“ unter Fehlerform); löst ein Trigger eine eigene Meldung aus, steht ihr Text in message (siehe „Ausnahme: Meldungen der Anwendung“)

Zum Schreiben braucht ein Aufrufer eine Regel mit der Ressourcenart tables und den Verben POST, PATCH, DELETE. Das Nur-Lese-Kennzeichen sperrt alle drei.

Abfrageoptionen beim Lesen: $select, $filter, $orderby, $top, $skip, $count. Alles davon wird zu SQL übersetzt und von der Datenbank ausgewertet.

Was $filter annimmt

BestandteilAngenommene Schreibweisen
Vergleicheeq ne gt ge lt le, Prüfung auf fehlenden Wert mit eq null / ne null
Feld gegen FeldParentId eq Id, Stufe lt Grenze: rechts ein Feldname statt eines Werts; beide Felder müssen zur Menge gehören, ein leeres Feld trifft weder den Vergleich noch seine Verneinung. Text gegen Zahl lehnt SQL Server ab (422) – seit Vertrag 0.4.0 (GatewayFilter.FieldComparison)
Verknüpfungenand, or, not, Klammern
Textsuchestartswith(feld,'x'), endswith(feld,'x'), contains(feld,'x'), substringof('x',feld)
Zahlen5, 12.5, mit Breitenangabe 5L, 12.5m, 12.5f, 12.5d
Zeitpunktedatetime'2023-01-01T00:00:00', datetimeoffset'…' sowie ohne Typnamen 2023-01-01T00:00:00Z
Kennungen (uniqueidentifier)guid'6f9619ff-8b86-d011-b42d-00cf4fc964ff' sowie ohne Typnamen 6f9619ff-8b86-d011-b42d-00cf4fc964ff (so schreibt sie der ODBC-Treiber)
Weitere Typentime'…', binary'…'

Beide Schreibweisen des Protokolls werden angenommen, weil die Verbraucher nicht einer Generation angehören: substringof und datetime'…' stammen aus der älteren Version, contains, der Zeitpunkt und die Kennung ohne Typnamen aus der neueren. Welche davon ankommt, entscheidet der Client und nicht die Anfrage.

Ein abgewiesenes Literal ist teurer als es aussieht. Ein Tabellenkalkulations- oder Berichtsclient meldet die 400 nicht weiter – er lässt die Bedingung fallen und liest stattdessen das ganze Objekt. Aus einer gezielten Abfrage wird so ein Durchzug über die ganze Tabelle, der bei großen Tabellen abbrechen kann. Deshalb sind die Literale des Protokolls Teil des Vertrags und keine Bequemlichkeit.

Welche Abfrageoptionen bedient werden

Bedient: $select, $filter, $orderby, $top, $skip, $count, $inlinecount, $format (nur json). Die Gesamtzahl wird in beiden Schreibweisen erfragt – $count=true und $inlinecount=allpages –, weil die ältere und die neuere Version des Protokolls sie verschieden nennen und der Client entscheidet, welche ankommt.

Abgewiesen mit 400:

AbgewiesenMeldung
$expand, $apply, $search, $compute, $skiptoken, $levels, $ref, $value, $id„wird von diesem Dienst nicht ausgeliefert“ ($skiptoken und $apply nimmt nur die OData-Oberfläche an, siehe Folgeseiten und Gruppieren dort)
jede andere Angabe mit $„ist keine bekannte Abfrageoption“
ein Wert, der weder true noch false ist ($count=yes)„erwartet 'true' oder 'false'“
eine andere Darstellung als JSON ($format=xml)„wird nicht ausgeliefert“

Eine übergangene Option ist schlimmer als eine abgewiesene. Eine Option ist eine Angabe darüber, welche Zeilen oder Spalten gewollt sind; sie fallen zu lassen erzeugt keinen Fehler, sondern ein anderes Ergebnis, das wie eine Antwort aussieht. Ohne diese Prüfung würde $fillter=Typ eq 'S' – ein Buchstabe zu viel – mit 200 und dem ganzen Objekt beantwortet statt mit den gesuchten Zeilen, und nichts in der Antwort sagte das.

Die Unterscheidung zwischen „nicht ausgeliefert“ und „unbekannt“ ist gewollt: Wer $expand schickt, hat etwas Richtiges geschrieben, das hier nicht bedient wird; wer $fillter schickt, hat sich vertippt. Beide brauchen eine andere Korrektur, und eine gemeinsame Meldung schickte den ersten auf die Suche nach einem Tippfehler, den es nicht gibt.

Antwortform

{ "data": [ … ], "rowCount": 100, "count": 100, "window": 100, "totalCount": 250, "nextLink": "/api/v1/cf/fachdaten/views/liste.json?$skip=100&wkl-page=…", "executionTimeMs": 0 }
FeldBedeutung
rowCountZeilen in dieser Antwort. Immer vorhanden, nie negativ
countSpiegel von rowCount. Ältere Schreibweise des Vorgängerdienstes; sie bleibt, weil bestehende Aufrufer sie lesen
windowFenstergröße, mit der tatsächlich gelesen wurde. Bei einem Prozeduraufruf nicht vorhanden
totalCountZeilen, die derselbe Filter insgesamt trifft. Nur bei $count=true vorhanden
affectedRowCountZeilen, die ein Prozeduraufruf geändert hat – nur wenn er kein Ergebnis lieferte
truncatedNur vorhanden, wenn die Zeilenobergrenze griff
nextLinkVerweis auf das nächste Fenster, pfadabsolut. Nur vorhanden, wenn der Dienst das Fenster gewählt und gefüllt hat – am Ende fehlt das Feld

window und nextLink sind die Ergänzungen gegenüber den älteren Versionen. Ohne window kann ein Aufrufer ein volles Fenster nicht von einem kurzen unterscheiden – und genau daran erkennt ein Aufrufer ohne $count, dass er am Ende angekommen ist. Die gewünschte Größe genügt dafür nicht: sie wird auf die konfigurierte Obergrenze begrenzt, und wer keine nennt, bekommt die konfigurierte Voreinstellung, die er nicht kennt. Ein Verbraucher, der das Feld nicht kennt, übergeht es.

nextLink sagt dasselbe in der Form, auf die ein Werkzeug ohne Kenntnis dieses Vertrags von selbst reagiert: einen Verweis, dem es folgt, bis er fehlt. window ist eine Abmachung, die nur ein eigens dafür geschriebener Aufrufer auswertet – jeder andere hört nach dem ersten Fenster auf und hält es für das ganze Ergebnis.

Wann ein Folgeverweis angeboten wird

AnfrageAntwort
kein $top, Fenster vollVerweis mit $skip + Fenstergröße, ohne $top
$top größer als MaxTop, Fenster vollVerweis mit dem Rest als $top – wer 25 000 verlangt, bekommt genau 25 000, verteilt auf mehrere Fenster
$top nicht größer als MaxTopkein Verweis – der Aufrufer hat bekommen, was er verlangt hat
Fenster kurz oder leerkein Verweis – das Ergebnis ist zu Ende

Alle anderen Optionen – Filter, Projektion, Sortierung, Funktionsargumente – übernimmt der Verweis unverändert, damit jedes Fenster ein Fenster desselben Ergebnisses ist. Nur die Zählung ($count, $inlinecount) entfällt: sie ist für alle Fenster eines Lesevorgangs dieselbe, und sie je Fenster zu wiederholen wiederholt den teuersten Teil für eine bekannte Antwort. Ein volles Fenster beweist nicht, dass weitere Zeilen existieren; endet das Ergebnis genau an seiner Kante, führt der Verweis auf ein leeres Fenster. Das ist eine richtige Antwort und billiger als eine Zählung bei jedem Lesen.

Der Verweis ist pfadabsolut (/api/v1/cf/…?…) und nennt keinen Host: hinter einem vorgeschalteten Proxy ist der Host, den dieser Prozess sieht, nicht der, den der Aufrufer benutzt hat.

totalCount fehlt, wenn niemand gezählt hat. Das ist nicht dasselbe wie 0. Wer die Unterscheidung zu einer Null vereinfacht, sagt einer Liste, es gebe nichts zu laden – über nachweislich vorhandenen Daten und ohne Fehlermeldung an irgendeiner Stelle.

Fehlerform

Angaben „seit Vertrag 0.x“ nennen die Version des Vertragspakets Wkl.Data.Gateway.Contracts, ab der eine Fehlerart oder ein Feld erkannt wird; ohne Angabe gilt sie seit 0.1.0. Ein Programm, das eine ältere Version festhält, liest die neueren Arten mit ihrem Statuscode, wie unten je Art beschrieben.

{ "error": { "code": "NOT_FOUND", "message": "…", "correlationId": "…" } }

Codes: BAD_REQUEST (400), UNAUTHENTICATED (401), FORBIDDEN (403), NOT_FOUND (404), CONFLICT (409), PRECONDITION_FAILED (412), LIMIT_EXCEEDED (429), DATABASE_REJECTED (422), UNAVAILABLE (503), DATABASE_UNREADABLE (503), TIMEOUT (504), INTERNAL (500).

UNAVAILABLE steht nicht nur für eine unerreichbare Datenbank, sondern für jeden unerwarteten Fehler im Dienst. Nach einer Schreibanfrage (POST, PATCH, DELETE, Abschluss einer Transaktion) heißt 503 deshalb: Ob geschrieben wurde, ist unbekannt. Eine Antwort 5xx wird für Idempotency-Key nicht gespeichert; eine Wiederholung schreibt dann womöglich ein zweites Mal. Den Satz vorher neu lesen.

LIMIT_EXCEEDED steht für jede Grenze des Dienstes: Anfragen je Zeitraum, Fehlversuche, Anzahl gebundener Werte, Größe des Rumpfs (Limits:MaxBodyBytes), offene Transaktionen. Für einen zu großen Rumpf weicht der Vertrag damit von RFC 9110, Abschnitt 15.5.14 ab, der 413 Content Too Large vorsieht: Ein Aufrufer soll alle Grenzen an einer Art erkennen. Ob Warten hilft (Ratenbegrenzung) oder nur eine kleinere Anfrage (Rumpf, Werte), sagt message.

TIMEOUT (seit Vertrag 0.3.0; vorher UNAVAILABLE) heißt: Die Anweisung lief und überschritt Limits:QueryTimeoutSeconds. Die Datenbank war erreichbar; ein Datenbanktreiber meldet das als Zeitüberschreitung (SQLSTATE HYT00), nicht als verlorene Verbindung. TIMEOUT und UNAVAILABLE gelten als vorübergehend (IsTransient).

DATABASE_UNREADABLE (seit Vertrag 0.6.0; vorher UNAVAILABLE) heißt: SQL Server war erreichbar, kann die Datenbank des Alias aber nicht lesen. Die Dateien der Datenbank lassen sich nicht lesen (823, 824, 605), oder die Datenbank ist offline (942), als verdächtig markiert (926), ihre Dateien sind nicht zu öffnen (945), sie wird noch wiederhergestellt (922) oder ihr Protokoll fehlt (9001). message nennt den Alias, etwa „Die Datenbank der Datenquelle 'lager' ist auf dem Server derzeit nicht lesbar. Bitte den Betreuer verstaendigen.“; Dateinamen und Nummer stehen nur im Protokoll, details fehlt. Sie gilt nicht als vorübergehend: Wiederholen hilft erst, wenn jemand am Server handelt. Ein Aufrufer, der den Code nicht kennt, liest den Statuscode 503 und behandelt sie wie UNAVAILABLE. Der .NET-Client Wkl.Data.Gateway.Client meldet sie ab Version 0.2.0 als GatewayProblemKind.DatabaseUnreadable (nicht vorübergehend); die Version 0.1.0 kennt den Code nicht und meldet GatewayProblemKind.Unavailable, also „vorübergehend“. Eine 503 bedeutet in keinem Fall „keine Daten“. Umgekehrt belegt das Fehlen des Codes keine lesbare Datenbank: Er steht nur für die oben genannten Fehlernummern; ein Lesefehler aus einem anderen Grund kommt als UNAVAILABLE.

Nach einem kurzen Verlust des Laufwerks unter den Datenbankdateien bleibt das so, bis der Dienst „SQL Server“ auf dem Datenbankserver neu gestartet wird; Anweisungen, deren Seiten noch im Speicher von SQL Server liegen, gelingen in dieser Zeit weiter.

Eine unbekannte Ressource und eine für den Aufrufer nicht lesbare Ressource antworten beide mit NOT_FOUND. Ausnahmen: Ein lesbares Objekt, das der Aufrufer so nicht ändern darf, antwortet FORBIDDEN; eine geprüfte Anweisung antwortet für unbekannte wie für verborgene Objekte gleich mit FORBIDDEN. Die Unterscheidung würde den Dienst zum Verzeichnis machen: ein Aufrufer könnte durch Vergleich der Antworten erfahren, welche Objekte existieren.

Was eine Fehlerantwort nie enthält: Datenbankname, Verbindungszeichenfolge, Anweisungstext, den Namen eines Objekts, das der Aufrufer nicht sehen darf. Diese Angaben stehen im Protokoll, auffindbar über die correlationId.

Meldungen von SQL Server (details, seit Vertrag 0.5.0). Lehnt die Datenbank eine Anweisung ab, trägt die Antwort die Meldungen von SQL Server, in der Reihenfolge, in der SQL Server sie meldet – wie eine direkte Verbindung über den Treiber von SQL Server, damit Code auf die Nummer verzweigen kann (etwa 2627 „Schlüssel vergeben“ gegen 547 „Bezug verletzt“):

{ "error": { "code": "DATABASE_REJECTED", "message": "Der Vorgang wurde von der Datenbank abgelehnt.",
    "correlationId": "…",
    "details": [ { "code": "547", "sqlState": "23000", "severity": 16, "released": true,
                   "message": "Die INSERT-Anweisung steht in Konflikt mit der CHECK-Einschränkung …" } ] } }
FeldInhalt
codeMeldungsnummer von SQL Server, als Text (OData-Form von details)
sqlStateSQLSTATE, den der Treiber von SQL Server für diese Nummer meldet: 23000 Einschränkung, Schlüssel, Pflichtwert; 22001 Wert zu lang; 22018 Umwandlung; 22007/22008 Datum oder Zeit; 22003 Überlauf; 22012 Division durch null; 42S02/42S22 Objekt bzw. Spalte unbekannt; 25000 Transaktionszustand; 40001 Deadlock; 01000 Hinweis; sonst 42000
severitySchwere: bis 10 ein Hinweis, der die Ablehnung begleitet, ab 11 ein Fehler
releasedtrue: message ist der Text von SQL Server. false: der Text nennt ein Objekt, das der Aufrufer nicht sehen darf; message ist dann ein Text des Dienstes, Nummer und SQLSTATE stimmen trotzdem
messageText, wie oben beschrieben

details trägt höchstens zehn Meldungen. Die Nummer erhält der Aufrufer für jede davon. Den Text nur, wenn er keinen Namen enthält, den der Aufrufer nicht sehen darf: keine Tabelle, Sicht, Routine, Einschränkung, keinen Trigger und keinen Index eines Objekts, das seine Regeln nicht erreichen, und weder die Anmeldung des Alias noch den Servernamen. Geprüft wird der Name als ganzes Wort, unabhängig von Anführungszeichen; so greift die Prüfung auch in deutschen Meldungen, die Namen ohne Anführung nennen („in der lager-Datenbank“). Der Name der Datenbank wird durch den Alias ersetzt. Ein verborgener Name, der zufällig ein gewöhnliches Wort ist, hält einen harmlosen Text zurück; die Nummer kommt trotzdem an.

Meldungen mit einem Wert. Umwandlungsfehler (245, 8114, Datumswerte 241 und 242), ein doppelter Schlüssel (2627, 2601) und ein abgeschnittener Wert (2628, 8152) nennen einen Wert („… beim Konvertieren des Werts 'X' …“, „Der doppelte Schlüsselwert ist (X)“). Ob X die Eingabe des Aufrufers war oder aus den Daten gelesen wurde, ist von außen nicht zu unterscheiden: Der Filter des Aufrufers, eine eigene Anweisung (INSERT … SELECT, UPDATE … FROM, MERGE), eine Prozedur oder ein Trigger kann einen gelesenen Wert gezielt in eine eindeutige oder zu kurze Spalte bringen. Läuft die Anweisung als Anmeldung des Alias, hält der Dienst den Text dieser Meldungen deshalb immer zurück (released false); Nummer, SQLSTATE und Statuscode (409 beim vergebenen Schlüssel) kommen an. Grund: Blendet die Datenbank Zeilen je Person aus (Sicherheit auf Zeilenebene über den SESSION_CONTEXT, den der Dienst setzt), kann SQL Server solche Ausdrücke vor oder neben der Sicherheitsbedingung auswerten, und der Text verriete einen Wert aus einer ausgeblendeten Zeile; unter der Anmeldung des Alias ist die Prüfung des Dienstes die einzige Sicherung. Läuft die Anweisung als die Person (Identitätswechsel oder eigene SQL-Anmeldung), entscheidet SQL Server selbst, was sie lesen darf, und der Text kommt wie über eine direkte Verbindung. Die Regel hängt an der Identität, nicht an der Form der Anweisung.

details fehlt bei einer verlorenen Verbindung (503), einer nicht lesbaren Datenbank (503 DATABASE_UNREADABLE) und einer Zeitüberschreitung (504): Das sind Aussagen über die Verbindung, nicht Antworten der Datenbank auf die Anweisung. message und Statuscode bleiben unverändert.

Ausnahme: Meldungen der Anwendung. Löst die Anwendung in der Datenbank selbst eine Meldung aus – THROW mit eigener Nummer oder RAISERROR mit einem Text, also Fehlernummer ab 50000 und Schwere 11 bis 16 –, steht deren Text als message in der Antwort (422 DATABASE_REJECTED), auf jedem Weg: Prozeduraufruf, Schreiben in Tabellen (auch aus einem Trigger), OData und Durchreiche. Eine solche Meldung ist für den Menschen geschrieben, der den Vorgang ausgelöst hat, und eine direkte Verbindung zeigte sie ihm ebenso. Was sie nennt, entscheidet, wer sie schreibt. Meldungen von SQL Server selbst (Nummer unter 50000) stehen nur in details; message bleibt dann der Text des Dienstes.

Prozeduren aufrufen

Eine gespeicherte Prozedur wird mit POST auf procedures/{name} aufgerufen; der Rumpf ist ein JSON-Objekt mit den Argumenten nach Namen.

POST /api/v1/cf/fachdaten/procedures/auftrag_anlegen.json
X-Api-Key: …
Idempotency-Key: 6c1e0c7a-…
Content-Type: application/json
 
{ "kunde_id": 4711, "betreff": "Kühler Nord", "stand": "2026-10-10T17:00:00.123+02:00", "notiz": null }

Der Dienst führt daraus SET XACT_ABORT ON; EXEC [schema].[auftrag_anlegen] @kunde_id = @p0, … aus.

RegelWarum
Argumentnamen ohne @; ein Name mit @ wird als ungültiger Bezeichner abgewiesen (400)Das Präfix setzt der Dienst. Ein mitgeschicktes gäbe @@name, ein Name nur aus @ einen namenlosen Parameter – beides scheiterte erst in der Datenbank mit einer Meldung über die Prozedur statt über die Anfrage
Aufruf nach Namen; ein weggelassenes Argument nimmt den Vorgabewert der ProzedurEin Aufruf nach Position kann einen optionalen Parameter nicht überspringen
JSON-null wird NULLEin fehlender Wert muss sich von einem leeren Text unterscheiden; als Abfrageargument käme ein optionaler Text als '' an und überschriebe einen Wert, der bleiben sollte
Eine Prozedur ohne Parameter wird ohne Rumpf aufgerufenDas ist der gewöhnliche Aufruf, kein Fehler
Ein Objekt oder eine Liste als Wert wird abgewiesen (400)Kein Prozedurparameter nimmt es an; als Text eingeebnet bekäme die Prozedur etwas, das sie nie deklariert hat
Keine Transaktion des Dienstes um den Aufruf; SET XACT_ABORT ON davorDie Prozedur führt ihre eigene. Eine äußere Transaktion würde verschachteln: ein inneres ROLLBACK verwürfe auch die äußere, ein inneres COMMIT zählte nur herunter. XACT_ABORT rollt bei einem Laufzeitfehler zurück, statt eine offene Transaktion mit ihren Sperren in den Verbindungspool zu geben
Ein Aufruf ist genau ein EXECWas mehrere Schritte braucht, ist eine Prozedur mit mehreren Schritten in einer Transaktion; zwei Aufrufe hintereinander ließen bei einem Fehler dazwischen einen halben Stand zurück

Typabbildung der Argumente. Gebunden wird ohne Typangabe; SQL Server wandelt den Wert in den Typ des Parameters.

JSON-WertGebunden als
Textnvarchar
Ganzzahl (4711)bigint
Dezimalzahl ohne Exponent (12.50)decimal, exakt
Zahl mit Exponent (1E-300)float
true / falsebit
nullNULL

Folge für Zeitpunkte: Es gibt kein JSON-Datum. Ein Zeitpunkt geht als Text, etwa ISO 8601 (2026-10-10T17:00:00.123+02:00), und SQL Server wandelt ihn in den Typ des Parameters – auch in einen varchar-Parameter, den die Prozedur selbst umwandelt (CONVERT(…, 127)).

Antwortform.

{ "data": [ { "ergebnis": "angelegt", "auftrag_id": 815, "stand": "2026-10-10T17:00:00.123+02:00" } ], "rowCount": 1, "count": 1, "executionTimeMs": 12 }
FeldBedeutung
dataDie erste Ergebnismenge der Prozedur, als Liste von Objekten (Spaltenname → Wert). Weitere Ergebnismengen werden nicht gelesen. Ohne Ergebnismenge eine leere Liste
rowCount, countZeilen in data
affectedRowCountNur wenn die Prozedur keine Ergebnismenge lieferte: die Zahl der geänderten Zeilen; data ist dann eine leere Liste
truncatedDie Zeilenobergrenze des Alias hat gegriffen
executionTimeMsLaufzeit im Dienst

Es gibt kein Fenster und keinen nextLink: Eine Prozedur liefert die Ergebnismenge, für die sie geschrieben ist, und der Aufrufer kann keinen Teil davon verlangen. Werte der Ergebnismenge stehen in derselben Form wie beim Lesen; ein datetimeoffset mit seinem Versatz.

Keine OUTPUT-Parameter, kein RETURN-Wert auf diesem Weg. Diese Adresse liest weder das eine noch das andere, und sie liest nur die erste Ergebnismenge. Was der Aufrufer erfahren soll – die vergebene Kennung, der neue Stand, ein Ergebniswort –, gibt die Prozedur hier als Ergebnismenge zurück (SELECT … AS ergebnis, … AS id). Wer Ausgabeparameter, den Rückgabewert oder alle Ergebnismengen braucht, ruft die Prozedur als Anweisung auf (EXEC @rueckgabe = …), siehe Ausgabeparameter, mehrere Ergebnismengen, Beschreiben.

Fehler.

Ursache in der ProzedurAntwort
THROW mit eigener Nummer, RAISERROR mit Text, ab Schwere 11422 DATABASE_REJECTED, message ist der Text der Meldung
Ein Fehler von SQL Server selbst (etwa Division durch null, Fremdschlüssel)422 DATABASE_REJECTED, Text „Der Vorgang wurde von der Datenbank abgelehnt.“
RAISERROR bis Schwere 10 (Hinweis)Kein Fehler: 200 mit dem Ergebnis; der Hinweistext geht verloren
Primärschlüssel oder eindeutiger Index verletzt (2627, 2601)409 CONFLICT
Zeitlimit des Dienstes überschritten (Limits:QueryTimeoutSeconds)504 TIMEOUT
Datenbank nicht erreichbar503 UNAVAILABLE
Datenbank auf dem Server nicht lesbar (823, offline, verdächtig)503 DATABASE_UNREADABLE
Fehlende Berechtigung (229, 230, 262, 297, 300, 916)403 FORBIDDEN, wenn die Anweisung als Person läuft (Identitätswechsel oder SQL-Anmeldung); sonst 422 – dann fehlt die Berechtigung der Anmeldung des Alias, und das ist eine Lücke in der Einrichtung, nicht beim Aufrufer

Seit Vertrag 0.5.0 stehen Nummer, SQLSTATE und Schwere jeder Meldung in details, der Text nur, wenn der Dienst ihn freigibt (siehe Fehlerform); vorher stand nur der Text der Anwendungsmeldung in message. Ein Aufrufer, der je nach Grund verschieden handeln soll, bekommt den Grund trotzdem besser als Ergebniszeile (ergebnis = 'gesperrt'): Eine Nummer der Anwendung ab 50000 ist für jeden Grund frei wählbar, aber nur eine Abmachung zwischen Prozedur und Aufrufer. Und eine Meldung unter Schwere 11 ist ein Hinweis, kein Fehler: Sie bricht nichts ab und erscheint nirgends in der Antwort.

Rechte. Ein Aufrufer braucht eine Regel mit der Ressourcenart procedures und dem Verb POST; das Nur-Lese-Kennzeichen sperrt den Aufruf, denn der Dienst kann nicht wissen, ob eine Prozedur schreibt. Eine Prozedur, die der Katalog des Alias nicht kennt oder der Aufrufer nicht aufrufen darf, antwortet mit 404. Wiederholungen nach einem Verbindungsabbruch: Der Idempotency-Key gilt auch hier, siehe Wiederholte Schreibanfragen.

Lange Abfragen: Optionen im Rumpf

Die Abfrageoptionen einer Leseanfrage stehen in der Adresse, und deren Länge begrenzt der Host vor dem Dienst: IIS nimmt in der Vorgabe höchstens 2 048 Bytes Abfragezeichenfolge an (requestFiltering/requestLimits/@maxQueryString, Microsoft: Request Limits), Kestrel 8 KB Anforderungszeile (KestrelServerLimits.MaxRequestLineSize). Die IIS-Vorlage des Installationspakets hebt die Grenze auf 8 192 Zeichen an. Eine Leseanfrage darf ihre Optionen deshalb auch im Rumpf tragen – in zwei Formen, auf beiden Oberflächen:

POST  /odata/v4/fachdaten/dbo/Artikel/$query            Content-Type: text/plain
POST  /odata/v4/fachdaten/dbo/Artikel/$count/$query
POST  /api/v1/cf/fachdaten/tables/artikel.json/$query
QUERY /odata/v4/fachdaten/dbo/Artikel                    (ebenso an den übrigen Adressen)
 
$filter=Nummer%20eq%201%20or%20Nummer%20eq%202&$select=Nummer,Bezeichnung&$count=true

Der Rumpf ist genau der Text, der sonst hinter dem ? stünde. POST …/$query ist die Form, die OData 4.01 dafür vorsieht (OData Version 4.01 Part 1: Protocol, Abschnitt „Passing Query Options in the Request Body“), und kommt durch jeden Proxy, der POST durchlässt. QUERY ist die Methode für sichere Leseanfragen mit Rumpf aus dem Entwurf der IETF „The HTTP QUERY Method“ (draft-ietf-httpbis-safe-method-w-body); als Entwurf nimmt der Dienst sie zusätzlich an, ohne sich auf seine endgültige Form festzulegen. Einen Rumpf an GET nimmt der Dienst bewusst nicht an: Nach RFC 9110, Abschnitt 9.3.1 hat er dort keine festgelegte Bedeutung, Proxys verwerfen ihn oder weisen die Anfrage ab, und ein Zwischenspeicher, der nach der Adresse speichert, gäbe die Antwort eines Filters für einen anderen aus.

RegelGrund
Geprüft, begrenzt und protokolliert wird als LesenEin Aufrufer mit reinem Leserecht darf es; beurteilt nach der Methode, bräuchte er das Recht zu schreiben
Der Rumpf wird erst nach Anmeldung und Freigabe gelesen, begrenzt durch MaxBodyBytesEinen Rumpf zu lesen ist Arbeit; eine unberechtigte Anfrage soll sie nicht auslösen
Optionen in Adresse und Rumpf: 400Welcher von zwei $filter gälte, könnte niemand beim Lesen der Anfrage sagen
Nur text/plain (oder application/x-www-form-urlencoded)JSON ist eine andere Form der Anfrage; ihn als Optionen zu lesen, hieße ihn falsch zu lesen
Die Folgeseite zeigt auf die Ressource, nicht auf $queryEin Client folgt ihr mit GET; ist sie selbst zu lang, schickt er sie wieder als $query

Ab der Übernahme sind beide Formen derselbe Lesevorgang: derselbe Übersetzer, dieselben Grenzen, dieselbe Seitenbildung. Wie viele Werte eine Bedingung nennen darf, hängt deshalb nicht am Weg, sondern an der Form der Bedingung; siehe Lange Wertelisten.

Lange Wertelisten

Eine Bedingung wie „eine dieser Nummern“ kommt als Kette von Gleichheiten an: Nummer eq 1 or Nummer eq 2 or …. Die Filtersprache von OData 4.0 kennt kein in (erst 4.01 führt es ein, URL Conventions 4.01); der ODBC-Treiber schreibt ein IN (…) aus Access deshalb so, und der typisierte Client schreibt eine Mengenprüfung ebenso. Einzeln gebunden wäre jeder Wert ein eigener Parameter, und davon nimmt SQL Server in einer Anweisung höchstens 2 098 an: Ein Aufruf trägt 2 100 Parameter, zwei davon belegt sp_executesql selbst.

Der typisierte Client lässt in einer Mengenprüfung höchstens 20 000 Werte zu (seit Vertrag 0.2.0, vorher 1 000); die Grenze prüft das Vertragspaket, bevor die Anfrage den Prozess verlässt.

Der Dienst fasst eine solche Kette deshalb zusammen. Mehr als 100 Gleichheiten auf derselben Spalte innerhalb eines or belegen einen einzigen Parameter, gleich wie viele es sind: Sie reisen als ein XML-Dokument. Die Anweisung liest das Dokument zuerst in eine Variable und vergleicht die Spalte mit den Werten daraus, jeden gelesen in dem Typ, den sein Parameter gehabt hätte.

RegelGrund
Bis 100 Werte bleibt jeder Wert ein eigener ParameterDer Optimierer sieht dann die Werte selbst, und die kurzen Schlüssellisten, mit denen eine Anwendung ihre Sätze nachliest, bleiben unverändert (Access fragt je zehn Schlüssel ab)
XML, nicht JSON und keine TabellenwertübergabeXML liest die Engine auf jeder Kompatibilitätsstufe. OPENJSON und STRING_SPLIT verlangen Stufe 130, eine Tabellenwertübergabe einen Typ, der erst in der Datenbank angelegt werden müsste
Das Dokument wird einmal in eine Variable gelesenIn der Bedingung selbst wandelte die Engine die Liste für jede geprüfte Zeile neu um; bei langen Listen wüchse die Laufzeit mit der Zahl der Werte mal der Zahl der Zeilen
Die Liste trifft genau die Sätze, die dieselben Werte als Parameter träfenJeder Wert steht in einer Form im Dokument, die die Engine verlustfrei zurückliest (Tabelle unten)
Kann eine Liste das nicht leisten, wird Wert für Wert gebundenGemischte Arten von Werten, ein Zeitpunkt außerhalb des Bereichs von datetime für eine solche Spalte, eine Uhrzeit außerhalb eines Tages, Text mit Zeichen, die XML nicht tragen kann, Dezimalwerte, deren längster ganzzahliger Teil und größte Skala zusammen mehr als 38 Stellen ergeben. Dann gilt wieder die Grenze von 2 098 Werten
Die Liste wird ausdrücklich als frei von fehlenden Werten ausgewiesenBei NOT IN muss die Engine sonst für jede Zeile prüfen, ob die Liste einen fehlenden Wert enthält – dann träfe die Bedingung keine Zeile. Mit dem Hinweis entfällt diese Prüfung, und NOT IN über eine lange Liste bleibt schnell
Gleichheiten unter einem and werden nicht zusammengefasst(a eq 1 and b eq 2) or … – etwa die Paare eines zusammengesetzten Schlüssels – ist keine Liste einer Spalte

Verlustfrei je Typ. Die Liste trifft in jeder Spalte dieselben Sätze wie die einzeln gebundenen Werte, auch bei Dubletten, leeren und nur aus Leerzeichen bestehenden Texten, Zeilenumbrüchen und Zeichen außerhalb der Basisebene von Unicode.

TypForm im DokumentWarum diese Form
GanzzahlenZiffern, gelesen als bigint–
decimal, numericZiffern, gelesen als decimal(38, s) mit der größten Skala der ListeBeide Vergleiche sind exakt
float, realganzzahlige Mantisse und ZweierpotenzAls Text liest die Engine manche Werte als Nachbarwert (subnormale und große ganzzahlige Werte); in dieser Form weicht keiner ab
datetime, smalldatetimevorab auf 1/300 s gerundet, wie der Treiber einen Parameter rundetUngerundet träfen manche Werte nicht dieselben Sätze
datetime2, date, time, datetimeoffsetmit sieben Nachkommastellen–
TextZeilenenden und Tabulatoren als Zeichenreferenz; ein leerer Wert als Element ohne TextEin wörtlicher Wagenrücklauf würde beim Lesen des Dokuments zum Zeilenvorschub. Verglichen wird unter der Sortierfolge der Spalte, mit wie ohne Unterscheidung von Groß- und Kleinschreibung
BinärwerteBase64–
bit, uniqueidentifiergewöhnliche Schreibweise–

Was die Länge einer Liste begrenzt: der Rumpf der Anfrage. Die Voreinstellung von 1 MB für MaxBodyBytes fasst als Richtwert rund 36 000 Werte bei kurzem Spaltennamen und kurzen Zahlenwerten; längere Spaltennamen und Werte verkürzen die Liste entsprechend. Die Grenze von 2 098 Einzelwerten bleibt für alles, was keine Liste ist – viele verschiedene Spalten, Vergleiche mit gt oder lt, Textsuche und Ketten von höchstens 100 Werten je Spalte, die zusammen mehr ergeben. Eine solche Anfrage wird mit 429 („Die Anfrage bindet zu viele Einzelwerte.“) abgewiesen, bevor sie die Datenbank erreicht.

Darstellungen: JSON und XML

Jede Adresse des eigenen Vertrags endet auf .json oder .xml; die Endung entscheidet, keine Accept-Kopfzeile. Das ist Absicht: Mehrere Tabellenkalkulations- und Berichtswerkzeuge bieten keine Möglichkeit, eine Kopfzeile zu setzen, aber jedes kann auf eine Adresse zeigen. Eine Endung, die keine der beiden Darstellungen nennt, wird mit 400 abgewiesen und nicht stillschweigend als JSON beantwortet.

Erfolg und Fehler kommen in der angeforderten Darstellung. Ein Client, der XML anfragt und im Fehlerfall ein JSON-Objekt bekäme, kann genau die Antwort nicht lesen, die er am dringendsten braucht.

Die XML-Form folgt einer festen Zeilenkonvention, weil JSON und XML nicht symmetrisch sind: Eine Liste hat keinen eigenen Elementnamen, denn ihre Einträge tragen keinen Schlüssel.

<?xml version="1.0" encoding="utf-8"?>
<results xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <data>
    <row>
      <nummer>4711</nummer>
      <GeändertAm>2026-09-22T21:16:00</GeändertAm>
      <bemerkung xsi:nil="true" />
      <field name="Menge (kg)">12.5</field>
    </row>
  </data>
  <rowCount>1</rowCount>
</results>
RegelWarum
Jeder Eintrag einer Liste wird <row>Ein Listeneintrag hat keinen Schlüssel, aus dem ein Elementname werden könnte
Ein Spaltenname wird zum ElementAuch mit Umlaut: gültige XML-Namen sind nicht auf ASCII beschränkt
Ein Name, der kein XML-Name ist, wird <field name="…">Leerzeichen, Klammern, führende Ziffer. Weder umbenannt noch weggelassen – der Aufrufer muss die Spalte unter ihrem echten Namen finden. In einer Anfrage nennt er sie kodiert, siehe Zeitpunkte, Zahlen und Namen
Ein fehlender Wert ist ein leeres Element mit xsi:nil="true"Ein leeres Element allein ist von der leeren Zeichenkette nicht zu unterscheiden, und bei einer nullbaren Textspalte sind das zwei verschiedene Tatsachen
Werte sind kulturunabhängig und wie in JSONDezimaltrennzeichen Punkt, Zeitpunkt nach ISO 8601, Binärwert als Base64
Zeichen, die XML verbietet (Steuerzeichen außer Tab und Zeilenwechsel), lassen sich nicht darstellenDie Antwort scheitert dann mit 503; solche Daten über JSON lesen

Eigene Anweisungen (Durchreiche)

Freizuschalten, siehe die eine Ausnahme. Ein Aufrufer ohne das Recht sql, dessen Regeln den Alias nennen, erreicht dieselbe Adresse mit geprüften Anweisungen; dann gilt zusätzlich der Abschnitt Geprüfte Anweisungen. Der Rumpf trägt die Anweisung und, wo sie Platzhalter hat, deren Werte:

POST /api/v1/cf/fachdaten/sql.json
X-Api-Key: …
 
{
  "sql": "SELECT b.name, COUNT(*) AS anzahl FROM apparat a JOIN betrieb b ON b.id = a.betrieb WHERE a.stand = @stand GROUP BY b.name",
  "parameters": { "stand": 3 }
}

Werte reisen als Parameter, nie im Text. Das ist hier keine Schutzmaßnahme – der Text gehört ohnehin dem Aufrufer –, aber es verhindert, dass ein Wert als Syntax gelesen wird, und genau das macht aus einem Datum oder einer Dezimalzahl je nach Spracheinstellung der Sitzung einen anderen Wert. Parameternamen sind gewöhnliche Bezeichner und werden ohne @ geschickt.

Die Antwort hat die gewohnte Erfolgsform, ergänzt um columns:

{
  "columns": [
    { "name": "name",   "type": "Edm.String", "storeType": "nvarchar", "nullable": true, "maxLength": 50 },
    { "name": "anzahl", "type": "Edm.Int32",  "storeType": "int",      "nullable": true }
  ],
  "data": [ { "name": "Halle 1", "anzahl": 42 } ],
  "rowCount": 1,
  "executionTimeMs": 12
}

columns gibt es nur hier, und das ist kein Beiwerk: Über ein Objekt des Katalogs kann ein Client die Form vorher im Schemadokument nachlesen, über eine selbst geschriebene Anweisung entsteht sie erst beim Ausführen. Ein Client, der nur die Zeilen bekäme, müsste die Typen aus den Werten erraten, die zufällig in der ersten Zeile stehen – und ein leeres Ergebnis hätte dann gar keine Form. Deshalb steht columns auch dann in der Antwort, wenn die Anweisung kein Ergebnis liefert: als leere Liste, die aussagt „keine Spalten“, statt eines fehlenden Feldes, das nichts aussagt. Die Typnamen sind dieselben wie im Schemadokument der OData-Oberfläche, mit zwei Unterschieden: Werte von datetime, datetime2 und smalldatetime stehen als Wanduhrzeit ohne Z oder Versatz, obwohl der Typ Edm.DateTimeOffset heißt; eine Spalte eines nicht angebotenen Typs trägt "type": null. storeType nennt den Typnamen der Datenbank, wie Store.Type im Schemadokument: Am Protokolltyp allein erkennt ein Client nicht, dass ein Edm.Binary eine geography enthält oder ein Edm.String einen sql_variant. Eine Spalte, deren Wert die Datenbank vergibt, trägt zusätzlich "identity": true; SQL Server meldet das für eine Identitätsspalte, die unverändert aus einer Tabelle stammt. Zeilen tragen ihre Werte unter dem Namen der Spalte; eine Spalte ohne Namen oder mit einem schon vergebenen Namen bekommt deshalb einen eigenen (Column2, Nummer1) und trägt den Namen der Datenbank in label: leer für einen Ausdruck ohne Namen, den wiederholten Namen für die zweite Spalte dieses Namens. ODBC Driver 18 zeigt beide so, wie die Datenbank sie meldet. Ein xml-Wert steht in der Form, in der ihn SqlClient schreibt – ein leeres Element als <r /> statt <r/>; das Dokument ist dasselbe.

Weiteres zur Form:

FeldBedeutung auf diesem Pfad
dataDas erste Ergebnis mit Spalten. Zähler, die ein Auslöser vorher ausgibt, sind nicht die Antwort auf die gestellte Frage. Weitere Ergebnisse stehen in results, siehe unten
affectedRowCountSumme der geänderten Zeilen über alle Anweisungen des Textes; fehlt, wenn die Anweisung keinen solchen Zähler erzeugt
truncatedDie Zeilenobergrenze des Alias hat gegriffen; die Antwort ist gekürzt

Jede Spalte hat einen eigenen Namen. Eine Spalte ohne Namen (SELECT MAX(a), MIN(b)) heißt Column mit ihrer Position, wie in einer DataTable; ein doppelter Name (SELECT a.id, b.id) bekommt die kleinste Zahl, die ihn eindeutig macht (id1). Die Zeile trägt ihre Werte unter dem Namen; ohne eindeutige Namen kämen mehrere Spalten als ein Wert an.

Zeit- und Zeilenobergrenze kommen aus Limits wie überall sonst. Der Schlüssel, gegen den eine Regel unter Limits:Objects geprüft wird, lautet hier {alias}/sql/ – ein Muster wie fachdaten/sql/* setzt also die Grenzen dieses Pfades, ohne die der Tabellen anzurühren. Das ist die Stelle, an der eine lange Auswertung mehr Zeit bekommt als ein Datenblatt.

Geprüfte Anweisungen

Für einen Aufrufer ohne das Recht sql nimmt der Dienst an derselben Adresse eine Anweisung an, wenn der Alias geprüfte Anweisungen zulässt (AllowCheckedStatements, Vorgabe an) und eine Regel des Aufrufers den Alias nennt. Der Dienst zerlegt die Anweisung, prüft jedes genannte Objekt gegen die Regeln des Aufrufers und führt eine neu geschriebene Fassung aus, in der jedes Objekt mit seinem Schema genannt ist. Was zulässig ist, steht unter Geprüfte Anweisungen. Rumpf und Antwort sind dieselben wie oben; zusätzlich gilt:

FallAntwort
Der Text ist kein T-SQL400 mit Zeile und Spalte der Fundstelle
Die Trennzeile GO, eine Schemaänderung, dynamisches SQL, ein Name mit Datenbank oder Server, eine nicht zulässige Funktion oder Form403; die Meldung nennt die Form, bei mehreren Anweisungen auch die Nummer der betroffenen (Anweisung 2 von 3: …)
Ein Objekt ist unbekannt oder für das, was die Anweisung damit tut, nicht freigegeben403; die Meldung nennt das Objekt, wie es geschrieben wurde, und den Zugriff. Unbekannt und verboten werden gleich beantwortet

Im Audit-Strom steht die ausgeführte, neu geschriebene Fassung.

Ausgabeparameter, mehrere Ergebnismengen, Beschreiben

Drei Zusätze an derselben Adresse decken ab, was eine ODBC-Anwendung bei Microsofts Treiber gewohnt ist: Ausgabe- und Rückgabeparameter, SQLMoreResults und die Spalten einer vorbereiteten Anweisung vor dem Ausführen. Sie gelten für eigene wie für geprüfte Anweisungen gleich; eine Datenquelle, deren Provider sie nicht kann, antwortet mit 400, bevor sie die Datenbank anspricht.

Ausgabeparameter (outputs). Der Rumpf nennt je Ausgabeparameter seinen Edm-Typ; bei Text und Binärdaten wahlweise mit Länge:

POST /api/v1/cf/fachdaten/sql.json
X-Api-Key: …
 
{
  "sql": "EXEC @rueckgabe = dbo.auftrag_anlegen @kunde = @kunde, @nummer = @nummer OUTPUT",
  "parameters": { "kunde": 4711 },
  "outputs": { "rueckgabe": "Edm.Int32", "nummer": { "type": "Edm.String", "maxLength": 20 } }
}
RegelWarum
Zulässige Typen: Edm.Boolean, Byte, Int16, Int32, Int64, Decimal, Single, Double, Date, TimeOfDay, DateTimeOffset, Guid, Binary, String; ein anderer Name → 400Ein Ausgabeparameter braucht seinen Typ, bevor die Anweisung läuft; SQL Server kann ihn nicht nachträglich melden
Ohne Länge: Text als nvarchar(max), Binärdaten als varbinary(max); Edm.Decimal als decimal(38,10); Edm.DateTimeOffset als datetime2Weite Text- und Binärtypen schneiden nichts ab. Verluste: Ein Dezimalwert mit mehr als zehn Nachkommastellen wird gerundet, ein datetimeoffset verliert seinen Versatz
Steht derselbe Name auch in parameters, ist der Parameter Ein- und AusgabeSo bindet Microsofts Treiber einen INPUT_OUTPUT-Parameter
Namen ohne @, gewöhnliche BezeichnerWie bei parameters

Die Antwort trägt die Werte nach Namen, null für NULL:

{ "columns": [], "data": [], "rowCount": 0, "outputs": { "rueckgabe": 0, "nummer": "A-2026-0815" }, "executionTimeMs": 9 }

outputs steht nur in der Antwort, wenn die Anfrage outputs nannte. SQL Server schickt die Werte nach der letzten Ergebnismenge; der Dienst liest deshalb alle Mengen zu Ende, bevor er sie einträgt.

Mehrere Ergebnismengen (results). Liefert eine Anweisung mehr als eine Ergebnismenge mit Spalten, steht die erste wie bisher in columns/data, jede weitere in dieser Reihenfolge in results:

{
  "columns": [ { "name": "id", "type": "Edm.Int32", "storeType": "int", "nullable": false } ],
  "data": [ { "id": 1 } ],
  "rowCount": 1,
  "results": [
    { "columns": [ { "name": "name", "type": "Edm.String", "storeType": "nvarchar", "nullable": true, "maxLength": 50 } ],
      "data": [ { "name": "Halle 1" } ], "rowCount": 1 }
  ],
  "executionTimeMs": 11
}

Ein Client, der nur eine Menge kennt, liest dieselbe Antwort wie vorher. Die Zeilenobergrenze gilt je Menge; eine gekürzte Menge trägt "truncated": true. Ergebnisse ohne Spalten (Zähler einer Änderung) erscheinen nicht als eigene Menge.

Beschreiben ohne Ausführen (describe). Mit "describe": true führt der Dienst nichts aus und antwortet mit den Spalten der ersten Ergebnismenge:

{ "sql": "SELECT id, name FROM dbo.betrieb WHERE id = @id", "parameters": { "id": 1 }, "describe": true }
{ "columns": [ { "name": "id", "type": "Edm.Int32", "storeType": "int", "nullable": false }, { "name": "name", "type": "Edm.String", "storeType": "nvarchar", "nullable": true, "maxLength": 50 } ], "data": [], "rowCount": 0, "executionTimeMs": 3 }

Die Beschreibung läuft unter derselben Anmeldung und denselben Prüfungen wie die Ausführung; ein Aufrufer erfährt also nichts über Objekte, die er nicht ausführen dürfte. Die Werte in parameters bestimmen nur die Typen der Platzhalter. Eine Anweisung, die eine Zeile einfügt, fügt mit describe keine ein.

Wiederholte Schreibanfragen (Idempotency-Key)

Ein Client auf einer schlechten Verbindung schickt eine Änderung, der Dienst speichert sie, und die Antwort geht auf dem Rückweg verloren. Der Client kann das nicht von einer Anfrage unterscheiden, die nie ankam. Wiederholt er sie, entstünde ein zweiter Satz – oder ein Schreibkonflikt (412) für eine Änderung, die durchging, weil sich die Zeilenversion inzwischen bewegt hat.

Trägt eine Schreibanfrage (POST, PATCH, DELETE) die Kopfzeile Idempotency-Key (bis 128 sichtbare ASCII-Zeichen, vom Client zufällig gewählt), beantwortet der Dienst jede Wiederholung mit der gespeicherten ersten Antwort und der Kopfzeile Idempotent-Replayed: true, statt ein zweites Mal zu schreiben. Der ODBC-Treiber schickt sie bei jedem Schreiben mit. Die Kopfzeile wirkt bei jeder Methode außer GET und HEAD und auf jeder Adresse, auch beim Prozeduraufruf und bei eigenen Anweisungen.

FallAntwort
Derselbe Schlüssel, dieselbe Methode, Adresse, Anmeldung und derselbe RumpfGespeicherte Antwort (Status, Rumpf, ETag, Location)
Wiederholung, während die erste noch läuftWartet auf die erste
Derselbe Schlüssel mit anderem Rumpf422
Erste Antwort 401, 429 oder 5xxNicht gespeichert; die Wiederholung läuft wirklich – nach 5xx womöglich ein zweites Mal

Gespeichert werden Erfolg sowie Ablehnungen aus Gründen der Anfrage (400, 403, 404, 409, 412, 422), 10 Minuten lang. Auch ein 422 aus einem THROW der Prozedur zählt dazu: Eine Wiederholung mit demselben Schlüssel bekommt die gespeicherte Antwort (mit derselben correlationId), auch wenn der Grund in der Datenbank inzwischen behoben ist; ein neuer Versuch braucht einen neuen Schlüssel. Gespeichert wird nur im Speicher des Prozesses: Ein Neustart beendet das Fenster. Grenzen: Antworten über 1 MiB werden nicht gespeichert, höchstens 10 000 Einträge (die ältesten fallen heraus); der Rumpf einer Anfrage mit Idempotency-Key wird vor der Anmeldung ganz gelesen, ohne die Grenze MaxBodyBytes; eigene Ablehnungen (400, 422) dieser Prüfung kommen als Text, nicht in der Fehlerform. Die Anmeldung zählt über die Kopfzeile des Schlüssels (ApiKeyHeader, Vorgabe X-Api-Key) und Authorization. Eine gespeicherte Antwort wird nicht erneut berechtigt – ein inzwischen gesperrter Schlüssel erhält sie noch bis zum Ablauf der zehn Minuten. Eine Windows-Anmeldung zählt über ihr Konto, nicht über ihren Handschlag, der auf jeder neuen Verbindung anders lautet; die Adresse des Clients zählt nicht, weil sie sich mit der abgebrochenen Verbindung ändern kann.

Installationspakete herunterladen

GET /download/ listet die Installationspakete aus dem Ordner Gateway:Downloads:Path (Vorgabe downloads, relativ zum Inhaltsstamm; leer schaltet das Herunterladen ab) mit Größe, SHA-256 und – nur bei .exe, aus deren Versionsangabe – der Version, als JSON, für einen Browser als Seite. GET /download/{datei} liefert die Datei. Angeboten werden nur Dateien direkt in diesem Ordner mit der Endung .msi oder .exe, ohne Anmeldung, denn sie enthalten kein Geheimnis; jeder andere Name, ein Unterordner oder .. ergibt 404. Die Ratenbegrenzung gilt je Herkunft.

Schemastand für den WKL Bootstrapper

GET /schema-state/{alias}/{produkt} liefert den Schemastand eines Produkts in der Datenbank des Alias, damit der WKL Bootstrapper nur die Anwendungsversionen anbietet, die zu ihr passen:

{ "format": 1, "product": "Demo", "schema": 13, "applications": { "min": "2.4.0", "max": "" }, "updated": "2026-10-22T10:05:00Z" }

schema ist die Schemaversion, applications der Bereich der Anwendungsversionen, die mit ihr arbeiten (max leer: ohne Obergrenze). Der Dienst liest den Stand aus wkl.Schemastand mit der Anmeldung der Datenquelle; diese Anmeldung muss Mitglied der Datenbankrolle wkl_leser sein. Anwender brauchen keine Berechtigungen in der Datenbank.

AntwortBedeutung
200Der Stand als JSON, mit ETag
304If-None-Match nennt den aktuellen ETag
401Nicht mit Windows angemeldet, und Gateway:SchemaState:Anonymous ist aus
404Alias unbekannt, sein Provider spielt keine Datenbank-Updates ein, oder das Produkt hat keinen Stand
503Die Datenbank ist nicht lesbar

Angemeldet wird nur mit Windows; jedes Konto, das Windows anmeldet, erhält die Antwort, denn sie enthält keine Daten der Datenbank. Ohne Anmeldung antwortet der Dienst nur, wenn `Gateway:SchemaState:Anonymous` eingeschaltet ist. Der Dienst hält eine Antwort 30 Sekunden vor; eine nicht lesbare Datenbank wird beim nächsten Aufruf erneut gefragt.

Anwendung und Version melden (X-Wkl-Application)

Eine Anwendung sendet in jeder Anfrage den Kopf X-Wkl-Application: <produkt>/<version>, etwa Demo/2.4.0 (Produkt: Buchstaben, Ziffern, ., -, _, höchstens 64 Zeichen; Version: zwei bis vier Zahlen). Der Dienst merkt sich je Alias, wann welche Version zuletzt angefragt hat. Ein Datenbank-Update, das ältere Anwendungen bricht, wartet, solange eine ältere Version innerhalb des Sitzungsfensters angefragt hat. Ohne gültigen Kopf gilt eine Anfrage als „unbekannt“; unbekannte Anwendungen halten kein Update auf. Der Wert ist eine Angabe des Programms, kein Sicherheitsmerkmal.

Bemerkungen

  • Um einen Prozeduraufruf wird keine äußere Transaktion gelegt. Die Schreibprozeduren dieses Produkts enthalten je eine eigene; eine Schachtelung würde ihre Fehlerbehandlung stillschweigend verändern. Stattdessen läuft SET XACT_ABORT ON vor dem Aufruf. Hat der Client selbst eine Transaktion geöffnet, läuft der Aufruf in ihr und ohne XACT_ABORT, wie über eine direkte Verbindung: Ein ROLLBACK in der Prozedur beendet dann auch die Transaktion des Clients. Um ein erzeugtes Schreiben dagegen schon: es bringt keine eigene mit, und ohne sie könnte der Wächter einen Vorgang, der die falsche Zahl Zeilen traf, nicht zurücknehmen.
  • Eine Tabelle mit einer Pflichtspalte, die sich nicht benennen lässt, nimmt kein Anlegen an. Das betrifft nur Namen, deren kodierte Form länger als 128 Zeichen wäre; Sonderzeichen allein machen einen Namen nicht unbenennbar. Ist eine solche Spalte Pflicht und hat keinen Vorgabewert, kann kein Aufrufer einen neuen Satz vollständig angeben; der Dienst meldet das vorab, statt die Ablehnung der Datenbank abzuwarten. Ändern und Löschen sind davon nicht betroffen.
  • Ein Argument an einer Tabelle ist ein Schlüssel. GET …/tables/X.json?Id=5 liest genau diesen Satz; ein Argument, das nicht zum Schlüssel gehört, wird mit 400 abgewiesen, statt übergangen zu werden; übergangen, lieferte die Anfrage sonst das erste Fenster der ganzen Tabelle.
  • Gezählt wird in der Anweisung, nicht im Treiber. Der Treiber rechnet Zeilen mit, die Trigger ändern; eine Tabelle mit Historien-Triggern würde dann für jede Einzeländerung mehrere Zeilen melden. @@ROWCOUNT direkt nach der Anweisung ist davon unberührt.

Siehe auch

de/wiki/data-conectivity/database-gateway/reference/http-contract.txt · Zadnja sprememba: uporabnika Wolfgang van der Stille