Kuonen SQL

Handbuch

Syntax, Datentypen, Funktionen und Fehlernummern je Abfragesprache — jeder Abschnitt mit einem Beispiel aus dem Beispielkatalog und, wo die Sprache einen eigenen Sprachmeilenstein der Roadmap hat, dem Stand jedes einzelnen Punktes daraus.

Über Port 4000 (Empfehlung; voreingestellt aus) spricht KuonenSQL GraphQL über HTTP(S): das Schema wächst aus dem Katalog (Tabellen, Sichten, Funktionen, Fremdschlüssel in beide Richtungen), Mutationen sind Transaktionen, Abonnements laufen als Live-Abfragen über WebSocket — alles auf demselben Datenbestand wie T-SQL. Der Server liefert unter /graphql auch eine eigene Oberfläche zum Ausprobieren. Die Beispiele setzen zwei Tabellen voraus; ihr Text steht am Anfang des ersten Beispiels (in T-SQL ausführen).

Punkt Beschreibung Stand
G16.1 Der GraphQL-Parser Lexer und Parser für ausführbare Dokumente nach der Spezifikation (Oktober 2021): Operationen, Fragmente (auch inline), Variablen mit Standardwerten, Direktiven, alle Wertformen samt Blockstrings und `\u{…}`; Fehler mit Zeile und Spalte, Verschachtelung auf 128 Ebenen begrenzt. **Belegt gegen graphql-js 16.14.2** an 1902 Quelltexten (Kitchen-Sink, Fehlerfälle, 1800 zufällig verfälschte Dokumente): angenommen/abgewiesen, Baum und Fehlerstelle stimmen in jedem Fall überein; bewusst anders: Steuerzeichen im Text (die Spezifikation verbietet sie, graphql-js nimmt sie in Zeichenketten an) und Typdefinitionen (SDL ist für einen Server nicht ausführbar) ✅
G16.2 Das Schema aus dem Katalog Jede Tabelle ein Objekttyp, jede Spalte ein Feld: `Int`, `Float`, `String`, `Boolean`, `ID` (Ein-Spalten-Primärschlüssel), dazu die eigenen Skalare `BigInt`, `Decimal`, `DateTime`, `Date`, `Time`, `Uuid`, `Json`, `Binary` (bigint und decimal gehen als Zeichenkette hinaus, damit nichts verloren geht); NOT NULL wird `!`. Je Tabelle Filter (`T_where`: eq, ne, gt, gte, lt, lte, in, nin, like, is_null, dazu `_and`/`_or`/`_not`), Sortierung, Einfüge-, Änderungs- und Upsert-Typ, Aggregat- und Verbindungstypen. Namen, die GraphQL nicht kennt, werden zu Bezeichnern (Kollisionen bekommen `_2`). Neu gerechnet, sobald sich der Katalog ändert (Fingerabdruck der Tabellen und Fremdschlüssel). Das Schema als SDL liest graphql-js (`buildSchema`, `validateSchema`) ohne Fehler ✅
G16.3 Abfragen: Felder, Filter, Sortierung, Seiten Je Tabelle `T(where, orderBy, limit, offset)`, `T_by_pk`, `T_aggregate` (count, sum, avg, min, max in der Datenbank gerechnet) und `T_connection(first, after)` mit undurchsichtigem Cursor und `totalCount`; Variablen, Aliasse, Fragmente, `@include`/`@skip`. Übersetzt in T-SQL-Text mit Parametern (nie ein Wert im Text) und ausgeführt in **derselben Engine-Sitzung** wie über TDS. Das Dokument wird vor der Ausführung gegen das Schema geprüft (Spezifikation Abschnitt 5): an 982 Dokumenten gegen graphql-js `validate` wird in jedem Fall gleich angenommen oder abgewiesen ✅
G16.4 Verschachtelung über die Fremdschlüssel Ein Fremdschlüssel wird in beide Richtungen ein Feld (Kindzeile → Elternzeile; Elternzeile → Liste mit `where`, `orderBy`, `limit`, `offset` je Elternzeile), auch für Selbstbezüge und zusammengesetzte Schlüssel. **Kein N+1:** je Ebene ein Stapel, nicht einer je Zeile (300 Kunden: dieselben drei Abfragen wie bei vier; 1204 Schlüssel: Brocken zu 500). Ab vier Schlüsseln als Verbund mit Werteliste statt `IN (…)`: gemessen 19-mal schneller ✅
G16.5 Mutationen `insert_T`, `update_T`, `delete_T` und `upsert_T` (nur bei selbst gesetztem Primärschlüssel) in **einer Transaktion**: scheitert ein Feld, wird alles zurückgenommen und alle Wurzelfelder sind `null`. `returning` samt Verschachtelung über `OUTPUT … INTO` (auch bei Tabellen mit Triggern). Dieselben Berechtigungen, Einschränkungen und Trigger wie über TDS. **Zwei Engine-Fehler dabei behoben:** Tabellenvariablen und `#temp` verlangten ein Recht (Fehler 229), und ein über `ALTER SERVER ROLE sysadmin ADD MEMBER` ernanntes Mitglied ging nicht an der Rechteprüfung vorbei ✅
G16.6 Introspektion, Grenzen und Berechtigungen `__schema`, `__type`, `__typename`: die Abfragen von GraphiQL und Apollo (`getIntrospectionQuery`, einfach und mit allen Angaben) liefern ein Schema, das graphql-js (`buildClientSchema`, `validateSchema`) fehlerfrei liest und das dem Katalog gleicht; mit curl über HTTPS gegen den laufenden Server gegengeprüft. **Grenzen:** Tiefe 10, Felder 1000, Zeilen je Anfrage 100 000, Seitengröße 10 000, Zeitgrenze 30 s (wirkt bis in die Engine: ein endloser Trigger wird beendet und zurückgerollt), gleichzeitige Anfragen (Hälfte der Kerne). **Berechtigungen:** HTTP Basic mit einer SQL-Anmeldung *mit* Kennwort; das Schema zeigt nur, was die Anmeldung lesen und ändern darf; Sicherheitsrichtlinie und Maskierung (S12.1) greifen Zeile für Zeile; Datenbankzugriff nach S12.17 (403); nach fünf Fehlversuchen 429. Voreingestellt **aus** (`KUONENSQL_GRAPHQL_PORT` oder viertes Startargument), HTTPS mit dem Zertifikat des Servers ✅
G16.7 Der Nachweis, dass es den SQL-Server nicht bremst Dieselbe Messreihe wie R13.5 (`tools/leistung/Nebeneinander-GraphQL.ps1`), dreimal wiederholt, Median: gewöhnliche GraphQL-Last (zwei Clients, verschachtelte Abfrage, rund 39 Anfragen je Sekunde) kostet die SQL-Seite **8 bis 27 %**. Der Anfangsbefund (40 bis 55 %) lag nicht an GraphQL, sondern an der **Arbeitsplatz-GC** des Servers: dieselbe Last über TDS kostete ebenso viel, mit Server-GC (jetzt gesetzt) bleibt der Rundlauf neben zwei schweren Lesern unberührt. Verschachtelte Abfrage: 44 ms über HTTPS (36 ms im Server) gegen 79 ms für den Verbund-SELECT über TDS. **Ehrlich:** die sättigende Last (`-Nebenlast voll`) wurde nicht gefahren (keine Stresslast); 8 bis 27 % liegen in der Grössenordnung von R13.5 bei sättigender Redis-Last (5 bis 18 %), sind aber nicht null. Zahlen in ✅
G16.8 Dasselbe in SSMS sichtbar Schema `graphql` im Katalog (nur, wenn der Endpunkt offen ist): `graphql.endpunkte` (Port, Verschlüsselung, Verbindungen, Zähler, Grenzen) und `graphql.anfragen` (laufende und die letzten 200 beendeten: Sitzung, Anmeldename, Datenbank, Art, Operation, Text, Status, Beginn, Dauer, Stapel, Zeilen, Fehler). Jede Verbindung ist zugleich eine Sitzung in `sys.dm_exec_sessions` (`client_interface_name = 'GraphQL'`), ihre SQL-Stapel stehen in den DMVs. **Nicht in einem laufenden SSMS angesehen** — belegt über dieselben Katalogabfragen (`sys.schemas`, `sys.views`, `sys.columns`), die SSMS stellt ✅
G16.9a Filter über Beziehungen `where` nimmt auch die Beziehungsfelder (`Kunden(where: {Bestellungen: {Betrag: {gt: 100}}})`, `Bestellungen(where: {Kunde: {Ort: {eq: "Bern"}}})`): jede Beziehung wird eine korrelierte `EXISTS`-Unterabfrage mit eigenem Alias, beliebig tief und auch für Selbstbezüge, in `_and`/`_or`/`_not`, in Kindlisten sowie bei `update`/`delete`; alle Werte bleiben Parameter, die Rechte gelten unverändert. Das Schema (SDL, Introspektion, Validierung) ist gegen graphql-js neu belegt ✅
G16.9b Sortieren nach Beziehungsspalten `orderBy: [{Kunde: {Name: DESC}}]` sortiert nach Spalten der Elternzeile (beliebig tief, auch Selbstbezug): der Wert kommt als korrelierte Einzelwert-Unterabfrage in den `ORDER BY`, fehlt die Elternzeile, ist er NULL. Nur die Richtung Kind → Eltern (eine Zeile je Zeile); Sortieren nach Aggregaten der Kindzeilen bleibt offen ✅
G16.9c Upsert über `on_conflict` `insert_T(objects, on_conflict: {constraint: T_pkey, update_columns: [Spalte]})`: jeder eindeutige, ungefilterte Schlüssel, dessen Spalten sichtbar und beim Einfügen setzbar sind, ist ein Wert von `T_constraint` (Primärschlüssel stabil `T_pkey`); je Objekt erst `UPDATE` der genannten Spalten, trifft es nichts `INSERT`, in derselben Transaktion; ohne `update_columns` bleibt die vorhandene Zeile unberührt und wird nicht zurückgegeben. Der Schemastand merkt jetzt auch neue oder entfernte Indizes. Ohne `where` an `on_conflict` (Hasura kennt eines) ✅
G16.9d Einfügen samt Kindzeilen `insert_Kunden(objects: [{Name: "A", Bestellungen: [{Id: 100, Betrag: 5}]}])`: `T_insert` bekommt je Kindliste ein Feld, dessen Typ (`Kind_insert_by_Fremdschluessel`) den Einfügetyp der Kindtabelle ohne die Fremdschlüsselspalten trägt. Die Elternzeile wird eingefügt, ihr Schlüssel (auch ein Zählerwert) landet in einer Schlüssel-Tabellenvariable, die Kindzeilen werden per `INSERT … SELECT` daraus gefüllt — alles in der einen Mutations-Transaktion, scheitert ein Kind, ist auch die Elternzeile weg. Eine Ebene tief (keine Enkelkinder), nicht zusammen mit `on_conflict`, nur bei Tabellen mit Primärschlüssel; Einfügen der Elternzeile *aus* einer Kindzeile heraus (Kind → Eltern) ist nicht gebaut ✅
G16.9e `distinct_on` `Kunden(distinct_on: [Ort])` an Liste, Verbindung, Aggregat und Kindliste: je Wertekombination der Spalten bleibt die erste Zeile nach `orderBy` (ohne `orderBy`: die `distinct_on`-Spalten aufsteigend, dann der Schlüssel), `NULL` ist eine Gruppe; in der Kindliste zählt die Gruppe je Elternzeile. T-SQL kennt kein `DISTINCT ON`, es wird `ROW_NUMBER() OVER (PARTITION BY …)` mit einer zweiten Nummer für die Reihenfolge; `totalCount` und Aggregate rechnen über die zusammengefassten Zeilen. Aufzählung `T_select_column` (ohne Json-Spalten). Sollwerte aus SQL Server 2025 ✅
G16.9f Aggregate und Seiten innerhalb der Verschachtelung Jede Kindliste hat zwei Geschwister: `Kunden { Bestellungen_aggregate(where) { count sum { Betrag } … } }` und `Kunden { Bestellungen_connection(first, after, where, orderBy, distinct_on) { totalCount pageInfo edges { cursor node { … } } } }`, je Elternzeile gerechnet, auch für Selbstbezüge und mehrstufig. Das Aggregat ist **eine** Abfrage je Schlüsselbrocken mit `GROUP BY` der verweisenden Spalten (kein N+1, belegt über die Stapelzahl); eine Elternzeile ohne Kinder hat Anzahl 0 und sonst null. Der Cursor zählt je Elternzeile. Werte am Orakel (SQL Server 2025) gegengerechnet. Ohne `distinct_on` am Aggregat, sonst dieselben Argumente wie an der Wurzel ✅
G16.9g Gebündelte und gespeicherte Anfragen **Batching:** ein JSON-Feld von Anfragen (`[{"query": …}, …]`, 1 bis 32) gibt ein Feld von Antworten in derselben Reihenfolge, nacheinander in einer Verbindung ausgeführt, ein Fehler nimmt die anderen nicht mit. **Gespeicherte Anfragen** nach dem Protokoll der Apollo Automatic Persisted Queries (`extensions.persistedQuery` mit `version: 1` und `sha256Hash`, per POST und GET, auch im Bündel): ohne Text und unbekannte Prüfsumme `PersistedQueryNotFound`, mit Text wird die Prüfsumme nachgerechnet (falsche: 400, nichts gemerkt) und der Text gemerkt, danach genügt die Prüfsumme. Der Speicher hält höchstens 1000 Einträge (der älteste fällt heraus); die Prüfsumme gibt keine Rechte, jede Anmeldung braucht die eigenen. Die Sicherheitsobergrenzen (Rumpf 1 MiB) gelten für das ganze Bündel ✅
G16.9h Anmeldung mit Bearer-Token und Windows-Konto **JWT** (`Authorization: Bearer …`, RFC 7519/7515): `HS256` mit gemeinsamem Geheimnis (mindestens 32 Byte) oder `RS256` mit dem öffentlichen Schlüssel, eingestellt über `KUONENSQL_GRAPHQL_JWT_HS256` oder `KUONENSQL_GRAPHQL_JWT_RS256_PEM`, dazu `_ISSUER`, `_AUDIENCE`, `_CLAIM` (Name der Anmeldung, vorgabemäßig `sub`). Das Verfahren steht in der Einstellung, nicht im Token (`none` und Verwechslung HS/RS scheitern); `exp` ist Pflicht, 60 Sekunden Nachsicht; der Name muss eine freigeschaltete SQL-Anmeldung sein, ihre Rechte gelten; dieselbe Sperre nach fünf Fehlversuchen wie bei Basic, für jeden Grund derselbe Text. **Konten des Betriebssystems** (`CREATE LOGIN [RECHNER\name] FROM WINDOWS`) melden sich über HTTP Basic an, das Kennwort prüft wie bei TDS das System (`LogonUser` beziehungsweise PAM). Token aus Node.js (`crypto`) und der Wert aus RFC 7515 A.1 als Orakel. **Nicht belegt:** die echte Kennwortprüfung eines Systemkontos (ohne dessen Kennwort nicht prüfbar; der Test setzt die Prüfung ein und belegt, was der Endpunkt daraus macht). Nicht gebaut: Negotiate/Kerberos über HTTP, OpenID-Discovery und JWKS-Abruf (kein Netzzugriff im Server) ✅
G16.9i Mitgelieferte Oberfläche `GET /graphql` (auch `/graphql/<datenbank>`) oder `/graphiql` mit `Accept: text/html` und ohne `query=` liefert **eine** HTML-Seite, die alles trägt: Anmeldung (Name und Kennwort oder Bearer-Token, nur im Speicher der Seite), Abfrage- und Variablenfeld, Antwort mit Status, Dauer und Größe, Strg+Eingabe zum Ausführen, der Schemabaum aus der Introspektion (ein Klick auf ein Feld fügt eine Abfrage mit den ersten Spalten ein), Verlauf im lokalen Speicher. **Nichts Fremdes:** kein Skript und keine Schrift von außen, `Content-Security-Policy` mit Einmalzahl (`default-src 'none'`, `connect-src 'self'`, kein `unsafe-inline`), `X-Frame-Options: DENY`, Daten vom Server nie als HTML. Die Seite ist wie `/health` ohne Anmeldung erreichbar, sie enthält keinen Katalogtext. **Im Browser angesehen** (Anmelden, Schema laden mit 96 Typen, Feld einfügen, Strg+Eingabe, Antwort 200) und das Skript mit `node --check` als gültiges JavaScript bestätigt. Kein Editor mit Syntaxfärbung und Auto-Vervollständigung (ein Textfeld) ✅
G16.9j Tabellen mit Schreib- aber ohne Leserecht Eine Anmeldung mit `INSERT`, `UPDATE` oder `DELETE` ohne `SELECT` sieht die Tabelle jetzt im Schema — **nur als Mutation**: `insert_T`, `update_T`, `delete_T` je nach Recht, Ergebnis nur `affected_rows` (Zahl über `@@ROWCOUNT`), kein Objekttyp, kein `returning`, kein Abfragefeld, keine Beziehung, kein `on_conflict`, kein Upsert, keine Kindzeilen, und der Filter `T_where` ohne Spalten (nur `{}` und die Verknüpfungen). Der Grund steht am Orakel (SQL Server 2025, Benutzer nur mit `INSERT`/`UPDATE`/`DELETE`): Einfügen, Ändern und Löschen ohne Bedingung gehen, `OUTPUT` und jede Bedingung auf einer Spalte scheitern mit Fehler 229. Wer auch lesen darf, behält den vollen Umfang ✅
G16.9k Sichten als lesbare Typen Jede Sicht (`CREATE VIEW`) ist ein Typ mit Liste, Filter, Sortierung, Seite mit Cursor, Aggregat und `distinct_on` wie eine Tabelle — ohne Schlüssel (`_by_pk`), ohne Beziehungen und ohne Mutationen. Die Spalten sind die, die beim Anlegen ermittelt wurden; Seiten ordnen bei einer Sicht nach allen Spalten, damit gleiche Zeilen keine Lücken reißen. Änderung (`CREATE OR ALTER`) und `DROP` stehen im nächsten Schema. **Dabei gefunden und im Motor behoben:** eine Sicht prüfte das Recht an den Tabellen *darunter* statt an sich selbst — wer nur die Sicht lesen durfte, kam nicht heran, wer nur die Tabelle lesen durfte, kam an die Sicht. Am Orakel (SQL Server 2025, Benutzer nur mit `SELECT` auf die Sicht: Sicht 2 Zeilen, Tabelle Fehler 229) gilt die Besitzkette; jetzt auch im Motor (`_besitzkette`), also über TDS wie über GraphQL ✅
G16.9l Funktionen und Prozeduren als Felder **Skalarfunktionen** werden Abfragefelder (`Rabattpreis(preis: "200.00", prozent: 25)`), ein Parameter mit Standardwert darf fehlen, ein ausdrückliches `null` ist `NULL`. **Tabellenwertfunktionen** (inline und mit mehreren Anweisungen) lesen sich wie Sichten, die Parameter nehmen: `KundenInOrt(ort: "Bern", where, orderBy, limit)`, dazu `_aggregate` und `_connection`; jeder Aufruf hat seinen eigenen Text im `FROM` mit Parametern (`[dbo].`). **Prozeduren** werden Änderungsfelder `exec_Name(…)` in der Transaktion der Mutation, Ergebnis `returnValue`, `output` (die `OUTPUT`-Parameter als Objekt) und `resultSets` (Felder von Zeilenobjekten); ein Fehler der Prozedur nimmt die ganze Mutation zurück. Rechte: `EXECUTE` je Skalarfunktion und Prozedur, `SELECT` auf die Tabellenwertfunktion, das Schema folgt ihnen. Tabellen- und Cursorparameter haben keine Entsprechung in JSON und stehen als Hinweis im Schema. Sollwerte aus SQL Server 2025. Dabei im Motor behoben: `DEFAULT` als Argument einer Skalarfunktion (`SELECT dbo.f(200, DEFAULT)`) war ein Fehler, am Orakel ist es der Standardwert. **Bleibt im Motor, nicht in GraphQL:** im Rumpf einer Prozedur prüft der Motor das Recht an den Tabellen weiter selbst (SQL Server: Besitzkette), wer nur `EXECUTE` hat, scheitert dort mit 229 ✅
G16.9m Abonnements (Live-Abfragen über WebSocket) Das Schema bekommt `Subscription` mit denselben Feldern wie `Query` (Tabellen, Sichten, Funktionen, Aggregate, Seiten; ohne `_server` und Introspektion), `subscriptionType` in der Introspektion, die Regel „genau ein Feld der obersten Ebene“ im Prüfer (gegen graphql-js 16 belegt, 991 Dokumente). Transport: WebSocket nach **graphql-transport-ws** unter `/graphql[/datenbank]` (`connection_init`, `subscribe`, `next`, `error`, `complete`, `ping`/`pong`, Schließcodes 4400/4401/4408/4409/4429 wie in der Spezifikation des Protokolls); Anmeldung als Kopfzeile beim Aufbau oder in `connection_init` als `payload.Authorization` (Basic oder Bearer), Ursprungsprüfung. Ein Abonnement liefert sofort ein Ergebnis und danach **bei jeder Änderung des Ergebnisses**: es rechnet nur, wenn sich der Änderungszähler der Speicherschicht bewegt hat (dazu drei Nachläufe und eine Kontrolle alle 30 Sekunden), sendet nur, wenn die Antwort anders ist, und hat die Rechte der Anmeldung. Grenzen: 8 je Verbindung, 128 im Dienst, Nachricht höchstens 1 MiB, Takt 50 ms bis 60 s (Vorgabe 1 s, `KUONENSQL_GRAPHQL_ABO_TAKT_MS`). Abfragen und Mutationen über denselben Weg geben ein `next` und `complete`. Im Prüfstand-Seitenwerkzeug gibt es den Knopf „Live“, **im Browser angesehen** (Antwort erneuerte sich nach einem Einfügen). Kein Altprotokoll `subscriptions-transport-ws` ✅
G16.9n GraphQL-Reiter im Prüfstand Die Konsole des Prüfstands kennt GraphQL als gebaute Sprache (Standardport 4000, `GraphQlClient`): das Dokument (oder ein JSON mit `query`, `variables`, `operationName`) geht per HTTP Basic an `/graphql/<datenbank>`, `data` kommt als eine Zeile Text, jeder Fehler als Meldung mit SQL-Nummer und Feldname. HTTPS mit dem selbst ausgestellten Zertifikat des Servers wird angenommen (der Prüfstand ist ein Werkzeug für den eigenen Server), spricht der Endpunkt kein TLS, geht derselbe Aufruf nach vier Sekunden oder einem Abbruch in Klartext und merkt sich das Verfahren je Endpunkt. Ein geschlossener Port meldet sich als „Port aus“ mit dem Hinweis auf `KUONENSQL_GRAPHQL_PORT`. Vier Beispiele im Katalog (Filter, Verschachtelung samt Aggregat, `distinct_on`, Mutation mit Einfügen und Löschen ohne Spur) samt Voraussetzung, im Handbuch mit der Punktetabelle; der Wächter führt sie gegen einen echten Endpunkt aus (HTTP und HTTPS). Nicht im Browser gesehen ✅
G16.9o Besitzkette im Rumpf von Prozeduren und Funktionen Aus G16.9l übrig, betrifft den Motor: wer nur `EXECUTE` auf eine Prozedur hat, scheitert in deren Rumpf am Tabellenrecht (229). SQL Server prüft bei gleichem Besitzer von Prozedur und Tabelle kein Tabellenrecht (Besitzkette), bei dynamischem SQL (`EXEC(@s)`, `sp_executesql`) und bei verschiedenem Besitzer aber doch. Sollwerte aus SQL Server 2025 ✅
G16.9p `EXECUTE AS` an Prozeduren und Funktionen, `EXECUTE` auf Skalarfunktionen Aus G16.9o übrig: `WITH EXECUTE AS` wird gelesen und verworfen (nur Trigger führen es als Metadatum), der Rumpf läuft immer als Aufrufer; und das Recht `EXECUTE` auf eine Skalarfunktion prüft der Motor bei T-SQL nicht (Orakel verlangt es; berechnete Spalten und CHECK-Bedingungen rufen Funktionen ohne Recht des Aufrufers, das muss dort die Besitzkette tragen). Erledigt: der Rumpf läuft unter dem gewählten Prinzipal (CALLER, SELF, OWNER, benannter Benutzer; Rechte, `USER_NAME()`, `SUSER_SNAME()`, `ORIGINAL_LOGIN()`, Rückkehr auch nach Fehlern, `execute_as_principal_id`, 15151 beim Anlegen, 15136 beim Löschen des Benutzers); `EXECUTE` auf Skalarfunktionen wird bei T-SQL geprüft, die Besitzkette trägt im Rumpf, berechnete Spalten und CHECK rufen ohne Recht auf, ein DEFAULT verlangt es vom Aufrufer (alles am Orakel gemessen). Die Berechtigung IMPERSONATE für das Anweisungs-EXECUTE AS und EXECUTE AS an Triggern ON ALL SERVER und LOGON sind als G16.9q erledigt. Sollwerte aus SQL Server 2025 ✅
G16.9q `EXECUTE AS` mit Recht `IMPERSONATE`, `EXECUTE AS` an Server- und Anmeldetriggern Die zwei Reste aus G16.9p. Erledigt: das Anweisungs-`EXECUTE AS USER/LOGIN` prüft das Recht (15517 beim Benutzer, 15406 bei der Anmeldung, auch für sa und bei einer Rolle oder einem unbekannten Ziel; frei sind das eigene Ziel, sysadmin, beim Benutzer db_owner samt dbo; sonst `IMPERSONATE` oder `CONTROL` auf das Ziel, auch über eine Rolle, bei der Anmeldung auch `IMPERSONATE ANY LOGIN`; ein DENY sticht jedes GRANT; Recht auf die Anmeldung gilt nicht für den Benutzer und umgekehrt); `GRANT/DENY/REVOKE IMPERSONATE ON USER::/LOGIN::` (Anmeldung ins Serverrecht, nur aus master 4621, unbekanntes Ziel 15151) samt `sys.database_permissions` (Klasse 4) und `sys.server_permissions` (Klasse 101); `WITH EXECUTE AS` an Triggern ON DATABASE, ON ALL SERVER und LOGON wird ausgeführt (Benutzer bzw. Anmeldung wechseln, Rückkehr danach; OWNER ist dort ungültig 1083; SELF beim Serverauslöser ist die Anmeldung des Anlegers; `execute_as_principal_id` zeigt die Anmeldung); eine Anmeldung ohne Benutzer arbeitet in der Datenbank als guest. Sollwerte aus SQL Server 2025 ✅
G16.9 Die sättigende Messreihe aus G16.7 Nach M35 verschoben (**LL25.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit ✅

Beispiele

CREATE TABLE pruefstand_kunden (Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Ort NVARCHAR(50) NULL);
CREATE TABLE pruefstand_bestellungen (
    Id INT IDENTITY(1,1) PRIMARY KEY, KundeId INT NOT NULL, Betrag DECIMAL(10,2) NOT NULL,
    CONSTRAINT fk_pruefstand_bestellungen_kunden FOREIGN KEY (KundeId) REFERENCES pruefstand_kunden(Id));
INSERT INTO pruefstand_kunden (Name, Ort) VALUES ('Anna', 'Bern'), ('Bruno', 'Zürich'), ('Clara', 'Bern');
INSERT INTO pruefstand_bestellungen (KundeId, Betrag) VALUES (1, 10.00), (1, 20.50), (2, 5.25);

Abfragen

Zeilen mit Filter und Sortierung

Jede Tabelle ist ein Feld der Abfrage. where, orderBy, limit und offset stehen als Argumente; die Werte sind immer Parameter, nie Teil des SQL-Textes.

{ pruefstand_kunden(where: {Ort: {eq: "Bern"}}, orderBy: [{Name: DESC}]) { Id Name Ort } }
Erwartetes Ergebnis
Antwort
{"pruefstand_kunden":[{"Id":"3","Name":"Clara","Ort":"Bern"},{"Id":"1","Name":"Anna","Ort":"Bern"}]}

distinct_on: je Ort der erste Kunde

T-SQL kennt kein DISTINCT ON; der Server übersetzt es in ROW_NUMBER() OVER (PARTITION BY …). Je Wertekombination bleibt die erste Zeile nach orderBy.

{ pruefstand_kunden(distinct_on: [Ort], orderBy: [{Ort: ASC}, {Id: ASC}]) { Ort Name } }
Erwartetes Ergebnis
Antwort
{"pruefstand_kunden":[{"Ort":"Bern","Name":"Anna"},{"Ort":"Zürich","Name":"Bruno"}]}

Verschachtelung

Kunden mit ihren Bestellungen und deren Summe

Ein Fremdschlüssel ist in beide Richtungen ein Feld. Die Bestellungen aller Kunden kommen mit einer einzigen Abfrage, nicht mit einer je Kunde; das Aggregat rechnet die Datenbank, je Kunde.

{ pruefstand_kunden(orderBy: [{Id: ASC}]) {
    Name
    pruefstand_bestellungen(orderBy: [{Id: ASC}]) { Betrag }
    pruefstand_bestellungen_aggregate { count sum { Betrag } }
} }
Erwartetes Ergebnis
Antwort
{"pruefstand_kunden":[{"Name":"Anna","pruefstand_bestellungen":[{"Betrag":"10.00"},{"Betrag":"20.50"}],"pruefstand_bestellungen_aggregate":{"count":2,"sum":{"Betrag":"30.50"}}},{"Name":"Bruno","pruefstand_bestellungen":[{"Betrag":"5.25"}],"pruefstand_bestellungen_aggregate":{"count":1,"sum":{"Betrag":"5.25"}}},{"Name":"Clara","pruefstand_bestellungen":[],"pruefstand_bestellungen_aggregate":{"count":0,"sum":{"Betrag":null}}}]}

Mutationen

Einfügen und Löschen in einer Transaktion

Eine Mutation ist eine Transaktion: alle Wurzelfelder laufen nacheinander, scheitert eines, ist alles zurückgenommen. Dieses Beispiel legt einen Kunden an und löscht ihn wieder — und hinterlässt keine Spur.

mutation {
  neu: insert_pruefstand_kunden(objects: [{Name: "Dora", Ort: "Basel"}]) { affected_rows returning { Name Ort } }
  weg: delete_pruefstand_kunden(where: {Name: {eq: "Dora"}}) { affected_rows }
}
Erwartetes Ergebnis
Antwort
{"neu":{"affected_rows":1,"returning":[{"Name":"Dora","Ort":"Basel"}]},"weg":{"affected_rows":1}}