Kuonen SQL

Roadmap

Aus den Roadmap-Dateien beider Teile erzeugt und zur Bauzeit eingebettet – Seite und Datei können nie auseinanderlaufen.

Server KuonenSQL · Studio MultiSQLManager

Server KuonenSQL

Meilenstein 1: Der Lebensfunke (TDS-Handshake & `SELECT 1`) (4/4)
PunktTitelBeschreibungStand
A1.1TDS-PreloginPre-Login-Paket parsen und beantworten (Verschlüsselung verhandeln)✅
A1.2TDS-Login7Login-Paket empfangen, Anmeldedaten bestätigen, Bestätigungstoken senden✅
A1.3TDS-Batch & `SELECT 1`SQL-Batch-Token empfangen, hartkodiert `1` als Integer zurückgeben (`COLMETADATA`, `ROW`, `DONE`)✅
F1.1Orakel-GrundgerüstIntegrationstest-Projekt mit Docker-MSSQL aufsetzen✅
Meilenstein 2: T-SQL Ausdrücke & Orakel-Typen (8/8)
PunktTitelBeschreibungStand
C2.1T-SQL LexerStreaming-Tokenizer für Literale, Bezeichner, Schlüsselwörter und Operatoren; Gleitkomma- (`1.5e-2`), Binär- (`0x1A2B`) und Währungsliterale …✅
C2.2Pratt-Expression-ParserVorranggesteuerter Ausdrucks-Parser nach T-SQL-Rangfolge (`*` `/` `%` vor `+` `-` `&` `^` `|` vor Vergleichen vor `NOT` vor `AND` vor `OR`) …✅
B2.1Ganzzahlen & Überlauf`bit`, `tinyint` (0–255), `smallint`, `int`, `bigint` als eigenständige Typen mit **Fehler 8115** bzw.✅
B2.2Exaktes `decimal(p,s)``Int128`-Festkomma mit den T-SQL-Regeln für Genauigkeit und Skalierung bei `+ - * / %` inklusive Skalenreduktion oberhalb von 38 Stellen; `SUM` → …✅
B2.3Zeichenketten & Collation`varchar`/`nvarchar`/`char`/`nchar` mit Längen, `Latin1_General_CI_AS` (Trailing-Whitespace, Groß-/Kleinschreibung), Codepage 1252 im TDS-Strom …✅
B2.4Datum, Währung & GUID`date`, `time(n)`, `datetime2(n)`, `datetime` (1/300 s), `smalldatetime`, `datetimeoffset(n)`, `money`/`smallmoney` sowie `uniqueidentifier` mit der …✅
B2.5SkalarfunktionenMathematik (`ROUND`, `FLOOR`, `CEILING`, `POWER`, `SIGN`, `SQRT`, `LOG`, Trigonometrie), Zeichenketten (`UPPER`, `LOWER`, `LEFT`, `RIGHT` …✅
F2.1Orakel-Differentialtest`tools/oracle`: 231 Ausdrücke laufen gegen SQL Server 2022 und KuonenSQL; verglichen werden Datentyp samt Länge/Genauigkeit/Skalierung, Wert und …✅
Meilenstein 3: Tabellen, Speicher & Dapper-CRUD (6/6)
PunktTitelBeschreibungStand
D3.1Speicher-BackendSeitenbasierte Ablage (`SlottedPage`, `MemoryTable`) mit Write-Ahead-Log: Schema, Zeilen und `IDENTITY`-Zähler überstehen einen Neustart.✅
C3.1DDL: `CREATE TABLE`Alle SQL-Server-2022-Typen samt Facetten, `PRIMARY KEY`, `UNIQUE`, `FOREIGN KEY`, `CHECK`, `DEFAULT` und `IDENTITY(start, schritt)`; dazu …✅
C3.2DML: `INSERT INTO`Mehrzeilige `VALUES`, `INSERT ... SELECT`, Standardwerte, `IDENTITY`-Vergabe mit `SCOPE_IDENTITY()`/`@@IDENTITY`/`IDENT_CURRENT` …✅
C3.3DML: `SELECT ... WHERE`Tabellenscan mit Filterausdrücken, `TOP`, `ORDER BY`, `OFFSET/FETCH`, `SELECT ... INTO`✅
C3.4DML: `UPDATE` & `DELETE`Zeilen ändern und löschen samt `OUTPUT inserted.*`/`deleted.*` mit Aliasnamen; durchgesetzt werden NOT NULL (515), PRIMARY KEY (2627), eindeutige …✅
F3.1Dapper-Testsuite`ExecuteAsync`, `QueryAsync<T>`, `QuerySingleAsync<T>`, parametrisierte Stapel und ein Repository mit `IDENTITY`, Fremdschlüssel und zurückgenommener …✅
Meilenstein 4: Systemkatalog & EF-Core-Readiness (5/5)
PunktTitelBeschreibungStand
E4.1Metadaten-Katalog (`sys.*`)`sys.tables`, `sys.columns`, `sys.objects`, `sys.types`, `sys.schemas`, `sys.indexes`, `sys.index_columns`, `sys.foreign_keys` …✅
E4.2Systemfunktionen`OBJECT_ID()`, `COL_LENGTH()`, `DATABASEPROPERTYEX()`, `SERVERPROPERTY()`, `SCOPE_IDENTITY()`, `IDENT_CURRENT()`, dazu `@@KVERSION` mit der Version …✅
E4.3EF-Migrations-Tabelle`__EFMigrationsHistory` wird angelegt, gelesen und fortgeschrieben; `sp_getapplock`/`sp_releaseapplock` mit echten Sperren über den ganzen Prozess …✅
E4.4EF-Core-End-to-End-Test`DbContext.Database.MigrateAsync()` gegen KuonenSQL: Sperre, Verlaufstabelle, Schema, `SaveChangesAsync` mit vom Server vergebenen Schlüsseln und ein …✅
C4.1`MERGE`Vollständige Anweisung: Quelle als `(VALUES …)` oder Tabelle, `ON`-Bedingung, `WHEN MATCHED`, `WHEN NOT MATCHED [BY TARGET]` …✅
Meilenstein 5: Relationale Abfragen & Transaktionen (4/4)
PunktTitelBeschreibungStand
C5.1Joins & Aggregationen`INNER/LEFT/RIGHT JOIN`, `CROSS JOIN`, `CROSS/OUTER APPLY`, `GROUP BY`, `HAVING`, `COUNT/SUM/AVG/MIN/MAX`, Fensterfunktionen✅
C5.2Transaktionssteuerung`BEGIN TRAN`, `COMMIT`, `ROLLBACK`, `SAVE TRANSACTION`, `@@TRANCOUNT` mit Undo-Journal — seit M3 auch über die TDS-Anforderung 0x0E, die SqlClient …✅
C5.3`OUTPUT`-Klausel`INSERT`/`UPDATE`/`DELETE`/`MERGE ... OUTPUT` mit `inserted.*`, `deleted.*`, `$action` und Aliasnamen✅
C5.4Paging & Sortierung`ORDER BY`, `TOP (n)`, `OFFSET n ROWS FETCH NEXT m ROWS ONLY`✅
Meilenstein 6: Härtung, Benchmarks & Distribution (8/8)
PunktTitelBeschreibungStand
F6.0**N gleichzeitige Schreiber**Jede Verbindung erhält eine eigene Sitzung, die Katalog, Tabellen und Protokoll mit der Hauptinstanz teilt, aber eigene Transaktionen …✅
F6.1Cross-Platform HärtungDocker-Image für `linux/amd64` und `linux/arm64` ✅; Testmatrix auf `windows-latest`, `macos-latest` (Apple Silicon) und `ubuntu-24.04-arm` ✅.✅
F6.2Minimales Docker-ImageChiseled Noble-Container (Ubuntu 24.04 ohne Shell, ohne Paketverwalter, als Benutzer 1654), Start bis zur Bereitschaft in rund 1,6 s.✅
F6.3Lizenz-Pipeline und Schutz der Auslieferung`Kuonen.Global.Licensing` ist eingebunden und wird beim Start ausgewertet.✅
F6.4Stresstests & Benchmarks`tests/Kuonen.Data.KuonenSql.Benchmarks` misst mit BenchmarkDotNet den Durchsatz bei 1, 2, 4 und 8 gleichzeitigen Schreibern (wahlweise mit ebenso …✅
F6.6Leseweg ohne Kopie je ZeileDer Leseweg legte für **jede** gelesene Zeile ein Wörterbuch an: erst alle Sitzungsvariablen hineinkopieren, dann drei Einträge je Spalte (bloßer …✅
F6.5VersionsnummernDieselbe Regel wie in Kuonen.PDFTools: die Versionsnummer IST die Meilensteinnummer (`1.<Meilenstein>.<Patch>`), `AssemblyVersion` bleibt bei …✅
H6.7Abbruch beim Warten auf eine SperreDer Verbindungshandler beginnt das Lesen der nächsten Nachricht schon VOR einem SqlBatch/RPC statt danach (`Attentionwache.LiesVoraus`) — derselbe Lesevorgang, nur früher, nie abgebrochen (kein Risiko für den `SslStream`), der Stapel läuft weiter auf dem Verbindungs-Task. Trifft dabei ein TDS-Attention ein, bricht `Sperrverwaltung.Warte` das Warten sofort ab (`CancellationToken` + Monitor-Weckruf statt Polling), und zwischen zwei Anweisungen bzw. Schleifendurchläufen beendet `PruefeAttention` auch einen langen Stapel ohne Sperrwarten. Die Antwort ist genau EIN `DONE_ATTN`; am SQL-Server-Orakel gemessen (`SqlCommand.Cancel()`/`CommandTimeout`, auch zwischen Anweisungen) bleibt die Transaktion dabei GENAU so offen wie zuvor (@@TRANCOUNT/XACT_STATE() unverändert, bereits ausgeführte Anweisungen bleiben) — anders als beim Deadlock (1205) wird nicht zurückgerollt. Der heisse Weg bleibt unverändert: viele kleine Stapel lokal über TDS 0,074 ms vorher, 0,076 ms nachher (kürzeste Laufzeit; die erste Fassung mit eigenem Thread je Stapel lag bei 0,106 ms).✅
Meilenstein 7: SSMS-Vollkompatibilität (Object Explorer & Dialoge) (22/22)
PunktTitelBeschreibungStand
E7.1`SELECT @var = …` & typisierte NULLsVariablenzuweisung ohne Resultset, `DECLARE` mit deklariertem Typ, `ISNULL` mit Typ des 1.✅
E7.1aVariable und Gruppenspalte neben einem Aggregat**Erledigt (2026-10-05, Nebenbefund aus PR27.14a):** `SELECT @x = @x + COUNT(*) FROM L`, schon `SELECT @x + COUNT(*)` und `SELECT g + COUNT(*) … GROUP BY g` meldeten „Ungueltiger Spaltenname“. Der Rest um ein Aggregat liest jetzt die Variablen bzw. die erste Zeile der Gruppe; eine Zuweisung, die ihre Variable liest, nimmt Aggregate als Spalten und weist je Ergebniszeile zu (mit GROUP BY sammelt sie über die Gruppen). `VariablenzuweisungAggregatTests`✅
E7.2Server-/Datenbank-Eigenschaften`SERVERPROPERTY`-Vollständigkeit, Registry-Werte (`xp_instance_regread`), `SUSER_SID` als `varbinary`.✅
E7.3Schemas & Prinzipale`sys.schemas.principal_id`, korrekte Alias-Auflösung in JOINs (Fehler 207)✅
E7.4Protokolldatei-Viewer`xp_readerrorlog` / `sp_readerrorlog` mit `datetime`-Spalte✅
E7.5Katalogsichten nach SQL-Server-Vorlage149 Systemsichten mit exakten Spalten (Name, Reihenfolge, Typ) aus SQL Server 2022 generiert (`SystemViewTemplates.cs`, Generator in …✅
E7.6`sql_variant` im TDS-Strom`sys.configurations` meldet seine vier Wertspalten jetzt als `sql_variant` statt als `int`, ebenso alle anderen 13 Katalogspalten dieses Typs.✅
E7.7Knoten-DurchstichStatt SSMS per UI-Automation fernzusteuern, wird es von Hand durchgeklickt und jede Fehlermeldung abgearbeitet.✅
E7.8RPC-VollständigkeitEin RPC-Aufruf, der nicht `sp_executesql` war, bekam eine leere Antwort.✅
E7.9Typtreue ResultsetsSpaltentypen auch bei 0 Zeilen (Schema, `CAST`, `CASE`, `ISNULL`, Operatoren), typisierte NULLs bei `LEFT JOIN`, Spaltennamen ohne Alias wie SQL …✅
E7.10Parser-Lücken aus SMO-Abfragen`LIKE … ESCAPE` und Zeichenklassen ✅, Tabellenhinweise `WITH (NOLOCK)` ✅, abgeleitete Tabellen im JOIN (Seite „Prozessoren“) ✅, `IF … BEGIN … END` …✅
E7.11Restliche SMO-Objekte`xp_fixeddrives`, `xp_get_tape_devices`, `LOGINPROPERTY`, `COLLATIONPROPERTY` ✅; dazu `sys.fn_varbintohexstr`, `sys.fn_varbintohexsubstring` und …✅
E7.12XEvent Profiler & SQL Server ProfilerSQL Server Profiler verbindet sich (Pre-Login-Antwort als TABULAR RESULT 0x04 mit TRACEID, bytegleich zum Orakel).✅
E7.15Serverberechtigungen im Dialog`sys.server_principals` meldet alle 31 Prinzipale des Auslieferungszustands (Login `sa`, die 19 Serverrollen, die Zertifikatsanmeldungen und die …✅
E7.14Servereigenschaften ändern`sp_configure` und `sys.sp_configure` mit null, einem oder zwei Argumenten; `RECONFIGURE [WITH OVERRIDE]` übernimmt den eingestellten Wert in den …✅
E7.16AktivitätsmonitorDer Server führt jetzt eine echte Sitzungsverwaltung: jede TDS-Verbindung bekommt eine Kennung ab 51, die auch `@@SPID` und der Antwortkopf melden …✅
E7.17Fehlende Verwaltungs- und KatalogsichtenDie 21 beim Durchgehen der `EXISTS`-Muster gefundenen Sichten sind angelegt und antworten mit den richtigen Spalten: `sys.dm_tran_locks` …✅
E7.18Dateifreigabe`sys.dm_os_enumerate_filesystem` und `sys.dm_os_file_exists` sind die Sichten, über die SSMS das Dateisystem des Servers durchsucht — beim Anlegen …✅
E7.19Vergleiche mit sql_variant`=`, `<>`, `<`, `>`, `IN`, `BETWEEN`, `ORDER BY` und `CASE` auf `sql_variant` wandeln nicht mehr, sondern ordnen nach Familienrang und Wert wie SQL Server 2025; `sys.database_scoped_configurations.value` trägt je Eintrag den Grundtyp des Orakels. Der SSMS-Dialog „Datenbank-Eigenschaften“ (SMO `DatabaseScopedConfiguration`) lädt wieder.✅
E7.20Benutzerprofil für SMO`sp_MSdbuserpriv N'serv'` liefert wie das Orakel eine Zeile (sysadmin 7, `CONTROL SERVER` 6, `CREATE ANY DATABASE`/dbcreator 2, sonst 0); der SSMS-Dialog „Datenbank-Eigenschaften“ bricht nicht mehr mit `NullReferenceException` in `ServerConnection.UserProfile` ab.✅
E7.21Kompatibilitätsgrad 170 und @@MICROSOFTVERSION**Erledigt 04.10.2026 (SSMS-Fund: im Dialog Datenbankeigenschaften fehlte „SQL Server 2025 (170)“).** Am Orakel 2025 gemessen: `@@MICROSOFTVERSION` = 285217677 (0x1100138D, Hauptversion 17, Bau 5005) - KuonenSQL meldete 0x10000000 (Hauptversion 16), SSMS leitet daraus die angebotenen Grade ab; jetzt aus `Serverkennung` gepackt. `ALTER DATABASE … SET COMPATIBILITY_LEVEL` wurde still übergangen - jetzt wirksam und dauerhaft, 100 bis 170 in Zehnerschritten, sonst 15048. Ein Datenordner aus der 160er-Zeit hebt beim ersten Laden wie ein SQL-Server-Upgrade die Systemdatenbanken (master, model, msdb, tempdb) auf 170, Benutzerdatenbanken behalten ihren Grad (`DatabaseInfo.Versionsgrad`, einmalig - ein bewusst gesetzter Grad übersteht danach jeden Neustart). Dazu: `ServerkennungTests` in der Sammlung „Sicherung“ (Wettlauf um die Datenwurzel, der die Veröffentlichung von 1.12.12 stoppte). Tests `MicrosoftVersionTests`, `KompatibilitaetsgradTests` (rot zuerst; Sabotage: Systemdatenbanken nicht angehoben → 4 rot).✅
E7.13Werkzeuge`tools/ssms-compat`: Orakel-Vergleich aller mitgeschnittenen SSMS-Abfragen und aller Systemsichten✅
Meilenstein 8: Vollständiges Typensystem (SQL Server 2022) (11/11)
PunktTitelBeschreibungStand
B8.1Gleitkomma`float(n)`, `real` mit IEEE-754-Semantik, TDS `FLTN`✅
B8.2Währung`money`, `smallmoney` (Int64 × 10⁻⁴, eigene Rundungsregeln), TDS `MONEYN`✅
B8.3Datum/Zeit vollständig`datetime` (1/300 s), `smalldatetime`, `date`, `time(n)`, `datetime2(n)` mit Präzision, `datetimeoffset(n)`; TDS …✅
B8.4Binärdaten`binary(n)`, `varbinary(n|max)`, `image`: DDL, Speicher, Vergleiche, Hexliterale `0x…` als `varbinary`.✅
B8.5Fixe Längen & MAX-Typen`char(n)`/`nchar(n)` mit Auffüllung; `varchar(max)`/`nvarchar(max)`/`varbinary(max)` auch weit über 8000 Bytes (geprüft mit 120.000 Bytes) — solche …✅
B8.6`rowversion` / `timestamp`Eigener Typ mit Katalogkennung 189 und `max_length` 8, im Katalog immer als `timestamp` gemeldet.✅
B8.7`sql_variant`Wert und Grundtyp, `SQL_VARIANT_PROPERTY()` mit `BaseType`, `Precision`, `Scale`, `TotalBytes`, `MaxLength` und `Collation`, Speicherung in der Zeile …✅
B8.8`xml`Eigener Typ mit Katalogkennung 241 und `max_length` -1, auf der Leitung `XMLTYPE` (0xF1) als PLP-Strom.✅
B8.11XML-Schemasammlungen`CREATE XML SCHEMA COLLECTION name AS '<xsd>'`, `ALTER … ADD` und `DROP`, dazu der Typ `xml(Sammlung)` — auch als `xml(DOCUMENT …)` und …✅
B8.9Spezialtypen`hierarchyid`, `geometry` und `geography` lassen sich als Spalten anlegen, speichern ihren Binärwert und melden sich im Katalog mit den Kennungen von …✅
B8.10SortierungenAlle **5508** Sortierungen von SQL Server 2022 stehen bereit; `sys.fn_helpcollations()` listet sie mit derselben Beschreibung, und …✅
Meilenstein 9: T-SQL-Sprachumfang SQL Server 2022 (20/20)
PunktTitelBeschreibungStand
C9.1Datums-/Zeitfunktionen`GETDATE`, `SYSDATETIME`, `GETUTCDATE`, `SYSUTCDATETIME`, `SYSDATETIMEOFFSET`, `CURRENT_TIMESTAMP`, `DATEPART`/`DATENAME` (auch `ISO_WEEK` …✅
C9.2Mathematik`ROUND` (auch mit negativer Stellenzahl und Abschneiden), `CEILING`/`FLOOR` mit der Genauigkeit der Vorlage, `POWER`, `SQRT`, `SQUARE`, `SIGN` …✅
C9.3Zeichenketten`STUFF`, `REPLICATE`, `SPACE`, `ASCII`, `CHAR`, `NCHAR`, `UNICODE`, `TRANSLATE`, `SOUNDEX` (in der Fassung von SQL Server, nicht der des …✅
C9.4Konvertierung`TRY_CAST`, `TRY_CONVERT`, `PARSE`, `TRY_PARSE`, alle `CONVERT`-Stilcodes von 0 bis 131 einschliesslich der islamischen Kalenderstile 130 und 131 …✅
C9.5Aggregate & Fenster`COUNT_BIG`, `STDEV`/`STDEVP`, `VAR`/`VARP`, `CHECKSUM_AGG`, `NTILE`, `PERCENT_RANK`, `CUME_DIST`, `FIRST_VALUE`/`LAST_VALUE` …✅
C9.6Neu in SQL Server 2022`GENERATE_SERIES`, `STRING_SPLIT(…, enable_ordinal)`, `JSON_OBJECT`/`JSON_ARRAY`, `JSON_PATH_EXISTS`, `ISJSON(…, Art)` (siehe C9.13).✅
C9.7Mengen & Umformung`MERGE` samt `OUTPUT $action` ist mit M4 erledigt, `TRUNCATE TABLE` und `SELECT … INTO` für `#temp` mit M3.✅
C9.8JSON & XML-Ausgabe`FOR JSON PATH` (Punkte im Alias verschachteln, Namenskonflikt → **13601**, namenlose Spalte → **13605**) und `FOR JSON AUTO` (je Tabelle eine Stufe …✅
C9.9Prozedurales T-SQL**Erledigt:** `PRINT` und `RAISERROR` bis Schweregrad 10 kommen als Meldung (INFO-Token) beim Client an, in SSMS unter „Meldungen“, an ihrer Stelle …✅
C9.14Typableitung über abgeleitete TabellenSpalten aus abgeleiteten Tabellen, CTEs, `APPLY`, Tabellenfunktionen, `STRING_SPLIT`/`OPENJSON` und `UNION` behalten beim Weiterrechnen ihren Typ …✅
C9.17Korrelation in eine abgeleitete Tabelle`EXISTS (SELECT 1 FROM (SELECT … ROW_NUMBER() OVER (…) …) x WHERE x.KundenID = k.KundenID)` und die skalare Variante mit `TOP 1 … ORDER BY` laufen …✅
C9.18`MERGE`: Alias im `SET` und abgeleitete Quelle`USING (SELECT …) AS quelle` und `THEN UPDATE SET ziel.Spalte = …`; die Quelle darf auch eine Sicht, eine CTE oder ein dreiteiliger Name sein.✅
C9.19Nicht reservierte Schlüsselwörter als NameKein Wort ist mehr Schlüsselwort-Token, das SQL Server nicht reserviert: neben `ROWS` jetzt auch `AFTER`, `APPLY`, `CAST`, `CATCH`, `DATABASES` …✅
C9.20Weiter nach AnweisungsfehlernEin Fehler auf Anweisungsebene (etwa 8134, 220/8115, 512, 515, 547, 2627, 2812, 3701, 3903, 16916, `RAISERROR`) beendet nur die Anweisung; der Stapel …✅
C9.16Vorrang von `INTERSECT``INTERSECT` bindet stärker als `UNION` und `EXCEPT`: `SELECT 1 UNION ALL SELECT 2 INTERSECT SELECT 2` ergibt `1, 2`.✅
C9.15Kommaverbund und `VALUES` als Verbundquelle`FROM a, b [, c]` (ANSI-89) läuft; SMO schreibt seine Abfragen so.✅
C9.12`FULL OUTER JOIN``FULL [OUTER] JOIN` behält beide Seiten; wer keinen Partner hat, bekommt NULL – auch über mehrere Stufen, wobei die linke Seite einer späteren Stufe …✅
C9.13Mengen- und Zeilenfunktionen`STRING_SPLIT`, `GENERATE_SERIES` und `OPENJSON` als Tabellenfunktionen in `FROM` und in `CROSS`/`OUTER APPLY` mit Bezug auf die linke Seite …✅
C9.11CLR-Assemblies (`CREATE ASSEMBLY`)`CREATE ASSEMBLY … FROM 0x… | 'pfad' WITH PERMISSION_SET = SAFE | EXTERNAL_ACCESS | UNSAFE`, `DROP ASSEMBLY [IF EXISTS]` …✅
C9.10Temporäre ObjekteLokale `#`-Tabellen gehören ihrer Verbindung: wie bei SQL Server wird der Name auf 116 Zeichen mit `_` aufgefüllt und trägt die Sitzungskennung als …✅
Meilenstein 10: Schema-DDL, Constraints & Indizes (29/29)
PunktTitelBeschreibungStand
C10.1`ALTER TABLE``ADD`/`DROP`/`ALTER COLUMN`, `ADD CONSTRAINT`, `DROP CONSTRAINT` ✅ (mit M3).✅
C10.2Constraint-Durchsetzung`FOREIGN KEY`, `CHECK`, `UNIQUE` und `DEFAULT` sind durchgesetzt (mit M3).✅
C10.3**Indizes: Nutzung beim Suchen**Anlegen und Durchsetzen ✅ (`CREATE [UNIQUE] [NON]CLUSTERED INDEX`, `DROP INDEX`, 1505/2601).✅
C10.4Sequenzen & Identity`IDENT_CURRENT`, `SET IDENTITY_INSERT`, `DBCC CHECKIDENT` ✅ (mit M3).✅
C10.5Berechnete Spalten & SynonymeBerechnete Spalten (`c AS a * 2 + b`, auch `PERSISTED` und `PERSISTED NOT NULL`) werden bei `INSERT` und `UPDATE` nachgerechnet; schreiben lässt sich …✅
C10.6Schemas & Sicherheit`CREATE SCHEMA` sowie der Satz der Sicherheitsanweisungen: `CREATE`/`ALTER`/`DROP` für `LOGIN`, `USER`, `ROLE` und `SERVER ROLE` …✅
C10.7Temporale Tabellen`GENERATED ALWAYS AS ROW START/END [HIDDEN]`, `PERIOD FOR SYSTEM_TIME` und `WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = …))`; ohne Namen heißt der …✅
C10.8Weitere Indexarten`COLUMNSTORE` (gruppiert und nicht gruppiert), räumliche Indizes (`USING GEOGRAPHY_AUTO_GRID` …), primäre und sekundäre XML-Indizes ( …✅
C10.9`SCHEMABINDING`Die Sequenzen stehen mit **C10.4**.✅
C10.10DDL- und Anmelde-Trigger`CREATE [OR ALTER] TRIGGER … ON DATABASE` und `ON ALL SERVER` mit allen 308 Ereignistypen und -gruppen aus `sys.trigger_event_types` (eine Gruppe wie …✅
C10.11Triggeroptionen und Schaltbarkeit`WITH ENCRYPTION` (Text in `sys.sql_modules` und `OBJECT_DEFINITION` ist NULL), `WITH EXECUTE AS` (`execute_as_principal_id`, -2 für OWNER) und …✅
C10.12Dauerhaftigkeit der DDLDas Transaktionsprotokoll schrieb bisher nur neun Anweisungsarten mit (`CREATE`/`DROP TABLE`, `CREATE`/`DROP VIEW`, `INSERT`, `UPDATE`, `DELETE` …✅
C10.13`CREATE DATABASE` wie aus SSMSDer Dialog „Neue Datenbank“ scheiterte mit **102** an `CONTAINMENT` – `CREATE DATABASE` kannte nur `name [COLLATE …]`.✅
C10.14SSMS-Mitschnitt gegen SQL Server 2025Die Dialoge „Neue Datenbank“, „Eigenschaften“, Tabellen-Designer, Benutzer/Anmeldung und „Löschen“ wurden an einem echten SQL Server 2025 per …✅
C10.15Abgleich des SystemkatalogsNeues Werkzeug `tools/oracle/Compare-Katalog.ps1` vergleicht jede Sicht und Tabellenfunktion der Schemas sys und INFORMATION_SCHEMA (699 bei SQL …✅
C10.16Nachprüfung M9/M10Eine Nachprüfung der als erledigt geführten Punkte fand echte Lücken; alle mit Orakelfällen geschlossen.✅
C10.17RPC-Parameter, OUTPUT, RückgabestatusParametrisierte Aufrufe über den echten Treiber verloren Werte: jedes Datum kam als `1900-01-01`, jeder Binärwert als `0x00` an; `money` …✅
C10.18DML über Sichten und CTEs`INSERT`, `UPDATE`, `DELETE` und `MERGE` auf eine Sicht oder einen CTE scheiterten mit **208**, `WITH … MERGE` mit **102**.✅
C10.19`OUTPUT … INTO`, 334, Namensfehler in TRY`OUTPUT … INTO ziel [(spalten)] [OUTPUT …]` fehlte bei allen DML-Anweisungen (**102**): jetzt bei INSERT, UPDATE, DELETE und MERGE, in Tabellen und …✅
C10.20Eingebaute Funktionen beim ÜbersetzenEine unbekannte Funktion (**195**) meldete erst die Ausführung – der Stapel lief bis dahin, ein nie erreichter Zweig fiel gar nicht auf.✅
C10.21Cursor über Sichten und abgeleitete TabellenEin Cursor über eine Sicht oder abgeleitete Tabelle war stets eine Momentaufnahme: `KEYSET` sah keine Änderungen und keine gelöschten Zeilen …✅
C10.22CLR-Tabellenwertfunktionen, -Aggregate und -TriggerWas C9.11 noch offen ließ: **Tabellenwertfunktionen** `CREATE FUNCTION … RETURNS TABLE (spalten) [ORDER (…)] AS EXTERNAL NAME …` im Muster von SQL …✅
C10.23Cursorvariablen vollständig`DECLARE @c CURSOR, @x int` scheiterte an der Liste (**102**), `SET @c = name` (Variable auf einen benannten Cursor) meldete **207**, und …✅
C10.24Zeilensperren zwischen SitzungenBisher sperrte keine Sitzung über das Ende einer Anweisung hinaus: B konnte eine Zeile ändern, die A noch nicht bestätigt hatte, und As `ROLLBACK` …✅
C10.25REPEATABLE READ, SERIALIZABLE, SNAPSHOTDie Stufen wurden angenommen, lasen aber wie READ COMMITTED.✅
C10.26SNAPSHOT vollständigWas C10.25 offenließ: SNAPSHOT sah je Anweisung den zuletzt bestätigten Stand statt den **ab Transaktionsbeginn**, und den Updatekonflikt **3960** …✅
C10.27Vier Randfälle aus der Nachmessung**4104**, wenn hinter einem Alias der Tabellenname steht (`SELECT dbo.T.wert FROM dbo.T x`); **8120**, **8121** und **8127** für eine Spalte ausserhalb von `GROUP BY` — in der Auswahlliste, in `HAVING` und in `ORDER BY`.✅
C10.28DacFx: Extrahieren, Exportieren, Importieren, VeroeffentlichenSSMS-Datenebenenanwendung und sqlpackage laufen gegen KuonenSQL (208 an sysconfigures, OPTION (USE HINT), CONTEXT_INFO, XML_SCHEMA_NAMESPACE, sys.json_indexes, ROLLBACK IMMEDIATE, REVOKE TO, Prinzipal-SIDs, sp_addrolemember, IS_ROLEMEMBER, BaseType, Kompatibilitaetsgrad 170, NOT NULL in COLMETADATA); .bacpac/.dacpac kreuzweise mit SQL Server 2025.✅
C10.28aDacFx-Rest: Modulschalter und StapeltextQUOTED_IDENTIFIER und ANSI_NULLS stehen je Modul (Sicht, Funktion, Prozedur, Trigger) in sys.sql_modules und OBJECTPROPERTY, ueberstehen Kontrollpunkt und Wiederanlauf, und das Modul laeuft mit seinem ANSI_NULLS; die Definition ist der ganze Stapeltext (ALTER und CREATE OR ALTER als CREATE); neu sp_helptext. DacFx-Rundlauf mit QI-aus-Modul gegen KuonenSQL und SQL Server 2025 (auch kreuzweise).✅
Meilenstein 11 (optional): ISO SQL:2023 und SQL Server 2025 (7/7)
PunktTitelBeschreibungStand
B11.1Nativer `json`-TypDer Typ `json` als Spalte, Variable und Parameter (`system_type_id` 244, auf der Leitung `nvarchar(max)` – genau wie bei SQL Server gegenüber jedem …✅
C11.3ISO SQL:2023 Property Graphs`SQL/PGQ` schreibt SQL Server als eigene Graphtabellen: `AS NODE`, `AS EDGE`, `MATCH`, Kantenbedingungen und `SHORTEST_PATH` samt `FOR PATH` und den Pfadaggregaten.✅
C11.4Der Kern von SQL, den 2022 noch nicht hatte`CURRENT_DATE` und `SUBSTRING(text, anfang)` ohne Längenangabe stehen seit je im Standard; SQL Server hat sie erst 2025 nachgezogen.✅
B11.2`vector(n)`Der Typ `vector(n)` als Spalte, Variable und Parameter: n Gleitkommazahlen einfacher Genauigkeit, hineingegeben als JSON-Feld, herausgegeben in der …✅
B11.4Methoden der CLR-TypenDie Typen selbst stehen (B8.9); hier kommen ihre Methoden dazu.✅
C11.1Reguläre Ausdrücke`REGEXP_LIKE`, `REGEXP_REPLACE`, `REGEXP_SUBSTR`, `REGEXP_INSTR` und `REGEXP_COUNT` samt ihren Schaltern `c`, `i`, `s` und `m`.✅
C11.2Weitere Funktionen`PRODUCT()` als Aggregat und als Fensterfunktion (Ergebnistyp wie `SUM`, `decimal` jedoch stets `decimal(38,6)`, Überlauf 8115, ein nicht numerischer …✅
Meilenstein 12: Sicherheit, externe Quellen und Betriebsfeatures (54/54)
PunktTitelBeschreibungStand
S12.1Sicherheit auf Zeilen- und SpaltenebeneRow-Level Security (`CREATE SECURITY POLICY`, Filter- und Blockprädikate, 33504), Dynamic Data Masking (`MASKED WITH`, vier Funktionen, `UNMASK`) und `EXECUTE AS USER` mit Wi…✅
S12.2Verschlüsselung im RuhezustandHauptschlüssel, Zertifikate und symmetrische Schlüssel samt `ENCRYPTBYKEY`, `ENCRYPTBYPASSPHRASE`, `ENCRYPTBYCERT` und `HASHBYTES`; die Schlüsselverwaltung von Always Encrypt…✅
S12.3aExterne Datenquellen: die Buchführung`sp_addlinkedserver` und Verwandte, `sys.servers` mit den Verbindungsservern, `CREATE/DROP EXTERNAL DATA SOURCE` samt `sys.external_data_sources`. `OPENROWSET`/`OPENDATASOURCE` geben **15281** wie SQL Server, `OPENQUERY` auf einen unbekannten Server **7202**. Verbunden wird seit S12.3b.✅
S12.3bExterne Datenquellen: wirklich verbindenVerbindungsserver, `OPENQUERY`, `OPENROWSET`, `OPENDATASOURCE` und viergliedrige Namen `srv.db.schema.tabelle` (lesen und `INSERT`) **verbinden wirklich** — über einen **eigenen TDS-Client im Kern** (Pre-Login, TLS, Login7, SQLBATCH, alle Typen samt `sql_variant` und Grossobjekten; Längen gedeckelt, Fuzz-Test mit 1200 verfälschten Antworten), zu SQL Server wie zu KuonenSQL selbst. `EXEC ('… ? …', wert) AT srv`, `EXEC srv.db.schema.proc`, `sp_addlinkedsrvlogin`/`sp_droplinkedsrvlogin` mit `sys.linked_logins` (7416, 15007, 15190), `sp_testlinkedserver`, `connect`/`query timeout` mit der Fehlerfolge des Orakels (7412 + 10061/2, 7399/7320). Anbieter wie SQL Server 2025: `SQLNCLI`/`SQLOLEDB`/`MSOLEDBSQL`, fremde Treiber **7222**. Ohne MSDTC: Fernaufruf in einer Transaktion **7391**, mit `remote proc transaction promotion` aus ausserhalb der Transaktion. Gegenprobe desselben Stapels am Orakel.✅
S12.3cExterne Datenquellen: der Rest`UPDATE`/`DELETE` über viergliedrige Namen (Schlüssel erfragen, vorgefilterte Zeilen in einen lokalen Spiegel, die unveränderte Anweisung darüber, dann **eine** gezielte Anweisung über den Schlüssel — `SET id = id + 1` gelingt wie am Orakel; ohne Schlüssel je Zeile `TOP (1)` binär verglichen), `INSERT OPENQUERY(…)` in die Tabelle hinter der Abfrage (7344, 16955, 213, 405), OUTPUT-Parameter und Rückgabewert eines Fernaufrufs über **RPC** (der eigene TDS-Client liest RETURNSTATUS/RETURNVALUE), benannte Instanzen über den **SQL-Browser** (SSRP; unbekannte Instanz −1 wie am Orakel) und ein eigener Browser, **voreingestellt aus** (`KUONENSQL_SQLBROWSER_PORT`), **Filter und Spaltenauswahl zum fremden Server** (Vergleiche mit Zahl/Geld/Zeitpunkt; 100000 Zeilen, ein Treffer: 242 → 0,83 ms), ein **Verbindungspool** mit RESETCONNECTION und Deckel (`SELECT 1` 2,76 → 0,45 ms), der eigene Servername vorn im Namen liest lokal, die **Metadatenprüfung zur Übersetzungszeit** (7314, 207, `OPENQUERY` über `sp_describe_first_result_set` mit 7412/8180, 11529, 11525/11526/11509/11530, 7357) und eine **echte verteilte Transaktion** über den eigenen Koordinator mit Zwei-Phasen-Commit zwischen KuonenSQL-Servern — vorbereitete Transaktionen überdauern Verbindung und Neustart, der Koordinator liefert Entscheidungen nach; SQL Server als Teilnehmer bräuchte MSDTC und bleibt bei **7391**. Gegenprobe am Orakel, Fuzz für RPC-Antworten und SSRP.✅
S12.3dExterne Datenquellen: Feinheiten`sp_describe_first_result_set` meldet, was sich nicht bestimmen lässt, wie SQL Server (Pfadanalyse über IF/WHILE/TRY/RETURN/THROW, Prozeduren und dynamisches SQL aus Konstanten: 208/2812 + 11529, Übersetzungsfehler + 11501, 11509–11526, 11530, 214; 47 Fälle gegen das Orakel) und wird damit auch bei **KuonenSQL als Ziel** von `OPENQUERY` gefragt (führt seit 9ea42fe nichts aus); `UPDATE`/`DELETE OPENQUERY(…)` und `FROM OPENQUERY(…) r` ändern die Tabelle hinter der Abfrage (7344, 16955, 405); ein fernes Ziel, das nur als Alias in einem `JOIN` steht; die Zeilen **vor** einem Fehler des fremden Servers (durchreichendes SELECT: Zeilen, mit ORDER BY nur der Kopf, mit Aggregat nichts — wie am Orakel); Zeichenketten, `IN`, `BETWEEN`, `LIKE` gehen zum fremden Server (am Orakel **unabhängig** von `collation compatible`, verglichen in der Sortierung der fernen Spalte), die drei Sortierungsoptionen stehen in `sys.servers`/`sp_helpserver`; `sys.dm_tran_active_transactions` zeigt die schwebenden Transaktionen, `KILL 'uow' WITH COMMIT|ROLLBACK` entscheidet sie (6115, 8169, 6110, 6113, 6108); die Nachlieferung nach dem Neustart meldet sich mit einer **Abschlussmarke** der Transaktion an statt mit dem Kennwort der Sitzung.✅
S12.3eExterne Datenquellen: ResteWas S12.3d begründet offen lässt, bis auf einen Rest (S12.3f): `KILL sitzung` beendet jetzt wirklich eine Verbindung (vorher meldete KuonenSQL dort 102) — am Orakel gemessen vier Meldungen (Bereich 1..32767 sonst **6101**, unbekannte/schon getrennte Sitzung **6106**, Hintergrundsitzung **6107**, eigene Sitzung **6104**), danach schliesst die Sitzung ihre offene Transaktion selbst zurück und meldet sich ab — über einen eigenen Abbruch-/Schliess-Griff je Verbindung (`Sitzungsdaten.ErzwingeVerbindungsende`), keine zweite Stelle, die den Zustand einer fremden Sitzung anfasst. Die Beschreibung der **eingebauten** Systemprozeduren (`sp_who` …) bleibt wie schon dokumentiert leer (kein T-SQL-Rumpf), jetzt mit Regressionstest gegen den am Orakel gemessenen Unterschied (9 echte Spalten dort). `sys.dm_tran_active_transactions` zeigt jetzt auch **gewöhnliche** Transaktionen (Art 1, Zustand 2, ohne UOW, am Orakel gemessen) neben den schwebenden verteilten — eine schon vorbereitete zählt nicht doppelt. Syntaxfehler haben Schwere **15** statt des bisherigen Standards 16 (am Orakel gemessen für die ganze Familie 102/137/156/174/195 — anders als 207/208, die bei 16 bleiben), an **einer** Stelle behoben (`KuonenSqlException`, nicht an den rund hundert Fundstellen einzeln). Dazu ein Robustheitsfund derselben Sitzung: `DATEADD(hour, 1, '…+02:00')` liess über eine datetimeoffset-Spalte eine **ArgumentException der .NET-Laufzeit** entkommen (.NET's `DateTime.TryParse` deutet einen Zeitzonen-Offset ohne `AdjustToUniversal` als lokale Verschiebung und setzt `Kind=Local`, was `new DateTimeOffset(dt, TimeSpan.Zero)` bei einer Maschine ausserhalb UTC zum Werfen bringt) — behoben durch einen erkannten Zeitzonen-Suffix vor der generischen .NET-Parse-Stufe (`datetime`/`smalldatetime` lehnen einen numerischen Offset ab wie am Orakel: 241/295, `datetime2`/`date`/`time` ignorieren ihn wie am Orakel, statt zu verschieben). Gefilterter Lauf über 1720 + 418 Tests bleibt grün.✅
S12.3fExterne Datenquellen: RPC-Fehlerstrom`EXEC (…) AT` mit Zeilen **vor** einem Fehler des fremden Servers, **am Draht nachgebildet**: der Byte-Abgleich war kein mehrtägiger zwischen zwei Servern, sondern einer zwischen Server und Client und liess sich mit einem durchreichenden TCP-Proxy und einem Token-Zerleger in einem Nachmittag messen. Am Orakel schliesst der aufrufende Server die abgebrochene Menge mit `DONEINPROC(MORE|ERROR)` **vor** dem `ERROR`-Token — deshalb meldet `SqlClient` `Number = 0`, `Class = 11`, nicht die echte Nummer —, beendet den Stapel mit `DONE(ERROR, 0x00FD)`, nimmt die offene Transaktion zurück, und `TRY … CATCH` fängt den Fehler mit der echten Nummer. Alles nur, wenn der Fehler IM Zeilenstrom kommt; nach einer vollständigen Menge oder vor der ersten Zeile ist es ein gewöhnlicher Fehler. KuonenSQL schickt dieselbe Tokenfolge und verhält sich gleich (Leser merkt sich `Fernmenge.Unterbrochen`, Fehler mit `NachZeilen`/`Stapelabbruch`, Schreiber mit `ExecutionResult.Abgebrochen`); 6 Tests mit dem am Orakel mitgeschnittenen Strom, je Teilpunkt eine Sabotage-Probe.✅
S12.3gExterne Datenquellen: Rahmung der Antworten**Behoben (30.09.2026).** Ein durchreichender Mitschnitt mit Zerleger der Token stellte Orakel 2025 und KuonenSQL nebeneinander, an rund 130 Stapeln (Abfragen, DML, DDL, `SET`, `DECLARE`, `PRINT`, `IF`/`WHILE`/`GOTO`, `TRY`/`CATCH`, Transaktionen, Cursor, `EXEC`, `EXEC … AT`, RPC). Statt jede Antwort mit einem einzigen DONE zu schliessen, führt die Engine jetzt ein **Rahmenprotokoll** (`Rahmenereignis`: Menge, Meldung, Fertig, Prozedur beginnt/endet, Transaktion beginnt/endet/zurück, Fehler des fremden Servers) in der Reihenfolge des Geschehens, und `CreateBatchResponseMitRahmen` schreibt daraus die Token wie das Orakel: **jede Anweisung mit eigenem DONE** und ihrem am Orakel gemessenen Befehlscode (u. a. `INSERT` C3, `DELETE` C4, `UPDATE` C5 — bisher waren die beiden letzten vertauscht —, `MERGE` 0x117, `SELECT INTO` C2, `SET … ON` B9, `OFF` BA, `PRINT` F7, `RAISERROR` F6, `WAITFOR` F3, `USE` E2, `RETURN` DB, Cursor 0x20/0x2B/0x2C, `CREATE`/`ALTER`/`DROP`/`TRUNCATE` je nach Art, `IF`/`WHILE` C0 je Auswertung, `GOTO` CA), alle DONE ausser dem letzten mit `DONE_MORE`, ein Stapel ohne eine solche Anweisung mit dem leeren DONE 0x00FD; **`SET NOCOUNT ON` nimmt `DONE_COUNT` aus den Token** (der Client sieht bei `ExecuteNonQuery` -1 statt der Zeilenzahl — bisher wurde NOCOUNT am Draht ignoriert, ein client-sichtbarer Fehler); **`TRY`/`CATCH` mit den DONE-Token 0x015D, 0x015E, 0x015F** (die scheiternde Anweisung im TRY schliesst noch ihr eigenes DONE); **`BEGIN TRAN`/`COMMIT`/`ROLLBACK` mit `ENVCHANGE` 8/9/10 und dem 8-Byte-Deskriptor** (nur die äusserste Transaktion; ein Sicherungspunkt-Rollback meldet keinen); **`EXEC`, `EXEC … AT`, `sp_executesql` und parametrisierte Aufrufe mit `DONEINPROC` je Anweisung, `RETURNSTATUS` und `DONEPROC` (0x00E0)** — beim Aufruf an den fremden Server steht der `RETURNSTATUS` nach JEDER Ergebnismenge und bei einem Aufruf ohne Menge gar nicht (dafür mit eingeschalteter Zählung ein eigenes `DONEINPROC`), sein Fehler steht vor einem `DONEPROC` mit `DONE_ERROR`; ein Fehler in der LETZTEN Anweisung schliesst den Stapel mit einem einzigen DONE (bisher folgte ein zweites); `USE` meldet zusätzlich die Meldung 5701 (`SqlClient` zeigt sie als `InfoMessage`) und `ENVCHANGE` 7. Bei einem RPC-Aufruf gilt der vom Server gebaute Vorspann (`DECLARE …`) nicht als Anweisung. **Nicht nachgebildet, mit Grund:** `SESSIONSTATE` und die Schlüsseltabelle der Spaltenverschlüsselung in `COLMETADATA` schickt das Orakel nur, weil der Client die Erweiterungen aushandelt und der Server sie bestätigt — KuonenSQL bestätigt sie nicht, der Client erwartet sie also nicht; eine `NOT NULL`-Konstante geht als `intn` statt als fester Typ 0x38 (der Client liest beides als `int`); der Wert des Transaktionsdeskriptors zählt am Orakel eigenständig hoch. Belegt durch `TdsRahmenTests` (zwölf Stapelgruppen mit den Sollfolgen vom Orakel; Sabotage: Zählung ignoriert → 1 rot, `COMMIT` als `ROLLBACK` gemeldet → 1 rot). Was der Mitschnitt noch als Abweichung zeigt (einzelne Anweisungsarten und Systemprozeduren), steht als S12.3h.✅
S12.3hRahmung: einzelne Anweisungsarten und Systemprozeduren**Behoben (30.09.2026).** Alle vier Punkte am Orakel 2022/2025 gemessen (durchreichender Mitschnitt, Zerleger der Token, Fehler nach Nummer/Status/Schwere, INFO-Texte wörtlich): **(a)** `ALTER INDEX` (REBUILD/REORGANIZE/DISABLE/SET, ALL, Indexhinweise mit 308/315/8655), `CREATE/UPDATE/DROP STATISTICS` (Histogramm, Dichtevektor, mittlere Länge aus den Zeilen; Ids geteilt mit den Indizes; `STATS_DATE`, `INDEXPROPERTY`, `sys.stats`) und die üblichen `DBCC`-Formen (CHECKDB/TABLE/CATALOG/ALLOC/FILEGROUP/CONSTRAINTS/IDENT, SHOW_STATISTICS, SHOWCONTIG, SHRINKFILE/-DATABASE, SQLPERF, USEROPTIONS, OPENTRAN, INPUTBUFFER, LOGINFO, TRACE*, HELP …) tragen ihren Befehlscode (E6, 0151, C8/F1/C9) samt den INFO-Meldungen; `SET` mit Wert meldet F9 (`LOCK_TIMEOUT`, `LANGUAGE`, `TEXTSIZE`, `TRANSACTION`, `DATEFORMAT`) oder BE (`DATEFIRST`); **(b)** `sp_rename` meldet `ENVCHANGE` 8/9, INFO 15477 und `RETURNSTATUS`, auch im Fehlerfall; **(c)** `FETCH` schickt `TABNAME`, `COLINFO`, `ORDER` und die verdeckte Spalte `ROWSTAT`; **(d)** eine scheiternde `SELECT`-Anweisung im `TRY` schickt ihr `COLMETADATA` vor dem Fehler; dazu der Parserfehler `BEGIN SET NOCOUNT OFF END`. **Begründete Grenzen (zur Zeit von S12.3h; drei davon später aufgelöst):** `DBCC OUTPUTBUFFER`/`MEMORYSTATUS`/`PINTABLE` — **seit S12.3k nachgebildet** (`NETSTATS`/`LRUSTATS`/`CACHESTATUS` antworten auch am Orakel mit 2526); die Systemtabellen-Zeilen von `CHECKDB`/`CHECKALLOC` sind der feste Stand einer frischen Datenbank; Histogramme über 200 verschiedene Werte — **seit S12.3j** die Stufenwahl des Orakels (mit benannter Grenze); `CHECKCONSTRAINTS` — **seit S12.3i** mit echter Prüfung; Ids, Zeiten und LSN der Ausgabe sind je Lauf verschieden (hier lokale Zeit) und stehen nicht im Vergleich.✅
S12.3i`NOCHECK`/`WITH CHECK` und `DBCC CHECKCONSTRAINTS` mit echter Prüfung**Behoben (30.09.2026).** Am Orakel 2025 gemessen (derselbe Mitschnitt wie S12.3h, dazu `sqlcmd`): **`ALTER TABLE … [WITH CHECK] CHECK`/`NOCHECK CONSTRAINT ALL`/`namen`** schaltet Fremdschlüssel und CHECK ab und ein (`sys.check_constraints`/`sys.foreign_keys`: `is_disabled`, `is_not_trusted`; `NOCHECK` nimmt das Vertrauen, `CHECK` lässt es, `WITH CHECK CHECK` prüft vorher alle Zeilen und stellt es wieder her); eine abgeschaltete Einschränkung wird beim Einfügen, Ändern und Löschen nicht mehr erzwungen (auch die Kaskade des Fremdschlüssels nicht), hält aber die Zieltabelle weiter fest (`DROP TABLE` 3726); der Zustand überlebt Neustart und Rollback (`CheckZustaende`, Katalogabzug). **`ADD CONSTRAINT`** prüft die vorhandenen Zeilen (Fehler 547 mit Zustand 0, ohne 3621, Abschluss 0x00FD), `WITH NOCHECK` nicht und lässt die Einschränkung unvertraut. Schaltfehler wie am Orakel: 11415 (Schlüssel/Standardwert), 3733 (Einschränkung einer anderen Tabelle), 4917 (unbekannt), jeweils mit dem zweiten Fehler 4916 vor demselben DONE (neu: `KuonenSqlException.Nachspann` darf ein Fehler sein). **`DBCC CHECKCONSTRAINTS`** findet jetzt wirklich: ohne Namen alle Tabellen der Datenbank (in Anlagereihenfolge), mit Tabelle deren *eingeschaltete* Einschränkungen (`ALL_CONSTRAINTS`: alle), mit dem Namen einer Einschränkung nur diese (auch abgeschaltet, auch `schema.name`). Je Tabelle zwei Gruppen, erst die Fremdschlüssel, dann die CHECK; **in jeder meldet eine Zeile nur die erste (in Anlagereihenfolge) Einschränkung, die sie verletzt**; je Einschränkung jeder Wo-Text einmal, sortiert, **höchstens 200 je Gruppe** (`ALL_ERRORMSGS` hebt auf). Spalten `Table`/`Constraint` (nvarchar, Länge = längste Ausgabe + 1) und `Where` (nvarchar(max)), Merkmale 0x0021/0x0021/0x0009; der Wo-Text nennt die gelesenen Spalten mit Wert (`[d] = '1999-05-05' AND [n] = '50.25'`; Datum/Zeit im Format 121, `smalldatetime` mit `.000`, `float`/`real` wie C `%g` mit sechs Stellen und dreistelligem Exponenten, genaue Ziffern mit Aufrunden beim Gleichstand, Binärwerte `0x…`, Hochkomma verdoppelt), ist NULL, sobald ein Wert NULL ist, und `1 = 1` ohne gelesene Spalte; die Menge schliesst mit **`DONEINPROC`** (MORE, ohne Zählung, curCmd C1) statt mit DONE. Sortiert wird nach der Windows-Wortsortierung der nvarchar-Menge (Bindestrich und Hochkomma zählen im ersten Durchgang nicht). **Die drei Grenzen sind aufgelöst (S12.3l, 30.09.2026):** (1) bei mehreren verletzten CHECK meldet die Zeile die Einschränkung mit der **kleinsten Objektnummer** — an 24 Tabellen mit 23 Regeln am Orakel gemessen: die Wahl weicht von der zuerst angelegten genau dort ab, wo die Objektnummern der Regeln über die int-Grenze laufen (dann gewinnt die erste Regel hinter dem Umbruch: die 10., 15., 19.); KuonenSQL vergibt aufsteigend ohne Umbruch, also gewinnt die zuerst angelegte, eine ersetzte (`DROP`/`ADD`) ist die jüngste, Aus- und Einschalten ändert nichts; (2) `ORDER BY` und Vergleiche über `nvarchar` folgen der Windows-Wortsortierung (S12.3l); (3) die Fehler einer Anweisung am Draht und `SELECT` auf `sys.*` (S12.3l). Belegt durch `DbccEinschraenkungenTests` (18 Fälle, Sollwerte vom Orakel; Sabotage: jede Zeile unter *allen* verletzten Einschränkungen statt der ersten → 2 rot).✅
S12.3jHistogramm: die Stufenwahl des Orakels**Behoben mit benannter Grenze (30.09.2026).** Am Orakel 2025 gemessen (`CREATE STATISTICS … WITH FULLSCAN`, `DBCC SHOW_STATISTICS … WITH HISTOGRAM`, rund 120 Verteilungen): das Orakel gibt **nicht** jedem Wert eine Stufe, solange es unter 200 sind — es verschmilzt Nachbarn, die sich im Histogramm nicht unterscheiden ließen: **zwei Werte gleicher Häufigkeit und gleichen Abstands zum Vorgänger werden ein Paar, von links nach rechts; der kleinste Wert steht immer allein; NULL ist eine eigene Stufe.** Schon `1, 2, 3` (je einmal) ergibt zwei Stufen (`1` und `3` mit `RANGE_ROWS` 1), ein Primärschlüssel 1 bis 9 fünf — KuonenSQL lieferte bisher je Wert eine Stufe. Zusätzlich schließt sich ein Einzelwert der Häufigkeit 1 einem folgenden Paar an, wenn dessen beide Abstände zusammen seinen ergeben (Dreiergruppe; bei Häufigkeit 2 nie). Der Abstand ist der Wert (Ganzzahl, Festkomma), bei Zeiten und Datum die Ticks, bei `float` eine feine Einheit, bei `uniqueidentifier` die Zahl in der Vergleichsreihenfolge (`…09` zu `…10` ist 7, nicht 1), bei Zeichenketten die Zahl der ersten acht Zeichen (Ziffern und Buchstaben fortlaufend, Groß-/Kleinschreibung gleich: `k009` zu `k010` bricht das Paar, `az` zu `ba` ebenso). Mehr als 200 Stufen werden auf 200 (die NULL-Stufe kommt hinzu: 1000 Werte und NULL ergeben am Orakel 201) verschmolzen, **die ähnlichsten Nachbarn zuerst** (kleiner Unterschied der mittleren Häufigkeit, dann kleinere Breite, dann links): ein auffällig häufiger Wert bleibt Stufengrenze, wie beim Orakel (1000 Werte, jeder 50. 100-mal: alle 20 Schwerwerte sind Grenzen mit `EQ_ROWS` 100). **Belegt:** 14 feste Verteilungen Zeile für Zeile gleich dem Orakel; je 10 von 10 Zufallsverteilungen (Häufigkeit bis 4, Abstand 1) und 12 von 12 (Häufigkeit bis 2, Abstände bis 3) identisch. **Grenze, genau:** (1) bei mehr als rund 400 *dicht liegenden einmaligen* Werten (ein Identitäts-Schlüssel mit 1000 Zeilen) fasst das Orakel weiter zusammen, in einer Reihenfolge, die mit der Zahl der Werte wechselt (201 Werte: Paare; 202: Paar, Einzelwert bei 4, Paare; 399: Dreiergruppe, 197 Paare; 512: Zweier- und Vierergruppen gemischt, je N anders, bei verschobenem Wertebereich gleich) — KuonenSQL liefert dieselbe Zahl Stufen (200), nur an anderen Stellen; (2) bei einmaligen Werten mit *ungleichen* Abständen bildet das Orakel weitere Drei- und Vierergruppen nach einer Regel, die mit den Messungen nicht gefunden wurde (0 von 8 Zufallsverteilungen stimmen; mit Häufigkeit 2 und 3 gemischt 9 von 12); (3) die Abstände von `nvarchar` und Zeichenketten mit Sonderzeichen folgen den Gewichten der Sortierung, hier nur angenähert. Die Summen (`RANGE_ROWS + EQ_ROWS`), `DISTINCT_RANGE_ROWS`, `AVG_RANGE_ROWS` und die Schwerwertgrenzen stimmen auch dort. **Nachgemessen (S12.3l):** für **dichte, gleich häufige Werte mit gleichem Abstand** bis **400 Werte** ist die Stufenwahl jetzt Zeile für Zeile die des Orakels (`DichteStufen`: alle N von 3 bis 400 gleich, siehe S12.3l); der Rest der Grenze steht als **S12.3m**. Belegt durch `StatistikStufenTests` (8 Fälle; Sabotage: keine Paare → 6 rot).✅
S12.3k`DBCC OUTPUTBUFFER`, `MEMORYSTATUS`, `PINTABLE`/`UNPINTABLE`**Behoben (30.09.2026).** Am Orakel 2025 gemessen: die Vermutung aus S12.3h, alle sechs zeigten den Zustand des Orakel-Prozesses, war nur halb richtig. **`NETSTATS`, `LRUSTATS`, `CACHESTATUS` antworten am Orakel selbst mit 2526 (Zustand 3)** — KuonenSQL tat das schon, nichts nachzubilden (Test belegt es). **`PINTABLE`/`UNPINTABLE (db, objekt)`** sind dort ohne Wirkung und nehmen jede ganze Zahl an (auch eine, die es nicht gibt; `-1` auch): nur die Meldung 2528; eine Option (auch `NO_INFOMSGS`, `TABLERESULTS`) ist 2532, eine andere Argumentzahl 2583, eine Zeichenkette, Dezimalzahl, NULL oder eine Zahl über `int` 2560 (Zustand 9) — jetzt ebenso. **`OUTPUTBUFFER (spid [, anforderung])`** zeigt den Ausgabepuffer der Sitzung als Hexabzug in der Spalte `Output Buffer` (`nvarchar(77)`), 500 Zeilen `00000010 00 0d … ….` zu 16 Bytes (Versatz, Bytes, Zeichen; Tabulator und Druckbares, sonst Punkt): KuonenSQL hält das letzte Paket, das es der Sitzung geschickt hat (`Sitzungsdaten.LetzteAusgabe`, vom Paketschreiber gesetzt), und zeigt es, mit Nullen auf 8000 Bytes aufgefüllt — wie am Orakel die Antwort auf den Stapel davor; SPID 0, -1 oder unbekannt 7955, eine Hintergrundsitzung nur die Meldung 7957, Anforderung 1 ist 7958 (Zustand 2), negativ 7962, `TABLERESULTS` 2532. **`MEMORYSTATUS`** liefert die **180 Mengen** des Orakels (Prozess und System, Speicherverwalter, Knoten 0 und 64, 144 Clerk-Mengen zu sechs Zeilen, Puffer, Uhrenstände, Plan-Cache, Global Memory Objects, Abfrage-Speicher, Gateways, Speicherpools, Broker) mit denselben Überschriften, Zeilennamen, Zeilenzahlen und Spaltenarten (`bigint`, `int`, `float`) — die Werte sind die des KuonenSQL-Prozesses (Arbeitssatz, verwalteter Haufen, Plan-Cache, laufende Sitzungen; Summenmengen summieren ihre Knoten), nicht die des Orakels; `NO_INFOMSGS` und `TABLERESULTS` ändern die Form nicht, ein Argument ist 2583. **Benannt, nicht offen:** die Zahl der Mengen schwankt beim Orakel von Lauf zu Lauf (178 und 180 gesehen: Clerks tauchen nach Zustand auf), hier steht die Folge einer Aufnahme; und der Inhalt des Ausgabepuffers ist der von KuonenSQL (Paketkopf, Token), nicht der Bytefolge des Orakels — beides gehört zu dem, was sich je Server unterscheidet. Belegt durch `DbccBetriebTests` (41 Fälle; Sabotage: Optionen nicht geprüft → 4 rot).✅
S12.3lFehler der Datenänderung am Draht, `nvarchar`-Wortsortierung, Spaltenmerkmale von `sys.*`, die Regel von `CHECKCONSTRAINTS`, dichte Histogramme**Behoben (30.09.2026), je Punkt am Orakel 2025 gemessen.** **(1) Fehler am Draht:** `547` hat Zustand 0 (`515` und `8115` Zustand 2, `2601` und `2627` Schwere 14); die Meldung `3621` steht **vor** dem `DONE` der Anweisung (Nachspann) statt dahinter mit einem zweiten `DONE`, und dieses `DONE` trägt den Befehlscode der Anweisung (`INSERT` 0x00C3, `UPDATE` 0x00C5, `DELETE` 0x00C4, `MERGE` 0x0117); `3621` folgt auch `8115`/`8134` in einer Datenänderung, nicht aber in einem `SELECT`. Neu ist das Rahmenereignis `Fehler`: Anweisungen vor dem Fehler behalten ihr `DONE` (vorher ging die ganze Antwort den alten Weg ohne sie); in Prozeduren, `TRY`, bei `XACT_ABORT`, mit Zeilen oder aus fremder Hand bleibt der alte Weg. PostgreSQL meldet `3621` nicht mehr als Notiz. `TdsRahmenFehlerTests` (4 Fälle; Sabotage: `3621` wieder hinter dem `DONE` → 3 rot). **(2) `nvarchar` nach der Windows-Wortsortierung** (`ORDER BY`, `<`, `>`, `BETWEEN`, `MIN`/`MAX`, Indizes; alles über den einen Vergleich `SqlArithmetic.Compare`): Bindestrich und Hochkomma zählen zuerst nicht (`1, -1, 10, -10`), bei Gleichheit entscheidet ihre Stelle (wer keins hat, vorn; wer das erste früher hat; bei gleicher Stelle das Hochkomma vor dem Bindestrich; eine kürzere Folge vor der längeren); die druckbaren ASCII-Zeichen stehen in der Reihenfolge der Windows-Tabelle (Leerzeichen, Hochkomma, Bindestrich, dann die Satz- und Sonderzeichen, die Ziffern, die Buchstaben), ohne ICU (plattformgleich); mit Groß-/Kleinschreibung steht der Kleinbuchstabe vor dem großen, vor der Stelle der Sonderzeichen; Umlaute gehen über die Sprachregeln. 400 Zufallsketten je Alphabet (mit Satzzeichen, Leerzeichen, Umlauten, CI und CS) ohne Abweichung gegen `ORDER BY` am Orakel. **`varchar`** unter `SQL_Latin1_General_CP1_*` hat eine andere, eigene Tabelle (alle Zeichen, die keine Ziffer und kein Buchstabe sind, nach ihrem Code, Bindestrich und Hochkomma zählen mit; mit CS der Großbuchstabe zuerst) und ist jetzt ebenso nachgebildet. `WortsortierungTests` (9 Fälle; Sabotage: Wortsortierung abgeschaltet → 3 rot, SQL-Tabelle abgeschaltet → 2 rot). **(3) `SELECT` auf `sys.*`:** jede Spalte, die direkt aus einer Systemsicht kommt, meldet die Merkmale des Orakels in `COLMETADATA` — NOT NULL als fester Typ ohne Nullbit (`int` 0x38, `datetime` 0x3D, `bit` 0x32 …) mit 0x0008 statt nullable 0x0001, berechnete Spalten 0x0020/0x0021, `sysname` als Benutzertyp 256, die Zeichenspalten des Katalogs mit ihrer Sortierung (`0904100000` u. a.) statt der der Datenbank; die Tabelle (608 Sichten, `SELECT TOP 0 * FROM sys.<sicht>` am Orakel mitgeschnitten, erzeugt mit `tools/ssms-compat/Generate-Sichtmerkmale.py`) steht in `Sichtmerkmale.Daten.cs`; Verbunde und Aliasse werden aufgelöst, ein `ORDER BY` auf einen Ausdruck ebenso. Ein Wert, den das Orakel nie als NULL liefert, hier aber doch (z. B. `security_id` in `sys.dm_exec_sessions`), lässt die Spalte nullbar. `sys.objects` liefert jetzt `create_date`/`modify_date` (vorher NULL). Solche Abfragen werden gesammelt statt gestromt. `SichtmerkmaleTests` (14 Fälle; Sabotage: feste Typen abgeschaltet → rot). **(4) `CHECKCONSTRAINTS` bei mehreren verletzten CHECK:** die Zeile meldet die mit der kleinsten Objektnummer (siehe S12.3i). **(5) Dichte Histogramme** (siehe S12.3m): `DichteStufen` bildet die Stufenwahl des Orakels für gleich häufige Werte mit gleichem Abstand bis 400 Werte nach — eine Halde (binärer Min-Haufen, Schlüssel gleich: die Stelle entscheidet) mit den Kosten der Kanten zwischen Einheiten. Gemessen: das Ergebnis hängt nur von der Zahl der Werte ab (Wert, Abstand und gemeinsame Häufigkeit 1 bis 3 ändern nichts); die erste Verschmelzung ist immer die der Werte 2 und 3; ab 202 Werten steht ein Einzelwert an Stelle 3, ab 398 eine Dreiergruppe vorn, bei 400 eine zweite hinten. `DichteStufenTests` (16 Fälle; Sabotage: Kosten ohne Stellenparität → 11 rot).✅
S12.3mHistogramm: Häufigkeitsklassen der Kanten und mehrfache Zwangsverschmelzung**Behoben (01.10.2026), je Punkt am Orakel 2025 gemessen.** **(1) Wer zuerst verschmilzt:** hat die Liste mehr Werte als Platz, ist der erste Schlüssel einer Kante die **Klasse ihrer ersten Häufigkeit** (Häufigkeit geteilt durch die Klassenbreite, ganzzahlig), erst dann der Unterschied der beiden Häufigkeiten, dann die linke Stelle; die frühere Summe als dritter Schlüssel war ein Abbild davon, solange jede Häufigkeit eine eigene Klasse hatte (bei 201 Werten stimmen jetzt 56 von 56 Zufallsverteilungen mit Häufigkeit bis 4 bis 400, vorher 27). **(2) Die Klassenbreite hängt von der Gesamtzeilenzahl ab** (das war die offene Frage nach der Schwelle 2^k-1, die bei 6144 Zeilen von 7 auf 15 springt): sie ist die größte Zweierpotenz, die höchstens (Zeilen durch 512, ganzzahlig) mal Wurzel 2 ist (unter 1024 Zeilen 1); die Sprünge liegen auf die Zeile genau bei 1024, 1536, 3072, 6144, 11776, 23552, 46592, 93184 und 185856 Zeilen (gemessen je mit einer Zeile weniger und genau auf der Schwelle; an der Zeile hängt nur die Gesamtzahl, nicht Lage, Abstand oder Spitze am Ende). **(3) Mehrere Kanten verschmelzen** (5 bis 20 über dem Platz): sie werden nacheinander gewählt, Kanten nebeneinander ergeben Dreiergruppen, die letzte Kante nie; **hinter einer Einheit zählt nicht die Häufigkeit des nächsten Werts**, sondern die Klasse der Bereichszeilen der entstehenden Stufe (Summe der Einheit) und der Unterschied, mit dem die Einheit entstand (`(45,45)` und `(50,45)` ziehen den Nachbarn mit, `(48,48)`, `(52,45)` und `(20,70)` nicht). Von 40 gemessenen Verteilungen stimmen 22 (vorher 4). **Test:** `StatistikHaeufigkeitsklassenTests` (48 Fälle: die neun Schwellen mit Zeile davor und darauf, die Klassen, die Reihenfolge einer Kante, 14 Zufalls- und 7 Mehrfachverteilungen, 9 Einheitskanten; Sabotage: Zeilen durch 1024 → 16 rot, Unterschied der Einheit ignoriert → 9 rot, Wahl nach der Ankunft durch Wahl unter allen Kanten ersetzt → 1 rot). **Was offen bleibt, steht in S12.3n.**✅
S12.3nHistogramm: dichte Werte über 400 und die Reste der mehrfachen Verschmelzung**Umgesetzt (02./03.10.2026), je Regel am Orakel 2025 gemessen.** `Stufenhalde` liest die Werte der Reihe nach zu **201 Einheiten** (kleinster Wert allein, ohne Haldeneintrag; der neueste offen). Kante = linke Einheit mit ihrer Nachbarin, Schlüssel (Klasse der Bereichszeilen der entstehenden Stufe, Unterschied: bei Einzelwert die Häufigkeitsdifferenz, sonst der Unterschied, mit dem die Einheit entstand). Halde: binärer Min-Haufen im Feld; Senken tauscht auch bei Gleichstand, nimmt das rechte Kind nur, wenn es echt kleiner ist; Heben streng; Entfernen = letzter Eintrag in die Lücke, senken, sonst heben. Verschmelzen: der Eintrag der rechten Einheit fällt, der linke wird an Ort neu bewertet, der Vorgänger entfernt und neu eingefügt. Je weiterer Wert genau eine Verschmelzung: die eben fertige junge Kante gewinnt gegen die Spitze, wenn sie kleiner ist oder gleich und höchstens so teuer wie die letzte Entnahme; **sonst schwebt sie und kommt erst unmittelbar vor dem nächsten Verschmelzen einer Spitze mit Anheben in die Halde** (03.10.2026; ersetzt die Ablage ohne Heben samt den Sonderregeln für Bau und Schluss, am Schluss geht die schwebende Kante der Spitze vor, wenn sie echt billiger ist; Zufallsverteilungen 323 statt 138 von 400). Danach Toleranzpaare (S12.3m) und der Einheitendurchgang mit 5-%-Dichte je Seite und Verkettung; **ein mehrteiliges X verschmilzt mit einem Bereich, wenn alle Werte von X und des Bereichs gleich häufig sind** (03.10.2026, nicht nur Einsen: 35 von 35 neuen Verschmelzungen richtig, keine falsche; 351 von 400); ein Einzelwert X nur vor einem Bereich aus lauter Einsen, ein Einzelwert Y nach der 1/20-Regel. **Belegt** Zeile für Zeile: dichte Werte für jedes N von 3 bis 1781, Abstände 1/3 für jedes N von 3 bis 1023, alle 1001 Proben der Zusammenfassung, 351 von 400 Zufallsverteilungen (`docs/messreihen/s123n-zufall.txt`), 34 Präfix-Reihen (Zufall, dichte Werte mit Klassenbreite 4 bis 32, Sonden; `s123n-reihen.txt`) bis auf benannte Stellen. `StatistikMessreihenTests` (Zufall und Präfixe als „bis auf den benannten Rest“: jede Verbesserung oder jeder Rückschritt fällt auf). Sabotagen: Senken bei Gleichstand aus → 246 rot; schwebende Kante ohne Anheben → 65 rot; Einheitendurchgang wieder nur Einsen → Zufallstest rot. **Der Rest steht in S12.3o.**✅
S12.3oHistogramm: Reihenfolge gleich teurer Kanten in langen Läufen und nicht gebildete Toleranzpaare**Begründet abgeschlossen (04.10.2026, Entscheid des Auftraggebers): durch Messung von aussen nicht ableitbar.** Das Modell (binärer Min-Haufen über Einheiten, S12.3n) trifft dichte Werte für jedes N bis 1781, Abstände 1/3 bis 1023, alle 1001 Proben und 351 von 400 Zufallsverteilungen Zeile für Zeile. Die verbleibenden Abweichungen betreffen nur die Wahl zwischen **gleich teuren** Kanten in langen Läufen (dichte Werte ab N = 1782, Breite 4 ab 942, Breite 8 ab 1570, 19 Zufallsfälle) und 27 von 1987 Toleranzpaaren nach dem Bau der Halde — die Stufen haben dann gleiche Kosten, die Schätzung bleibt gleichwertig. Geprüft und verworfen: über 3000 Gleichstands- und Haldenregeln, Gleitkomma-Schlüssel, rund 200 andere Warteschlangen (d-när, Paarungs-, Binomial-, Linksbaum-, Turnierbaum, sortierte Listen …), 350 gezielte Orakelsonden (eine Naht-Vertauschung, keine einzelne Ursache; Toleranzpaare ohne Kipppunkt, abhängig vom ganzen Lesezustand). Werkzeuge: `tools/histogramm/stufenhalde_nachbau.py`, `warteschlangen_hypothesen.py`, `orakelsonde.py`; Daten `docs/messreihen/s123n-*.txt`, `s123o-sonden.txt`; Herleitung in `docs/umsetzung.md` (Absätze S12.3o). Die Rest-Listen in `StatistikMessreihenTests` halten die bekannten Fälle fest, jede Änderung schlägt an.✅
S12.4aBetriebsfeatures: EinstellungenAbfragespeicher (`ALTER DATABASE … SET QUERY_STORE`, `sys.database_query_store_options`) — **voreingestellt an**, wie in SQL Server 2025. Ressourcenkontrolle vollständig: `CREATE/ALTER/DROP RESOURCE POOL` und `WORKLOAD GROUP`, `ALTER RESOURCE GOVERNOR`, `internal` und `default` geschützt. Der Stapelmodus und die übrigen 40 `sys.database_scoped_configurations` lassen sich stellen und zurücklesen. `MEMORY_OPTIMIZED = ON` gibt **41337** wie SQL Server ohne passende Dateigruppe. **Gedrosselt wird nichts**, und das sagt der Server auch.✅
S12.4bBetriebsfeatures: mit WirkungWas S12.4a nur speichert, wirkt. Der **Abfragespeicher sammelt** über TDS je Anweisung Text, Plan, Laufzeitwerte je Intervall und Sperrwartezeiten (`sys.query_store_query`/`_query_text`/`_plan`/`_runtime_stats`/`_runtime_stats_interval`/`_wait_stats`); `OPERATION_MODE`, `QUERY_CAPTURE_MODE` (AUTO ab der 30. Ausführung wie am Orakel), `INTERVAL_LENGTH_MINUTES` (153), `MAX_STORAGE_SIZE_MB` mit Bereinigung oder `READ_ONLY`/65536; `sp_query_store_flush_db`, `_force_plan`/`_unforce_plan`, `_remove_query`/`_remove_plan`, `_reset_exec_stats` (12402/12403/12406); master/tempdb ohne, msdb aus. Heisser Weg gemessen: 55 ns und 0 Bytes je Erfassung, mit 8 Fäden 57 ns dank Streifen je Prozessor; aus kostet er eine Prüfung. Die **Ressourcenkontrolle drosselt**: Klassifizierungsfunktion (10902/10906/10920, wirksam ab `RECONFIGURE`) ordnet Sitzungen ein, `GROUP_MAX_REQUESTS` lässt warten, `REQUEST_MAX_CPU_TIME_SEC` zählt Verstösse ohne Abbruch wie SQL Server 2025, `sys.dm_resource_governor_*` zeigen gemessene Zähler, `RESET STATISTICS`. Bereichsweit wirken `VERBOSE_TRUNCATION_WARNINGS` (8152), `GLOBAL_TEMPORARY_TABLE_AUTO_DROP` in tempdb und die `MAXDOP`-Prüfung (12108).✅
S12.4cBetriebsfeatures: der RestDie **Messwerte des Abfragespeichers überleben den Neustart**: der Kontrollpunkt (S12.14) schreibt Einstellungen, Zähler, Intervalle, Abfragen und Laufzeitwerte mit in die Datendatei, `sp_query_store_flush_db` löst ihn aus, das Einspielen des Protokolls zählt nicht als Ausführung. Anweisungen **in Blöcken und Modulen** (`IF`/`WHILE`, Prozeduren, Funktionen, Trigger) werden mit `object_id` erfasst, Variablen und Parameter stehen als Kopf davor (`(@y int,@x int)SELECT …`), `sp_executesql` mit Parametern heisst `User`. Einfache Abfragen werden **parametrisiert** abgelegt wie am Orakel (`(@1 tinyint)SELECT [a] FROM [t] WHERE [a]=@1`, `UPDATE … set …` mit zwei Leerzeichen vor `WHERE`, `values(@1,@2)`, Typen `tinyint`…`numeric(p,s)`/`varchar(8000)`/`nvarchar(4000)`/`money`/`float`/`varbinary(8000)`, `COLLATE`, `BETWEEN`), `OR`/`IN`/`LIKE`/`TOP`/Verbund/Module nicht; die Form entsteht nur beim ersten Auftreten eines Texts. Ein **Planer** teilt eingeordneten Anfragen Planerplätze in Quanten von 4 ms nach `MIN`/`MAX_CPU_PERCENT` und `IMPORTANCE` (1:3:9) zu, `CAP_CPU_PERCENT` legt Pausen ein, `ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU` (5853/5856) stellt die Plätze; über TDS gemessen 25,0 % bei `MAX 25`, 69,8 % bei `MIN 70`, 89,6 % High gegen Low, 30 % bei `CAP 30`. Ohne Klassifizierungsfunktion eine Prüfung auf null; vorher/nachher an vielen kleinen Stapeln in der Streuung, 160 Bytes je Stapel weniger. Ohne Entsprechung, weil KuonenSQL die Mechanik nicht hat (seriell, kein Kostenoptimierer, kein Plancache): `MAXDOP` als Grenze, `QUERY_OPTIMIZER_HOTFIXES`, `LEGACY_CARDINALITY_ESTIMATION`, `PARAMETER_SNIFFING`, die Rückkopplungs- und Stapelmodus-Schalter, `IDENTITY_CACHE` (KuonenSQL springt nie, verhält sich also immer wie `OFF`).✅
S12.4dBetriebsfeatures: ArbeitsspeicherEine **Speicherzuteilung je Anfrage**: je Pool ein Semaphor (Ziel drei Viertel des Pool-Speichers, auch unter `max server memory`), vor jeder Abfrage mit Sortierung, Gruppierung, `DISTINCT`, `UNION`, Fenster oder Hash-Verbund (512/1024 KB Minimum, `TOP` bis 100 ohne), gekürzt auf `REQUEST_MAX_MEMORY_GRANT_PERCENT` (auch mit Nachkommastellen) — **8657** mit Schweregrad 17, Stapelabbruch und zurückgenommener Transaktion, wenn das Minimum darüber liegt; Warten mit `RESOURCE_SEMAPHORE` bis `REQUEST_MEMORY_GRANT_TIMEOUT_SEC`, danach das Minimum (`forced_grant_count`), wie am Orakel. Ehrlicher Messpunkt: gebucht wird, was die Operatoren halten, aus dem Aufbau der Objekte. Die **Sortierung lagert aus** (sortierte Läufe in Dateien, Mischen, mit `TOP` nur das Nötige). `sys.dm_exec_query_memory_grants`, `sys.dm_exec_query_resource_semaphores`, `used_memgrant_kb` und Zähler je Pool/Gruppe, `granted_query_memory`, `*_query_max_used_memory` im Abfragespeicher. **`MAX_IOPS_PER_VOLUME`** drosselt die Auslagerungs-E/A in 64-KB-Blöcken (Lesen und Schreiben zusammen, wie am Orakel), `sys.dm_resource_governor_resource_pool_volumes`. Der Abfragespeicher erfasst `SELECT dbo.f(1)` und **`PARAMETERIZATION FORCED`** (36 Formen am Orakel abgelesen).✅
S12.4eBetriebsfeatures: Auslagern der übrigen Operatoren**Gruppierung und `DISTINCT` lagern jetzt nach Grace aus** statt nur zu buchen: `Hashpuffer<TLive, TStored>` verteilt Zeilen nach dem Hash ihres Gruppierungs-/Entdoppelungsschlüssels auf 16 feste Fächer (zwei gleiche Schlüssel landen dabei immer im selben Fach), reicht die Zuteilung nicht, schreibt er sie über denselben `Auslagerungslauf<T>` wie die Sortierung — kein zweiter Auslagerungsweg. **`INSERT … SELECT`, `UPDATE`/`DELETE … FROM` mit Verbund, `MERGE` gegen eine echte Quelle und der Körper einer CTE fordern jetzt ebenfalls eine Zuteilung an** (vorher gingen sie am Zuteilungsweg vorbei); am Orakel gemessen braucht `INSERT … SELECT … ORDER BY` ohne `TOP` **keine** (die Einfügereihenfolge ist nicht zugesichert, wird wegoptimiert), mit `GROUP BY`/`TOP … ORDER BY` aber doch; `UPDATE … FROM JOIN` 1024–2048 KB, `DELETE … FROM JOIN` 512–1024 KB, `MERGE` gegen eine Unterabfrage 1536 KB — ohne Kostenoptimierer gilt hier einheitlich die feste Grösse eines SELECT-Verbunds (1024 KB). Beim Bauen eine echte Leistungsregression gefunden und behoben: eine erste Fassung materialisierte jeden Zeilenkontext bei jeder Zeile in ein Dictionary, auch ohne je auszulagern — das 2,8-Fache einer gewöhnlichen Gruppierung; behoben über zwei Typparameter (die rohe, oft lazy Ankunftsform bleibt bis zum tatsächlichen Auslagern unangetastet) und denselben schnellen Weg, den `Buche()` für einen `ZeilenKontext` schon hatte. Punktabfrage/`GROUP BY`/`DISTINCT` liegen danach bei oder unter dem Stand vor diesem Punkt. **Was begründet offen bleibt, steht als S12.4f.**✅
S12.4fBetriebsfeatures: Hash-Verbund und Fenster lagern aus**Der Hash-Verbund lagert jetzt nach Grace aus:** passt seine rechte Seite nicht mehr in die Zuteilung, verteilt der `Hashpuffer` (derselbe wie bei Gruppierung und `DISTINCT`, über denselben `Auslagerungslauf` — kein zweiter Weg) BEIDE Seiten nach dem Hash des Verbundschlüssels auf dieselben 16 Fächer, und jedes Fach wird für sich verbunden — INNER/LEFT/RIGHT/FULL, NULL-Schlüssel (finden ohnehin keinen Partner) und mehrere verkettete Verbunde (jede Stufe für sich). Ausgelagert wird nur, wenn sich beide Seiten sicher verteilen lassen; passt auch nur ein Schlüssel nicht zur Art seiner Spalte (Zeichenkette gegen `int`), bleibt es beim bisherigen vollen Weg — eine solche Zeile muss jedes Fach der Gegenseite sehen. **Fensterfunktionen mit gemeinsamem `PARTITION BY` lagern ebenfalls aus:** die Eingabe kommt nach dem Partitionsschlüssel in Fächer, jede Partition rechnet für sich mit ihren `ROWS`-/`RANGE`-Rahmen und ihrer Sortierung. Der Partitionsschlüssel ist dieselbe Regel (`Fensterschluessel`), nach der `ComputeWindowFunction` selbst Zeilen einer Partition zuweist — zwei Zeilen derselben Partition haben so garantiert dasselbe Fach. **`sys.dm_exec_query_stats` fuehrt `*_grant_kb`, `*_used_grant_kb`, `*_ideal_grant_kb` und `*_spills`** (zuvor deklariert, aber leer): Zuteilung, Verbrauch, Wunsch und die auf Auslagerungsdateien geschriebenen 8-KB-Seiten, je Stapel. Leistung vorher/nachher (Engine im Prozess, Release, 1000 Zeilen, bester von 15 Läufen im Wechsel): Punktabfrage 144,5 → 131,2–141,7 µs, Verbund 4320 → 3774–4441 µs, LEFT-Verbund 5247 → 4908–5549 µs, `GROUP BY` 1958 → 1635–1983 µs, Fenster 3708 → 3540–3863 µs — im Rauschen (±15 %), nichts langsamer. Was begründet bleibt, steht als S12.4g.✅
S12.4gBetriebsfeatures: Grenzen des Auslagerns**Behoben (30.09.2026).** **Fenster ohne `PARTITION BY` und Fenster, die verschieden partitionieren, lagern jetzt aus:** ihre Eingabe kommt in einen `Zeilenspool` — die Zeilen in ihrer Reihenfolge, solange sie in die Zuteilung passen in einer Liste, danach in einer Datei (derselbe `Auslagerungslauf` wie Sortierung, Gruppierung und Verbund, ein Weg fürs Auslagern), wahlfrei gelesen über einen Block-Zwischenspeicher (8 × 64 KB, jeder gelesene Block ein E/A-Vorgang des Pools) und einen direkt abgebildeten Zeilen-Zwischenspeicher (256 Plätze, damit Nachbarzeilen für `RANK`, `LAG`, `ROWS`-Rahmen nichts kosten). Die Fensterrechnung liest ihre Zeilen darüber wie über eine Liste (`IReadOnlyList` statt `List`); jede Funktion partitioniert und sortiert über ihre EIGENEN Indexlisten, die Quelle wird nur einmal gelesen — auch ein Verbundstrom mit `NEXT VALUE FOR`, der sich nicht wiederholen lässt. Der Sortierschlüssel einer Zeile wird dabei nur einmal gerechnet und erst, wenn ein Vergleich ihn braucht (das macht auch die Fenster im Speicher schneller: vorher wertete jeder Vergleich beide Ausdrücke neu aus). **`sys.dm_exec_query_stats` führt jetzt eine Zeile je Anweisung** wie SQL Server (bisher je Stapel): Stelle im Stapeltext in Byte (`statement_start_offset`/`_end_offset`), Ausführungen, Zeit, gelieferte oder betroffene Zeilen (`total_rows`), Zuteilung und Auslagerung je Anweisung. Am Orakel 2025 gemessen: nur Anweisungen mit Plan zählen (ein `SELECT` mit Tabelle, `INSERT`, `UPDATE`, `DELETE`, `MERGE` — nicht `DECLARE`, `SET`, `IF`, `CREATE`, kein `SELECT` ohne Tabelle), das Ende ist die Stelle des letzten Zeichens ohne Semikolon und Leerraum — auch bei der letzten Anweisung, **nie -1** —, und eine Anweisung in einer Schleife zählt je Durchlauf. Anweisungen aus Prozeduren und dynamischem SQL zählen nicht mit (dort hat SQL Server ein eigenes Handle); ein Stapel ohne eine zählende Anweisung steht wie bisher als Ganzes da. **Wie am Orakel, darum kein Fehlbestand:** das Minimum ist fest (512 KB Sortierung, Gruppierung, `DISTINCT`, Fenster; 1024 KB Hash-Verbund) und nur der Wunsch wächst mit den Zeilen. **Begründete Ausnahmen, am Orakel nicht auslösbar (AGENTS.md verbietet dafür Last und serverweite Eingriffe):** `MIN_IOPS_PER_VOLUME` reserviert E/A nur unter Wettbewerb um ein Volumen (KuonenSQL hat ein Volumen und wartet nie auf ein anderes) — ohne Last am Orakel nicht zu beobachten; **8645** kam am Orakel nie (statt eines Fehlers bekommt die wartende Anfrage nach der Frist ihr Minimum, `forced_grant_count`), ein Fehler ohne Vorbild wird nicht erfunden. **Aus dem Auslagern herausgelöst und nach P15.8 verschoben** (es sind Fragen des Stromwegs, nicht der Zuteilung): der Verbund ohne hashbaren Schlüssel und Ergebnisse, die bis zum Senden im Speicher bleiben. Belegt durch `FensterAuslagerungTests` (sieben Fälle ohne gemeinsame Partition, Sabotage: Zuteilung ohne Grenze → 7 rot) und `AnweisungsstatistikTests` (Sollwerte vom Orakel, Sabotage: Zählung abgeklemmt → 6 rot).✅
S12.5REST aus T-SQL`sp_invoke_external_rest_endpoint` mit den Prüfungen von SQL Server (**31643**, **31610**, **31605**, **31608**) und **einem Riegel mehr**: der Zielliste `KUONENSQL_RESTZIELE`. Ohne beide geht kein Byte hinaus.✅
S12.8Aenderungsverfolgung**Beides steht.** Change Tracking: `SET CHANGE_TRACKING`, `ENABLE CHANGE_TRACKING`, `CHANGETABLE(CHANGES …)` und `(VERSION …)`, `WITH CHANGE_TRACKING_CONTEXT`, drei Funktionen, zwei Sichten. Change Data Capture: die fuenf Prozeduren, das `cdc`-Schema mit seinen fuenf Tabellen und je Aufnahme eine `_CT`-Tabelle, die beiden Tabellenfunktionen samt Nettoansicht, sieben `fn_cdc_*`-Funktionen. **KuonenSQL braucht dafuer keinen Agenten** — die Zeile steht sofort da. Zwoelf Fehlernummern Wort fuer Wort.✅
S12.9Auftragsverwaltung in msdbDie neun Tabellen samt den 21 Kategorien und 7 Zeitplaenen einer frischen msdb, `sp_add_job` und Verwandte. **Neu:** `sp_help_job` mit seinen vier Ergebnismengen (32, 23, 19 und 9 Spalten) samt den Ueberschriften, `sp_help_jobstep`, `sp_help_schedule`, `sp_help_jobhistory` (11 Spalten, mit `@mode = FULL` 17), dazu `sp_add_schedule`, `sp_attach_schedule`, `sp_detach_schedule`, `sp_delete_schedule` und `sp_update_schedule` mit `sysschedules` und `sysjobschedules`. Ein Agent laeuft nicht: kein Auftrag laeuft, kein Verlauf entsteht, und `next_run_date` bleibt 0.✅
S12.10Sortierungen: die SpeicherungDie vier Ebenen, die Wirkung auf Vergleich und Sortierung, `sys.fn_helpcollations()` und `COLLATIONPROPERTY` stehen mit **B8.10**.✅
S12.11Massenimport und -export`BULK INSERT` liest CSV- und Trennzeichendateien, mit `FORMATFILE` (klassisch und XML), `CODEPAGE` und `DATAFILETYPE`. `OPENROWSET(BULK …)` liest eine Datei als Tabelle, ohne sie zu importieren. **Neu:** **`bcp` von Microsoft selbst** — beide Richtungen, alle vier Datenformen, klassische und XML-Formatdatei, über den echten TDS-Massenkopierstrom und `sp_describe_first_result_set`. Die Dateien sind mit denen von SQL Server 2025 **bytegleich**.✅
S12.6`SET`-Optionen mit WirkungDie Optionen wurden nur gemerkt; jetzt tragen sie. Ein Sitzungsschalter bringt sie in den Ausdrucksauswerter: `ANSI_NULLS`, `ANSI_PADDING`, `CONCAT_NULL_YIELDS_NULL`, `ARITHABORT`/`AN…✅
S12.7Sicherung, Wiederherstellung und Serveranweisungen**Eine wirkliche Sicherung:** `BACKUP DATABASE`/`LOG` schreibt Katalog, Rechte und alle Zeilen in eine Datei, `RESTORE DATABASE` holt sie zurueck — auch unter einem anderen Namen. Dazu `FILELISTONLY` (22 Spalten), `HEADERONLY` (59), `VERIFYONLY`, `LABELONLY`, die Optionen `INIT`/`NOINIT`/`DIFFERENTIAL`/`COPY_ONLY`/`REPLACE`/`FILE`/`MOVE`/`NAME`/`DESCRIPTION`, die fuenf Verlaufstabellen in `msdb`, `CHECKPOINT` und `ALTER SERVER CONFIGURATION`. Sechs Fehlernummern Wort fuer Wort. **Das Dateiformat ist nicht das von Microsoft** — dessen ist nicht veroeffentlicht.✅
S12.12aErweiterte Ereignisse: Ziele und HandschlagDie aufgezeichneten Ereignisse sind jetzt lesbar: `sys.dm_xe_session_targets.target_data` liefert beim Ringpufferziel das vollstaendige `<RingBufferTarget>`-XML mit allen sechs Attributen, den Typangaben, den Aktionen und dem Text eines Abbildungstyps; beim Dateiziel das gemessene `<EventFileTarget>`. `sys.fn_xe_file_target_read_file` gibt dieselben Ereignisse im **anderen** gemessenen Format zurueck — ohne Typangaben, mit CDATA. `sys.fn_MSxe_read_event_stream` schickt den am Orakel Byte fuer Byte abgelesenen Handschlag. **Offen bleibt der Metadatenvorspann des Livestroms** — rund 5 KB in einem undokumentierten Binaerformat, das zu raten hiesse, SSMS falsche Zahlen vorzulegen.✅
S12.12bLivestrom: Vorspann und EreignissätzeDer Binärstrom von `sys.fn_MSxe_read_event_stream` ist **am Orakel vermessen und wird aus dem eigenen Katalog erzeugt**: Handschlag, Metadatenvorspann (Pakete, Ereignisse samt Spalten, Aktionen, Karten mit Schlüsselbreite, Typen — jedes Objekt unter seiner Nummer beim Server, dafür stehen `dm_xe_packages`/`_objects`/`_object_columns` jetzt als Referenzdaten in Serverreihenfolge bereit) und die Puffer mit den Ereignissätzen. Byte-gleich zum Orakel für 20 Vorspanne über alle 2943 öffentlichen Ereignisse und für Sätze mit und ohne Aktionen; **XELite** und **XEvent.Linq** (der Leser des SSMS-Profilers) lesen den Strom von KuonenSQL mit denselben Ereigniskennungen, Feldern, Aktionen und Typen wie den des Orakels. Unbekannte Sitzung 25728, Dateiweg 25718.✅
S12.12cLivestrom: offen halten`sys.fn_MSxe_read_event_stream` **hält die Antwort offen wie SQL Server**: nach Handschlag und Vorspann geht je Takt von `MAX_DISPATCH_LATENCY` (bei `INFINITE` 4 s, gemessen) ein Puffer als eigenes TDS-Paket hinaus, nur mit Ereignissen nach dem Anmelden; gleichzeitige Leser bekommen Byte für Byte denselben Puffer; Attention endet mit DONE_ERROR und genau einem DONE_ATTN, STOP/DROP mit **25727**, ein Verbindungsabriss meldet den Leser ab — alles am Orakel vermessen, auch über RPC. **XELite** und **XEvent.Linq** (SSMS 22) lesen einen offenen Strom von KuonenSQL wie den des Orakels. Die Puffernummer ist zugeordnet (seit START, mit Aufteilung `0xFFFFFFFF`), die Satzvariante `0x4A` als Speicherrest erkannt; der gewöhnliche Antwortweg ist gemessen nicht langsamer.✅
S12.12dLivestrom: GrenzfälleWas S12.12c begründet offen liess, ist am Orakel vermessen und behoben: die Spalte `data` heisst jetzt **`image`** statt `varbinary(max)` (Ursache war `TdsTokenWriter.HasFacets`, das `image` bei mehr als einer Zeile über `SqlTypeSpec.Union` neu ableitete — dessen Binärzweig kennt nur die modernen Typen); der Strom bleibt jetzt **immer** offen — auch mit einer Meldung davor, einer Ergebnismenge davor und mit `WHERE type = 0` —, während eine Anweisung *danach* im selben Stapel nie mehr läuft (`ExecutionEngine.StromHaeltStapel()`). **Weiterhin nicht zuordenbar, benannt:** `u32 1` im Paketkopf, die Artzahlen 72/72/56/48/80/96/40/32, `0xCC` am Pufferkopfende und der Zusatz `0x100000008`, den von 75 Aktionen nur `event_sequence` trägt — sie hängen an nichts, was sich von aussen ändern liesse; KuonenSQL schreibt die gemessenen Werte.✅
S12.12eLivestrom: Reste**Begründet abgeschlossen, nach erneuter Prüfung (2026-09-26):** die Nummer eines zweiten Puffers im selben Takt (`MAX_DISPATCH_LATENCY`) lässt sich am Orakel nicht zuverlässig erzwingen (eine Zwei-Puffer-im-selben-Takt-Lage ist eine Timing-Frage des Servers selbst, keine, die ein Client von aussen deterministisch auslösen kann) — KuonenSQL vergibt in diesem Fall die nächste laufende Nummer (`NaechstePuffernummer()`), eine vertretbare, im Code selbst benannte Annahme. Ein RPC-Aufruf mit Rückgabestatus oder OUTPUT-Parametern hält weiterhin keinen Strom offen, unverändert seit S12.12c: die Funktion `sys.fn_MSxe_read_event_stream` hat kein Rückgabeobjekt, an das sich OUTPUT anhängen liesse, es gibt also nichts zu messen. Beides bleibt eine Grenze, keine Lücke.✅
S12.13Getrennte DatenbankenBisher gab es einen einzigen Katalog; die Datenbanken waren nur Namen, und `USE` wirkte auf alle Verbindungen.✅
S12.14Datendateien und KontrollpunktBisher war das Protokoll der einzige dauerhafte Speicher: jeder ändernde Stapel landete darin, beim Start wurde alles von vorne eingespielt, und …✅
S12.15Integrierte Anmeldung gegen das Betriebssystem`CREATE LOGIN [RECHNER\name] FROM WINDOWS` (15401), geprüft per `LogonUser` unter Windows und per **PAM** unter Linux und macOS. Die Zwischenschicht macht daraus einen Prinzi…✅
S12.16SSMS: „Neue Abfrage“ übernimmt die Anmeldung**Gemessen, kein Serverfehler:** mit einem Kennwort übernimmt „Neue Abfrage“ die Anmeldung; mit dem leeren Kennwort von `sa` fragt SSMS jedes Mal neu — ein leeres Kennwort behandelt SSMS wie „keins gespeichert“. Abhilfe ist ein Kennwort (`ALTER LOGIN sa WITH PASSWORD = …`). Beim Versuch fielen drei echte Fehler auf und sind behoben: `ALTER LOGIN sa` meldete 15151 (sa lag nicht im Anmeldeverzeichnis), `CREATE`/`ALTER LOGIN` aus dem SSMS-Dialog scheiterte an `MUST_CHANGE`/`OLD_PASSWORD`/`UNLOCK` (102), und die Servereigenschaften „Erweitert“ stürzten ab, weil `xp_msver` im Linux-Container `Linux x86_64` als Windows-Version meldete (jetzt wortgleich wie das Orakel). Das Server-Symbol („läuft“) kommt vom Windows-Dienstverwalter, nicht über TDS; die Dienststeuerung hat der Nutzer zurückgestellt. `MUST_CHANGE` wird angenommen, aber noch nicht durchgesetzt✅
S12.17Datenbankzugriff für Anmeldungen ohne sysadminBisher lieferte `HAS_DBACCESS` immer 1 und jede Anmeldung konnte jede Datenbank öffnen. Jetzt braucht es dafür einen eigenen Datenbankbenutzer (`CREATE USER … FOR LOGIN`), Besitz (`ALTER AUTHORIZATION ON DATABASE::`) oder `guest` — `master`/`tempdb`/`msdb` haben `guest` von Haus aus an, wie am Orakel. `USE` und die Standarddatenbank beim Anmelden melden ohne das **916** bzw. **4060**, wortgleich zum Orakel; sysadmin-Mitglieder und die Besitzerin gehen wie immer an jeder Prüfung vorbei (dafür jetzt auch `ALTER SERVER ROLE sysadmin ADD MEMBER` — die neun mitgelieferten Serverrollen liessen bisher gar keine Mitglieder zu). `sys.databases`/`master.dbo.sysdatabases` zeigen ohne `VIEW ANY DATABASE` nur `master`, `tempdb` und die eigenen Datenbanken, GRANT/DENY/REVOKE davon wirkt serverweit (neu `Serverinstanz.Serverberechtigungen`, unabhängig von der Datenbank, in der man gerade sitzt) und steht in `sys.server_permissions`.✅
S12.17bDatenbankzugriff: RestDie Anfrage, die SSMS' Objekt-Explorer für die Datenbankliste schickt, ist **doch messbar**: im Protokoll des Server-Containers steht die Sitzung eines live verbundenen SSMS (`dist\ssms-protokoll.ps1` sucht nur die letzten Minuten und hatte sie deshalb nicht gefunden). Wortgleich als Prüfling in die Tests (`SsmsAbfragen`), als Anmeldung ohne sysadmin gegen Orakel und KuonenSQL gehalten. **Erster Befund:** KuonenSQL wies sie mit **229** ab — die vier `#tmp`-Tabellen, die sie anlegt und füllt, verlangten das Recht `INSERT`; eine Anmeldung ohne sysadmin konnte **keine einzige temporäre Tabelle benutzen** (weder `#t` noch `##t` noch eine Tabellenvariable), am Orakel geht das für `public` ohne jedes Recht. Behoben in `PruefeZugriff` (temporäre Objekte sind frei), Gegenbeleg: eine gewöhnliche Tabelle bleibt ohne Recht zu. **Zweiter Befund:** die Besitzer-Zuordnung war eine Angabe je Datenbank und wirkte nur aus der Zieldatenbank oder aus master heraus; aus einer dritten Datenbank gesetzt ging sie verloren (am Orakel gilt sie aus jeder). Der Datenbankbesitz steht jetzt serverweit im Katalog von master (`Datenbankbesitzer`/`SetzeBesitzer`, ein Altbestand im eigenen Katalog gilt weiter, wenn master keinen hat); `BesitztDatenbank` (DROP LOGIN, 15174) und `IstEigentuemer` lesen dieselbe Stelle. **Dazu die Breite:** alle 209 verschiedenen Stapel aus dem SSMS-Protokoll als Anmeldung ohne sysadmin gegen das Orakel gehalten — 160 davon sind unschädlich (kein `ALTER`/`CREATE`/`DROP`/`DELETE` außer auf `#tmp`) und laufen am Orakel durch; **alle 160 laufen auch bei KuonenSQL durch**, bei 143 mit gleicher Zeilenzahl (die übrigen 17 unterscheiden sich im Inhalt — welche Datenbanken, Windows-Konten, mitgebrachte Tabellen in master —, nicht im Recht).✅
S12.18App-Abnahme T-SQL**nopCommerce 4.x (linq2db):** die Grunddaten kommen per `SqlBulkCopy` mit `KeepIdentity`; das scheiterte mit 544. Am Orakel (SQL Server 2025) vermessen und alle Optionen von `INSERT BULK` und `BULK INSERT` gleich umgesetzt: `KEEP_IDENTITY` (Werte uebernehmen, Zaehler zieht nach; ohne sie neue Werte, die Quelle wird ignoriert), `KEEP_NULLS` (sonst erbt NULL den Standardwert), `CHECK_CONSTRAINTS` (Bedingungen und Fremdschluessel nur mit), `FIRE_TRIGGERS` (Trigger nur mit); `TABLOCK`, `ROWS_PER_BATCH`, `ORDER` werden angenommen. `SqlBulkCopy` schickt `KeepIdentity` nicht als Namen, sondern laesst die Identitaetsspalte ohne die Option aus der Spaltenliste - dazu traegt der `SET FMTONLY`-Kopf jetzt das Identitaetsmerkmal. `BULK INSERT` und jeder Strom wirken ganz oder gar nicht (ein Strom ist ein Batch; mit `BatchSize` schickt der Client je Batch einen eigenen). `ROWS_PER_BATCH` und `ORDER` sind wie bei SQL Server reine Hinweise: gelesen, ohne Wirkung.✅
S12.19Ein Rechtemodell für alle Zugänge**Nutzerwunsch 01.10.2026.** Ein Benutzer, einmal angelegt, gilt über TDS, MariaDB (3306) und PostgreSQL (5432) mit demselben Kennwort; einzige Wahrheit sind Anmeldungen, Serverrollen, Datenbankprinzipale, Mitgliedschaften und GRANT/DENY/REVOKE der Engine (Modell SQL Server 2025). Rollen von PostgreSQL und MariaDB sind dieselben serverweiten Rollen; keine Nebenspeicher je Dialekt (`Pgzugriff`, `pg:role:*` entfallen). Über jede Zugriffsart änderbar, in jedem Katalog gleich sichtbar und gleich wirksam. Die Datenbankliste zeigt in jedem Zugang nur, was man benutzen darf — **bewusst strenger als echtes SQL Server und PostgreSQL** (Nutzerwunsch). Scheiben siehe Untertabelle S12.19a–j. Technisch unmöglich (genau begründet in der Umsetzung): eine MariaDB-Rolle und ein Benutzer gleichen Namens nebeneinander, `TRIGGER` und `TRUNCATE` getrennt (beides `ALTER` in SQL Server).✅
S12.19aKern: Merkmale, serverweite Mitgliedschaft, wirksame PrinzipaleAnmeldungsmerkmale im Kern statt `pg:role:*` (Verbindungsgrenze, gültig bis, Replikation, RLS umgehen, Rollen erben, md5-Ablage) über `ALTER LOGIN`/`ALTER SERVER ROLE … WITH` (Erweiterung, wie `DEFAULT_ROLE`), sichtbar in `LOGINPROPERTY`; `sys.server_role_members` vollständig (feste Rollen, Rolle in Rolle); die Rechtefrage zählt Datenbankbenutzer, Datenbankrollen und geerbte Serverrollen (T-SQL und PostgreSQL erben, MariaDB nur die eingeschaltete Rolle — die gemessene Semantik des Protokolls). `IS_SRVROLEMEMBER` und 33232 am Orakel gemessen; ein DENY an Benutzer oder Rolle sticht jedes GRANT.✅
S12.19bPostgreSQL-Rollen auf dem Kern`CREATE/ALTER/DROP ROLE|USER|GROUP` werden T-SQL: mit `LOGIN` eine Anmeldung, ohne eine Serverrolle; `SUPERUSER`→sysadmin, `CREATEDB`→dbcreator, `CREATEROLE`→securityadmin, `NOLOGIN` auf einer Anmeldung → deaktiviert; `GRANT rolle TO` → Serverrollenmitglied; `pg_roles`, `pg_authid`, `pg_auth_members`, `\du` aus `sys.*`; Altbestand (`pg:role:*`, Rollen je Datenbank) wird beim Start übernommen. Dazu: Kennwort einer Rolle ohne `LOGIN` bleibt bis `LOGIN`, `SET ROLE` und `GRANT rolle` prüfen die Berechtigung, ein Recht an eine Rolle hat ein Gegenstück in der Datenbank (SSMS sieht es).✅
S12.19cRechte ohne Nebenspeicher`Pgzugriff` entfällt: `TRIGGER`→`ALTER` auf die Tabelle, `USAGE` auf eine Folge → Recht auf die Folge, Schema- und Datenbankrechte in der Rechteverwaltung der Engine; `has_*_privilege` und die `aclitem`-Spalten (`relacl`, `nspacl`, `datacl`, mit Vergebendem und `*`) lesen `sys.database_permissions`; Besitz einer Datenbank durch eine Rolle im Kern (die Ausnahme in `DialektArchitekturTests` entfällt). Eine Rechtefrage im Kern (`Catalog/Rechtefrage.cs`) für Engine und `has_*_privilege`; `USAGE` auf ein Schema ist `VIEW DEFINITION` (in PostgreSQL wirksam), auf eine Folge `UPDATE`, `UPDATE` auf eine Folge `ALTER`; `TEMPORARY` hat in SQL Server kein Recht und gilt für jeden.✅
S12.19dAnmelden über alle drei ZugängeEine Vorprüfung für TDS, MariaDB und PostgreSQL: deaktiviert/`NOLOGIN`/`ACCOUNT LOCK`, Rolle ohne Anmeldung, Verbindungsgrenze (`CONNECTION LIMIT` = `MAX_USER_CONNECTIONS`), gültig bis — je mit der Fehlernummer des Protokolls. Gezählt über die Sitzungen aller Zugänge; eine Rolle ohne Anmeldung kam bisher als „unbekannt“ ohne Kennwort durch.✅
S12.19eMariaDB auf dem gemeinsamen Modell`ACCOUNT LOCK/UNLOCK`, `WITH MAX_USER_CONNECTIONS`; `mysql.user`, `mysql.db`, `mysql.tables_priv`, `mysql.roles_mapping`, `SHOW GRANTS`, `information_schema.*_PRIVILEGES`, `APPLICABLE_ROLES` zeigen, was T-SQL und PostgreSQL geändert haben. Dazu `GRANT CREATE, CREATE USER ON *.*` = `CREATEDB`/`CREATEROLE`; Anmeldungen und Rollen teilen sich die Namen.✅
S12.19fDatenbankliste nur, was man darfEine Regel im Kern für `sys.databases`/`sysdatabases`, `SHOW DATABASES`/`SCHEMATA` und `pg_database`: eigener Datenbankbenutzer, Besitz (auch über eine Rolle) oder guest; alle nur mit sysadmin, `VIEW ANY DATABASE`, `CREATE ANY DATABASE` oder `CONTROL SERVER` — ausdrücklich erteilt, nicht über `public`. **Bewusst strenger als SQL Server und PostgreSQL** (Nutzerwunsch); PostgreSQL prüft den Zugang jetzt beim Verbinden (42501), `postgres`/`template1` stehen jedem offen wie master.✅
S12.19g`ALTER DEFAULT PRIVILEGES`Vorgaberechte für künftige Objekte im Kern (auch als T-SQL-Anweisung, im Protokoll), angewendet beim Anlegen in jedem Zugang samt Folgen der Identitätsspalten; `pg_default_acl` liest den Kern; am Orakel P34–P42 gemessen.✅
S12.19hP18.13-ResteName einer temporären Tabelle auf `_`, fremde `pg_temp_M` sichtbar, Rollback der ersten temporären Tabelle behält die Schema-Marke nicht (am Orakel P43–P46), Umbenennen einer Datenbank im Protokoll der Engine.✅
S12.19jVerwaltungsrechte wirksamWer anlegen, ändern, löschen, Rollen zuweisen und Rechte vergeben darf, prüft der Kern für jeden Zugang (bisher durfte über T-SQL jede Anmeldung `CREATE LOGIN`, `CREATE DATABASE` und sich selbst jedes Recht geben): `ALTER ANY LOGIN` (securityadmin), `CREATE SERVER ROLE`/`ALTER ANY SERVER ROLE`, Mitglied einer festen Rolle, `CREATE ANY DATABASE` (dbcreator), `db_owner`/`db_securityadmin`/`db_accessadmin`, Besitz, `WITH GRANT OPTION` samt `CASCADE` und Vergebendem (`grantor_principal_id`) — mit den am Orakel gemessenen Nummern 15247, 15151, 262, 4613, 4611; MariaDB und PostgreSQL melden ihre eigenen (Orakel P01–P52, M01–M25). `CREATEROLE` und MariaDB `CREATE USER` = securityadmin samt `CREATE SERVER ROLE`/`ALTER ANY SERVER ROLE`; `ADMIN` auf eine Rolle = Besitz oder `ALTER` auf `SERVER ROLE::`/`LOGIN::`, geprüft je Anschluss; MariaDB `GRANT OPTION` je Objekt. Eine MariaDB-Rolle und ein Benutzer gleichen Namens bleiben technisch unmöglich (eindeutige Namen in `sys.server_principals`). Wer anlegt, besitzt; das Protokoll führt die Anmeldung je Stapel. Dazu wer Objekte anlegen, ändern und löschen darf (T-SQL am Orakel gemessen: 262, 2760, 3701, 1088; PostgreSQL `CREATE` auf das Schema und Besitz).✅
S12.19iKreuztestsJe Richtung (T-SQL→MariaDB→PostgreSQL und zurück): anlegen, ändern, Rolle zuweisen, Recht vergeben, entziehen, löschen — in A geändert, in B und C sichtbar und wirksam (Anmeldung, erlaubte/verbotene Anweisung mit der Fehlernummer des Protokolls). Gefunden: ein unbekannter (auch gelöschter) Name meldete sich ungeprüft an; ein dreiteiliger Name ohne Zugang meldete 229 statt 916.✅
Meilenstein 13 (optional): Der Redis-Befehlssatz auf derselben Ablage (6/6)
PunktTitelBeschreibungStand
R13.1Der Befehlssatz, vollständigWas heute fehlt: die **sortierten Mengen** (`ZADD`, `ZRANGE`, `ZRANGEBYSCORE`, `ZRANK`, `ZINCRBY`, `ZREM`, `ZCARD`, `ZCOUNT`), die **Bitfelder** …✅
R13.2Sitzung, Stapel und Benachrichtigung`MULTI`/`EXEC`/`DISCARD`/`WATCH` als optimistische Klammer, **Pipelining** (mehrere Befehle in einem Paket, Antworten in derselben Reihenfolge) …✅
R13.3Dieselbe Ablage, zwei SichtenJeder Redis-Schlüssel erscheint im Schema `redis` als Zeile: `redis.schluessel` (Name, Art, Ablaufzeit, Grösse), `redis.texte`, `redis.hashes` …✅
R13.4Auf Geschwindigkeit gebautDer Redis-Weg bekommt eine eigene, kurze Bahn: RESP ohne Zwischenobjekte aus dem Puffer lesen (`ReadOnlySequence<byte>` statt Zeichenketten), die …✅
R13.5Der Nachweis, dass es den SQL-Server nicht bremstEine Messreihe, die beides zugleich laufen lässt: SQL-Last allein, Redis-Last allein, beide zusammen.✅
R13.6Dauerhaftigkeit, wenn gewünschtRedis-Daten liegen bewusst nur im Hauptspeicher.✅
Meilenstein 14: Hundert Prozent SQL Server 2025 (23/23)
PunktTitelBeschreibungStand
V14.1Die Messstrecke gegen 2025 als Pflicht**Umgestellt (25.09.2026):** `batches.expected.txt` und die eingebetteten Referenzdaten kommen jetzt aus SQL Server 2025 (Port 14335); der 2022-Stand liegt als `batches.expected-2022.txt` daneben. 1111 Stapel ohne Abweichung. Die **Vektorsuche** gehört nicht dazu: `VECTOR_SEARCH` gibt es in SQL Server 2025 RTM-CU9 nicht (gemessen: Syntaxfehler **156**).✅
V14.2aVektorrechnung, bitgenau`VECTOR_DISTANCE` (`cosine`, `euclidean`, `dot`), `VECTOR_NORM` und `VECTOR_NORMALIZE` stehen **bitgenau** wie am Orakel: das Skalarprodukt über acht Spuren in einfacher, die Normen in doppelter Genauigkeit. `VECTOR_SEARCH` gibt es in SQL Server 2025 RTM-CU9 nicht.✅
V14.2bVektorsuche mit `PREVIEW_FEATURES`**Nachgemessen (25.09.2026), an einer eigenen Testdatenbank:** ohne `ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON` bleibt `CREATE VECTOR INDEX` ein Syntaxfehler (**Unknown object type 'VECTOR'**) und `VECTOR_SEARCH` Fehler **156** — mit dem Schalter braucht `CREATE VECTOR INDEX` zusätzlich `SET QUOTED_IDENTIFIER ON` (Fehler **1934** sonst), dann legt es den Index wirklich an (`sys.vector_indexes`), und `VECTOR_SEARCH` liefert Zeilen samt `distance`. Der Syntaxfehler **156** der V14.1-Messung kam also vom fehlenden Schalter, nicht davon, dass es die Funktion nicht gäbe. `CREATE VECTOR INDEX` stand bereits (siehe V14.2a); neu ist `VECTOR_SEARCH(TABLE=…, COLUMN=…, SIMILAR_TO=…, METRIC=…, TOP_N=…)` als Tabellenfunktion mit vorangestellter `distance`-Spalte (Typ `float`, wie bei `VECTOR_DISTANCE`), alle fünf Argumente sind **Pflicht** (ohne eines meldet auch das Orakel 102 — gemessen, nicht angenommen). Gesucht wird über einen vollständigen Durchlauf (wie beim Index selbst, docs/umsetzung.md#v142), nicht über den DiskANN-Baum: auf den kleinen Prüfdaten dieses Projekts macht das keinen Unterschied. `norm2` über zehn Stellen bleibt bitgenau offen, siehe **V14.2c**.✅
V14.2c`norm2` und `norm1` über acht und mehr Stellen, bitgenau**Behoben (30.09.2026).** Ein Fremdlauf mit 60 Vektoren von 1 bis 39 Stellen am Orakel 2025 zeigte, dass der Bit-Rest nie eine Frage der Wurzel war: bis 7 Stellen stimmte die einfache Doppelsumme, **ab 8 Stellen rechnet SQL Server die vollen Achterblöcke in acht `float`-Spuren** (Quadrat bzw. Betrag UND Summe je in einfacher Genauigkeit), zählt die Spuren der Reihe nach in doppelter Genauigkeit zusammen und addiert den Rest (unter 8 Stellen) einzeln in doppelter Genauigkeit. Das gilt auch für `norm1` (dort stimmte die Zehner-Probe nur zufällig). Zugleich fiel beim Prüfen der Abstände an langen Vektoren auf, dass **das Skalarprodukt ab 32 Stellen anders läuft**: verschmolzenes Multiplizieren/Addieren (FMA), vier Achterspuren nebeneinander (je 32 Stellen ein Durchgang), Rest in die erste, je Spur `(s0+s1)+(s2+s3)`, dann Baum — das Kosinus-, Euklid- und Skalarprodukt-Ergebnis stimmt damit an 59 Paaren (58 beim Kosinus). Belegt durch `VektorNormenOrakelTests` (Sabotage: Achterspuren abgeklemmt → Normen rot, vier Akkumulatoren abgeklemmt → Abstände rot).✅
V14.2dKosinus: ein Paar mit 35 Stellen, eine `float`-Einheit daneben**Behoben (30.09.2026).** Die Ursache war nicht die Formel (`1 - p/(sqrt(na)*sqrt(nb))` stimmte an allen Paaren unter 32 Stellen), sondern die **Summationsreihenfolge**: SQL Server rechnet den Kosinus mit einem **anderen Kern als `dot`/`euclidean`** — `dot` und `euclidean` legen die Achterblöcke reihum auf **vier** Sammler, der Kosinus nur auf **zwei** (gerade und ungerade Blöcke), je Spur dann `s0+s1`. Belegt am Orakel 2025 (Port 14335) mit Paar-Sonden (Wert 2^24 an einer Stelle, Einsen an zwei anderen: „verschmelzen die Einsen vor dem Treffen?“) an `cosine` und `dot` bei 32, 34 und 64 Stellen, dazu 300 Zufallspaare mit 8 bis 70 Stellen und 277 Fingerabdruck-Proben. Nebenbei fiel auf: der Rest hinter den vollen 32er-Durchgängen kommt bei **beiden** Kernen erst **nach** dem Zusammenlegen der Sammler in die acht Spuren (bisher davor in die erste) — `dot` stimmte darum ab 34 Stellen an gut der Hälfte der Paare nicht; jetzt 300 von 300. Belegt durch `VektorNormenOrakelTests` mit 60 neuen Paaren von 33 bis 70 Stellen (Sabotage: Kosinus auf vier Sammler → rot). **Ausserdem behoben:** zwei Prüfungen dieses Tests standen durch ein wörtliches `\n` im Kommentar **auskommentiert** (die Kosinus-Prüfung — darum sah das `?` grün aus — und die `VECTOR_NORMALIZE`-Prüfung); beide laufen jetzt.✅
V14.3Unscharfer Zeichenkettenvergleich**Gebaut (30.09.2026), nachdem die frühere Streichung widerlegt ist:** `EDIT_DISTANCE`, `EDIT_DISTANCE_SIMILARITY`, `JARO_WINKLER_DISTANCE` und `JARO_WINKLER_SIMILARITY` gibt es in SQL Server 2025 RTM-CU9 **mit `PREVIEW_FEATURES = ON`** (ohne: 195) — die Messung vom 25.09. hatte den Schalter ausgelassen. Am Orakel vermessen und nachgebaut: Windows-Sortierung verlangt (**9847** bei `SQL_*`, auch bei einem `COLLATE`-Ausdruck darauf); Gross-/Kleinschreibung und Akzente folgen der Sortierung (CI/AI, CS, BIN2); zwei Sortierungen → 468; nur `varchar`/`nvarchar` derselben Art, nicht MAX/`text` (8116, benannt nach dem Argument); Argumentzahl 174/189; nachgestellte Leerzeichen zählen nicht, führende schon; UTF-16-Codeeinheiten (ein Ersatzpaar zählt zwei); `EDIT_DISTANCE` ist die **Damerau-Levenshtein-Distanz mit benachbarter Vertauschung** (`ab`→`ba` = 1), der dritte Wert ist `max_distance` (Ergebnis höchstens Grenze+1, NULL → NULL, negativ = keine Grenze, größer als `int` → 8115); die Ähnlichkeit ist `(1 − Distanz/Länge)·100`, halbe nach oben; Jaro-Winkler mit Fenster `max/2−1`, Zuschlag 0,1 je Anfangszeichen bis 4 ohne Schwelle, **Vertauschungen nach Rang statt nach Buchstabe** (bei wiederholten Buchstaben ein anderes Ergebnis, an 300 Paaren gemessen). Bitgenau an 300 Paaren (`UnscharfVergleichTests`).✅
V14.4Die neuen Zeichenketten- und JSON-Funktionen`BASE64_ENCODE`/`BASE64_DECODE` samt URL-sicherer Schreibweise, `UN…✅
V14.5Reguläre Ausdrücke, der Rest`REGEXP_MATCHES` und `REGEXP_SPLIT_TO_TABLE` als Tabellenfunktionen, mit `match_id`, `start_position`, `end_position`, `match_value` und den Gruppen als JSON in `substring_ma…✅
V14.6Externe Modelle und die KI-Funktionen`CREATE/ALTER/DROP EXTERNAL MODEL` und `sys.external_models` stehen samt den Fehlernummern des Orakels; `AI_GENERATE_EMBEDDINGS` meldet **31643** — der Weg nach draußen bleibt hinter derselben Freischaltung wie S12.5.✅
V14.7Optimiertes Sperren`ALTER DATABASE … SET OPTIMIZED_LOCKING = ON` (ohne beschleunigte Wiederherstellung **12133**), `is_optimized_locking_on` in `sys.databases` und das, was man davon sieht: ohne die Option KEY/PAGE/OBJECT, mit ihr nur OBJECT und eine Sperre auf der Transaktionsnummer.✅
V14.8Der Katalog von 2025, vollständig`sys.databases` mit allen 98 Spalten von 2025 (und ohne die doppelte), die Vektorspalten in `all_columns`/`system_columns`/`parameters`, `data_clustering_ordinal`, `ring_buffer_group`/`create_time`, `max_duration` und die zehn neuen `sp_configure`-Optionen. SSMS-Korpus: 177 von 179 gleich, die beiden übrigen sind umgebungsabhängig.✅
V14.9aÄnderungsstrom: VerwaltungDie zehn Prozeduren (`sp_enable_change_event_stream` bis `sp_help_change_event_stream_tables`), die beiden Sichten `sys.dm_change_event_stream_*` und der Riegel `PREVIEW_FEATURES` — Spalte für Spalte am Orakel vermessen. Das Senden selbst bleibt aus: es braucht eine Gegenstelle.✅
V14.9bÄnderungsstrom: das Nachrichtenformat und der eigene Kafka-Client**Am Orakel vermessen, nicht behauptet:** `@destination_type` kennt nur `AzureEventHubsAmqp`/`AzureEventHubsApacheKafka` (V14.9a hatte ein drittes, allgemeines „Kafka" erfunden — behoben), und `@destination_location` muss auf eine `*.servicebus.windows.net`-Adresse zeigen (23642 sonst, egal welche Zielart) — ohne eine echte, kostenpflichtige Azure-Event-Hubs-Instanz lässt sich das über die Leitung gehende Byte-Format am Orakel darum **nicht** ablesen. Gebaut: `AenderungsstromCloudEvent` (eine eigene, in sich stimmige CloudEvents-1.0-JSON-Nachricht je Zeilenänderung), ein **eigener** Kafka-Client von Grund auf (Metadata v1, Produce v7, Record-Batch-Format 2 samt CRC32C, SaslHandshake/SaslAuthenticate für SASL/PLAIN, TLS über `SslStream`) und `KafkaAufteilung` (Trennungswahl nach `partition_key_scheme`, mit der Regel, dass Änderungen derselben Tabelle immer in dieselbe Trennung fallen — Reihenfolge je Tabelle). **Ende-zu-Ende gegen einen echten, eigens gestarteten Kafka-Container verifiziert:** `kafka-console-consumer.sh` (ein unabhängiges Werkzeug) liest die von KuonenSQL gesendete Nachricht Schlüssel und Wert byte-für-byte zurück.✅
V14.9cÄnderungsstrom: an die Engine angebunden und wiederaufsetzbarEine bestätigte `INSERT`/`UPDATE`/`DELETE` an einer angemeldeten Tabelle merkt die Änderung jetzt in `Aenderungsstrom.Warteschlange` vor (gehängt an derselben Stelle wie CDC, `FireAfterTriggers`) — **nie synchron im Commit-Pfad**; scheitert die Anweisung oder wird die Transaktion zurückgenommen, nimmt eine `AenderungsstromUndoAction` die Vormerkung zurück, "bestätigt" heißt also wirklich commited. Neu `AenderungsstromAbtaster`: der periodische Protokollabtaster (Vorgabe 5 s wie am Orakel, `maxtrans` 10000), sendet über `IAenderungsstromSenke` (echte Umsetzung `KafkaAenderungsstromSenke` per `KafkaProduzent`) und schreibt die zuletzt gesendete Position auf `Stromtabelle.GesendetBis`. **Ein nicht erreichbarer Broker hält den Server nie auf** — jede Gruppe wird einzeln versucht, ein Fehler überspringt nur sie für diesen Takt. `Katalogabzug.Datenbankinfo(JsonNode)` füllte die `Aenderungsstrom`-Eigenschaft bisher **nie** (nur ein Getter, generischer Setter-Pfad griff nicht) — neu `Aenderungsstrom.Wiederherstellen(quelle)`; Anmeldung, Warteschlange und Positionszähler überleben jetzt Kontrollpunkt und Neustart. **SASL/PLAIN + TLS Ende-zu-Ende gegen einen eigens gestarteten, gesicherten Kafka-Container gefahren** (SASL_SSL, selbstsigniertes Zertifikat) — dabei einen echten Kodierungsfehler gefunden und behoben: `SaslHandshakeAsync` schrieb vor der Mechanismus-Zeichenkette ein unbelegtes `Int32(1)`, das das Wire-Format dort nicht kennt; der Broker las die ersten zwei Bytes davon als Zeichenkettenlänge 0, der Mechanismus kam **leer** an (Fehlercode 33) — nie aufgefallen, weil V14.9b nur den PLAINTEXT-Weg Ende-zu-Ende gefahren hatte. Behoben, `KafkaProduzentSaslTests` hält die korrekte Kodierung über einen Kafka-Doppelgänger auf Loopback fest; `kafka-console-consumer.sh` (unabhängig) hat die über SASL_SSL gesendete Nachricht byte-für-byte zurückgelesen — sowohl vom rohen `KafkaProduzent` als auch von der vollen Kette.✅
V14.9dÄnderungsstrom: im Server-Host verdrahtet, die zwei Sichten gefüllt**Der eigentliche Befund lag noch vor der Zeile:** `AenderungsstromAbtaster` wurde im echten Server-Host (`Program.cs`) **nirgends erzeugt** — eine bestätigte Änderung landete in der Warteschlange und blieb dort für immer, der Änderungsstrom sendete im echten Betrieb nie ein Byte. Neu `Kafka/AenderungsstromServerbindung` (die eine Stelle, die eine `ExecutionEngine` mit einer Senke verbindet und den Takt startet — von `Program.cs` UND von Tests genutzt) und `Kafka/KafkaEinstellungen` (TLS/SASL aus der Umgebung, wie bei `MariaDbEinstellungen`); der Abtaster läuft jetzt mit dem Takt aus `AenderungsstromAbtaster.VorgabeIntervall` und wird beim Herunterfahren sauber entsorgt. **Mit einem echten Server-Start belegt, nicht nur an der Klasse:** ein echter `TcpListener` bedient `TdsConnectionHandler.ProcessConnectionAsync` über die statische `Engine` (genau die aus `Program.cs`), eine echte `Microsoft.Data.SqlClient`-Verbindung führt die Anweisungen über die Leitung aus — eine bestätigte Änderung an einer angemeldeten Tabelle kommt bei der Senke an, eine an einer nicht angemeldeten Tabelle nicht (Gegenbeleg). Dazu `sys.dm_change_event_stream_errors`/`…_log_scan_sessions`: der Abtaster trägt jeden Umlauf (`Aenderungsstrom.Abtastprotokoll`) und jeden Fehlschlag (`Aenderungsstrom.Fehlerprotokoll`) je Datenbank ein — unabhängig davon, ob dabei etwas zu senden war, wie am Orakel auch leere Umläufe geloggt werden; die vier LSN-Spalten und `command_id` führt KuonenSQL nicht mit (kein echtes Transaktionsprotokoll je Sendung) und stehen als leere Protokollstelle. **Die CDC-Wiederaufsetzbarkeit nach Neustart ist inzwischen behoben (V14.9e), die Status-Marke stand hier nur noch aus Versehen auf 🔶** — die Ursache ist gefunden (derselbe Bug wie bei V14.9c: `Aenderungserfassung.Aufnahmen` hat keinen Setter, der Katalogabzug-Rundlauf verliert sie), die Behebung braucht aber einen eigenen `Wiederherstellen`-Weg und ist absichtlich nicht mit hineingezogen.✅
V14.9eÄnderungserfassung (CDC): Wiederaufsetzbarkeit nach Neustart**Behoben (26.09.2026).** Ursache genau wie in der Zeile vermutet: `Aenderungserfassung.Aufnahmen` war `IReadOnlyList<Aufnahme>` mit reinem Getter über eine private, gesperrte Momentaufnahme (`[.. _aufnahmen]`) — `Katalogabzug.Datenbankinfo(JsonNode)` kann nur Eigenschaften mit Setter oder einer `IList`-Sammlung in-place befüllen, `IReadOnlyList<T>` ist keins von beiden, und ohne die geschriebene `Aufnahmen`-Liste blieb schon das Schreiben leer. `Aufnahmen` gibt jetzt wie `Aenderungsstrom.Gruppen` die live gebundene `List<Aufnahme>` zurück (Lege/Entferne/Erfasst/AufnahmenVon/Finde bleiben unter `_sperre`); der private Zähler `_folgenummer` ist die öffentliche Eigenschaft `LetzteFolgenummer` geworden (wie `Aenderungsstrom.LetzteVergebenePosition`); `Aufnahme.Angelegt` ist von einem reinen Getter auf `init` gestellt, sonst hätte `sp_cdc_help_change_data_capture` nach einem Neustart die aktuelle Uhrzeit statt der wirklichen Anlagezeit gezeigt. Neu `Aenderungserfassung.Wiederherstellen(quelle)` nach dem Vorbild von `Aenderungsstrom.Wiederherstellen`, eingehängt in `Katalogabzug.Datenbankinfo(JsonNode)`. `ExecutionEngine.Aenderungserfassung.cs` brauchte keine Änderung — die drei Stellen, die `.Aufnahmen` direkt lesen, vertragen die live Liste unverändert; `Rowsetfunktionen.Erfassung.cs`/`ExpressionEvaluator.Aenderungserfassung.cs` nutzen ohnehin nur `Erfasst`/`AufnahmenVon`/`Finde`, die schon vorher korrekt sperrten. **Verifiziert mit zwei `ExecutionEngine`s auf demselben Datenverzeichnis und einem echten `CHECKPOINT` dazwischen** (wie bei V14.9c): Einschaltzustand, die Aufnahme samt Spaltenliste/Indexspalten und die schon erfasste Änderungszeile sind nach dem Neustart da, `cdc.fn_cdc_get_all_changes_…` würde dieselben Zeilen liefern, und eine neue Änderung nach dem Neustart bekommt eine **höhere** Folgenummer statt wieder bei 1 zu beginnen — kein Doppel. **Sabotage-Probe:** die neue `Wiederherstellen`-Einhängung in `Katalogabzug.Datenbankinfo` abgeklemmt → **genau der neue Neustart-Test rot**, die 37 übrigen CDC-Tests grün; zurückgebaut. **Beim Prüfen, ob andere Katalog-Teile dieselbe Falle haben, zwei weitere gefunden — beide zu gross für einen Nebenbei-Fix, siehe V14.11/V14.12.**✅
V14.10Die Ansage der VersionKuonenSQL gibt sich als **SQL Server 2025, Enterprise Developer Edition, unter Windows** aus - auf allen drei Systemen gleich: `@@VERSION`, `SERVERPROPERTY`, `sys.dm_os_host_…✅
V14.11Änderungsverfolgung (Change Tracking): Wiederaufsetzbarkeit nach Neustart**Behoben (26.09.2026).** `Aenderungsverfolgung.Abziehen()`/`Einspielen(JsonObject)` (von Hand geschrieben, wie `Abfragespeicher.Abzug.cs`) sichern beide Ebenen — Einschaltzustand/Aufbewahrung/die zwei Versionszähler auf Datenbankebene, dazu je verfolgter Tabelle (`Tabellenverfolgung.Abziehen`/`Einspielen`, `internal`) ihren vollen Zeilenstand (Schlüssel, Version, Erstellversion, gelöscht, Zeitpunkt, Kontext, Spaltenversionen) — verdrahtet in `Datendateien.cs` neben `abfragespeicher`. **Verifiziert mit zwei `ExecutionEngine`s auf demselben Datenverzeichnis und einem echten `CHECKPOINT`** (wie V14.9e): Einschaltzustand, Aufbewahrung, die schon erfasste `UPDATE`-Zeile samt Spaltenmaske und der Versionszähler sind nach dem Neustart da, eine neue Änderung wächst über die wiederhergestellte Version hinaus. **Sabotage:** die `Einspielen`-Einhängung in `Datendateien.LiesDatei` abgeklemmt → genau der Neustart-Test rot; zurückgebaut. **Nebenbefund, nicht behoben (siehe V14.13):** `CHANGE_RETENTION = n HOURS`/`MINUTES` wird vom Parser derzeit verworfen (`LiesOptionsklammer` schluckt die Masseinheit hinter der Zahl), nur `DAYS` kommt wirklich an — unabhängig von dieser Persistenz-Zeile, der Test nutzt darum bewusst `DAYS`.✅
V14.12Bereichskonfiguration (`ALTER DATABASE SCOPED CONFIGURATION`): Wiederaufsetzbarkeit nach Neustart**Behoben (26.09.2026).** `Bereichskonfiguration.Abziehen()`/`Einspielen(JsonObject)` — dieselbe Aufteilung, die die Zeile schon vorschlug: die beiden Wörterbücher (Haupt-/Zweitknoten) als `JsonArray` aus Name/Wert-Paaren, verdrahtet in `Datendateien.cs`. Dabei auch `Setze`/`Wert`/`ZweitWert`/`IstVorgabe` unter eine eigene Sperre gestellt (bis dahin ganz ohne Synchronisation). **Verifiziert:** `MAXDOP`, ein Zahlenschalter (`VERBOSE_TRUNCATION_WARNINGS = OFF`) und ein Zweitknoten-Wert (`FOR SECONDARY`) überstehen Kontrollpunkt und Neustart, eine unangetastete Option bleibt ihr Vorgabewert, `sys.database_scoped_configurations` liest denselben wiederhergestellten Stand. **Sabotage:** die `Einspielen`-Einhängung abgeklemmt → genau der Neustart-Test rot; zurückgebaut. **Dabei geprüft und mitbehoben (V14.9c/V14.9e-Nachtrag):** `Aenderungsstrom.Gruppen` (seit V14.9c) und `Aenderungserfassung.Aufnahmen` (seit V14.9e) gaben die **live gebundene** Liste zurück, damit der Katalogabzug sie per generischer Reflexion befüllen konnte — ein Aufzähler (Katalogabzug, `AenderungsstromAbtaster`, die drei direkten `.Aufnahmen`-Leser der Engine) lief dabei ungeschützt gegen `LegeGruppeAn`/`EntferneGruppe` bzw. `Lege`/`Entferne` und konnte mit `InvalidOperationException` ("Collection was modified") abstürzen — bei `Aenderungsstrom.Gruppen` war die Mutation obendrein **komplett ohne Sperre**. Beide Eigenschaften liefern jetzt nur noch eine Momentaufnahme unter einer eigenen Sperre (`Aenderungsstrom._sperre`/`Aenderungserfassung._sperre`, neue `LegeGruppeAn`/`EntferneGruppe`), persistiert über explizite `AbziehenGruppen`/`EinspielenGruppen` bzw. `AbziehenAufnahmen`/`EinspielenAufnahmen`, die der Katalogabzug direkt aufruft statt über die generische Reflexion. **Mit zwei Nebenläufigkeitstests belegt** (`AenderungsstromNebenlaeufigkeitTests`, `AenderungserfassungNebenlaeufigkeitTests`: ein Leser zählt in einer Schleife auf, während 500 Anmelde-/Abmelde-Wechsel laufen — wenige Threads, keine Last) und einem eigenen Sabotage-Nachweis je Klasse (Sperre umgangen → derselbe Test wirft dieselbe `InvalidOperationException`; zurückgebaut). Dazu ein Wächter `KatalogNeustartWaechterTests` (Abfragespeicher, Change Event Streaming, CDC, Change Tracking und Bereichskonfiguration überstehen denselben Kontrollpunkt/Neustart in einem Zug).✅
V14.13Änderungsstrom-Nebenläufigkeit über die Gruppen hinaus, plus der Aufbewahrungseinheit-Parser**Behoben (26.09.2026), in zwei Teilen.** **Teil 2 zuerst (Vorrang, weil live scharf):** `Aenderungsstrom.Warteschlange`/`Abtastprotokoll`/`Fehlerprotokoll` und `Stromgruppe.Tabellen` liefern jetzt wie `Gruppen`/`Aufnahmen` (V14.12) nur noch eine Momentaufnahme unter einer Sperre (`Aenderungsstrom._sperre` bzw. einer eigenen `_tabellenSperre` je `Stromgruppe`); verändert wird über neue gesperrte Methoden (`Vormerken`/`EntferneAusWarteschlange`, `MeldeTabelleAn`/`MeldeTabelleAb`, `TrageAbtastlaufEin`/`TrageFehlerEin`), die Warteschlange zusätzlich persistiert über `AbziehenWarteschlange`/`EinspielenWarteschlange` (dieselbe Nachtrag-Regel wie bei `Gruppen`) — `Stromgruppe.Tabellen` seit V14.13 ebenfalls, verschachtelt in `Aenderungsstrom.AbziehenGruppen`/`EinspielenGruppen`. Abtastprotokoll/Fehlerprotokoll sind bewusst flüchtig (siehe ihre Klassendoku, wie schon vor dieser Zeile) und werden darum nicht persistiert. **Zwei neue Nebenläufigkeitstests** (`AenderungsstromNebenlaeufigkeitTests`), je mit Sabotage-Probe: Sperre aus der jeweiligen Momentaufnahme entfernt → genau der isolierte Test rot, die übrigen grün; zurückgebaut. **Teil 1 — der Aufbewahrungseinheit-Parser, am Orakel gemessen statt geraten** (SQL Server 2025, `sys.change_tracking_databases`): die Masseinheit hinter `CHANGE_RETENTION` ist am Orakel **Pflicht** (`CHANGE_RETENTION = 7` ohne Einheit meldet Syntaxfehler 102), und `retention_period_units` zählt **MINUTES = 1, HOURS = 2, DAYS = 3** — nicht 4/5, wie es der tote Sonderfall angenommen hatte. Neuer, eigener Leser `PrattParser.LiesAenderungsverfolgungsOptionen` statt der Erweiterung von `LiesOptionsklammer` (dessen drei andere Aufrufer — Resource Governor, Query Store — die Masseinheit nirgends brauchen); die Konstanten in `Aenderungsverfolgung.AufbewahrungEinheitWort`/`Aufbewahrungsdauer()` auf die gemessenen Werte korrigiert (waren dort ebenfalls 4/5 statt 2/1). Ein Bestandstest (`Die_Aufbewahrungsdauer_Folgt_Der_Einheit`) schrieb die falschen Zahlen fest und ist **korrigiert statt gelöscht**; zwei neue Tests (`Theory` über HOURS/MINUTES/DAYS plus ein Gegenbeleg zur Optionsreihenfolge) fahren den vollen Weg über `ALTER DATABASE … SET CHANGE_TRACKING` bis in `sys.change_tracking_databases`. **Sabotage:** die Masseinheit im neuen Leser wieder verworfen → genau die 3 einheitsabhängigen Tests rot (HOURS, MINUTES, die Reihenfolge-Probe mit MINUTES), DAYS bleibt grün; zurückgebaut. **(3) war schon erledigt** (Bereichskonfiguration bekam ihre Sperre in V14.12) — die anschliessende systematische Suche nach weiteren unbewachten Katalog-Listen steht jetzt als eigene Zeile, siehe V14.14.✅
V14.14Zwei weitere unbewachte live Katalog-Listen, systematisch gefunden (`git grep`)**Behoben (26.09.2026).** `Ereignissitzung.Ereignisse`/`Ziele` (Extended Events) und `Auftrag.Schritte` (SQL-Agent-Schritte) liefern jetzt wie `Aenderungsstrom.Gruppen`/`Aenderungserfassung.Aufnahmen` (V14.12) nur noch eine Momentaufnahme unter einer eigenen Sperre (`Ereignissitzung._ereignisSperre`, `Auftrag._schritteSperre`); verändert wird über neue gesperrte Methoden (`FuegeEreignisAn`/`EntferneEreignisse`, `FuegeZielAn`/`EntferneZiele`, `MarkiereZielAusfuehrungen` — letzteres ersetzt eine Zaehlschleife über `Ereignisverwaltung.Melde`, dem heissen Weg je Anweisung, ohne die Momentaufnahme dafür zu ziehen, AGENTS.md §5; `Auftrag.FuegeSchrittHinzu`). `FindeEreignis` sucht aus demselben Grund jetzt direkt unter der Sperre statt über die Momentaufnahme. Bei den Ereignissen zusätzlich persistiert (`Ereignissitzung.AbziehenEreignisse`/`EinspielenEreignisse`, `…Ziele`, dieselbe Nachtrag-Regel wie bei `Aenderungsstrom.Gruppen`, verdrahtet über den neuen Helfer `Datendateien.EreignissitzungsAbzuege` für Schreiben UND Lesen) — `Auftragssichten.Auftraege` ist dagegen serverweit nie persistiert ("sie leben, solange der Server läuft"), also entfällt dort ein Nachtrag. **Ein Korrektur an der Zeile selbst:** `sp_delete_jobstep` gibt es in dieser Engine nicht (nachgeprüft, kein Treffer) — nur `sp_add_jobstep` hängt an, die Race trifft trotzdem zu (Anhängen gegen gleichzeitiges Lesen). **Drei Nebenläufigkeitstests plus ein Neustart-Test** (`EreignisUndAuftragNebenlaeufigkeitTests`): Ereignisse und Ziele je mit 500 Anmelde-/Abmelde-Wechseln gegen einen lesenden Hintergrund-Task, `Auftrag.Schritte` mit 5000 Anhängen (500 reichten hier nicht zuverlässig, siehe Sabotage), der Neustart-Test fährt `CREATE EVENT SESSION … ADD EVENT … ADD TARGET …` durch `WalLogManager`/`ExecutionEngine(enableWalRecovery: true)` und prüft, dass Ereignis und Ziel danach noch da sind. **Sabotage** (vier Proben, je genau isoliert): die Sperre in `FuegeEreignisAn`/`EntferneEreignisse` umgangen → der Ereignisse-Test wirft zuverlässig (4/5, dann 5/5 Wiederholungen); dieselbe Probe bei `FuegeZielAn` → der Ziele-Test wirft 5/5, die übrigen drei Tests bleiben grün; die Sperre in `Auftrag.FuegeSchrittHinzu` umgangen → bei 500 Anhängen **kein** Fehlschlag in 20 Wiederholungen (**die Probe hat den TEST entlarvt, nicht den Code** — die Iterationszahl war zu niedrig für diese Objektgröße; auf 5000 geschärft, danach 8/8 zuverlässig); die Nachtrag-Einhängung in `Datendateien.LiesServerteil` abgeklemmt → genau der Neustart-Test rot (`Assert.Single` auf eine leere Liste), die drei Nebenläufigkeitstests bleiben grün. Alle vier zurückgebaut, `grep -rn "SABOTAGE" src/ tests/` leer.✅
V14.15Abtastprotokoll/Fehlerprotokoll wachsen im Dauerbetrieb unbegrenzt**Behoben (26.09.2026).** Der Abtaster trägt alle 5 Sekunden einen Umlauf ein (`AenderungsstromAbtaster`) — ohne Deckel wächst `Aenderungsstrom.Abtastprotokoll`/`Fehlerprotokoll` im Dauerbetrieb unbegrenzt. **Am Orakel nachgesehen, nicht angenommen:** weder `sys.dm_change_event_stream_log_scan_sessions` noch `sys.dm_change_event_stream_errors` tragen eine Spalte, die auf eine feste Größe hindeutet (Spaltenliste am Orakel abgefragt, SQL Server 2025) — beide sind reine Umlaufprotokolle ohne erkennbaren Deckel, und ohne erreichbare Kafka-/Event-Hubs-Gegenstelle lässt sich kein echter Umlauf erzeugen, an dem sich eine Verwerfung beobachten ließe. Die Zahl ist darum **begründet gewählt statt gemessen**: neue Konstante `Aenderungsstrom.ProtokollDeckel` = 1000, derselbe Wert wie die Vorgabe des Rings eines Extended-Events-Ziels (`Ereignissitzung.Ringgroesse`) — genug Verlauf für eine Fehlersuche (bei 5-Sekunden-Takt gut 80 Minuten), ohne dass der Abtaster den Prozess unbegrenzt wachsen lässt. `TrageAbtastlaufEin`/`TrageFehlerEin` verwerfen jetzt unter derselben Sperre (`_sperre`) den ältesten Eintrag, sobald der Deckel überschritten wird. **Verifiziert** (`AenderungsstromProtokollDeckelTests`): über den Deckel hinaus bleibt die Zahl der Einträge konstant, die jüngsten bleiben erhalten und die ältesten fallen weg, unterhalb des Deckels bleibt jeder Eintrag (Gegenbeleg). **Sabotage:** die Deckel-Bedingung in `TrageAbtastlaufEin` abgeklemmt → genau die 2 `Abtastprotokoll`-Deckeltests rot, die 3 übrigen (Fehlerprotokoll, Gegenbeleg) grün; zurückgebaut.✅
V14.16`SELECT DISTINCT … ORDER BY <Ausdruck>`: 145 wie SQL Server**Behoben (04.10.2026), am Orakel SQL Server 2025 gemessen (30 Fälle):** ein ORDER BY-Ausdruck muss bei DISTINCT als derselbe Ausdruck in der Auswahlliste stehen — `(SELECT NULL)`, `(SELECT 1)`, `(SELECT v)`, `ABS(v)`, `v+1` zu `SELECT DISTINCT v` ergeben 145; `v+1` zu `SELECT DISTINCT v+1` (auch geklammert, mit Alias), Alias, Spaltennummer und `t.v` zu `v AS w` sind erlaubt (Letzteres sortierte vorher nicht). `GleicherAusdruck` in `ExecutionEngine.ErgaenzeSortierspalten`. Übersetzer (PG, MariaDB, SPARQL) bauten nicht darauf. Sabotage: Prüfung abgeklemmt → 6 Tests rot.✅
Meilenstein 15: Hohe Leistung und Stabilität (23/23)
PunktTitelBeschreibungStand
P15.1Die Messreihe selbst`tools/leistung`: zehn SQL-Lasten gegen KuonenSQL und SQL Server 2025, `redis-benchmark` gegen KuonenSQL und Redis 7 im selben Netz, und die Messung beider Wege nebeneinander✅
P15.2Keine Abfrage bringt den Dienst zu Fall`TOP` ohne Sortierung schneidet den Eingang ab, auch mit einer Fensterfunktion, die nur die Stelle zählt. Ein solcher Stapel im Protokoll machte den Server unstartbar✅
P15.3Der kurze Weg für Aggregate und VergleicheEin Aggregat über eine blanke Spalte geht nicht mehr je Zeile durch den Ausdrucksauswerter; der Wertvergleich bekam einen kurzen Weg für zwei Werte derselben Art✅
P15.4aSpalten erst beim Zugriff entziffernAuch schmale Tabellen (bis 12 Spalten) entziffern nur die Spalten, die die Abfrage ansieht (`Zeilensicht.SchmalerWert`, Merker als `ulong`, kein Stellen-Feld). Dabei behoben: eine einzeln gelesene `numeric`-Spalte einer breiten Tabelle kam als `decimal` zurück✅
P15.4bEin kleinerer Wert`SqlTypeSpec` trägt Sortierung und XML-Sammlung in **einem** Verweis (`_zusatz`) statt in zwei: 32 → 24 Byte, damit `SqlValue` (Typ + Wert) **40 → 32 Byte**. Die öffentliche Form bleibt (`Collation`, `Xmlsammlung`, `with`, Positionsaufruf), die Gleichheit bleibt wertbasiert. Gemessen: Belegung −5 bis −9 % (z. B. `SUM(w*g)` 36,8 → 34,9 MB, Auswahl mit `CASE` 35,6 → 32,5 MB), die Laufzeit liegt im Rauschen✅
P15.4cZahlen ohne Hülle**Erledigt (03.10.2026).** Erst gemessen (Release, 50 000 Zeilen, Stichprobe der Belegungen über `GCAllocationTick`): die Hüllen der Zahlen machten **0 bis 14 %** der Belegung aus, das `SqlValue[]` 28–61 %. Eine echte Union (Verweis und Zahl übereinander) lehnt der Typlader ab; den Platz schafft stattdessen ein gepackter `SqlTypeSpec` (Grundtyp, Länge, Genauigkeit, Skala, Merker in einem `ulong`: **24 → 16 Byte**), daneben Verweis + `long` — `SqlValue` bleibt **32 Byte**, `int`/`long`/`double`/`float` liegen im Wert, die Markierung ist der Verweis auf eine von vier Zahlmarken. Alle Typprüfungen der `RawValue`-Zugriffe gehen über `Objekt`/`AlsInt32`/`AlsInt64`/`AlsDouble`/`AlsSingle` ohne Einschachteln; `RawValue` schachtelt nur noch dort ein, wo ein `object` verlangt ist (JSON, `Convert`, CLR). Belegung vorher → nachher: `SUM(w*g)…` 42,2 → 37,0 MB, `CASE/ABS/UPPER` 37,7 → 36,5, `w+g*2, b*3` 27,5 → 26,7, `f*2.5e0, f+w` 33,4 → 30,0, `GROUP BY g` 66,2 → 56,5, `SELECT *` 60,4 → 55,9, `WHERE … LIKE` 25,0 → 25,0; Hüllenanteil überall **0 %**. Laufzeit im Rauschen (kürzeste aus 3×5 Läufen, z. B. `GROUP BY` 128 → 120 ms, `CASE` 82 → 68 ms, `SELECT *` 91 → 105 ms)✅
P15.5Die Abfrage einmal übersetzen statt je Zeile deuten`Ausdrucksplan`: WHERE und Auswahl einer Einzeltabelle werden einmal in Delegaten übersetzt — Literale als fertige Werte, Spalten als Stelle in der Zeile, Rechen-, Vergleichs- und AND/OR-Operatoren als gewählter Aufruf; alles Übrige fällt je Zeile auf den bisherigen Deuter zurück (ein Weg, keine zweite Semantik). Gemessen −12 bis −35 % Laufzeit, −16 % Belegung bei Dezimalliteralen✅
P15.5bDer Plan für Verbunde, Prädikate und CASEDer Ausdrucksplan übersetzt jetzt auch `BETWEEN`, `IN (Liste)`, `LIKE`, unäre Operatoren, `IS NULL`, `CASE` (der Ergebnistyp wird an der ersten Zeile ermittelt und nur bei wechselndem Spaltentyp neu), `ABS`/`LEN`/`UPPER`/`LOWER` — und liest Spalten in **Verbunden** (ON, WHERE, Auswahl) über einen einmal aufgelösten `Verbundweg` statt über die Wörterbücher der Sichtenkette. Die Rechenwege stehen einmal (`ExpressionEvaluator.…Wert`), Deuter und Plan rufen dieselben. Gemessen: Verbund `ON a.g = b.g AND …` 143 → 84 ms, `ON … BETWEEN` 171 → 84 ms, `WHERE` im Verbund 186 → 104 ms, `CASE`/`ABS`-Auswahl 160 → 140 ms bei −22 % Belegung✅
P15.5cAggregate über Ausdrücke im Durchlauf`SUM(a*b)`, `AVG(d*2)`, `MIN(w+g)`, `COUNT(ausdruck)` … ohne GROUP BY rechnen im Durchlauf mit dem Plan statt die Zeilen zu sammeln und je Zeile zu deuten; Wert, Typ und Fehler wie auf dem langen Weg (Test gegen dieselbe Abfrage mit zusätzlichem `STRING_AGG`). Gemessen `SUM(w*g), SUM(ABS(w-g)), AVG(d*2)` über 50 000 Zeilen 120 → 101 ms, Belegung 45,8 → 36,8 MB✅
P15.5dDer Plan für Gruppierung, ISNULL/COALESCE/IIF und CAST`ISNULL`, `COALESCE`, `IIF` und `CAST`/`CONVERT`/`TRY_CAST` laufen als Plan (Typen der Zweige aus je einem `Ergebnistyp`, Zieltyp einmal gelesen); `GROUP BY` übersetzt Schlüssel und die Argumente von SUM/AVG/MIN/MAX/COUNT je Gruppe. Gemessen ISNULL/IIF/COALESCE-Auswahl 150 → 84 ms, CAST-Auswahl 147 → 69 ms, `GROUP BY ISNULL(w,0)%4` mit vier Aggregaten 222 → 164 ms. **Nebenbefund:** über einer leeren Menge liefert `SUM(a*b)` einen untypisierten NULL-Wert (`SUM(w)` den Spaltentyp) — unverändert, angleichen wäre eine Verhaltensänderung✅
P15.5eDer Plan für den Rest**Behoben (03.10.2026).** `IS DISTINCT FROM` läuft als Plan (`ExpressionEvaluator.VerschiedenWert`). Die übrigen Funktionen: 74 eingebaute, die jedes Argument als Wert lesen (Mathematik, Zeichenketten, Datum, JSON), bekommen ihre Spalten und zusammengesetzten Argumente vom Plan ausgerechnet und als `VorberechneterWert` gereicht; die Funktion selbst bleibt die des Deuters (ein Weg, keine zweite Semantik), Literale und Datumsteile bleiben, wie sie geschrieben sind. Nicht dabei, weil sie keine Werte lesen oder faul auswerten: Aggregate, Fenster, CHOOSE, Methoden, Nutzerfunktionen, Sitzungs- und Fehlerfunktionen. HAVING und Ausdrücke um Aggregate (`ROUND(AVG(w), 1)`) werden einmal je Abfrage übersetzt statt je Gruppe umgebaut und gedeutet (`UmAggregate`, die Aggregate einer Gruppe als vorberechnete Werte), die übrigen Spalten je Gruppe über den Plan der Projektion. Argumente, PARTITION BY und ORDER BY eines Fensters über einen je Fenster gebauten Plan (`Fensterwert`), die SET-Ausdrücke von UPDATE ebenso. Dabei behoben: `AVG(…) OVER` rechnete eigens in decimal (AVG über int lieferte decimal) und brach bei `d * w` mit `InvalidCastException` ab; es läuft jetzt wie das gewöhnliche Aggregat. Die Typableitung von FLOOR/CEILING (Scale 0), REPLICATE (nicht an die Länge des Arguments gebunden) und DATEADD über einer Zeichenkette (datetime) folgt dem Wert. Gemessen (Release, 50 000 Zeilen, kürzeste von 5): Funktionen in Auswahl und WHERE 97 → 76 und 79 → 73 ms, IS DISTINCT FROM 33 → 31, HAVING 98 → 89, Ausdrücke um Aggregate 93 → 87; Fenster und UPDATE unverändert (die Zeit liegt dort nicht in den Ausdrücken: UPDATE mit festem Wert 447 ms gegen 541 ms mit Ausdrücken, die Fensterrechnung selbst rund 260 ms je 50 000 Zeilen im Sortieren und Verteilen)✅
P15.5fDer Auslagerungsweg der Gruppierung**Behoben (03.10.2026).** Ausgelagert wurde der ganze Zeilenkontext als JSON (alle Sitzungsvariablen, drei Namen je Spalte, je Wert die ganze Typbeschreibung). Jetzt legt eine `Kontextablage` nur die Zeile ab: ein Zeilenkontext derselben Quelle als seine blossen Werte (Schema, Namensplan und Variablen einmal je Abfrage), eine Verbundsicht mit denselben Namen als Werte in deren Reihenfolge, sonst Paare; je Wert die Nummer seiner Typbeschreibung und der Rohwert binär (JSON nur für Werte ohne bekannte .NET-Form). Derselbe Weg dient dem partitionierten Fenster, dem Fensterspool und der linken Seite eines Verbunds (`Hashpuffer`/`Wahlfreispool` mit `herstellen`, `Auslagerungslauf` schreibt abgelegte Bytes ohne JSON). Gemessen (`GruppierungAuslagerungTests`, 20 000 Zeilen, 500 Gruppen, Pool mit 2 %): **1 564 → 62 Byte je Zeile**, die ausgelagerte Gruppierung **885 → 143 ms** (im Speicher 80 ms; Faktor 11 → 1,8). Gruppen im Durchlauf zu aggregieren wie SQL Server bleibt unnötig: das Auslagern kostet jetzt rund 3 µs je Zeile✅
P15.6Der Redis-Weg ohne KopieDer Leser reicht Argumente als Ausschnitte des Puffers (`ReadOnlyMemory`) durch den Verteiler, statt je Argument ein `byte[]` anzulegen; Befehlsname ohne Zeichenkette, Zahlen im Antwortpuffer ohne Zeichenkette. In-process, 700 Befehle je Paket (SET/GET/INCR/HSET/HGET/LPUSH/LPOP): **385 → 305 ns und 591 → 185 B je Befehl**. Argumente über zwei Puffersegmente werden einzeln kopiert; die Ablage behält ihre Werte (eine Kopie beim Speichern, wie zuvor)✅
P15.6bRedis-Vergleich nach P15.6 nachmessenNach M35 verschoben (**LL24.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit✅
P15.7DauerlaufNach M35 verschoben (**LL21.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit✅
P15.8Verbund und Ergebnis ohne Obergrenze im Speicher**Behoben (30.09.2026).** **(a)** Ein Verbund ohne hashbaren Schlüssel (keine Gleichheit in `ON`, Ungleichung, gerechneter Ausdruck) mit einer rechten Seite, die nicht in die Zuteilung passt, läuft **blockweise**: die rechte Seite in einem Spool (`Wahlfreispool`, Datei, wahlfrei gelesen), die linke in Blöcken von höchstens einem Viertel der Zuteilung (und 256 Zeilen), die rechte Seite je Block einmal gelesen; Trefferflags je rechter Zeile laufen über die Blöcke (`RIGHT`/`FULL`), die der linken je Block (`LEFT`/`FULL`). Dieselbe Menge wie im Speicher (Reihenfolge rechts-major, wie am Orakel ohne `ORDER BY` nicht zugesichert), Schreibbytes im Pool belegen das Auslagern. **(b)** Eine schlichte Abfrage — ein Stapel mit einer `SELECT`-Anweisung (beim parametrisierten Aufruf davor nur die `DECLARE`s der Parameter), jede Ausgabespalte ein Spaltenverweis, kein `DISTINCT`/`GROUP BY`/Fenster, kein `INTO` — sammelt ihr Ergebnis nicht mehr, sondern reicht jede Zeile **beim Erzeugen** an die `IErgebnissenke` weiter (innerhalb des Stapels: Sperren, Transaktion und Katalog gelten noch); die Senke am Draht (`TdsErgebnissenke`) schreibt Spaltenkopf und Zeilen in Paketen (Puffer 32 KB) und schickt den Rest mit dem Ende der Nachricht. Mit `ORDER BY` (und `TOP n`) nimmt der Sortierpuffer die Zeilen, und erst die Ausgabe mischt die ausgelagerten Läufe Zeile für Zeile (`Sortierpuffer.Sortiert`). Gemessen am Haufen des Prozesses (erzwungene Sammlung, `ErgebnisstromTests`): ein Kreuzprodukt mit 40 000 Zeilen, zusammen rund 12 MB, hält als Strom 0,4 MB Spitze, als ganze Antwort 20 MB. Ein Fehler mitten in den Zeilen kommt hinter den Zeilen davor an, ein Attention des Clients beendet den Strom. **Grenzen, jede mit dem, was sie brauchte (P15.8c):** ein Ausdruck in der Auswahl, `DISTINCT`, `GROUP BY`, Fenster, mehrere Anweisungen im Stapel und Aufrufe mit `OUTPUT` sammeln weiter — die Kopfzeile ihrer Spalten kommt heute aus den Werten (weiteste Ausprägung über alle Zeilen) und steht erst nach der letzten Zeile fest; gestromt würde sie aus den statischen Typen der Ausdrücke kommen müssen (ein eigener Umbau der Typableitung), bei mehreren Anweisungen müsste der Draht ihre Vorspann-Token vor der ersten Zeile schicken; `CROSS JOIN` fordert wie am Orakel (0 KB) keine Zuteilung an, hat also nichts, woran ein Auslagern sich misst; die Zeilen der Eingabe selbst (`ScanRows`) sind ein Abbild der Tabellenseiten im Speicher, ein seitenweises Lesen ist ein eigener Schritt.✅
P15.8cDer Strom für Ausdrücke, DISTINCT und mehrere Anweisungen**Behoben (03.10.2026, ).** **(a)** Ausdrücke, `DISTINCT` und `GROUP BY` strömen über einen `Ergebnisspool` (1 MB im Speicher, darüber Datei); ein `Kopfsammler` führt die Typen mit, der Kopf ist derselbe wie über die ganze Antwort. 40 000 Zeilen: Ausdrücke 450 KB statt 20 461 KB Spitze, DISTINCT 5 992 statt 56 048 KB, GROUP BY 12 473 statt 45 759 KB (die Schlüssel im Hashpuffer, der selbst auslagert). **(b)** Jede schlichte Abfrage der obersten Ebene strömt, auch hinter anderen Anweisungen und im Aufruf mit `OUTPUT`: was davor geschah, geht als Vorspann aus dem Rahmenprotokoll mit der ersten Zeile hinaus. 20 470 KB → 430 KB (Stapel), 366 KB (Aufruf). **(c)** Die Eingabe wird seitenweise gelesen und je Seite auf Sperren geprüft: 41 814 KB → 182 KB. Grenze damals: bei Heap, Schlüsseländerung oder Löschen hielt der Strom an der nächsten Seite bzw. zu Beginn der Anweisung statt an der Zeile (geschlossen mit P15.11). Dazu die Nebenbefunde aus P15.5e/f: ein typloses NULL aus einer Spalte gab `SUM(LEN(nvarchar(max)))` als `int` und `CONCAT` als `varchar`; jetzt gilt der breitere statische Typ. Sabotage: gesammelter Weg aus → 3 von 3 rot.✅
P15.9pgbench: Abfall von Lauf zu Lauf und Kosten je Transaktion (aus LL23.1)**Behoben (02.10.2026).** Der Abfall 73 → 59 → 33 tps kam aus dem Merker der Stapeltexte (`sys.dm_exec_query_stats`): bei Texten mit Literalen verdrängten gleichzeitige Sitzungen denselben ältesten, der Merker wuchs ohne Grenze (48 000 Einträge nach 2 min) und jedes Verdrängen sortierte ihn ganz. Dazu fünf Kosten je Transaktion: ein fsync je Commit unter einer Sperre, die auch `WalLogManager.Position` nach **jedem** Stapel nahm (jede Sitzung wartete auf die Platte, auch `SELECT 1`), die Server-GC stand unter falschem Namen in der Projektdatei (`ServerGarbageCollector`), jedes UPDATE verschob das Schlüsselverzeichnis zweimal (Megabytes bei einer Million Zeilen) und legte den Datensatz neu an (der alte Platz blieb belegt, rund 530 Byte je Transaktion). Behoben mit Gruppen-Festschreiben, Ersetzen am Ort im Verzeichnis und in der Seite, Server-GC, Verdrängen unter Sperre. Nebenbei: das Opfer eines Sperrrings lief im PostgreSQL-Port ausserhalb der Transaktion weiter (Summen wichen ab), jetzt `25P02` bis `END`. **Gemessen** mit `tools/pglast` (pgbench-Mischung, einfaches Protokoll, Skala 10, 16 Verbindungen, WAL, im selben Prozess): 171 → 876 tps, drei Läufe zu 3 min 798/841/772 tps bei 215/223/222 MB Heap nach vollem GC; ein `SELECT 1`-Zeuge daneben vorher −69 bis −94 %, nachher ohne Verlust. Zahlen und Profil in✅
P15.9bpgbench: Sperrringe ohne Ring und kürzere Datensätze**Behoben (02.10.2026).** **(a)** Bei streng geordnetem Zugriff meldete die Engine etwa einen `40P01` je 100 000 Transaktionen. Wer auf eine Zeile traf, wartete, bis der Inhaber **gar keine** Sperre mehr hielt, und die Kante „wartet auf“ zeigte auf die Sitzung: begann dieselbe Sitzung sofort die nächste Transaktion, wartete man auf diese mit, und eine Kante zu einer beendeten Transaktion schloss einen Ring. `Sperrkonflikt` trägt jetzt die Transaktion des Inhabers (die Menge seiner Sperren), Warten und Ringsuche gelten nur, solange genau sie läuft (`SperrringOhneRingTests`, ohne Zeitzufall; die alte Bedingung liefert dort `Deadlock`). `tools/pglast` 3 × 3 min: 0 Fehler in 418 219 Transaktionen, vorher 4 in 437 105. **(b)** Mit Primärschlüssel bleibt auch ein **kürzerer** Datensatz beim UPDATE an seinem Ort, die Rücknahme schreibt den längeren zurück✅
P15.9cPlatz längerer Datensätze und der Heaps**Behoben (03.10.2026).** Vorher zog ein Datensatz, der beim UPDATE länger wurde, und jeder geänderte Datensatz eines Heaps ans Ende, und der alte Platz wurde nie frei. Jetzt bleibt jede Zeile an ihrem Ort (Seite und Slot), auch die eines Heaps: nicht länger an Ort und Stelle, länger hinter dem belegten Platz derselben Seite (`SlottedPage.TryRelocateRecord`); die erste wachsende Zeile einer Seite verdichtet sie (`SlottedPage.Verdichte`, Slots bleiben, nur Offsets wandern). Verdichtet wird nur, wenn keine Rücknahme die alten Bytes der Seite noch braucht: Lösch- und Änderungsrücknahmen halten ihre Seiten fest, solange sie im Protokoll der Transaktion oder Anweisung stehen (`IUndoAction.Halte`, `SlottedPage.Gehalten`), die laufende Änderung ihre eigenen. Passt eine Zeile auch verdichtet nicht mehr auf ihre Seite, sucht sie Platz auf einer Seite der Freiplatzliste (Seiten mit Gelöschtem oder Verlassenem), sonst hinten; das Einfügen verdichtet die letzte Seite, bevor es eine neue anlegt, und hängt sonst wie bisher hinten an (die Reihenfolge eines ungeordneten Durchlaufs bleibt die des Einfügens). `WHERE CURRENT OF` auf einem Heap sucht ab dem Ort, an dem der Cursor zuletzt traf (`Cursorzustand.Suchbeginn`, `MemoryTable.OrteAb`). Gemessen (`SeitenplatzTests`): 40 Zeilen 200-mal abwechselnd auf 300 und 1 Zeichen geändert: vorher 1,33 MB, jetzt 2 Seiten (mit und ohne Primärschlüssel); 18 Zeilen 200-mal gelöscht und neu eingefügt bleiben auf einer Seite (vorher 6); `CursorartenTests` Heap mit 5 000 Zeilen 1 s (ohne den Suchbeginn 19 s)✅
P15.9dKontrollpunkt unter Dauerlast**Behoben (03.10.2026).** Vorher (gemessen mit `tools/pglast`, 2 min, 16 Verbindungen) gelang unter Last kein einziger Kontrollpunkt: `Kontrollpunkt()` verlangte, dass keine Transaktion offen ist, das Protokoll wuchs ohne Grenze. Jetzt ist er **unscharf**: offene Transaktionen, die nur Zeilen geändert haben, halten ihn nicht auf. Für ihre Zeilen geht das bestätigte Bild in die Datendateien — aus ihren Rücknahmen (`IUndoAction.TrageBestaetigtesBildEin`, rückwärts, die älteste gewinnt) statt aus der Sperrverwaltung, denn ein Heap ohne Schlüssel hat dort keinen eindeutigen Eintrag. Den Stand zieht er unter der Stapelsperre in den Speicher (`Datendateien.Abziehen`, `MemoryTable.Abbild` kopiert Datensätze ohne Entziffern, wo es keine Grossobjekte gibt), geschrieben wird ausserhalb. Gekürzt wird nur bis zum ersten Glied, das eine offene Transaktion noch braucht (`WalLogManager.Kuerzen(beginn, datenEnde)`: die neue Generation entsteht als eigene Datei und ersetzt die alte erst ganz geschrieben; ein Kontrollpunktsatz vorn nennt, wie weit die Datendateien in den Rest reichen). Der Wiederanlauf liest ab dem Beginn und spielt nur ab, was nach dem Kontrollpunkt endete. Gemessen in `KontrollpunktUnterLastTests`: 16 Sitzungen nach TPC-B, alle mit offener geänderter Transaktion, 12 Kontrollpunkte von 12 gelingen, das Protokoll bleibt bei 12 Byte (ohne Kürzen über 50 KB), nach dem Absturz stimmen die Summen. Weiter warten muss er (wie bisher) nur auf eine offene Transaktion, die mehr als Zeilen geändert hat (Katalog, Rechte), die vorbereitet oder verteilt ist, oder deren erster Stapel vor ihrem Beginn schon etwas festgeschrieben hat (`INSERT …; BEGIN TRAN; …` steht als ein Glied im Protokoll, der Wiederanlauf könnte es nur ganz abspielen). pgbench und SqlClient schicken `BEGIN` als eigenen Stapel. **Nachgemessen (03.10.2026, ):** pglast zeigte unter 16 Clients keinen einzigen Kontrollpunkt (Generation blieb bei 2, Protokoll 6,6 → 72 MB in 120 s), weil der Kontrollpunkt nicht auf die Stapelsperre wartete und Stapel sie auch beim Warten auf Zeilensperren hielten. Jetzt gibt ein wartender Stapel in einer abbildbaren Transaktion die Stapelsperre ab, der Kontrollpunkt wartet bis 250 ms und läuft wegen der Schwelle im Hintergrund. Nebenläufiger Test ohne direkten Aufruf (16 Fäden, 3 s, Schwelle 16 KB): vorher 1 Kontrollpunkt und 156–201 KB, jetzt 8 und 22 KB, Summen nach dem Neustart richtig. Nachgemessen mit `tools/pglast` (Release, Skala 10, 16 Verbindungen, 120 s, 03.10.2026): **7 von 7 Kontrollpunkten gelungen**, Protokoll zwischen 0,9 und 8,4 MB statt linear bis 72 MB, 98 785 Transaktionen, 0 Fehler, Summen stimmen; 823 tps (vorher 919)✅
P15.10Eingebettete Nutzung aus mehreren Fäden**Behoben (03.10.2026, ).** Riefen mehrere Fäden die Engine direkt ohne eigene Sitzung auf, teilten sie sich die Direktsitzung (`Sitzungsverwaltung.AktuelleOderNeue`); ein zweiter gleichzeitiger Stapel galt als geschachtelt und nahm die Stapelsperre nicht, ein Kontrollpunkt lief neben ihm. Gemessen (8 Fäden nach TPC-B, 2 s, Schwelle 16 KB, Neustart): nach dem Neustart 408 statt 277 Buchungen, Summen 1949/1934 statt 1313 (Änderungen doppelt abgespielt). Jetzt besitzt der Faden, der den äussersten Stapel ausführt, die Sitzung (`Sitzungsdaten.Stapelfaden`, per `Interlocked.CompareExchange`); trifft ein Stapel die Direktsitzung im Besitz eines anderen Fadens, meldet seine Engine eine eigene Sitzung an und führt den Stapel darin als äussersten aus (mit Stapelsperre), solange sie eine Transaktion offen hält, danach meldet sie sie wieder ab. TDS und PostgreSQL-Port waren nicht betroffen (eigene Sitzung je Verbindung). Sabotage: Umschalten abgeklemmt → 2 von 2 rot.✅
P15.11Der Strom wartet an der betroffenen Zeile (Grenze aus P15.8c(c))**Behoben (03.10.2026, ).** Vorher hielt ein gestromtes Ergebnis bei einer fremden Löschung, Heap-Änderung oder Schlüsseländerung schon vor der ersten Zeile an (3000 Zeilen, betroffen Zeile 2900: 0 Zeilen beim Client), sonst an der ganzen Seite. Lösbar, weil die Stelle des bestätigten Bildes bis zum Transaktionsende fest ist: gelöschte Slots werden nicht wiederverwendet, die Rücknahme stellt die Zeile genau dort wieder her. Die Sperre merkt sich diese Stelle als Löschmarke (`Sperrverwaltung.Eintrag.Ort`, aus `MemoryTable.Schreibpruefung`), der Leser hält am ersten Slot einer Seite an, an dem eine Marke oder eine fremd gesperrte Zeile steht (`Hindernis`), liefert die Zeilen davor und liest nach dem Warten ab diesem Slot weiter. Nebenbei (aus dem Code hergeleitet, ohne eigenen Test): eine Zeile, die beim Ändern auf eine spätere Seite zog und deren Änderung zurückgenommen wurde, fehlte vorher im Strom; jetzt wartet der Leser an ihrer alten Stelle. Gemessen: alle sechs Fälle (Löschen, Heap, Schlüssel × COMMIT/ROLLBACK) über 2800 Zeilen beim Client vor dem Warten, danach der bestätigte Stand. Sabotage: Marke nicht beachtet → 2 von 6 rot (Löschen; Heap und Schlüssel fängt die neue Fassung am selben Slot).✅
Meilenstein 16 (optional): GraphQL als dritte Zugriffssprache (26/26)
PunktTitelBeschreibungStand
G16.1Der GraphQL-ParserLexer 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.2Das Schema aus dem KatalogJede 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.3Abfragen: Felder, Filter, Sortierung, SeitenJe 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.4Verschachtelung über die FremdschlüsselEin 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.5Mutationen`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.6Introspektion, 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.7Der Nachweis, dass es den SQL-Server nicht bremstDieselbe 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.8Dasselbe in SSMS sichtbarSchema `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.9aFilter ü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.9bSortieren 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.9cUpsert ü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.9dEinfü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.9fAggregate und Seiten innerhalb der VerschachtelungJede 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.9gGebü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.9hAnmeldung 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.9iMitgelieferte 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.9jTabellen mit Schreib- aber ohne LeserechtEine 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.9kSichten als lesbare TypenJede 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.9lFunktionen 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.9mAbonnements (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.9nGraphQL-Reiter im PrüfstandDie 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.9oBesitzkette im Rumpf von Prozeduren und FunktionenAus 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 SkalarfunktionenAus 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 AnmeldetriggernDie 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.9Die sättigende Messreihe aus G16.7Nach M35 verschoben (**LL25.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit✅
Meilenstein 17: MariaDB-Protokoll und -Syntax auf derselben Ablage (91/91)
PunktTitelBeschreibungStand
D17.1Der HandschlagPort 3306 (einstellbar, voreingestellt aus), Paketrahmen mit Folgenummer und 16-MB-Aufteilung, Handshake v10 samt Fähigkeitsbits und Versionsangabe (`11.x-MariaDB`), TLS über `SSLRequest`, `COM_QUIT`/`COM_PING`/`COM_INIT_DB`/`COM_RESET_CONNECTION`✅
D17.2Die Anmeldung`mysql_native_password`, `client_ed25519` (Ed25519 im eigenen Code) und `caching_sha2_password`, `COM_CHANGE_USER`, Auth-Switch; gegen dieselben Anmeldungen wie TDS, ohne zweite Benutzertabelle✅
D17.3Textabfragen und Ergebnismengen`COM_QUERY` mit Mehrfachanweisungen, Spaltendefinitionen, Textzeilen, `OK`/`ERR`/`EOF` samt `affected_rows`, `last_insert_id`, Warnungszahl und SQLSTATE; die MariaDB-Fehlernummern (1062, 1146, 1064 …) statt der SQL-Server-Nummern✅
D17.4Vorbereitete Anweisungen`COM_STMT_PREPARE`/`EXECUTE`/`FETCH`/`CLOSE`/`RESET`/`SEND_LONG_DATA`, das binäre Zeilenformat, Parameter mit `?`, Cursor; MariaDBs `COM_STMT_BULK_EXECUTE` für Stapel (am echten `mariadb:11` vermessen, 30.09.2026: erweiterte Fähigkeit `STMT_BULK_OPERATIONS` im Gruss und in der Anmeldeantwort, 1295 ohne sie, Hinweise NULL/DEFAULT/IGNORE, Typen des letzten Aufrufs, alles oder nichts, 1210/1835)✅
D17.4bBulk: Einzelergebnisse**Erledigt, mit einem Rohclient am echten `mariadb:11` (11.8.9) vermessen:** `BULK_UNIT_RESULTS` — Kennzeichen 0x40 an `COM_STMT_BULK_EXECUTE` liefert statt des OK eine Ergebnismenge `Id`/`Affected_rows` (beide BIGINT, Binärzeilen) mit einer Zeile je Parametersatz; `Id` ist der Zählerwert, den genau dieser Satz erzeugt hat (sonst 0, auch bei einem UPDATE nach einem früheren INSERT der Sitzung), der Gruss bietet jetzt `0x24` (`STMT_BULK_OPERATIONS` und `BULK_UNIT_RESULTS`). Der echte Server richtet sich allein nach dem Kennzeichen, nicht nach der Fähigkeit — hier ebenso. 3 neue Tests.✅
D17.4cPROGRESS, EXTENDED_METADATA, CACHE_METADATA**Erledigt, jedes Byte am echten `mariadb:11` (11.8.9) mit einem Rohclient gemessen:** der Gruss bietet wie der Server `0x3D`; **nur für einen Client, der sie anfordert**, gilt: `EXTENDED_METADATA` (hinter dem Originalnamen jeder Spalten- und Platzhalterdefinition ein Text, leer `00`, sonst `uuid`/`json` als Typname bzw. Format) und `CACHE_METADATA` (hinter der Spaltenzahl ein Kennbyte: 1 Definitionen folgen, 0 nicht — bei vorbereiteten Anweisungen merkt sich der Server die zuletzt geschickten und wiederholt sie erst nach einer Änderung); `PROGRESS` wird ausgehandelt, gemeldet wird nichts (keine Anweisung hat einen Fortschritt). Ein Client ohne die Bits sieht das Format von vorher. 7 neue Tests. Rest in D17.4d✅
D17.4dMetadaten und Bulk im Einzelnen**Erledigt (M17), Zahlen am echten `mariadb:11` gemessen (`MariaDbEinfuegeKennungTests`, `MariaDbLetzteKennungTests`):** `Id` und `LAST_INSERT_ID()` nennen bei einem `INSERT` mit mehreren Zeilen (auch im Bulk-Satz und bei `INSERT … SELECT`) den **ersten** erzeugten Wert statt des letzten; ein eigener Wert erzeugt nichts. Das `insert_id` des OK-Pakets nennt nur, was **diese** Anweisung erzeugt hat (ein `UPDATE`/`DELETE` nach einem `INSERT` meldete den Wert von vorhin, jetzt 0: `ExecutionEngine.AnweisungsAutowert`); **`LAST_INSERT_ID(ausdruck)`** setzt den Wert der Sitzung und das `insert_id` der Anweisung (`ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)`, `UPDATE … SET n = LAST_INSERT_ID(n + 1)`; war ein Durchreicher), `LAST_INSERT_ID(NULL)` setzt 0; `LAST_INSERT_ID()` ist eine Ganzzahl (war `decimal`) und ohne erzeugten Wert 0 (war NULL). `ON DUPLICATE KEY UPDATE` und `REPLACE` mit mehreren Zeilen laufen seit D17.5c Zeile für Zeile und folgen derselben Regel.✅
D17.4eRest: Zähler-Lücken, Fortschritt, erweiterte Metadaten**Erledigt (M17), am echten `mariadb:11` (11.8.9) gemessen (`MariaDbAutowertvorratTests`):** der `AUTO_INCREMENT`-Vorrat der Anweisung (`n` Wertezeilen reservieren `n` Werte, `INSERT … SELECT` Stapel 1, 2, 4 …, eine nur ändernde/auslassende Zeile verbraucht bei einer Zeile trotzdem einen Wert, bei mehreren gibt sie ihn dem Vorrat zurück, der Rest verfällt) und `REPLACE` als Löschen jeder Zeile mit gleichem Schlüssel plus Einfügen mit neuem Wert (Fremdschlüsselaktion, Trigger, betroffene Zeilen = gelöschte + eingefügte).✅
D17.4fErweiterte Metadaten und Fortschrittspakete**Erledigt (M17, Runde 11), jedes Byte aus dem Mitschnitt gegen den echten `mariadb:11` (11.8.9) (`orakel/mitschnitt-antworten.txt`, `MariaDbMetadatenTypenTests`):** mit `EXTENDED_METADATA` steht hinter dem Originalnamen das Tupel aus Kennbyte und Text: `inet4`/`inet6`/`point`/`linestring` und die weiteren Geometrienamen (Kennbyte 0), `uuid` (0), `json` als Format (1); `GEOMETRY` hat keines. Die Felder fester Länge: `INET4`/`INET6` als Zeichenkette 0xfe, Länge 60 und 156, Zeichensatz 224, Flags 0xa0; `UUID` ebenso mit Länge 144; Geometrie 0xff, Zeichensatz 63; `JSON` 0xfc mit der Höchstlänge, Zeichensatz 224. Die Marke reist als Typmarke (`ma:t:`, `ma:g:`) vom Katalog (Typbedingung, gemerkte Schreibweise `point`) über den Wert in die Ergebnisspalte. Fortschrittspakete (`ff ffff 01`, Stufe, Stufenzahl, Fortschritt in drei Byte, Name): braucht `ALTER TABLE … ALGORITHM=COPY` mindestens `progress_report_time` Sekunden und hat der Client `PROGRESS` verlangt, folgen vor dem OK die vier Stufen `copy to tmp table`, `Enabling keys` (zweimal) und `After apply log event`; `ALGORITHM` und `LOCK` in einem `ALTER` werden gelesen. Der Fortschrittswert der ersten Stufe steht bei 0 (der Server nennt den Stand zur Meldezeit).✅
D17.4gLAST_INSERT_ID in Triggern**Erledigt (M17, Runde 11), am echten `mariadb:11` (11.8.9) gemessen (T1 bis T14, `MariaDbLetzteKennungTriggerTests`):** schreibt der Trigger eines `INSERT` in eine andere Tabelle mit `AUTO_INCREMENT`, bleiben `LAST_INSERT_ID()` und `insert_id` die der Anweisung (die erste der eigenen Tabelle, auch bei mehreren Zeilen); ein `BEFORE`-Trigger sieht die Kennung der Sitzung von vorher, danach gilt die der Anweisung.✅
D17.5Der MariaDB-ParserEigener Lexer und Parser auf **denselben** Syntaxbaum: Backticks, `'` und `"` je nach `ANSI_QUOTES`, `#`- und `-- `-Kommentare, `/*! … */`, `LIMIT n OFFSET m` und `LIMIT m, n`, `:=`, `<=>`, `DIV`/`MOD`, `REGEXP`, `\`-Escapes, Benutzervariablen `@x` und Systemvariablen `@@x`✅
D17.5bDie volle Dialekt-Weiche im gemeinsamen Baum**Schrittweise (M17), je Konstrukt eine Scheibe.** **Erledigt: `LIMIT n`, `LIMIT n OFFSET m`, `LIMIT m, n`** sind Grammatik im gemeinsamen `PrattParser` (`IstLimitKlausel`, nur unter `Dialekt.LimitKlausel` (D17.13); derselbe Baum wie `OFFSET m ROWS FETCH NEXT n ROWS ONLY`, ohne `ORDER BY` mit derselben verdeckten Sortierung); `MariaDbLimitUebersetzer` ist gelöscht. **Scheibe 2: `WITH RECURSIVE`** liest der Parser selbst (`ParseWithCteStatement`; ein CTE-Name `recursive` bleibt ein Name, in T-SQL bleibt es Fehler 102). **Scheibe 3: `CAST(x AS UNSIGNED|SIGNED [INTEGER])` und `CONVERT(x, UNSIGNED|SIGNED [INTEGER])`** (`ParseCastExpression`/`ParseConvertExpression`; für CONVERT liest eine Vorschau im Quelltext das letzte Argument, weil MariaDB und T-SQL die Reihenfolge vertauschen). Neu möglich, was der Text-Übersetzer nicht konnte: `LIMIT` in einer abgeleiteten Tabelle. **Scheibe 4 und 5: `IF(b, a, c)` als Ausdruck und `DATE_ADD|DATE_SUB(d, INTERVAL n EINHEIT)`** liest der Parser selbst (`IF` vor `(` wird zu `IIF`, `INTERVAL` wird `DATEADD`; `MariaDbIfDatumParserTests`); `ErsetzeIf` und `ErsetzeDateAddSub` sind gelöscht (`MariaDbIfDatumParserTests`). Damit der Wiederanlauf einen Stapel mit `LIMIT` versteht, trägt eine Protokollfolge das Merkmal der MariaDB-Sitzung (Bit 1048576 der Transaktionsoptionen) und wird mit demselben Dialekt abgespielt (`MariaDbLimitWiederanlaufTests`). **Scheibe 6: `GROUP_CONCAT([DISTINCT] a [, b …] [ORDER BY …] [SEPARATOR s])`** (`ParseGroupConcat`, `Dialekt.GroupConcat`; derselbe Baum wie `STRING_AGG … WITHIN GROUP`), neu: mehrere Ausdrücke (wie `CONCAT`, eine Zeile mit NULL fällt weg); `MariaDbFunktionsUebersetzer` ist gelöscht. **Scheibe 7: `UPDATE/DELETE … LIMIT n`** ohne `ORDER BY` (`LiesDmlLimit`, derselbe Baum wie `TOP (n)`) und **`RETURNING`** an `INSERT`/`UPDATE`/`DELETE` (`LiesReturning`, `Dialekt.ReturningKlausel`; derselbe Baum wie `OUTPUT INSERTED.…`/`DELETED.…`; `RETURNING` ist dann kein Alias); die zwei Textfunktionen in `MariaDbDmlUebersetzer` sind gelöscht. Rest siehe D17.5c✅
D17.5cRest: die übrigen Text-Übersetzer im Parser**Scheibe 1 erledigt: `UPDATE/DELETE … ORDER BY … LIMIT n`** liest der Parser selbst (`LiesDmlLimit`/`MitDmlTop`, gleiche CTE-Form `SELECT TOP (n) * … ORDER BY …` im Baum; ein `ORDER BY` ohne `LIMIT` entfällt wie bei MariaDB an einer Einzeltabelle; `MariaDbDmlGeordnetParserTests`); nebenbei ein Engine-Fehler behoben (Fehler 207, wenn eine aktualisierbare CTE einen `ORDER BY`-Ausdruck mit `TOP` trägt: die verdeckte Sortierspalte zählte als Zielspalte). **Scheibe 2 erledigt: `INSERT IGNORE`, `REPLACE [INTO]`, `INSERT … ON DUPLICATE KEY UPDATE` (samt `VALUES(spalte)`) und `INSERT … SET`** liest der Parser (`PrattParser.Upsert.cs`, `Dialekt.UpsertKlauseln`, `InsertStatement.Upsert`); die Engine führt es Zeile für Zeile als `MERGE` über den Schlüssel aus dem Katalog aus (`ExecutionEngine.Upsert.cs`), `MariaDbUpsertUebersetzer` ist gelöscht (der Klammer-Teiler für `sp_executesql` steht jetzt als `Parser/Textteiler`, die Ausnahme im Architekturtest ist weg). Am echten `mariadb:11` mit denselben Anweisungen gemessen (Zeilen **und** betroffene Zeilen: geändert 2, unverändert 0, ersetzt 2, neu 1; `MariaDbUpsertParserTests`, 13) - neu gegenüber dem Text-Übersetzer: Schlüssel auch über `UNIQUE`, `INSERT … SELECT`, ohne Spaltenliste, `INSERT … SET`, zwei Zeilen mit demselben Schlüssel in einer Anweisung sehen einander, `REPLACE` setzt nicht genannte Spalten auf ihren Vorgabewert zurück, `INSERT IGNORE` nimmt für NULL in einer `NOT NULL`-Spalte den Vorgabewert des Typs und lässt Zeilen aus, die einen anderen Schlüssel/Fremdschlüssel/`CHECK` verletzen, eine unbekannte Spalte in `ON DUPLICATE KEY UPDATE` scheitert auch ohne Treffer. Hinter `WITH … INSERT IGNORE` lehnt die Engine mit 1235 ab statt still wegzulassen. Ohne Schlüssel in der Spaltenliste ist es ein gewöhnliches `INSERT`. Nebenbei geschlossen: `OUTPUT INSERTED.*`/`DELETED.*` kannte die Engine nur bei `MERGE` (damit ging auch `RETURNING *` nicht) - der Stern wird jetzt bei `INSERT`, `UPDATE` und `DELETE` zu den sichtbaren Spalten (`ExecutionEngine.AusgabeSterne`), und `RETURNING` liefert auch bei `REPLACE`, `ON DUPLICATE KEY UPDATE` und `INSERT IGNORE` die neuen Zeilen. **Scheibe 3 erledigt: `MATCH (spalten) AGAINST (anfrage [IN NATURAL LANGUAGE MODE | IN BOOLEAN MODE])`** liest der Parser (`PrattParser.Volltext.cs`, `Dialekt.MatchAgainst`) als Aufruf `__MDB_MATCH(bezug, modus, anfrage, spalten…)`; welcher `FULLTEXT`-Index gemeint ist, bestimmt die Auswertung aus den `FROM`-/`JOIN`-Tabellen der umgebenden Abfrage (`VolltextBezug` bekommt sie, wenn die Abfrage gelesen ist; auch über eine abgeleitete Tabelle), ein `MATCH` hinter `WHERE`/`AND`/`OR`/`NOT`/`HAVING`/`ON` ist wahr bei Relevanz über 0. `MariaDbVolltextUebersetzer` ist gelöscht; `MariaDbVolltextSuche` (die Rechnung) und die Verwaltung der `FULLTEXT`-DDL (`MariaDbVolltext`) bleiben. Am echten `mariadb:11` gemessen (`MariaDbMatchParserTests`, 5; ein Nachspiel von 15 Abfragen stimmt bis auf `WITH QUERY EXPANSION`, das wie vorher 1235 meldet). **Scheibe 4 erledigt: `LOAD DATA [LOCAL] INFILE` liest der Parser** (`PrattParser.LadeDaten.cs`, `Dialekt.LadeDaten`, `LadeDatenStatement`: alle Klauseln, `FIELDS`/`LINES`, `IGNORE n LINES`, Spaltenliste mit `@var`, `SET`); die Regex-Übersetzung `MariaDbLoadDataUebersetzer` ist weg. Die **Datei** liest weiter der Anschluss (`MariaDbLoadData`, ohne Textumformung der Syntax) und schickt der Engine ein `INSERT` mit den Daten: das Transaktionsprotokoll hält den Text der Anweisung, ein `LOAD DATA`, das nur auf eine Datei zeigt, überstünde den Neustart nicht (bei `LOCAL` gäbe es die Datei gar nicht mehr). `MariaDbLoadData*Tests` (41 mit dem Parser-Test, 4 neu) unverändert grün. **Scheibe 5 erledigt: die Spaltentypen von MariaDB** (`UNSIGNED`, `ZEROFILL`, Anzeigebreite, `ENUM`, `SET`, `YEAR`, `BIT(n)`, `UUID`, `INET4`/`INET6`, `DATETIME(p)`/`TIMESTAMP(p)`, `BOOL`, `DOUBLE [PRECISION]`, `TEXT`-/`BLOB`-Familie, Geometrietypen, `CHARACTER SET`/`COLLATE` an der Spalte) liest der Parser in `CREATE TABLE` und `ALTER TABLE … ADD|MODIFY|CHANGE` (`PrattParser.Spaltentypen.cs`, `Dialekt.MySqlSpaltentypen`): der Typname im Stil von T-SQL und die `CHECK`-Bedingungen des Typs kommen aus derselben Grammatik (in `ALTER` als eigene `ADD CONSTRAINT`-Schritte wie vorher), `MariaDbTypRueckuebersetzung` liest sie unverändert zurück; `MariaDbTypUebersetzer` ist gelöscht. `MariaDbSpaltentypenParserTests` (7) und die bestehenden Typ-Tests (`MariaDbTypenTests`, `MariaDbSpaltentypErweiterungTests`, `MariaDbZahldarstellungTests`) unverändert grün. Nebenbei: der Optionen-Ausdruck des DDL-Übersetzers fraß bei `… VARCHAR(50) COLLATE x)` die schliessende Klammer. **Scheibe 6 erledigt: `ALTER TABLE` mit mehreren Änderungen, `RENAME TABLE` mit Paaren, `DROP TABLE` mit Liste** (`PrattParser.AlterListe.cs`, `Dialekt.AlterMitAenderungsliste`): `ADD [COLUMN]` (auch `(spalte typ, …)`, `INDEX`/`KEY`/`UNIQUE KEY`, `PRIMARY KEY`, `FOREIGN KEY`, `CHECK`, `CONSTRAINT`), `MODIFY`, `CHANGE`, `ALTER COLUMN … [SET DEFAULT x | DROP DEFAULT]`, `DROP COLUMN|INDEX|KEY|FOREIGN KEY|CONSTRAINT|PRIMARY KEY`, `RENAME COLUMN|INDEX|TO`, `AUTO_INCREMENT = n`, `ENGINE`/`COMMENT`/Zeichensatz (ohne Wirkung), `FIRST`/`AFTER x` (gelesen, die Engine legt die Lage nicht fest) - als Block der vorhandenen Anweisungen; **was die Grammatik nicht kennt, ist ein Syntaxfehler statt eines stillen Weglassens.** Beim Nachspiel gegen `mariadb:11` (`alt_diff`) zeigte sich: nach der ersten Änderung las der T-SQL-Zweig den Rest der Anweisung bis zum Semikolon und warf ihn weg (`ADD COLUMN c INT, ADD COLUMN e INT NOT NULL DEFAULT 5` legte `e` nie an), `RENAME TABLE a2 TO a3, a3 TO a4` legte eine Tabelle namens `a3,` an, `DROP TABLE a, b` und `RENAME COLUMN` waren Syntaxfehler, `AUTO_INCREMENT=n` als Tabellenoption fiel weg, ein zweiter Primärschlüssel wurde nicht abgelehnt (1068), `DROP COLUMN`/`MODIFY` einer unbekannten Spalte meldeten 1105. Die Übersetzer für `RENAME TABLE`, `ADD COLUMN`, `MODIFY`, `CHANGE`, `AFTER`/`FIRST` und die Tabellenoptionen im `MariaDbDdlUebersetzer` sind gelöscht; `DROP TABLE a, b` gilt seit dem auch in T-SQL. Neu in der Engine: `AlterTableAktion.StandardSetzen` und `PrimaerschluesselEntfernen`, die Ablehnung eines zweiten Primärschlüssels. `MariaDbAlterListeParserTests` (9) plus zwei Leitungs-Tests, Nachspiel gegen `mariadb:11` stimmt bis auf `ON UPDATE CURRENT_TIMESTAMP` in einer Änderungsliste (bleibt ein Syntaxfehler, siehe D17.6e). **Noch Textumformung und damit offen,** je mit Grund: Mehrtabellen-`UPDATE`/`DELETE` (`MariaDbDmlUebersetzer`, `MariaDbMehrfachzielUebersetzer`: mehrere Anweisungen samt Zwischentabelle), DDL im MariaDB-Stil (`MariaDbDdlUebersetzer`: `CREATE TABLE` mit `AUTO_INCREMENT`, Tabellenoptionen und Inline-`KEY`, `IF NOT EXISTS`, `OR REPLACE`, `LIKE`, `AS SELECT`, Transaktions- und `KILL`-Anweisungen; `MariaDbOnUpdateAlterUebersetzer`: `ON UPDATE CURRENT_TIMESTAMP` als Trigger), Routinen und Trigger (`MariaDbRoutineUebersetzer`: Kontrollfluss mit `BEGIN…END`, Handler, Cursor-Nachbildung). Jede Scheibe muss dieselben Bäume liefern, die der Text heute ergibt, und wiederanlauffest sein (Kennung im Protokoll, D17.13). **Scheibe 7 erledigt (Runde 8): `UPDATE t1 JOIN t2 … SET …`, `UPDATE t1, t2 SET …` und `DELETE FROM t1 USING …` mit einem Ziel liest der Parser selbst** (`PrattParser.MehrtabellenDml.cs`, `Dialekt.UpdateMitTabellenliste`: die Tabellenliste mit `LiesQuellen(ohneFrom: true)`, das Ziel ist die Tabelle oder der Alias vor den Spalten in `SET`, ohne Bezug die erste Tabelle; derselbe Baum wie die T-SQL-Form `UPDATE ziel SET … FROM …`). `MariaDbDmlUebersetzer` (125 Zeilen) ist gelöscht; `MariaDbMehrtabellenParserTests` (8, Zeilen und betroffene Zeilen je am echten `mariadb:11` gemessen, ohne die Text-Übersetzer des Anschlusses). **Scheibe 8 erledigt (Runde 8): die Transaktionsanweisungen** (`START TRANSACTION` mit Merkmalen, `BEGIN [WORK]`, `COMMIT`/`ROLLBACK` mit `WORK`/`AND NO CHAIN`/`RELEASE` als stilles OK ohne offene Transaktion, `SAVEPOINT`, `ROLLBACK [WORK] TO [SAVEPOINT]`, `RELEASE SAVEPOINT`, `KILL CONNECTION` oder `KILL QUERY`) liest der Parser (`PrattParser.MariaTransaktion.cs`, `Dialekt.MariaTransaktionen`; `MariaDbTransaktionParserTests`, 12); **Scheibe 9: `CREATE OR REPLACE` für Sicht, Prozedur, Funktion und Trigger** ist im Parser `CREATE OR ALTER` (`Dialekt.CreateOrReplace`), **Scheibe 10: `AUTO_INCREMENT` an der Spalte** ist `IDENTITY` im Parser (`MariaDbCreateOrReplaceParserTests`, 3); die tote Rückfall-Regel `CREATE TABLE … LIKE` ist gelöscht. Entfallen ist ausserdem `MariaDbOnUpdateAlterUebersetzer` (157 Zeilen: `ON UPDATE CURRENT_TIMESTAMP` liest der Parser als Spalteneigenschaft). **Scheibe 11 erledigt: `CREATE TABLE` im MariaDB-Stil liest der Parser** (`PrattParser.CreateTableMaria.cs`, `Dialekt.CreateTableMaria`): Inline-`KEY`/`INDEX`/`UNIQUE KEY`/`FULLTEXT KEY` in der Spaltenliste (einfacher `KEY` ist ein `CREATE INDEX` hinter der Tabelle, namenlos `IX_tabelle_n`, `UNIQUE KEY` eine Einschränkung, Präfixlängen und `DESC` werden gelesen), Tabellenoptionen (`AUTO_INCREMENT = n` setzt den Zähler per `DBCC CHECKIDENT`, `ENGINE`, Zeichensatz, `COMMENT` ohne Wirkung), `IF NOT EXISTS` (Hinweis 1050 statt Anlegen), `OR REPLACE TABLE` (`DROP TABLE IF EXISTS` und `CREATE TABLE`), `AS SELECT` (`SELECT … INTO`) und bei Sichten `ALGORITHM`/`DEFINER`/`SQL SECURITY` sowie `CREATE VIEW IF NOT EXISTS`; die gespeicherte Definition einer Sicht trägt den Kopf nicht. **`MariaDbDdlUebersetzer` ist gelöscht;** übrig bleibt eine Textstufe, `MariaDbSpaltenzeichensatz.TabellenstandardAnTabellen` (der Zeichensatz der Tabelle muss vor dem Parsen an die Textspalten). Neu gegenüber dem Text: Index-Optionen (`USING BTREE`, `COMMENT`) hinter `KEY (…)`, `IF NOT EXISTS` auch mit `AS SELECT`, ein Kopf ohne `VIEW` ist ein Syntaxfehler. `MariaDbCreateTableParserTests` (12; die Sabotage-Probe `KEY` wird nicht nachgezogen machte 2 rot) und alle 1426 Tests der Klassen MariaDb, Parser, Dialekt und Architektur unverändert grün. **Noch Text, je eine eigene mehrtägige Scheibe ohne neues Verhalten für den Client:** **Scheibe 12 erledigt: Mehrtabellen-`UPDATE`/`DELETE` mit mehreren Zielen** (`UPDATE t1 JOIN t2 … SET t1.a = …, t2.b = …`, `UPDATE t1, t2 SET …`, `DELETE t1, t2 FROM …`, `DELETE FROM t1, t2 USING …`, auch `t1.*` und `LOW_PRIORITY`/`QUICK`/`IGNORE`) liest der Parser als neues Baumobjekt `MehrfachzielStatement` (Ziele, je Zuweisung ihr Ziel, Quellen, `WHERE`; `PrattParser.MehrtabellenDml.cs`). Die Schlüsselspalten der Ziele löst die Ausführung über den Katalog auf (`ExecutionEngine.ExecuteMehrfachziel`: eine Anweisung schreibt Schlüssel und alte Werte in `#__kmt`, danach ändert je Ziel eine Anweisung; die Anweisung ist als Ganzes atomar über `GanzOderGarNicht`, die Summe der betroffenen Zeilen ist das Ergebnis). `MariaDbMehrfachzielUebersetzer` (370 Zeilen) samt `NimmSumme` und der Hilfsvariable `@__kmt_zeilen` sind gelöscht; die 8 Leitungs-Tests `MariaDbMehrfachzielTests` unverändert grün, dazu `MariaDbMehrfachzielParserTests` (7: Baumform, beide Schreibweisen, Rollback auch in offener Transaktion, 102 ohne Primärschlüssel oder bei unbekanntem Ziel und bei `LIMIT`). **Scheibe 13 ✅ (in sechs Unterscheiben, je eigener Commit): die Routinen und Trigger liest der Parser, `MariaDbRoutineUebersetzer` (acht Dateien, rund 1900 Zeilen) ist gelöscht.** **13a:** `CALL name[(argumente)]` (`PrattParser.MariaCall.cs`, `Dialekt.MariaCall`): derselbe Baum wie `EXEC`; ob ein `@variable`-Argument `OUTPUT` ist, entscheidet die Ausführung an der Deklaration der Prozedur (`ExecuteProcedureStatement.AusgabeNachDeklaration`) statt des Katalogs beim Übersetzen; ein Vergleich `a = 1` als Argument ist kein benannter Parameter mehr. **13b:** der Kopf von `CREATE PROCEDURE`/`FUNCTION`/`TRIGGER` (`PrattParser.MariaRoutine.cs`, `Dialekt.MariaRoutinen`): Parameter mit `IN`/`OUT`/`INOUT` (`OUT` nimmt den Wert des Aufrufers nicht an), `RETURNS`, Merkmale, `DEFINER`, `OR REPLACE`, `IF NOT EXISTS` (`WennNichtVorhanden` an der Anweisung, die Ausführung prüft den Katalog erst beim Ausführen), beim Trigger Zeitpunkt, Ereignis, Tabelle, `FOR EACH ROW`, `FOLLOWS`/`PRECEDES`. **Die Definition im Katalog ist der Wortlaut des Clients** (vorher der übersetzte T-SQL-Text); ein Neustart liest sie mit dem Dialekt der Sitzung, den das Transaktionsprotokoll mitträgt (`MariaDbRoutineKopfParserTests`: Prozedur mit `OUT`, Funktion und Trigger überstehen einen Neustart und laufen danach). **13c:** der Rumpf (`PrattParser.MariaRumpf.cs`): `BEGIN … END` auch mit Marke, `DECLARE` von Variablen und Cursorn, `IF`/`ELSEIF`/`ELSE`, `WHILE`, `LOOP`, `REPEAT … UNTIL`, `LEAVE`/`ITERATE` (`BREAK`/`CONTINUE` oder `GOTO` auf `__ende_n`/`__weiter_n`; ein `ITERATE` in `REPEAT` springt vor die `UNTIL`-Prüfung), alles Übrige liest der Parser unverändert. Lokale Namen und Sitzungsvariablen sind kein Textersatz mehr, sondern ein Filter auf den Token (`MariaRumpfToken`: eine Variable oder ein Parameter wird `@name`, `@x` wird `@__u_x`, hinter einem Punkt bleibt es eine Spalte - der Text-Umschreiber machte daraus `t.@name`); die Zeilenmarken `/*L5*/` sind durch `Herkunftstext`/`Herkunftsbeginn` an jeder Anweisung ersetzt (der Text der Routine ab `CREATE`; `Routinenzeilen.ZeileBei`); die Spaltennamen eines `SELECT` im Rumpf vergibt der Parser (`Dialekt.AuswahlSpaltennamen`). Neu gegenüber dem Text: `CALL` im Rumpf kennt `OUTPUT`. **13d:** Handler, `SIGNAL`/`RESIGNAL`, `GET DIAGNOSTICS`, `FETCH … INTO`, `SELECT … INTO` (`PrattParser.MariaHandler.cs`, `MariaDbHandlerBedingung`): `EXIT` umschließt den Rest des Blocks mit `TRY … CATCH`, `CONTINUE` jede folgende Anweisung, `NOT FOUND`/`SQLWARNING` als Aktion hinter dem leeren Lesen bzw. als Fehler 02000 im `EXIT`; die Bedingung (`SQLEXCEPTION`, Fehlernummer, `SQLSTATE`) bildet `Dialekt.HandlerBedingungAlsTSql` auf die Nummern der Engine ab; `SELECT … INTO` an der Liste oder am Ende wird `SELECT @v = …` samt `@@ROWCOUNT`-Prüfung (kein Treffer = `NOT FOUND`, mehr als eine Zeile = 1172); feste Bausteine entstehen aus kleinen T-SQL-Fragmenten mit der Herkunft der Anweisung des Clients. **13e:** der Rumpf von `CREATE TRIGGER … FOR EACH ROW` (`PrattParser.MariaTrigger.cs`): `NEW.spalte`/`OLD.spalte` werden Variablen eines Cursors auf `inserted`/`deleted` (bei `UPDATE` über den Primärschlüssel verknüpft, ohne einen Fehler 1785), ein Rumpf ohne `NEW`/`OLD` läuft ohne Cursor, `BEFORE` ist `INSTEAD OF` und schreibt die Zeile hinter dem Rumpf mit den `NEW`-Werten selbst; `SET NEW.x = …` und `NEW.[x]` gehen, ein unbekanntes Feld ist 1054. **13f:** `MariaDbRoutineUebersetzer` samt `Dialekt.RoutinenrumpfAlsTSql`/`TriggerrumpfAlsTSql`, den Zeilenmarken und `FindeEndeDesBegin`/`EntferneZeilenkommentare` gelöscht; `MariaDbLexer.UebersetzeNormalisiert` kennt keinen Routinenschritt mehr. **Bleibt als Text:** die Normalisierung des Lexers, der Anweisungs-Teiler `MariaDbTextWerkzeuge.Anweisungen` (er hält `BEGIN … END` und `IF … END IF` beim Zerlegen eines Stapels zusammen - der Client schickt `;` innerhalb einer Routine) und die katalogabhängigen Vorstufen (`MariaDbTabelleLike`, `MariaDbVolltext`, `MariaDbSpaltenzeichensatz`, `MariaDbLoadData`): sie bauen keinen Baum, sondern bereiten den Text für ihn vor. **Belegt:** `MariaDbCallParserTests` (7), `MariaDbRoutineKopfParserTests` (9), `MariaDbRoutineRumpfParserTests` (8), `MariaDbRoutineHandlerParserTests` (11), `MariaDbRoutineTriggerParserTests` (7), je mit Sabotage-Probe (z. B. `OUTPUT` nicht gesetzt, `IF NOT EXISTS` abgeschaltet, `ITERATE` in `REPEAT` als `CONTINUE`, `EXIT … NOT FOUND` ohne Zähler, `UPDATE` des `BEFORE`-Triggers mit alten Werten, Zeilenzählung um eins verschoben); dazu die 1593 Tests der Klassen MariaDb, Dialekt, Architektur, Wal, Prozedur, Trigger und Parser unverändert grün, darunter die Anschlusstests über die Leitung (`MariaDbRoutinen*`, `MariaDbHandler*`, `MariaDbSelectIntoTests`, `MariaDbDiagnoseTests`, am echten `mariadb:11` gemessen). Ehrlich: Orakel und volle Testsuite wurden in diesem Lauf nicht gefahren (Vorgabe: nur betroffene Klassen).✅
D17.6DDL im MariaDB-Stil`CREATE TABLE` mit `AUTO_INCREMENT`, `ENGINE=`, `DEFAULT CHARSET`/`COLLATE`, Inline-`KEY`/`UNIQUE KEY`/`FULLTEXT`, `ON UPDATE CURRENT_TIMESTAMP` (als erzeugter AFTER-UPDATE-Trigger, am echten `mariadb:11` gemessen, 30.09.2026), `IF [NOT] EXISTS`, `CREATE TABLE … LIKE/AS SELECT`, `ALTER TABLE … MODIFY/CHANGE/ADD … AFTER/FIRST`, `RENAME TABLE`, `TRUNCATE`, Sequenzen, `CREATE OR REPLACE`✅
D17.6bON UPDATE CURRENT_TIMESTAMP in ALTER**Erledigt, gegen `mariadb:11` gemessen:** `ALTER TABLE t {ADD | MODIFY | CHANGE} [COLUMN] spalte … ON UPDATE CURRENT_TIMESTAMP` (`MariaDbOnUpdateAlterUebersetzer`): Primärschlüssel, übrige Spalten und die schon nachziehenden Zeitspalten kommen aus dem Katalog (die Zeitspalten aus dem Text des vorhandenen Triggers `__onupdate_t`), der Trigger wird bei jedem solchen `ALTER` neu gebaut; ein `MODIFY`/`CHANGE` derselben Spalte **ohne** die Klausel schaltet das Nachziehen ab, mit der Klausel wieder ein; ohne Primärschlüssel Fehler 1785 statt stillem Überlesen. Dazu ein Fund: `ALTER TABLE t ADD COLUMN c …` legte eine Spalte namens `COLUMN` an (T-SQL kennt das Wort dort nicht) — behoben. 6 neue Tests.✅
D17.6cON UPDATE sichtbar und dauerhaft richtig**Erledigt, Sollformen am echten `mariadb:11` gemessen:** `DEFAULT current_timestamp()` und `ON UPDATE current_timestamp()` stehen in `SHOW CREATE TABLE`, `SHOW COLUMNS` (Default/Extra `on update current_timestamp()`) und `information_schema.COLUMNS` (`COLUMN_DEFAULT`/`EXTRA`) — vorher schrieb `SHOW CREATE TABLE` das Standardwort als Text in Anführungszeichen (`DEFAULT 'current_timestamp'`), und eine Sicherung verlor die Klausel. Der nachziehende Trigger veraltet nicht mehr: hat die Tabelle einen, baut ihn jedes einzelne `ALTER TABLE … ADD|DROP|MODIFY|CHANGE` neu (mit den Spalten von jetzt); eine gestrichene Zeitspalte zieht nicht mehr nach. 6 neue Tests.✅
D17.6d`FULLTEXT` und `MATCH … AGAINST`**Erledigt, jeder Sollwert am echten `mariadb:11` (InnoDB) gemessen:** `FULLTEXT [KEY|INDEX] [name] (spalten)` im `CREATE TABLE`, `CREATE FULLTEXT INDEX`, `ALTER TABLE … ADD FULLTEXT`/`DROP INDEX` und `DROP INDEX … ON` werden im Katalog vermerkt (`MariaVolltextIndex`, überlebt den Neustart; ein leerer Ersatz statt eines Baums — gesucht wird direkt im Text). `MATCH (spalten) AGAINST (…)` in Natural Language Mode und Boolean Mode (`+ - * "…"`) mit der Relevanz von InnoDB: Summe von `tf * idf²` in **einfacher Genauigkeit**, `idf = log10(N / df)` (N = Zeilen der Tabelle, nach `DELETE`/`INSERT` neu gezählt), `log10(1,0001)` wenn alle Zeilen Treffer sind; Wörter aus Buchstaben, Ziffern und Unterstrich (der Apostroph trennt), 3 bis 84 Zeichen, ohne die 18 Stoppwörter; ohne passenden Index Fehler 1191. Als Bedingung (`WHERE`/`AND`/`OR`/`NOT`/`HAVING`/`ON`) heißt es „Relevanz > 0“. 12 Tests (`MariaDbVolltextTests`); Sabotage: Bedingung → 3 rot, Stoppwörter → 1, df = N → 1✅
D17.6eFULLTEXT, ALTER mit mehreren Änderungen, TIMESTAMP, Nulldatum, Zeichensätze**Erledigt (M17, Runden 7 und 8), jeder Sollwert am echten `mariadb:11` (11.8.9) gemessen:** `WITH QUERY EXPANSION`, Akzent- und Sortierungsgleichheit, der `FULLTEXT`-Index einer Tabelle `db.t` im Katalog von `db`, `MATCH` über `FROM db.t` einer anderen Datenbank (`MariaDbVolltextFremdeDatenbankTests`); **Runde 8: der Boolean Mode mit `>` `<` `~` und Klammern beliebiger Tiefe, Präfixe jeder Länge** (`MariaDbVolltextOperatorenTests`: 65 Einzelanfragen, 260 Zufallsanfragen, 8 Syntaxfehler 1064 wie am Server) — die „nicht erklärte winzige Rangfolge“ der kurzen Präfixe war `df` als Zahl der Einträge (Wort, Zeile) mit negativem Logarithmus bei mehr Einträgen als Zeilen, die Regeln stehen in `docs/umsetzung.md`; ein alter Test erwartete für `fox*` die Zahlen von `fox` und ist berichtigt. `ALTER` mit mehreren Änderungen samt `ON UPDATE` und `RENAME COLUMN`, `CREATE TABLE … LIKE`, `IF NOT EXISTS`-Fehler, Zeichensätze von Tabellen und Spalten samt 1366, `SET time_zone` und TIMESTAMP als UTC. **Runde 8, ebenfalls gemessen:** das Nulldatum `0000-00-00` und der 1. Januar 0001 sind zwei Werte (`MariaDbNulldatumUnterscheidungTests`; vorher las die Engine `0001-01-01` sogar als `1900-01-01`); `ON UPDATE CURRENT_TIMESTAMP` ist eine Spalteneigenschaft im `UPDATE`-Pfad statt eines erzeugten Triggers und gilt damit auch an `TEMPORARY`-Tabellen, an Tabellen einer anderen Datenbank und ohne Primärschlüssel (`MariaDbOnUpdateDirektTests`; `MariaDbOnUpdateAlterUebersetzer` ist gelöscht); `ALTER TABLE … CONVERT TO CHARACTER SET` wandelt Daten, Sortierung und die `TEXT`-Stufe wirklich um (`MariaDbConvertZeichensatzTests`). **Rest als eigene Zeile D17.6f.**✅
D17.6fTypumwandlung nach `binary`**Erledigt (M17, Runde 9), am echten `mariadb:11` (11.8.9) gemessen (`MariaDbConvertZeichensatzTests`):** `ALTER … CONVERT TO CHARACTER SET binary` macht aus `VARCHAR(n)` `VARBINARY(n)`, aus `TEXT`/`MEDIUMTEXT` `BLOB`/`MEDIUMBLOB`, aus `CHAR(n)` `BINARY(n)` mit Nullbytes aufgefuellt (`6A6B000000`, `LENGTH` 5), die Daten werden zu ihren Bytes (`latin1` ein Byte, sonst UTF-8), die Tabelle steht auf `DEFAULT CHARSET=binary`, und die Spalten bleiben danach Binaerspalten, wenn zurueck nach `utf8mb4` umgestellt wird. Nebenbei: `LENGTH` eines Binaerwertes zaehlte die Zeichen der Hex-Schreibweise, jetzt die Bytes. **Rest als eigene Zeile D17.6g.**✅
D17.6gRest: FULLTEXT-Formen mit abweichender Reihenfolge**Erledigt (M17, Runde 10), als Black-Box am echten `mariadb:11` (11.8.9) gemessen (138 Anfragen, `MariaDbVolltextGruppenTests`), kein Quelltext gelesen:** die Formen, die `MATCH` bisher mit 1235 abwies, rechnen jetzt wie der Server, `MariaDbVolltextSuche` kennt keine Ablehnung mehr. Die Regeln: (1) kommt ein Wort mehrfach in der Anfrage vor (auch als Präfix, in einer Phrase oder in einer Gruppe), zählt seine Häufigkeit `df` so oft, wie es vorkommt (`alpha alpha` bei zehn Zeilen und `df` 5 ist `df` 10, also der Sonderfall `log10(1.0001)` mit 1.9e-9), seine Relevanz aber je Zeile nur einmal, beim ersten Teil davor (ausser `~`), der die Zeile trifft; (2) die Teile einer Liste wirken der Reihe nach auf die Menge der Zeilen: ohne `+` fügt jeder Teil Zeilen hinzu und ein `-` nimmt die bis dahin enthaltenen heraus (samt Relevanz, `alpha -delta (beta gamma)`), mit `+` beginnt die Menge beim ersten `+`, jedes weitere schneidet, eine Gruppe ohne Operator dahinter fügt hinzu (`+alpha (beta gamma) +delta` ist Zeilen mit `delta` und mit `alpha` oder der Gruppe), ein `-` vor dem ersten `+` hält die Zeile aus der Menge; (3) nach einem `-` holt ein Wort die Zeile nicht zurück, eine Gruppe schon; (4) der Überschuss von `>` und `<` einer Gruppe zählt nur als Vorzeichen (`>(<alpha <beta)` wertet nicht ab); (5) ein verlangtes Stoppwort bleibt als Teil stehen, den keine Zeile erfüllt, eine Gruppe dahinter fügt Zeilen hinzu und nach ihr auch ein Wort (`+the (alpha beta) gamma`). **Erledigt in Runde 9 (aus dieser Zeile):** `TINYTEXT`/`TINYBLOB`, `SET`, `BIT(n)`, `BOOL`, `FLOAT` zeigen sich in `SHOW COLUMNS`, `SHOW CREATE TABLE` und `information_schema.COLUMNS` wie am Server (`MariaDbSpaltenartTests`).✅
D17.6hRest: FULLTEXT, ein Wort nach einer Gruppe bei echtem ersten `+`, Präfixe über zwei Spalten**Erledigt (M17, Runde 10), am echten `mariadb:11` (11.8.9) gemessen (K1 bis K12, `MariaDbVolltextRunde5Tests`):** nach einer Gruppe holen Wörter Zeilen in die Menge (auch bei einem echten ersten `+`: `+alpha (beta) gamma` nimmt die Zeile mit nur `gamma`), eine Gruppe hebt die Sperre eines vorangehenden `-` auf, ein Wort holt aber keine Zeile zurück, die ein `-` nach der Gruppe getroffen hat; bei einem Index über zwei Spalten addieren sich die Häufigkeiten der Spalten (`alp*` in `alpha alpha delta` und `alpha zeta eta` ist 3).✅
D17.6iFULLTEXT: Präfix mit mehreren passenden Wörtern in mehreren Spalten**Erledigt (M17, Runde 10), am echten `mariadb:11` (11.8.9) gemessen (M3 bis M12, `MariaDbVolltextRunde6Tests`):** bei einem Index über mehrere Spalten zählt das letzte passende Wort der ganzen Zeile in der Ordnung des Index (kürzeres Wort zuerst), und seine Vorkommen in allen Spalten werden gezählt (`foxy*`, `foxe*`, `fo*`); kommt das Präfix in allen Zeilen vor (der Sonderfall `df = N` mit `log10(1,0001)`), zählt bei mehreren Spalten nur die Zahl der Spalten mit einem Treffer (an `fox*` in einer Tabelle mit fünf Zeilen gemessen).✅
D17.7DML im MariaDB-Stil`INSERT … ON DUPLICATE KEY UPDATE` samt `VALUES()`, `INSERT IGNORE`, `REPLACE`, mehrzeiliges `INSERT`, `UPDATE`/`DELETE` mit `ORDER BY … LIMIT` und über mehrere Tabellen (`UPDATE t1 JOIN t2 … SET`, `UPDATE t1, t2 SET`, `DELETE FROM t1 USING …`, `DELETE t1 FROM … JOIN`; am echten `mariadb:11` gemessen, 30.09.2026), `RETURNING`, `LOAD DATA [LOCAL] INFILE`, `SELECT … FOR UPDATE`/`LOCK IN SHARE MODE`, `WITH RECURSIVE`, Fensterfunktionen✅
D17.7bSperrlesen und LOAD DATA**Erledigt:** `SELECT … FOR UPDATE`, `FOR SHARE` und `LOCK IN SHARE MODE` (nur im MariaDB-Dialekt; `Lesehinweis.Aktualisierung`/`Teilen`): in einer Transaktion bleiben die gelesenen Zeilen bis zu ihrem Ende für Schreiber gesperrt (`Sperrverwaltung.Erwerbe(…, nurSperre: true)` bzw. geteilte Sperre), gewöhnliche Leser werden wie bei InnoDB nicht aufgehalten, ohne Transaktion bleibt nichts zu halten. Dazu `START TRANSACTION` (fehlte). `LOAD DATA INFILE` auf den vorhandenen `BULK INSERT` (FIELDS TERMINATED/ENCLOSED, LINES TERMINATED, IGNORE n LINES; Datei durch dieselbe Freigabe wie jeder Dateizugriff; unlesbarer Wert → 1366/22007 mit zurückgenommener Anweisung, fehlende Datei → Fehler 13, `LOCAL` → 1148), gegen `mariadb:11` gemessen. 13 neue Tests.✅
D17.7cSperrlesen-Optionen und LOAD DATA im Einzelnen**Erledigt, jeder Sollwert am echten `mariadb:11` gemessen (zwei Sitzungen bzw. dieselben Dateien und Befehle):** `FOR UPDATE|FOR SHARE|LOCK IN SHARE MODE` mit `NOWAIT` (scheitert sofort mit 1205, dem Fehler des Zeitlimits), `WAIT n` (wartet n Sekunden, dann 1205, kommt durch, wenn die Sperre vorher endet) und `SKIP LOCKED` (gesperrte Zeilen fehlen, der Rest wird gesperrt; zwei neue `Lesehinweis`-Werte, `SelectStatement.SperrFrist`, Fehler 1222 heißt jetzt 1205). `LOAD DATA` liest die Datei jetzt selbst (`MariaDbLoadDataLeser`, rein, fuzzgeprüft) statt über `BULK INSERT` und erzeugt EIN mehrzeiliges `INSERT [IGNORE]|REPLACE`: Rückstrich-Fluchtfolgen und `\N` als NULL, angeführte Felder mit Trennern und Zeilenumbrüchen, das Wort `NULL` bei Anführung, `STARTING BY`, jedes `ESCAPED BY`, Spaltenliste mit `@var` und `SET spalte = ausdruck`, `REPLACE`/`IGNORE`, Feldzahl zu klein/groß im strengen Modus 1261/1262, und **`LOCAL INFILE`** (Paket 0xFB, die Fähigkeit `LOCAL_FILES` steht im Gruß; ohne sie des Clients weiter 1148; mit der echten `mariadb`-CLI und `--local-infile` geprüft). Konvertierungsfehler beim Einfügen jetzt 1366/1292 (22007) statt 1105. rund 40 neue Tests.✅
D17.7e`LOAD DATA … CHARACTER SET`**Erledigt, am echten `mariadb:11` gemessen:** die Dateibytes werden nach der Angabe gelesen — `latin1`/`ascii`/`binary` byteweise (0xE4 ergibt ä), `utf8`/`utf8mb3`/`utf8mb4` und keine Angabe als UTF-8, ein unbekannter Name meldet 1115. Bei `LOCAL` gilt die Angabe nicht (der Client liefert Text). Drei Tests (`MariaDbLoadDataOptionenTests`); Sabotage (latin1 als UTF-8 gelesen) → genau der latin1-Test rot✅
D17.7fRest: Sperrlesen, LOAD DATA, nicht strenger Modus**Erledigt (M17, Runde 8), jeder Sollwert am echten `mariadb:11` (11.8.9) gemessen:** **der nicht strenge Modus** (`sql_mode = '` und jedes `INSERT IGNORE`) wandelt unpassende Werte um statt sie abzuweisen — zu langer Text gekürzt (1265), ein Text ohne Zahl ist 0 (1366), mit einer Zahl am Anfang deren Wert (1265), Zahlen ausser Bereich der größte/kleinste Wert (1264), ein Datum, das es nicht gibt, ist das Nulldatum (1265), ein Wert ausser Bereich von `UNSIGNED`, `TINYINT`, `MEDIUMINT`, `BOOL`, `DECIMAL UNSIGNED` ist die nächste Grenze des Typs, bei `YEAR` 0000, mit 1264 (elf gemessene Fälle), `NULL` in `NOT NULL` ist bei mehreren Wertezeilen und im `UPDATE` der Nullwert mit 1048, eine weggelassene Spalte ohne Vorgabe der Nullwert mit 1364, jede Warnung mit `at row n` (`MariaDbNichtStrengTests`, 18 Fälle); `LOAD DATA` meldet 1261/1262 je Zeilenform und 1263 für `\N` in `NOT NULL`; **`LOAD DATA LOCAL` wirkt wie `IGNORE`** (nur Warnungen, auch im strengen Modus; der alte Test, der nach `zwei,falsch` einen Fehler erwartete, hielt Abweichendes fest) und ein **fehlendes Feld ist der Nullwert des Typs** (`'`, 0, `0000-00-00`), nur eine Spalte mit der Vorgabe NULL bekommt NULL, ein Feld zu viel entfällt (`MariaDbLoadDataLokalIgnoriertTests`); Festbreite-Felder (Breite = Anzeigebreite des Spaltentyps in Bytes, `@var` meldet 1409, Rest-Leerzeichen gekürzt) und die Fehlermeldung bei 1366/1264/1406 mit Spalte, Wert und Zeile (D17.7g) waren schon da. **Rest als eigene Zeile D17.7h.**✅
D17.7hTypbedingungen ohne strengen Modus: ENUM, SET, YEAR, BIT**Erledigt (M17, Runde 9), jede Meldung am echten `mariadb:11` (11.8.9) gemessen (`MariaDbNichtStrengAufzaehlungTests`):** ein ungültiger `ENUM`-Wert ist ohne strengen Modus der Leerwert mit 1265 (`Data truncated for column ...`, auch im `UPDATE`), ein `SET` behält seine gültigen Teile (`x,q` ergibt `x`, `q` den Leerwert), im strengen Modus ist es 1265 (nicht mehr 1406 `Data too long`, weil der Wert länger als jeder erlaubte ist); `YEAR` nimmt zweistellige Jahre an (1 bis 69 sind 2001 bis 2069, 70 bis 99 sind 1970 bis 1999, in jedem Modus), ausser Bereich ist 0 mit 1264 und `abc` 0 mit 1366; `BIT(4)` über 15 ist 15 mit 1264; eine Spalte, die ein `UPDATE` nicht anrührt, wird nicht neu geprüft (der Leerwert eines `ENUM` bleibt auch im strengen Modus bei jedem anderen `UPDATE` stehen). **Rest als eigene Zeile D17.7i.**✅
D17.7iSperrlesen unter SNAPSHOT, ENUM/SET-Nummern, Upsert über jeden eindeutigen Schlüssel**Erledigt (M17, Runde 9):** `FOR UPDATE`/`LOCK IN SHARE MODE` halten die Zeilen jetzt in jeder Isolationsstufe, auch unter `SNAPSHOT` und `READ_COMMITTED_SNAPSHOT` (`HalteZeilen`, `MariaDbSperrlesenTests`); eine Zahl in einer `ENUM`-Spalte ist die Nummer des Mitglieds, in einer `SET`-Spalte die Menge der Bits, ein `SET` steht in der Reihenfolge der Vereinbarung (`INSERT … VALUES (4, 2, 5)` ergibt `b` und `x,z`, am Server gemessen), eine ungültige Nummer (0, zu groß, `''`) ist im strengen Modus 1265, bei `YEAR` 100 1264; `INSERT … ON DUPLICATE KEY UPDATE` erkennt eine Doppelung in jedem eindeutigen Schlüssel (Primärschlüssel zuerst, dann die `UNIQUE`-Indizes) und ändert die erste Zeile, die einen davon teilt (`MariaDbUpsertSchluesselTests`); `INSERT IGNORE`, `REPLACE` und damit `LOAD DATA … IGNORE|REPLACE` erkannten schon jeden eindeutigen Index (der alte Vermerk „nur über den PRIMARY KEY“ stimmte nicht mehr). **Rest als eigene Zeile D17.7j.**✅
D17.7jENUM/SET/YEAR als Wert**Erledigt (M17, Runde 10), am echten `mariadb:11` (11.8.9) gemessen (Frage H3, `MariaDbWertetypTests`):** beim Lesen einer Spalte im MariaDB-Dialekt trägt der Wert eine Marke (`MariaWertmarken`: `ma:e:`/`ma:s:` mit den Mitgliedern, `ma:y`, im Feld `Xmlsammlung` des Wert-Typs, nur im Ausdruck, nicht in der Ablage), an der Rechnen, Vergleichen und Anzeige erkennen, was er ist: `n + 0` an einem `ENUM` ist die Nummer des Mitglieds, `s + 0` an einem `SET` die Menge der Bits, `n = 1` und `n = 2 OR n = 'c'` vergleichen die Nummer, ein `SET` wird mit Text in der Reihenfolge der Vereinbarung verglichen (`s = 'x,z'`), `FIND_IN_SET('z', s)` nennt die Nummer des Mitglieds (3, nicht die Stelle im Text 2), ein `YEAR` mit dem Wert 0 steht als `0000` (auch `CAST(y AS CHAR)`, während `HEX(y)` und `y + 0` 0 bleiben); ein `INSERT` ohne Spaltenliste nimmt eine Zahl ebenfalls als Nummer bzw. Bits. Der Weg lief über eine Marke am Wert statt über einen neuen Datentyp, damit Ablage, Katalog und die Leitung der anderen Dialekte unberührt bleiben.✅
D17.7kENUM/SET/YEAR in IN, BETWEEN, SUM, ORDER BY und weiteren Ausdrücken**Erledigt (M17, Runde 10), am echten `mariadb:11` (11.8.9) gemessen (J30 bis J93, `MariaDbWertetypRestTests`, 64 Anfragen):** `IN` und `BETWEEN` vergleichen je Eintrag die Nummer, wenn er eine Zahl ist, sonst den Text (`n IN (1,'b')`), Vergleiche mit Zahlen die Nummer (`n > 1`), mit Text den Text (`n < 'b'`, `c` ist grösser als `b` trotz Nummer 1); `SUM`/`AVG`, `ABS`/`FLOOR`, unäres Minus, `CAST … AS UNSIGNED|SIGNED` und ein einfaches `CASE n WHEN 1` rechnen mit der Nummer bzw. den Bits, `MIN`/`MAX`/`GREATEST`/`LEAST`/`UNION` mit dem Text, `ORDER BY`, `GROUP BY` und `DISTINCT` ordnen nach der Nummer, ein `YEAR` bleibt als `MIN(y)`, `COALESCE(y, 1)` und `y + 1` eine Zahl (0 statt 0000), steht aber in `CONCAT`, `LENGTH` und `LEFT` als `0000`; dazu `AVG` über ganze Zahlen als Dezimalwert mit vier Stellen (`2.0000`) und `GREATEST`/`LEAST` mit einem NULL als NULL. Die Datei von `LOAD DATA` wird ganz in einen Befehl gelesen (bis 128 MB; die Wahl, damit das Transaktionsprotokoll den Befehl hält).✅
D17.7gDatenfehler mit Spalte, Wert und Zeile; Typbedingungen**Erledigt (M17), jede Meldung am echten `mariadb:11` (11.8.9) gemessen (`MariaDbDatenfehlerTests`, 18 Fälle; Nachspiel `np_fehler`, `np_typ`, `np_alter_typ` mit `--text`: 0 Unterschiede):** eine Negativzahl in einer `UNSIGNED`-Spalte (und jeder Wert ausser Bereich von `TINYINT`/`SMALLINT`/`MEDIUMINT`/`INT`/`BIGINT UNSIGNED`, `DECIMAL`/`FLOAT UNSIGNED`, `YEAR`) meldet **1264** `Out of range value for column 'v' at row 1` (vorher 4025 einer `CHECK`-Bedingung), `ENUM`/`SET` 1265, `BIT(n)` 1406, `INET4`/`INET6` 1292 mit Wert und Spalte (`Incorrect inet4 value: 'x' for column `db`.`t`.`c` at row 1`); die Bedingungen, die die Typübersetzung erzeugt, tragen jetzt einen eigenen Namen (`TY<Art>__tabelle__spalte`) und sind von der `CHECK`-Bedingung des Anwenders (4025, `CONSTRAINT `t.v` failed for `db`.`t``, `CONSTRAINT_n`) unterscheidbar. Umwandlungs-, Bereichs- und Längenfehler nennen Spalte, Wert und die **Zeilennummer** (`at row 2`; beim `UPDATE` die wievielte Zeile gelesen wurde), ein ausdrückliches NULL ist 1048, eine weggelassene Spalte ohne Vorgabe 1364, `INSERT … VALUES (…, 'nodate')` 1292, `'zz'` in `DECIMAL` 1366 (vorher 1105 bzw. ohne Wert). **`ALTER TABLE … MODIFY … UNSIGNED` und `ADD CHECK` prüfen den Bestand** (scheitert mit 1264 bzw. 4025 samt Zeilennummer; meldet die Zeilenzahl wie der Server), die Bedingungen des alten Typs entfallen mit der Änderung, eine unbenannte `ADD CHECK` bekommt `CONSTRAINT_n`. `BOOL` ist `TINYINT(1)` (jeder Wert von -128 bis 127; vorher nur 0/1), `SET` prüft seine Werte, `INET4` lässt NULL durch. Drei bestehende Tests hatten 4025/„nur 0 und 1“ als gegeben genommen und folgen jetzt dem Server.✅
D17.7lALTER … MODIFY ENUM/SET, das nur Mitglieder anhängt**Erledigt (M17, Runde 10), am Server gemessen:** hängt `MODIFY e ENUM(…)` oder `SET(…)` nur Mitglieder ans Ende, baut der Server die Tabelle nicht um und meldet 0 betroffene Zeilen (`MariaDbAlterAufzaehlungTests`); jede andere Änderung der Liste und jede Typänderung meldet die Zeilenzahl.✅
D17.7mUUID prüft das Format**Erledigt (M17, Runde 10), am echten `mariadb:11` (11.8.9) gemessen (N2 bis N12, `MariaDbUuidTests`):** zweiunddreißig Hexziffern in beliebiger Schreibung, Bindestriche an beliebigen Stellen zwischen den Ziffern (auch `123e4567e89b-12d3-…` und `123e-4567-…`), die Ausgabe klein mit 8-4-4-4-12; nicht angenommen (1292 mit Wert und Spalte): geschweifte Klammern, ein Bindestrich am Anfang, 31 oder 33 Ziffern, andere Zeichen, ein Leerzeichen am Ende.✅
D17.7nUUID: Bindestriche am Ende, doppelt und vor der letzten Ziffer**Erledigt (M17, Runde 11), am echten `mariadb:11` (11.8.9) gemessen (P2 bis P5, `MariaDbUuidTests`):** ein Bindestrich am Ende ist 1292, zwei hintereinander und einer vor der letzten Ziffer werden angenommen, eine Schreibweise mit einem Bindestrich nach jeder Ziffer (31) wird abgelehnt; der Nachbau nimmt bis zu fünf Bindestriche an.✅
D17.7oUUID: Bindestriche bis dreißig**Erledigt (M17, Runde 11), am echten `mariadb:11` (11.8.9) gemessen (S2 bis S8 und U2 bis U11, `MariaDbUuidTests`):** mit 5 bis 30 Bindestrichen nimmt der Server die Schreibweise an, mit 31 (einer nach jeder Ziffer) lehnt er sie ab; der Nachbau nimmt bis zu dreißig an.✅
D17.7dMehrfachziele in UPDATE/DELETE**Erledigt, jeder Sollwert (Zeilen und betroffene Zeilen) am echten `mariadb:11` mit denselben Daten gemessen:** `UPDATE t1 JOIN t2 … SET t1.a = …, t2.b = …`, `UPDATE t1, t2 SET …`, `DELETE t1, t2 FROM …` und `DELETE FROM t1, t2 USING …` (`MariaDbMehrfachzielUebersetzer`). Zuerst schreibt eine Anweisung die **Schlüssel der Treffer** (bei UPDATE dazu die neuen Werte, vor jeder Änderung berechnet — `mzb.x = mza.v + 100` nimmt wie am Orakel das *alte* `mza.v`) in die Sitzungstabelle `#__kmt`, danach ändert je Zieltabelle eine Anweisung über diese Schlüssel; das Ganze steht in einer eigenen Transaktion bzw. einem Sicherungspunkt (scheitert das zweite Ziel, ist das erste zurückgenommen — Test mit Fremdschlüssel), und die **Summe** der betroffenen Zeilen aller Ziele kommt als OK-Zähler zurück (`@__kmt_zeilen`, über Text- und vorbereiteten Weg). LEFT JOIN ohne Gegenstück löscht nur links. Voraussetzung: jede Zieltabelle hat einen Primärschlüssel im Katalog — sonst bleibt es ein ehrlicher Syntaxfehler (1064). Dabei ein Fund: der Platzhalter-Zähler der vorbereiteten Anweisungen hielt jedes `#` für einen Kommentar (die Normalisierung hat sie längst entfernt) und übersah alle `?` hinter einer Sitzungstabelle. 8 neue Tests; zwei alte, die den Syntaxfehler festschrieben, sind umgedreht.✅
D17.8Die Typen und die FunktionsbibliothekAlle MariaDB-Typen samt `UNSIGNED`, `ENUM`, `SET`, `YEAR`, `BIT`, `JSON`, `UUID`, `INET4/6`; die Funktionen `CONCAT_WS`, `IFNULL`, `IF`, `GROUP_CONCAT`, `DATE_FORMAT`, `STR_TO_DATE`, `NOW`, `UNIX_TIMESTAMP`, `JSON_*`, `REGEXP_*` und die übrigen, jede gegen das Orakel gemessen✅
D17.8bDie Restfunktionen der Bibliothek (Bestandsaufnahme docs/umsetzung.md#d178)`CAST/CONVERT AS UNSIGNED|SIGNED [INTEGER]` (rundet numerische Quellen, schneidet stringförmige, HEX/BIN/OCT je mit eigener dritter Regel — alle gegen mariadb:11 gemessen), `HEX`/`UNHEX`/`BIN`/`OCT`/`CONV`/`CRC32`, `ELT`/`FIELD`, `DECIMAL/FLOAT/DOUBLE UNSIGNED` als Spaltentyp, `CHARACTER SET`/`COLLATE` je Spalte, `SHA2(x,224)` (eigenes SHA-224 nach FIPS 180-4, .NET hat keins), `PASSWORD()`/`AES_ENCRYPT`/`AES_DECRYPT`✅
D17.8c`ENCRYPT()`**Erledigt, jeder Sollwert vom echten `mariadb:11` (glibc-`crypt`) einmalig gemessen und als Bekannt-Antwort festgehalten:** `MariaDbDesCrypt` ist das klassische Unix-`crypt(3)` mit DES, im eigenen Code (S-Boxen, Schlüsselplan und die vom Salz abgewandelte Expansion — .NETs `DES` kann das Salz nicht); `ENCRYPT(str, salz)` liefert Salz + 11 Zeichen (21 Vektoren, darunter Salz `./`, `zZ`, ein Kennwort über 8 Zeichen — auf 8 Byte gekürzt —, Umlaute als UTF-8), `NULL` bei NULL und bei einem Salz unter zwei Zeichen, `*0` bei einem Zeichen außerhalb des Alphabets, leere Zeichenkette bei leerem `str`, ohne Salz ein zufälliges. Eine Sabotage in einer S-Box macht 23 von 33 Tests rot. 33 neue Tests.✅
D17.8dGeometrie-Grundlagen und `ENCRYPT` mit `$1$`/`$5$`/`$6$`**Erledigt, jeder Sollwert am echten `mariadb:11` gemessen:** (1) `ENCRYPT(str, '$1$salz$')` als MD5-crypt und `$5$`/`$6$` als SHA-crypt (`MariaDbHashCrypt`: Salz auf 8 bzw. 16 Zeichen gekürzt, `rounds=N$` von 1000 bis 999999999 — außerhalb, bei Nichtzahl und bei unbekannter Kennung `$n$` antwortet es `*0`, die Standardzahl 5000 fehlt in der Ausgabe; 19 Fälle, alle beim ersten Lauf gleich dem Server). (2) Geometrie auf dem vorhandenen Geo-Modell: die Spaltentypen `POINT`/`LINESTRING`/`POLYGON`/`MULTI…`/`GEOMETRYCOLLECTION`/`GEOMETRY` (als `geometry`), `ST_GeomFromText`/`ST_GeomFromWKB` (Aliase `ST_PointFromText` & Co. ohne Artprüfung wie bei MariaDB; ungültiger Text → `NULL`), `ST_AsText` in der Schreibweise von MariaDB (`POINT(1 2)`, Zahlen fest von 1e-15 bis unter 1e15, sonst `1e-16`/`1.2345678901234568e17`), `ST_AsBinary`, `POINT()`, `ST_X`/`ST_Y`, `ST_GeometryType`, `ST_SRID`, `ST_Distance`/`ST_Area`/`ST_Length`/`ST_NumPoints`/`ST_IsEmpty`, `ST_Contains`/`ST_Within`/`ST_Intersects`/`ST_Disjoint`/`ST_Equals`; auf der Leitung als `MYSQL_TYPE_GEOMETRY` mit vier Byte SRID vor dem WKB (`HEX(POINT(1,2))` gleich dem Server). Nebenbei behoben: ein `DOUBLE` stand im MariaDB-Port als `1.500000000000000e+000` — jetzt kürzeste Form wie beim Server (`SELECT SQRT(2)` gleich). 62 Tests (`MariaDbGeometrieTests`, `MariaDbEncryptTests`); Sabotage: Ausgabeform → 8 rot, SRID-Präfix → 3 rot✅
D17.8kGeometrie: Beziehungen, Linien, GeoJSON, SPATIAL**Erledigt (M17, Runde 9), alles am echten `mariadb:11` (11.8.9) gemessen:** `ST_Touches`/`ST_Crosses`/`ST_Overlaps`/`ST_Relate`/`ST_Contains`/`ST_Within` an 23 Geometrien gegeneinander (529 Paare, 14 Spalten, `MariaDbGeometrieBeziehungTests`) über eine eigene DE-9IM-Matrix (`Geobeziehung`: Zeugenpunkte für Punkt und Kurve, Flächeninhalt von Schnitt und Differenz für die flächigen Zellen); die Befunde: `ST_Contains` meint „umfasst“ (ein Punkt auf dem Rand ist enthalten), `ST_Overlaps` heisst gleiche Dimension plus gemeinsame Punkte plus keiner umfasst den anderen (zwei sich kreuzende Linien überlappen, zwei Polygone mit gemeinsamer Kante auch), `ST_Crosses` ist nur für Punkt/Linie, Punkt/Fläche, Linie/Fläche und Linie/Linie definiert (sonst NULL) und rechnet wie Overlaps, die Ziffern 0 bis 2 in `ST_Relate` gelten wie `T`; **`ST_Union`/`ST_Intersection`/`ST_Difference`/`ST_SymDifference` an Linien gegen Linien** (324 Ergebnisse, `MariaDbGeoLinien`: zerlegen an den Schnittpunkten, vom kleineren zum größeren Ende ausrichten, nach Anfang und Ende ordnen, an Punkten mit genau zwei Stücken zu Linienzügen verketten, Geraden-Ecken entfallen); `ST_PointOnSurface` (Mitte des ersten Abschnitts der untersten Schicht der Ecken-Y-Werte, Quadrat mit Loch `POINT(5 1.5)`); GeoJSON: ungültige Struktur ist NULL mit 4048, abgeschnittenes JSON NULL mit 4037, kein JSON NULL ohne Warnung, `[1]` ist `POINT(1 0)`; `ST_IsSimple` einer Mehrfachlinie prüft die Teile untereinander; `SPATIAL`-Indizes erscheinen in `SHOW CREATE TABLE`, `SHOW INDEX` (`Index_type` SPATIAL, `Sub_part` 32) und `information_schema.STATISTICS` (kopiert mit `CREATE TABLE … LIKE`, entfernt mit `DROP INDEX`/`DROP TABLE`, kein `MATCH`-Ziel); `ST_Buffer` eines Punkts (126 Ecken im Uhrzeigersinn ab dem untersten Punkt, die Ecken 33 und 97 fehlen wie am Server), mit Abstand 0 der Wert selbst, mit negativem Abstand an einem Punkt leer, beim Schrumpfen eines konvexen Polygons ohne Löcher; die Einzelzahlen des Punktpuffers weichen in der letzten Dezimalstelle (ein Bit) ab. Außerdem: `JOIN` ohne `ON` ist in MariaDB-Sitzungen ein Kreuzverbund. **Rest als eigene Zeile D17.8g.**✅
D17.8gST_Buffer mit positivem Abstand an Linien, Polygonen und Mehrfachgeometrien**Erledigt (M17, Runde 10), 55 Messungen am echten `mariadb:11` (11.8.9) Punkt für Punkt gehalten (`MariaDbPufferRandTests`, Fixture `Orakel/buffer-runde4-5.tsv`, Toleranz 1e-9):** der Ring läuft im Uhrzeigersinn ab dem tiefsten (bei Gleichstand dem linkesten) Punkt; Rundungen (Ecken und Kappen) laufen in Schritten von 2π/128 und sind vom Ende der Rundung rückwärts auf dem Raster verankert, der Anfang (die Normale der eingehenden Kante) steht als eigener Punkt davor; eine einspringende Ecke ist der Schnittpunkt der verschobenen Kanten, ein Punkt auf der Geraden bringt keinen Punkt hervor; bei einer Linie fehlt der zweite Punkt der Anfangskappe (die am ersten Punkt), bei einer Linie mit drei oder mehr Punkten auch der der Endkappe; ein Polygon verliert keinen Punkt, seine Löcher schrumpfen und laufen ebenfalls im Uhrzeigersinn ab ihrem tiefsten Punkt, ein zugewachsenes Loch fehlt; eine geschlossene Linie ergibt Aussen- und Innenrand; die Teile einer Mehrfachgeometrie (auch `GEOMETRYCOLLECTION` aus Punkt und Linie) stehen in ihrer Reihenfolge als `MULTIPOLYGON`, berühren sie sich, werden ihre Puffer vereinigt (`MultiPolygon` zweier Quadrate ergibt ein Polygon); ein negativer Abstand an Punkten und Linien ist leer.✅
D17.8nST_Buffer: Linien, deren Kappen und Ecken sich überlappen**Erledigt (M17, Runde 10), 47 weitere Messungen am echten `mariadb:11` (11.8.9) Punkt für Punkt gehalten (L23 bis L47, J18, `MariaDbPufferRandTests`):** der rohe Rand einer Linie, die ihren eigenen Puffer berührt (`LINESTRING(0 0,4 0,4 4,0 4,0 1)`, spitze Winkel wie `(0 0,4 0,0 1)`, U-Formen mit Abstand über der halben Breite), wird an seinen Schnittpunkten (auch T-Stücken) zerlegt und behält nur die Kanten, die die Fläche in Hauptrichtung trennen (`MariaDbGeoAufloesen`); an einer einspringenden Ecke gilt der Schnittpunkt der verschobenen Kanten, solange er auf beiden Kanten liegt, sonst beide Endpunkte der verschobenen Kanten (die Schleife fällt bei der Auflösung weg); zwei gleiche Punkte hintereinander und ein Stachel ohne Fläche (A, B, A) entfallen; Löcher in Polygonen und Linien wachsen mit (L23 bis L26).✅
D17.8oST_Buffer mit negativem Abstand an Polygonen mit Loch und einspringender Ecke**Erledigt für die einfachen Gestalten (M17, Runde 11), 21 Messungen am echten `mariadb:11` (11.8.9) Punkt für Punkt gehalten (L1, L2, L4, L7, L8, L10, R1 bis R8, R10, R11, R13, R14, B14, `MariaDbPufferRandTests`):** der Ring läuft im Uhrzeigersinn, der Aussenring wird nach rechts (innen), ein Loch nach links (in die Fläche) versetzt; liegt der Versatz ausserhalb der Kurve, steht ein Bogen aus vier Punkten, nämlich Anfang `A`, dann `A + 64 s` und `A + 63 s` auf dem Raster (`s` die Richtung des Bogens: das Loch +1, der Aussenring -1), dann das Ende; innerhalb der Schnittpunkt der verschobenen Kanten (oder beide Endpunkte). Ein zusammenfallendes konvexes Polygon ist leer, solange der Abstand den Umkreis nicht erreicht.✅
D17.8pST_Buffer mit negativem Abstand: die Fälle, die sich aus den Messungen ableiten lassen**Erledigt (M17, Runde 9), 38 weitere Messfälle V1 bis V38 am echten `mariadb:11.8.9`:** Bögen mit Vier-Punkt-Gestalt bei rasterförmigen Normalen, Loch gegen Aussenring, Umkreis-Zusammenfall bei Rechtecken. Was nicht aus den Daten ableitbar ist, liefert hier 1105 statt eines falschen Ergebnisses (26 Fälle, u. a. L3, L5, L9, V1 bis V5, V8 bis V10 ohne Treffer, im Test als Ausnahmen benannt). Zwei Fälle liefern ein abweichendes Ergebnis (V21, V38); alles zusammen steht in D17.8q.✅
D17.8qST_Buffer negativ: das Rechteck jenseits des Umkreises (Schnitt der Kreisvielecke)**Erledigt (M17, Runde 12), 541 Messfälle W1 bis W541 in 0.05-Schritten am echten `mariadb:11.8.9` (`Orakel/buffer-runde10.tsv`):** beim Rechteck ist das Ergebnis leer unter dem Umkreis und über der kürzeren Seite, dazwischen der Schnitt der vier Kreisvielecke um die Ecken. Der Server schneidet die 128-Eck-Vielecke, nicht die Kreise: die Spitzen sind die Schnittpunkte zweier Sehnen, die Bogenpunkte die Rasterpunkte des absoluten Winkelrasters 2 pi / 128. Gemessen an 4x4 und 6x4; die Obergrenze (kurze Seite) ist nur an diesen zwei Rechtecken belegt.✅
D17.8rST_Buffer negativ als Flächenverschneidung (Loch erreicht Aussenring, L-Form, Bogenform, Fünfeck)**Erledigt (M17, Runde 13), 650 von 704 Messfällen exakt:** der Rohversatz je Ring (Vier-Punkt-Bogen an einspringenden Ecken auch neben dem Raster, voller Bogen im Raster 2 pi / 128 sonst) wird an allen Schnittpunkten zerlegt und nach der Windungszahl behalten (Aussenring plus, Loch minus), dazu der Schnitt mit dem Urbild. Daraus folgen ohne Sonderfall die Spitzen des Rechtecks, das Verschmelzen des Lochs mit dem Rand (MULTIPOLYGON), die L-Form, das Fünfeck (R9, V35, V36, W305 bis W334) und das Abschneiden an der Polygonkante (X auf der Kante bei R9). Das Rechteck behält seinen eigenen Weg (`Linse`). Die 54 Ausnahmen stehen in D17.8s.✅
D17.8sRest von ST_Buffer negativ: Rundungs-Artefakte, Pfeile der Bogenform, Loch-Dreieck**Erledigt bis auf eine begruendete Grenze (M17, Runden 10 und 11, 754 Messfaelle, 669 exakt; die 85 uebrigen benannt in `Orakel/buffer-ausnahmen.txt`):** gebaut ist der Puffer nach innen mit Umlaufsinn (ein im Uhrzeigersinn gegebener Ring wird spiegelverkehrt gerechnet, Y13/Y44), Zerlegung an allen Schnittpunkten, Behalten nach der Windungszahl, Schnitt mit dem Urbild. **Grenze, technisch nicht herleitbar:** alle 85 Faelle haengen nicht an der Gestalt, sondern an der Arithmetik der Geometriebibliothek des Servers, die nur aus ihrem Quelltext (GPL, fuer KuonenSQL ausgeschlossen - Regel wie D17.6g, nur Black-Box-Messung) ablesbar waere: (1) 43 Faelle mit zusaetzlichem `POINT` oder flaechenlosem Vieleck an einer Originalecke (Dreieck 6/5 bei 0.95, 1.10, 1.15, 1.45 bis 1.60, nicht bei 1.00, 1.05, 1.20 bis 1.40; Bogenform `POINT(4 4)`) - das Auftreten springt ohne Monotonie mit dem Abstand, entscheidend ist, ob eine interne Rechnung genau null ergibt; Black-Box ist das ohne die Rechenvorschrift Bit fuer Bit nicht vorhersagbar (die Flaeche davor stimmt jeweils exakt). (2) Bogenform ab 2.85 (W161 bis W164, Y2 bis Y4, Y6 bis Y20): vom Pfeilkreuz an der Spitze behaelt der Server einen Pfeil und die Raute, der zweite Pfeil hat dieselbe Windungszahl; welcher bleibt, haengt nur am Drehsinn, nicht an einer Groesse der Gestalt (alle vier Anfangspunkte gleich) - also an der Abarbeitungsreihenfolge der Bibliothek. (3) Loch-Dreieck am 45-Grad-Eck (V38, W529 bis W541, Y31 bis Y35, Y38/Y39, Y42/Y43, Y45 bis Y48): der Server schneidet den Bogenanfang an einer Geraden x+y=3-d*sqrt(2) ab, die aus keiner Rohkante entsteht; mit Aussenring 30x30, anderem Dreieck und umgekehrtem Umlaufsinn gemessen, keine Regel aus Messungen ableitbar. Jeder dieser Faelle ist im Test als benannte Ausnahme gefuehrt, die rot wird, sobald er doch stimmt.✅
D17.8eRest: Geometrie und Krypto (Teil 1)**Erledigt (M17), jeder Sollwert am echten `mariadb:11` (11.8.9) gemessen (`MariaDbGeometrieErweitertTests`, 109 Fälle):** `ST_StartPoint`/`ST_EndPoint`/`ST_PointN` (nur Linien), `ST_NumGeometries`/`ST_GeometryN` (nur Mehrfachgeometrien, sonst NULL), `ST_Envelope` (immer ein Fünfeck, auch für Punkt und Strecke), `ST_Centroid` (nur Flächen, nach Fläche gewichtet, mit Löchern), `ST_ExteriorRing`/`ST_NumInteriorRings`/`ST_InteriorRingN`, `ST_IsClosed`/`ST_IsRing` (ausserhalb von Linien -1), `ST_IsSimple`, `ST_Dimension`, die `MBR*`-Familie (`MBRContains`/`Within`/`Intersects`/`Disjoint`/`Equal(s)`/`Touches`/`Overlaps` über die Umschliessungen, auch entartet), `ST_AsGeoJSON` (mit Nachkommastellen und `bbox`) und `ST_GeomFromGeoJSON` (Point bis GeometryCollection, `Feature`, dritte Koordinate ignoriert, SRID-Argument), `ST_Union`/`ST_Intersection`/`ST_Difference`/`ST_ConvexHull` (der vorhandene Verschneider, Ringe in der Ordnung von MariaDB: Aussenring im Uhrzeigersinn, Anfang beim kleinsten X). **Runde 9, am echten `mariadb:11` (11.8.9) gemessen (`MariaDbGeometrieRestTests`):** `ST_SymDifference` (beide Differenzen in einem Wert, `MULTIPOLYGON` bei zwei Teilen; Anfangspunkt des zweiten Rings bei Schnittpunkten wie schon bei den Mengenoperationen nicht deckungsgleich), `ST_GeomFromText` mit dritter Koordinate oder `Z`/`M` ist NULL (dieser Server kennt keine Z/M-Werte), Geometriespalten sind mit `=`, `<>`, `<=>` vergleichbar (die Bytes), `LENGTH` einer Geometrie zaehlt die 25 Bytes der Leitung (SRID + WKB), vier Byte SRID + WKB als Hex-Literal in einer `POINT`-Spalte werden gelesen und alles andere ist 1416, `SPATIAL KEY`/`CREATE SPATIAL INDEX`/`ADD SPATIAL INDEX` werden angenommen und verlangen `NOT NULL` (1252). **Rest als eigene Zeile D17.8k.**✅
D17.8mRest: Geometrie (Beziehungen, Puffer, Kennzahlen), aufgegangen**Erledigt, in andere Zeilen aufgegangen:** `ST_Touches`/`ST_Crosses`/`ST_Overlaps`/`ST_Relate`, `ST_PointOnSurface`, Linie-gegen-Linie in den Mengenoperationen und GeoJSON-Warnungen stehen in D17.8k, der Punktpuffer und die gemessenen Formen des Linien- und Polygonpuffers mit ihrem Rest in D17.8g. Diese Zeile führt nichts mehr offen.✅
D17.9Katalog und Verwaltungsbefehle`SHOW DATABASES/TABLES/COLUMNS/INDEX/CREATE TABLE/VARIABLES/STATUS/PROCESSLIST/WARNINGS/GRANTS` (samt `LIKE`/`WHERE` und `FULL`), `DESCRIBE`/`DESC`, `USE`, `SET NAMES`, `SET [SESSION|GLOBAL] sql_mode`/andere Variablen, `KILL [CONNECTION|QUERY]`, `EXPLAIN`, `GRANT`/`REVOKE`/`CREATE USER`/`DROP USER` auf dieselben Anmeldungen wie T-SQL (S12.17)✅
D17.9b`information_schema` im MySQL-Zuschnitt + ENUM/UNSIGNED-Rückübersetzung`information_schema` im MySQL-Zuschnitt für eine MariaDB-Sitzung (SCHEMATA/TABLES/COLUMNS/STATISTICS/KEY_COLUMN_USAGE/TABLE_CONSTRAINTS/REFERENTIAL_CONSTRAINTS/ROUTINES/VIEWS — Weiche nach `Sitzungsschalter.MariaDbDialekt`, eine T-SQL-Sitzung sieht weiterhin die eigene ISO-Sicht mit ANDEREN Spalten unverändert, siehe `SystemCatalogProvider.IsoAbfrage`); `SHOW CREATE TABLE`/`SHOW COLUMNS`/`information_schema.COLUMNS` lesen ENUM/UNSIGNED jetzt aus der CHECK-Einschränkung zurück (`MariaDbTypRueckuebersetzung`, EINE Stelle für alle drei); `SET(...)` bleibt mangels CHECK ein gewöhnliches `varchar(n)`✅
D17.9cFeinkörnige Rechte und KILL QUERY**Erledigt:** die MySQL-Rechteliste bei `GRANT`/`REVOKE` (SELECT/INSERT/UPDATE/DELETE/EXECUTE/REFERENCES, `ALTER` auf Tabellen, `SHOW VIEW` → `VIEW DEFINITION`, auf `db.*` zusätzlich `CREATE`/`CREATE VIEW`/`CREATE ROUTINE`); Rechte ohne engeres T-SQL-Gegenstück (`TRIGGER`, `INDEX`, `DROP`, `ALTER` auf `db.*`, `SELECT ON *.*`) werden abgelehnt statt zu weit erteilt. Dazu ein Fund in der Engine: ein datenbankweites `GRANT SELECT TO u` wurde bei Objektzugriffen nie berücksichtigt (`PermissionManager.HasPermissionInherited`, DENY auf dem Objekt sticht). `KILL QUERY id` bricht nur die laufende Anweisung ab (Opfer: 1317/70100, Verbindung bleibt; über den Abbruch-Token der Engine, `Sitzungsdaten.BrichAnweisungAb`), `KILL CONNECTION` beendet jetzt wirklich die MariaDB-Verbindung (der Handgriff fehlte), unbekannte Kennung 1094; `WAITFOR` ist abbrechbar (schlief blind). 9 neue Tests, gegen `mariadb:11` gemessen (1094; 1317 nach der Fehlertabelle, am Orakel kein langlaufender Fall erzeugbar).✅
D17.9dRechte durchsetzen (EXECUTE), SHOW GRANTS, KILL QUERY über TDS**Erledigt, Sollformen am echten `mariadb:11` mit denselben Benutzern und Rechten gemessen:** `EXECUTE` auf Prozeduren wird durchgesetzt (ein Aufruf von aussen ohne das Recht scheitert mit **1370** `execute command denied to user 'u'@'%' for routine 'db.p'`; im Inneren einer Prozedur oder eines Triggers gilt die Besitzkette und es wird nicht erneut geprüft; Verwalter brauchen es nie) — das gilt auch für T-SQL-Clients, denn `EXEC` prüfte vorher nichts; `GRANT|REVOKE EXECUTE ON PROCEDURE|FUNCTION db.p` wird angenommen; ein verweigertes SELECT/INSERT/UPDATE/DELETE meldet **1142** mit Benutzer und `db.tabelle` (vorher 1105); `SHOW GRANTS` zeigt statt der groben Näherung die einzelnen Rechte je Ebene (`USAGE`, Datenbank, Tabellen, Routinen, in der Reihenfolge des echten Servers); `KILL QUERY id` von einer MariaDB-Verbindung auf eine **TDS-Sitzung** bricht deren laufende Anweisung ab (Fehler 50000 `Die Anweisung wurde durch KILL QUERY abgebrochen.`, die Verbindung bleibt; in der Ruhe geschieht nichts). 9 neue Tests.✅
D17.9fRechte an DDL und EXECUTE auf Funktionen**Erledigt, jede Meldung am echten `mariadb:11` (11.8.9) mit demselben Benutzer (nur `SELECT`) gemessen:** in MariaDB-Sitzungen verlangen `CREATE TABLE`/`VIEW`/`PROCEDURE`/`FUNCTION`/`TRIGGER`, `DROP TABLE`/`VIEW`/`PROCEDURE`/`FUNCTION`, `TRUNCATE`, `ALTER TABLE`, `RENAME TABLE`, `CREATE`/`DROP INDEX` und `SHOW CREATE VIEW` ihr Recht (1142 `… command denied … for table `db`.`t``, bei `RENAME` `DROP, ALTER`; 1370 `alter routine`; 1044 fuer `CREATE ROUTINE`); `DROP`, `INDEX`, `TRIGGER`, `ALTER` (auch datenbankweit), `ALTER ROUTINE` und `CREATE TEMPORARY TABLES` lassen sich vergeben und stehen in `SHOW GRANTS` in der Reihenfolge des Servers; `EXECUTE` auf **Funktionen** wird beim Aufruf geprueft (1370). Dabei ein Fund: `GRANT ALL PRIVILEGES ON db.*` machte nur einen Datenbankbenutzer ohne Berechtigung (das erste `INSERT` scheiterte) — jetzt Mitglied von `db_owner`. Die Erkennung sitzt am MariaDB-Text (nicht am erzeugten T-SQL) und ist gegen Kommentare mitten im Schluesselwort gehaertet. 11 neue Tests. Reste in D17.9g✅
D17.9eEXPLAIN aus dem Weg der Engine**Erledigt, jede Vergleichszeile am echten `mariadb:11` (11.8.9) mit denselben Tabellen und Daten gemessen:** `EXPLAIN` beschreibt den Weg, den die Engine beim Ausfuehren **wirklich** nimmt — dieselbe Entscheidung `Suchziel` (Indexsuche oder Durchlauf), ohne Zeilen zu lesen: `const`/`ref`/`range`/`ALL` mit `possible_keys`, `key`, `key_len` (nach Typ und Nullbarkeit), `ref`, `rows` (die Kandidaten des Indexes) und `Extra` (`Using where`, `Using index condition`, `Using filesort`, `Using temporary`, `Impossible WHERE …`, `No tables used`); Verbunde mit `Using join buffer (flat, BNLH join)` (die Engine sucht Partner ueber Faecher, nicht ueber Indizes). Was sie nicht beschreiben kann (Mengenoperationen, Unterabfragen, Tabellenfunktionen, `INSERT`), wird mit 1105 abgelehnt statt geraten. 10 neue Tests. Reste in D17.9h✅
D17.9hEXPLAIN im Einzelnen**Erledigt (M17, Runden 8 und 9), jede Zeile am echten `mariadb:11` (11.8.9) mit denselben Tabellen gemessen (`MariaDbExplainRestTests`, `MariaDbExplainMengenTests`):** `UNION [ALL]` als `PRIMARY`/`UNION`/`UNION RESULT`, eine unkorrelierte Unterabfrage in der Auswahlliste als `SUBQUERY`, `EXPLAIN EXTENDED` mit `filtered`, `EXPLAIN INSERT`/`REPLACE … VALUES` und `INSERT … SELECT`; **Runde 9:** `EXCEPT` und `INTERSECT` (je ein Zweig: Zeilen `PRIMARY` und `EXCEPT`/`INTERSECT` mit `EXCEPT RESULT <except1,2>` bzw. `INTERSECT RESULT <intersect1,2>`), `UNION` und `EXCEPT` gemischt (`UNIT RESULT <unit1,2,3>`), abgeleitete Tabellen (einfach: der Server verschmilzt sie, es bleibt der Plan der inneren Abfrage als `SIMPLE`; mit Gruppieren, `DISTINCT` oder `LIMIT` ist es `PRIMARY` mit `<derived2>` (Zeilen der inneren Abfrage, bei `LIMIT n` höchstens n, `Using where` bei einer Bedingung der Hauptabfrage) und `DERIVED` mit `Using temporary`/`Using filesort`), und ein `UPDATE`/`DELETE` mit einer Bedingung ohne Index liest den Primärschlüssel (`type index`, `key PRIMARY`). **Runde 3 (22 weitere Pläne, `Weitere_Plaene_wie_der_Server`):** `EXCEPT`/`INTERSECT` mit beliebig vielen Zweigen (ein `RESULT <except1,2,3>`), `INTERSECT` vor `UNION`/`EXCEPT` und `UNION` mit `EXCEPT` in beiden Reihenfolgen als `UNIT RESULT`, `A EXCEPT B INTERSECT C` als abgeleitete Tabelle (`4|EXCEPT|<derived2>`, `DERIVED`, `INTERSECT RESULT <intersect2,3>`, `EXCEPT RESULT <except1,4>`), eine abgeleitete Tabelle mit Bedingung oder Sortierung der Hauptabfrage verschmilzt (`d.a` wird `a`, `a > 5` und `a < 9` schneiden sich zu einem Bereich mit 3 Zeilen), `DELETE` ohne Bedingung (`Deleting all rows`, keine Tabelle), `UPDATE` ohne Bedingung (`index`/`PRIMARY`), `ORDER BY <Primärschlüssel> LIMIT n` bei `DELETE`/`UPDATE`, `spalte IS NULL` als `ref`/`const`/`Using index condition`, `possible_keys` mit den Indizes aller Teile einer `AND`-Bedingung und `Using index condition; Using where`. **Rest als eigene Zeile D17.9k.**✅
D17.9kKostenplaner und EXPLAIN-Pläne des Servers, Kostenmodell im Clean Room**Erledigt (M17), jeder Plan und jede Kostenzahl am echten `mariadb:11` (11.8.9) gemessen:** die Engine plant einen MariaDB-Block selbst und führt genau den Plan aus, den `EXPLAIN` meldet - Durchlauf, überdeckender Indexdurchlauf, `ref`/`eq_ref`/`const`, Bereiche (auch über zwei Schlüsselteile), `index_merge` (union, sort_union), rowid filter, Verbundpuffer, `Range checked for each record`, Reihenfolge mit den kleinsten Kosten, Halbverbunde (Materialisierung, FirstMatch, LooseScan, DuplicateWeedout, herausgezogen), Antiverbunde (`MATERIALIZED`/`index_subquery`), MIN/MAX über die Indexenden, ORDER BY/GROUP BY/DISTINCT/LIMIT über Indizes. **Clean Room (Runde 19):** das Kostenmodell ist aus Black-Box-Messungen neu hergeleitet (Orakelrunden 1-10, k1, k2; Messreihen und Formeln in `docs/umsetzung.md`), kein Quelltext von MariaDB. Belegt: 160 + 24 Pläne gleich, alle 95 `FORMAT=JSON`-Pläne **mit den Kostenzahlen des Servers** (`MariaDbExplainJsonTests` vergleicht sie exakt, mit den Seiten, die die Engine schätzt), gut 100 Kostenzahlen und Seitenzahlen in `MariaDbKostenmodellTests`. **Grenze, genau benannt:** InnoDB rechnet mit seiner physischen Seitenbelegung - die Engine schätzt sie für Tabellen, die in der Reihenfolge des Schlüssels gefüllt sind (alle 13 gemessenen Tabellen exakt); nach Löschen oder ungeordnetem Einfügen (K85-K92: 161 statt 4 Seiten) und bei großen Tabellen, deren `n_rows` der Server nur stichprobenhaft schätzt, weichen Seiten und damit Kosten ab - dafür bräuchte es die Seitenbelegung von InnoDB selbst. Abgeleitete Tabellen im Verbund als eigene Zeile D17.9r.✅
D17.9rAbgeleitete Tabellen im Verbund (`key0`, `LATERAL DERIVED`), Mengenoperationen als JSON**Erledigt (M17), jeder Plan und jede Kostenzahl am echten `mariadb:11` (11.8.9) gemessen (Runden 10, 11, k3):** eine abgeleitete Tabelle im Verbund (auch hinter FROM) plant die Engine selbst - einmal materialisiert mit `key0` über die verglichenen Spalten (Zeilen je Schlüssel ein Zehntel, höchstens 10, mindestens ein Hundertstel; DISTINCT eindeutig), gruppiert über eine Spalte mit Index innen als `LATERAL DERIVED` je Zeile davor für genau diesen Schlüssel gerechnet (die Ausführung tut das), eine einfache verschmilzt mit der Hauptabfrage (`SIMPLE`, ein Stern nennt nur ihre Spalten). Kosten aus den Traces k3: Nachbearbeiten n · (2 · (0.16097 + 0.002334) + 0.011361 · log2 n), je Schlüssel Zeilen mal das, Nachschlagen 0.16097 + Zeilen · 0.111724. `EXPLAIN FORMAT=JSON` dazu (`materialized`, `lateral`, `outer_ref_condition`) und für gemischte UNION/EXCEPT/INTERSECT (`<derivedN>` mit `<intersect…>`, `<unit…>`) sowie die abgeleitete UNION mit hinabgereichter Bedingung. Belegt: die Liste `explain-ausnahmen.txt` ist leer (D1-D20 gleich, mit Kostenzahlen), Ergebnisse von Hand nachgerechnet.✅
D17.9gRechte, die nicht durchgesetzt sind (Funktionen beim Lesen)**Erledigt (M17), jede Meldung am echten `mariadb:11` (11.8.9) mit denselben Rechten gemessen (`MariaDbDdlRechteRestTests`, 5 Fälle):** `DROP TRIGGER` (TRIGGER auf der Tabelle des Triggers), `CREATE OR REPLACE TABLE`/`VIEW` (DROP zusätzlich, auch für einen neuen Namen), `CREATE TABLE … SELECT` (INSERT auf der neuen Tabelle), `RENAME` (INSERT und CREATE auf dem neuen Namen, alle Paare), ein Name `db.name` einer anderen Datenbank (gegen deren Rechte); fehlende Rechte stehen in der Reihenfolge der Rechtebits des Servers (`INSERT, CREATE` / `CREATE, DROP`). Nebenbei: `CREATE OR REPLACE TABLE` wird übersetzt (`DROP TABLE IF EXISTS` + `CREATE TABLE`), und `DROP` auf ein unbekanntes Objekt meldet 1051/4092/1360/1305/1008 statt 1105 (`MariaDbDropUnbekanntTests`). **Runde 8, geprüft, nichts davon ist technisch unmöglich:** **die Rechte der Quelle von `CREATE TABLE … SELECT` waren schon durchgesetzt** (`SELECT` auf jeder Tabelle der Abfrage, auch im Verbund und in einer Unterabfrage, vor dem Anlegen; die Roadmap-Zeile war veraltet, jetzt mit `MariaDbDdlRechteQuelleTests` belegt); **Spaltenrechte** brauchen ein Rechtemodell mit Spaltenlisten, das die Engine auch für T-SQL nicht hat (`GRANT SELECT ON t(a)`) — die Sollformen sind am Server gemessen und hier festgehalten: nach `GRANT SELECT (a), UPDATE (a), INSERT (id, a) ON t` liest `SELECT a` (auch `COUNT(*)`), `SELECT b` meldet 1143 `SELECT command denied to user ... for column 'b' in table 't'`, `SELECT *` 1142 (Tabelle), `UPDATE t SET a = 1 WHERE id = 1` 1143 für die Spalte `id` (SELECT), `INSERT INTO t VALUES (…)` ohne Spaltenliste 1142, `SHOW GRANTS` nennt `GRANT SELECT (`a`), INSERT (`id`, `a`), UPDATE (`a`), REFERENCES (`id`) ON ...`, `SHOW COLUMNS` zeigt nur die erlaubten Spalten, `information_schema.COLUMN_PRIVILEGES` und `mysql.columns_priv` führen sie; ein T-SQL-Client bekommt keine DDL-Prüfung (sein Besitzmodell, so gewollt); das Opfer eines `KILL QUERY` über TDS sieht Fehler 50000 (SQL-Server-Fehlerzahlen, keine MariaDB-Zahl). **Runde 9:** das Recht `EXECUTE` auf eine Funktion wird beim Lesen der Anweisung geprüft (`PruefeFunktionsaufruf`, nur Abfragen von aussen, nicht in Routinenrümpfen), eine leere Tabelle oder ein nie erreichter Zweig meldet 1370 (`MariaDbDdlRechteFunktionLesenTests`; die Meldung ist am Server gemessen, ob er bei leerer Tabelle ebenso früh meldet, steht als Frage in Runde 2). Eine **unbekannte** Funktion war schon beim Lesen 1305 (`MariaDbFunktionPruefungTests`, vier Formen am Server gemessen: leere Tabelle, `AND` mit falscher Seite, `FALSE AND f()`, nur `HAVING`). **Rest als eigene Zeile D17.9j.**✅
D17.9mRest aus D17.9g: DDL-Prüfung über T-SQL, KILL-Nummer, Mehrdatenbank-Namen**Erledigt (M17), jeder Teil belegt:** die DDL-Prüfung über T-SQL prüft der Kern (S12.19j, am Orakel SQL Server 2025 gemessen: 262, 2760, 3701, 1088; `RechtemodellDdlTests`); Spaltenrechte und `WITH GRANT OPTION` seit D17.9j/D17.9l, die Weitergabe im Kern (S12.19j); die Mehrdatenbank-Namen bei Tabellen, Sichten, Prozeduren, Funktionen und Triggern seit D17.9p (`MariaDbMehrdatenbankObjekteTests`), ihre Fehlerfälle in D17.9q; **KILL-Nummer:** das TDS-Opfer eines `KILL QUERY` sieht jetzt die Meldung, die SQL Server für einen von aussen abgebrochenen Stapel hat — **3980**, Klasse 16, `The request failed to run because the batch is aborted, this can be caused by abort signal sent from client, or another request is running in the same session, which makes the session busy.` (am Orakel SQL Server 2025 aus `sys.messages` gelesen; vorher 50000 mit eigenem Text); die Verbindung bleibt, ein MariaDB-Opfer sieht weiter 1317 (`MariaDbKillTdsTests`, mit der alten Nummer rot).✅
D17.9jSpaltenrechte, WITH GRANT OPTION (Anzeige), KILL-Fehlerzahl**Erledigt (M17, Runde 9), jede Meldung am echten `mariadb:11` (11.8.9) gemessen (`MariaDbDdlRechteSpaltenTests`):** die Spaltenrechte (`GRANT SELECT (a), UPDATE (a), INSERT (id, a) ON t`) gab es seit D17.16 — der alte Vermerk „Rechtemodell mit Spaltenlisten fehlt“ war veraltet; Runde 9 hat die Abweichungen zum Server geschlossen: `COUNT(*)` liest mit jedem Spaltenrecht, `SELECT *` ohne alle Spalten ist 1142 (Tabelle), nicht 1143, ein `INSERT` ohne Spaltenliste mit fehlenden Spalten 1142, und `SHOW GRANTS` führt Tabellen- und Spaltenrechte in **einer** Zeile (`GRANT SELECT, SELECT (`v`), INSERT (`id`, `v`), UPDATE (`v`) ON …`); `WITH GRANT OPTION` (Tabelle und Spalten) wird gespeichert (`PermissionManager.SetzeWeitergabe`), steht in `SHOW GRANTS` am Ende der Zeile und macht `IS_GRANTABLE` in `information_schema.TABLE_PRIVILEGES` zu `YES`; `REVOKE GRANT OPTION ON t FROM u` nimmt nur die Weitergabe, `REVOKE ALL PRIVILEGES, GRANT OPTION FROM u` jedes Recht bis auf `USAGE`; die Prüfung von `EXECUTE` auf eine Funktion beim Lesen der Abfrage kommt nach dem Tabellenrecht (`INSERT`/`UPDATE` ohne Recht sind 1142 vor 1370). Das Opfer eines `KILL QUERY` sieht in einer MariaDB-Sitzung 1317, ein TDS-Opfer die Nummer seines Protokolls (50000) — kein Fehler, die Nummern gehören dem Protokoll des Opfers. **Rest als eigene Zeile D17.9l.**✅
D17.9lWITH GRANT OPTION durchsetzen**Erledigt (M17, Runde 9), jede Meldung am echten `mariadb:11` (11.8.9) mit zwei Benutzern gemessen (`MariaDbDdlRechteWeitergabeTests`, Fragen G5 bis G29):** wer an einer Tabelle die Weitergabe nicht hat, bekommt bei `GRANT` 1142 (`GRANT command denied … for table`), wer sie hat, gibt nur Rechte weiter, die er selbst an der Tabelle hat (sonst 1142 mit dem fehlenden Recht, bei `SELECT, INSERT` das erste fehlende), auch Spaltenrechte, `WITH GRANT OPTION` und `REVOKE`; ein unbekannter Empfänger ist 1133 mit dem Zustand 28000; ein Benutzer ohne Verwalterrecht darf kein `CREATE USER`/`DROP USER` (1227) und nicht die Rechte eines anderen lesen (1044, `to database 'mysql'`); nach einem `REVOKE` bleibt die Weitergabe als `GRANT USAGE ON `db`.`t` … WITH GRANT OPTION` stehen; `IS_GRANTABLE` ist an `TABLE_PRIVILEGES`, `COLUMN_PRIVILEGES` und `SCHEMA_PRIVILEGES` YES, `mysql.tables_priv` führt `Select,Grant`, und `WITH GRANT OPTION` auf einer ganzen Datenbank steht in `SHOW GRANTS`. Nicht gemessen und darum unverändert: die Weitergabe auf Datenbank- und Serverebene durch einen Nicht-Verwalter.✅
D17.9iBenutzer, Rollen, Rechte und Datenbanken wie der Server**Erledigt (M17), jede Meldung am echten `mariadb:11` (11.8.9) gemessen (`MariaDbVerwaltungTests`, 18 Fälle; Nachspiel `np_admin2`, `np_rolle`, `np_dbzeichen`, `np_showkeep` mit `--text`: 0 Unterschiede bis auf Umgebung - Version, Laufzeit, Benutzername `root`/`sa`):** `RENAME USER` (Anmeldung, Benutzer, Rollen, Rechte in allen Datenbanken), `CREATE`/`ALTER`/`DROP USER` und `SET PASSWORD` mit den Fehlern des Servers, **Rollen** (global, als Serverrollen): `CREATE`/`DROP ROLE`, `GRANT rolle TO benutzer|rolle` (1959, 1133+1961, 1962), Rechte an eine Rolle, `SET ROLE`/`NONE`, `SET DEFAULT ROLE`, `CURRENT_ROLE()` - die Rechte einer Rolle gelten erst nach `SET ROLE`; `SHOW GRANTS` mit Kennwort-Abdruck (nur für Berechtigte), Rollen, ALL PRIVILEGES neben Tabellenrechten; `mysql.user` mit Rollen und `mysql.roles_mapping`; `CHARACTER SET`/`COLLATE` einer Datenbank (`CREATE`/`ALTER DATABASE`, `SHOW CREATE DATABASE`, `SCHEMATA`; 1115, 1273, 1253, 1302); die Diagnosefläche der Verwaltungsanweisungen; 1146 mit Datenbank, `USE` ohne Zugang als 1044. Dazu `CREATE TEMPORARY TABLE` (Sitzungstabelle, verdeckt die dauerhafte; `MariaDbTemporaerTests`, 7), `DROP … IF EXISTS`, `ALTER … IF [NOT] EXISTS` und `CREATE TABLE IF NOT EXISTS` mit den Hinweisen des Servers (`MariaDbObjektHinweiseTests`, 25), `TIMESTAMP` als eigener Typ, `TIME`/`YEAR`/`COLUMN_DEFAULT` wie der Server.✅
D17.9nRest aus D17.9i: Zeichensätze, ALL PRIVILEGES an Rollen, WITH ADMIN OPTION**Erledigt (M17), jeder Teil am echten `mariadb:11` belegt:** die Zeichensätze der Tabellen und Spalten sowie `@@character_set_database`/`@@collation_database` je Sitzung in D17.9o (`MariaDbDatenbankZeichensatzTests`); `ALL PRIVILEGES ON db.*` an eine Rolle ist ihr eigenes Recht (jedes Recht der Datenbank, keine Mitgliedschaft in `db_owner`, wirkt nur eingeschaltet, `SHOW GRANTS` zeigt `ALL PRIVILEGES`), `WITH ADMIN OPTION` wird durchgesetzt (ohne ADMIN 1698, `REVOKE ADMIN OPTION FOR`, `Admin_option`, `IS_GRANTABLE`) und ein Recht an eine Rolle in einer später angelegten Datenbank gilt sofort (S12.19, `RechtemodellMariaOrakelTests`). Sabotage beim Abschluss: `SHOW GRANTS` fasst die Einzelrechte nicht mehr zu `ALL PRIVILEGES` zusammen → der Orakeltest rot, zurückgebaut.✅
D17.9oZeichensätze je Datenbank (Teil von D17.9n)**Erledigt (M17, Runde 11), jede Zeile am echten `mariadb:11` (11.8.9) gemessen (`MariaDbDatenbankZeichensatzTests`, Y51 bis Y73):** `@@character_set_database` und `@@collation_database` folgen der aktuellen Datenbank (`USE`, `ALTER DATABASE`; sonst `utf8mb4`/`utf8mb4_uca1400_ai_ci`), und `SHOW VARIABLES` ebenso. Die Zeichensätze der Tabellen und Spalten (Vorgabe aus der Datenbank zur Anlegezeit, `SHOW CREATE TABLE` nennt einen Spaltenzeichensatz nur bei Abweichung, `information_schema.COLUMNS`/`TABLES`/`SCHEMATA`, `utf8mb4_bin` unterscheidet Gross und Klein) standen schon richtig und sind jetzt am Server belegt. Der Teil „Zeichensätze“ in D17.9n ist damit erledigt; dort bleiben die Rechte-Teile.✅
D17.9pMehrdatenbank-Namen bei Sichten, Prozeduren, Funktionen und Triggern (Teil von D17.9m)**Erledigt (M17):** `db.name` nennt die Datenbank auch bei `CREATE`/`DROP` von Sicht, Prozedur, Funktion und Trigger (bisher landete `CREATE VIEW db2.v` als Schema `db2` in der aktuellen Datenbank, `CALL db2.p()` fand nichts), `CALL db.p()` und `db.f(…)` laufen in der Datenbank der Routine (ihre Namen im Rumpf gehören dorthin), ein Trigger liegt in der Datenbank seiner Tabelle. `MariaDbMehrdatenbankObjekteTests` (die Sabotage, die Namen wieder als Schema zu lesen, macht ihn rot). Die Tabellen mit Datenbankname (`db.t`: SELECT, INSERT, UPDATE, DELETE, DDL, JOIN, `SHOW … FROM db`) standen schon. Damit entfällt die „Engine-Grenze“ in D17.9m (ein Name mit fremder Datenbank landet als Schema).✅
D17.9qFehlerfälle der Mehrdatenbank-Namen am Server messen**Erledigt (M17, Runde 18), jede Antwort am echten `mariadb:11` (11.8) mit denselben Anweisungen gemessen (Orakelrunde Z1 bis Z62, die verlorene Fragenliste Z1–Z45 neu geschrieben und erweitert; `MariaDbMehrdatenbankFehlerTests`, 36 Fälle):** im MariaDB-Dialekt ist der erste Teil von `db.name` immer eine Datenbank — Anlegen von Sicht, Prozedur, Funktion in einer unbekannten ist 1049, Aufruf, Lesen und `DROP` melden das Objekt mit dem Namen, wie geschrieben (1305 `PROCEDURE zq_b.p_fehlt`, 4092, 1360, 1051, 1146; bisher `dbo.p_fehlt` oder die aktuelle Datenbank), `IF EXISTS` als Hinweis mit derselben Nummer; `db.f()` ohne Funktion 1305 statt 1105; ein Trigger gehört in die Datenbank seiner Tabelle (1435 `Trigger in wrong schema`, auch ohne Datenbankteil am Trigger), eine Tabelle ohne Datenbankteil liegt in der Datenbank des Triggers; **der Rumpf einer Sicht liest in der beim Anlegen aktuellen Datenbank** (nicht in der der Sicht — bisher falsch, samt Test; `ViewDefinition.Namensdatenbank`, übersteht einen Neustart), der einer Routine in ihrer eigenen (eine fehlende Tabelle dort 1146 `zq_b.t`); `SHOW CREATE VIEW/PROCEDURE/FUNCTION/TRIGGER` auf Unbekanntes 1146 `db.v` bzw. 1305 mit Namen ohne Datenbank bzw. 1360, `SHOW TRIGGERS FROM db` liest die genannte Datenbank (unbekannt: 1049) in der Reihenfolge des Servers (Tabelle, Ereignis, Zeitpunkt, Anlegen), `SHOW PROCEDURE STATUS` und `SHOW FUNCTION STATUS` über alle Datenbanken, „already exists“ ohne Datenbankteil (nur der Trigger nennt sie). Sabotage: Namensdatenbank abgeschaltet → 2 rot; Meldungsnamen nicht umgeschrieben → 6 rot; je zurückgebaut.✅
D17.10Prozeduren, Funktionen, Trigger, Sichten`DELIMITER`-freie Körper über das Protokoll (der Anweisungs-Splitter hält `BEGIN…END`/`IF…END IF`/`WHILE…END WHILE` zusammen), `CREATE PROCEDURE`/`CREATE FUNCTION` mit `DECLARE … [DEFAULT …]`, `IF … THEN … [ELSEIF …]* [ELSE …] END IF`, `WHILE … DO … END WHILE`, `RETURN`, jeder Parameter/lokale Variable mit `@`-Präfix; `CALL` → `EXEC` (samt `OUTPUT` bei `OUT`/`INOUT`-Parametern); `CREATE TRIGGER … AFTER {INSERT|UPDATE|DELETE} … FOR EACH ROW` über einen erzeugten Cursor auf `inserted`/`deleted` (bei `UPDATE` über den Primärschlüssel korreliert) — ausgeführt auf demselben Ablauf-Ausführer wie T-SQL, kein zweiter Baum. `CREATE VIEW` braucht keine Übersetzung. **Erledigt; alles, was dieser Zeile folgte, steht in D17.10b bis D17.10i (alle ✅), es ist nichts Eigenes offen.** Gegen `mariadb:11` gemessen (`kuonensql-mariadb-orakel`, Port 13306), 8 neue Tests.✅
D17.10bLOOP/REPEAT/LEAVE/ITERATE, SIGNAL, NOT-FOUND-Handler**Erledigt, gegen `mariadb:11` (Port 13306) gemessen:** `[marke:] LOOP … END LOOP`, `REPEAT … UNTIL … END REPEAT`, `[marke:] WHILE`, `LEAVE`/`ITERATE marke` (innerste Schleife → `BREAK`/`CONTINUE`, äußere → `GOTO` auf erzeugte Sprungmarke; `ITERATE` in `REPEAT` wertet die UNTIL-Bedingung vorher aus wie MariaDB), `SIGNAL SQLSTATE '45000' [SET MESSAGE_TEXT = …]` → `THROW 50000` (Antwort 1644/45000 mit Text), `DECLARE CONTINUE HANDLER FOR NOT FOUND aktion` (Aktion folgt jedem späteren `FETCH`, das nichts liefert; `FETCH c INTO v` → `FETCH NEXT FROM c INTO @v`). 6 neue Tests.✅
D17.10cEXIT/CONTINUE-Handler für Fehler**Erledigt, gegen `mariadb:11` gemessen:** `DECLARE {EXIT | CONTINUE} HANDLER FOR {SQLEXCEPTION | <Fehlernummer> | SQLSTATE '…'}[, …] aktion` auf `TRY … CATCH` (`MariaDbRoutineUebersetzer.Handler.cs`): EXIT umschließt den Rest des Blocks, der äußere läuft danach weiter; CONTINUE umschließt jede Anweisung hinter der Deklaration; Handler gelten bis zum Blockende (auch der `NOT FOUND`-Handler jetzt); Fehlernummer und SQLSTATE werden über `Mariafehler` rückwärts auf die SQL-Server-Nummern abgebildet, ein nicht passender Fehler geht mit `THROW` unverändert weiter. 7 neue Tests.✅
D17.10eHandler-Sonderformen, RESIGNAL, GET DIAGNOSTICS**Erledigt, jeder Sollwert am echten `mariadb:11` mit denselben Rümpfen gemessen:** `RESIGNAL` (gibt den Fehler des Handlers unverändert weiter, mit Zustand `THROW`), `SIGNAL SQLSTATE 'xxxxx'` mit beliebigem Zustand und `MYSQL_ERRNO` (kommt so beim Client an: 1234/45001; ohne Nummer 1644 mit dem Standardtext) — Zustand und Nummer reisen im Meldungstext zwischen zwei `U+0001` durch `THROW`, `Mariafehler` löst sie beim Client auf —, Handler auf einen eigenen Zustand oder eine eigene Nummer (`FOR SQLSTATE '45001'`, `FOR 1234`; die Bedingung liest den Meldungstext, ein nicht passender Zustand lässt den Fehler durch), `GET DIAGNOSTICS CONDITION 1 v = RETURNED_SQLSTATE|MYSQL_ERRNO|MESSAGE_TEXT` und `GET DIAGNOSTICS v = ROW_COUNT` (bei einem Engine-Fehler übersetzt in die MariaDB-Nummer und den Zustand: 1062/23000), und `LEAVE marke` einer beschrifteten `BEGIN … END`-Marke (auch aus einer inneren Schleife heraus). Ein CONTINUE-Handler fängt jetzt auch das `SIGNAL` der eigenen Anweisungsfolge (das lief vorher an ihm vorbei). 11 neue Tests.✅
D17.10f`SELECT … INTO` und `NOT FOUND`**Erledigt, jeder Sollwert am echten `mariadb:11` gemessen:** `SELECT liste INTO var[, …] FROM …` im Prozedurrumpf (auch die Stellung hinter `FROM`, `DISTINCT`/`ALL`, mehrere Ziele, lokale Variablen, OUT-Parameter, Benutzervariablen) auf die Zuweisungsform von T-SQL; kein Treffer lässt die Variablen unverändert und löst den `NOT FOUND`-Handler aus (`CONTINUE` läuft weiter, `EXIT` beendet den Block, auch `SQLSTATE '02000'`; `GET DIAGNOSTICS` liefert `02000`/`1329`), ein Aggregat ohne Treffer ist eine Zeile und kein `NOT FOUND`, mehr als eine Zeile meldet 1172/`42000` (fangbar mit `FOR 1172`); dasselbe `EXIT HANDLER FOR NOT FOUND` beendet auch eine `FETCH`-Schleife. 12 Tests (`MariaDbSelectIntoTests`); Sabotage: `EXIT`-Signal abgeklemmt → genau die 4 `EXIT`-Tests rot, 1172-Prüfung abgeklemmt → genau die 2 1172-Tests rot✅
D17.10iRest: Handler und Diagnose**Teilweise erledigt (M17), am echten `mariadb:11` gemessen (`MariaDbHandlerRestTests`, 6 Fälle):** `FOR SQLWARNING` (fängt die Warnung 1329 eines leeren `SELECT … INTO`, als CONTINUE wie als EXIT — mehr Warnungen gibt die Engine nicht aus; ein Fehler geht vorbei), `RESIGNAL SET MESSAGE_TEXT = …, MYSQL_ERRNO = …` (der SQLSTATE des gefangenen Fehlers bleibt), `GET DIAGNOSTICS n = NUMBER` (1). Der **Engine-Fehler** „ein `OUT`-Parameter `VARCHAR(10)` nimmt die Länge des ersten zugewiesenen Werts an“ ist behoben (Ursache: das Binden des Aufrufer-Werts ohne Typ; jetzt nimmt jeder Wert den Typ des Parameters an, gegen SQL Server 2022 gemessen). **Nachtrag (D17.10i, erledigt, `MariaDbDiagnoseTests`, 24 Fälle, alle Werte am echten `mariadb:11` 11.8.9 gemessen):** `GET DIAGNOSTICS` liest alle Felder (`RETURNED_SQLSTATE`, `MYSQL_ERRNO`, `MESSAGE_TEXT`, `CLASS_ORIGIN` … `CURSOR_NAME`, `NUMBER`, `ROW_COUNT`) im Handler wie ausserhalb der Routine (Nummer ausser 1 ist 1758); der Text eines Engine-Fehlers ist der von MariaDB (`Duplicate entry '1' for key 'PRIMARY'`); `SIGNAL`/`RESIGNAL` mit allen Feldern, Zustand `01` als Warnung (1642), `02` als 1643, `00` als 1407 schon beim `CREATE`; `SQLWARNING` fängt jede Warnung (auch `SELECT 1/0` = 1365) und nimmt sie aus der Liste; Sitzungsvariablen (`@x`) im Rumpf von Prozedur, Funktion und Trigger (vorher ein Fehler 137) samt dynamischem Typ; `ROW_COUNT()` im Rumpf und die Summe im OK des `CALL`; ein Fehler im Rumpf beendet ihn (vorher lief er mit der nächsten Anweisung weiter und der Fehler erschien erst bei der übernächsten); 1406/1265/1264 bei zu langem oder zu grossem Wert einer Variablen oder eines Parameters; `OUT` nimmt den Wert des Aufrufers nicht an; `SELECT` im Rumpf nennt seine Spalten; Anweisungen ohne Tabelle lassen die Bedingungen der vorigen stehen; `CALL` einer fehlenden Prozedur ist 1305; `BEGIN` als Transaktionsbeginn. Dazu: der Hinweis 4094 (`At line n in db.routine`, je Routine, die der Fehler verlassen hat; die Zeile stammt aus Zeilenmarken im übersetzten Rumpf, `Routinenzeilen`), der `sql_mode` der Routine beim Anlegen, ein `UPDATE` meldet ohne `CLIENT_FOUND_ROWS` die geänderten statt der gefundenen Zeilen, `INSERT IGNORE` meldet die Doppelung als Warnung 1062, `@@warning_count`/`@@error_count`, `SHOW WARNINGS` mit ganzzahliger Nummer, ein `SELECT` mit gespeicherter Funktion löscht die Bedingungen der vorigen Anweisung.✅
D17.10dBEFORE-Trigger**Erledigt, der Ablauf (Werte setzen, per `SIGNAL` ablehnen, vor dem Löschen protokollieren) mit denselben Triggern und Anweisungen am echten `mariadb:11` gemessen:** `CREATE TRIGGER … BEFORE {INSERT|UPDATE|DELETE} … FOR EACH ROW` wird ein `INSTEAD OF`-Trigger (`MariaDbRoutineUebersetzer.Vorher.cs`), der je Zeile den Rumpf mit `NEW.x`/`OLD.x` als typisierten Variablen ausführt und danach die Zeile mit den **geänderten** NEW-Werten selbst schreibt bzw. löscht; ein `SIGNAL` im Rumpf bricht die ganze Anweisung ab. `SHOW TRIGGERS` nennt sie `BEFORE`. Grenzen: je Tabelle und Ereignis nur EIN solcher Trigger (T-SQL-Regel; MariaDB erlaubt mehrere), die Spalten gelten zum Zeitpunkt von `CREATE TRIGGER`, UPDATE und DELETE brauchen einen Primärschlüssel. 5 neue Tests.✅
D17.10gCREATE EVENT mit einem Zeitgeber**Erledigt, Sollwerte und Fehlernummern am echten `mariadb:11` (11.8.9) gemessen, der Zeitplan mit kurzen Takten (`EVERY 1 SECOND`):** `CREATE`/`ALTER`/`DROP EVENT`, `SHOW EVENTS`, `SHOW CREATE EVENT`, `information_schema.EVENTS`, ein Katalogobjekt je Datenbank, das den Neustart ueberlebt, und der Zeitgeber `MariaDbEreignisplaner` (ein Tick je Sekunde, voreingestellt aus wie `event_scheduler = OFF`, an mit `SET GLOBAL event_scheduler = ON` oder `KUONENSQL_MARIADB_EVENT_SCHEDULER=ON`), der faellige Rumpfe in einer eigenen MariaDB-Sitzung unter dem Definierer ausfuehrt; `STARTS`/`ENDS`/`AT` sind Ausdruecke, `ON COMPLETION [NOT] PRESERVE`, `ENABLE|DISABLE`, `COMMENT`, Recht `EVENT` (1044). 13 neue Tests. Reste in D17.10h✅
D17.10hEreignisse im Einzelnen**Teilweise erledigt (M17), am echten `mariadb:11` gemessen (`MariaDbEreignisRestTests`):** zusammengesetzte Intervalle (`EVERY '1:30' HOUR_MINUTE`, `DAY_HOUR`, `DAY_MINUTE`, `DAY_SECOND`, `HOUR_SECOND`, `MINUTE_SECOND`, `YEAR_MONTH`; Anzeige in `information_schema.EVENTS`, `SHOW EVENTS` und `SHOW CREATE EVENT` im Wortlaut mit Apostrophen, geplant wird mit der Summe in der kleinsten Einheit, ein Feld darf überlaufen, `'abc'`/`'0:0'` sind 1542) und der Eintrag `event_scheduler` in `SHOW PROCESSLIST` (bei eingeschaltetem Zeitgeber, `Daemon`, „Waiting for next activation“). **Offen:** eine fremde `DEFINER`-Angabe (der Anlegende ist immer der Definierer); `ON SLAVE`; die Warnung 1588 beim sofort verworfenen `AT` der Vergangenheit (nur das Verwerfen); ein Rumpf mit `DECLARE` (bräuchte eine Routine); gleichzeitige Rümpfe (sie laufen nacheinander); die Fortschrittsspalte `Progress` und `information_schema.PROCESSLIST` kennen den Zeitgeber-Faden nicht; die Nummer des Fadens ist frei gewählt **Weiter erledigt (D17.10h, Nachrunde):** Hinweise 1588/1544 und Warnung 1105, `DISABLE ON SLAVE`, fremde `DEFINER`-Angabe (1449), Rumpf mit `DECLARE`, `INTERVAL` in `STARTS`/`ENDS`, Zeitgeber in `information_schema.PROCESSLIST` (`MariaDbEreignisHinweiseTests`). **Runde 8, geprüft:** gleichzeitige Rümpfe sind eine Entscheidung des Zeitgebers (ein Faden, die Rümpfe laufen nacheinander; zwei Ereignisse zur selben Sekunde laufen in der Reihenfolge des Anlegens — am Server in einer Reihenfolge, die der Planer wählt); die Nummer des Fadens ist frei gewählt (am Server die nächste freie Verbindungsnummer, nicht vorhersagbar); beides ist kein Fehler, den ein Client sieht. **Runde 9, am echten `mariadb:11` (11.8.9) gemessen (`MariaDbEreignisGleichzeitigTests`):** jeder Rumpf eines fälligen Ereignisses läuft in einem eigenen Faden: ein langsamer Rumpf (`DO SLEEP(2.5)`) hält weder ein anderes Ereignis noch den nächsten Takt auf, dasselbe Ereignis überlappt sich selbst (acht Starts und acht Enden in acht Sekunden), und ein ausgeschalteter Zeitgeber lässt die laufenden Rümpfe zu Ende laufen; der Zeitgeber-Faden steht nur bei eingeschaltetem Zeitgeber in der Prozessliste, ohne laufende Rümpfe als eigene Zeilen; die Nummer des Fadens ist die nächste freie Verbindungsnummer beim Einschalten (so gebaut), die „freie Wahl“ und die „nacheinander“-Vermerke oben galten bis Runde 9. Nebenbei: `DO ausdruck[, …]` ist in MariaDB-Sitzungen eine Anweisung (wertet aus, wirft weg). Nichts Offenes.✅
D17.11Kurzmessung: bremst der MariaDB-Port den TDS-Port?**Kurz gemessen, ohne Dauerlast** (30.09.2026, ein Rechner, Release-Bau, 2000 Punktabfragen `WHERE id = ?` über EINE Verbindung je Protokoll, kürzeste von fünf Läufen; Werkzeug: MySqlConnector bzw. Microsoft.Data.SqlClient, nicht im Repo): TDS allein 818 µs je Abfrage, MariaDB-Port allein 153 µs; **TDS mit gleichzeitiger Last auf dem MariaDB-Port 870 µs (Faktor 1,06 — im Rauschen)**, MariaDB neben TDS 191 µs (Faktor 1,25); zum Vergleich der echte `mariadb:11` im Container über dieselbe Schleife 581 µs (dessen Weg führt über das Docker-Netz). **Was das NICHT belegt:** die Reihen sind nicht vergleichbar zwischen den Protokollen (TDS schickt jede Abfrage als parametrisierten RPC, MySqlConnector setzt den Wert clientseitig in den Text), es ist eine Verbindung je Protokoll und keine Belastung mit vielen — die Messreihe aus R13.5 mit mehreren Verbindungen und Dauerlast bleibt offen (**D17.11b**), weil dieser Auftrag ausdrücklich keine Dauerlast erzeugen sollte✅
D17.11bBelastungsmessreihe (30 Minuten)**Erledigt (M17), volle 30 Minuten gemessen (`tools/dauerlast`, Rohdaten `tools/dauerlast/messung-2026-09-30-30min.txt`):** eine TDS- und eine MariaDB-Verbindung (pymysql) zugleich, je auf 50 Anfragen/s gedrosselt (Lesen, Ändern, Einfügen gemischt), eigener Serverprozess (Debug-Bau, eigene Ports), alle 60 s CPU-Anteil des Serverprozesses, Durchsatz, Working Set und offene Verbindungen. Ergebnis: Leerlauf 0,26 % eines Kerns; unter Last im Mittel 7,6 % eines Kerns (6,1-11,9 %, das sind 0,2-0,4 % der 32 Kerne), Durchsatz stabil 50/s je Protokoll, **90 000 MariaDB- und 90 000 TDS-Anfragen, 0 Fehler**, 2 offene Verbindungen über die ganze Zeit; das Working Set stieg von 88 auf 102 MB (die Tabelle wächst um rund 36 000 eingefügte Zeilen, das sind etwa 400 Byte je Zeile - ein Leck lässt sich daraus weder beweisen noch ausschliessen, ein Anstieg ohne Zeilenwachstum war nicht Gegenstand). Der erste Lauf (`messung-2026-09-30.txt`) hatte nach 28 von 30 Minuten durch einen Neustart der Sitzung geendet, dieser ist der vollständige. **Nicht Teil dieses Punkts, weil AGENTS.md es untersagt (CPU-/Speicherlast durch Agenten):** `sysbench` gegen einen echten MariaDB und mehrere Verbindungen je Protokoll - das bleibt eine Messung, die der Hauptchat auf Zuruf macht✅
D17.12Treiber-Abnahme (Teil 1: PyMySQL, mysql-connector, MySqlConnector/Dapper, `mariadb`-CLI, `mariadb-dump`)**Erledigt, am eigenen Server (Scratch, kein Fremdwerkzeug im Testlauf) mit den echten Treibern geprüft:** PyMySQL, mysql-connector-python (rein und C-Erweiterung, auch `prepared=True`), MySqlConnector 2.4 mit Dapper (Stapel, Parameter, Transaktion, vorbereitet) und die `mariadb`-CLI laufen. Drei echte Fehler gefunden: `@@session.sql_mode`/`@@autocommit`/`@@max_allowed_packet` u. a. (Tabelle jetzt EINE Quelle für `SELECT @@x` und `SHOW VARIABLES`, `@@session.`/`@@global.`-Vorsatz), das Ende einer Ergebnismenge war ohne `DEPRECATE_EOF` ein 7-Byte-OK statt des klassischen EOF (mysql-connector hing) und mit `DEPRECATE_EOF` ein OK mit Kopf `0x00`, `TIMESTAMP(x[, y])` fehlte (jeder `DateTime`-Parameter von MySqlConnector). **`mariadb-dump --single-transaction` → Wiederherstellung** läuft in beide Richtungen (KuonenSQL → echter MariaDB und zurück, Daten gleich): dafür `LOCK/UNLOCK TABLES`, `FLUSH PRIVILEGES|LOGS|STATUS|HOSTS|TABLES` (als OK **ohne** Sperre), `SAVEPOINT`/`ROLLBACK TO`/`RELEASE SAVEPOINT`, gemischtes `SET @alt=@@X, X=0`, `COMMIT`/`ROLLBACK` ohne Transaktion als stilles OK, `SELECT SQL_NO_CACHE …` (die folgenlosen Hinweise fallen weg, `SQL_CALC_FOUND_ROWS` bleibt Fehler), `FOREIGN_KEY_CHECKS=0` (Sitzungsschalter: kein Prüfen, keine Kaskade, Verweis auf spätere Tabelle, Elterntabelle darf gehen), **eigene Werte in `AUTO_INCREMENT`-Spalten** (waren ein Fehler 544; `0`/`NULL` vergeben den nächsten, `NO_AUTO_VALUE_ON_ZERO`, `LAST_INSERT_ID()` bleibt), unbenannter `FOREIGN KEY (a) REFERENCES …` (riss als Inline-`KEY` entzwei), Fehler 1452 (Kindzeile) statt 1451, `SHOW CREATE TABLE` mit Fremdschlüsseln/`text`/Zahlenvorgaben/Sichten, `SHOW CREATE VIEW`, `SHOW FIELDS`/`COLUMNS` auf Sichten, `SHOW TRIGGERS`/`FUNCTION|PROCEDURE|PACKAGE STATUS`/`EVENTS`, `ALTER TABLE … DISABLE|ENABLE KEYS`, `CREATE ALGORITHM=… DEFINER=… SQL SECURITY … VIEW`. rund 70 neue Tests.✅
D17.12bTreiber-Abnahme, Teil 2 (PHP, Java, EF Core)**Erledigt, in Docker-Abbildern gegen den eigenen Server (Scratch, kein Fremdwerkzeug im Testlauf):** PHP `mysqli` und `PDO` (`php:8-cli`), MariaDB Connector/J 3.5 (`eclipse-temurin:21`) und EF Core mit Pomelo 9.0 (`dotnet/sdk:9.0`: `EnsureCreated`, Einfügen, Abfragen, Ändern, Löschen, Transaktion) laufen. Fünf echte Fehler gefunden und behoben: (1) ein zweites `COM_STMT_EXECUTE` ohne neue Typen (`new_params_bound_flag = 0`, so senden mysqli und PDO) las die Parameter als Text — 1366; (2) MariaDB Connector/J pipelinet `PREPARE` und `BULK_EXECUTE` mit der Kennung `0xFFFFFFFF` („die zuletzt vorbereitete“) — 1243; (3) `SET autocommit = 0` schaltete nicht auf implizite Transaktionen, `rollback()` nahm nichts zurück (jetzt `SET IMPLICIT_TRANSACTIONS`, das Einschalten schließt mit Commit ab, `@@autocommit` und das Statusbit folgen); (4) `@@performance_schema` fehlte (Connector/J fragt es beim Anmelden); (5) `DELETE … RETURNING 1` (Pomelo prüft so das Löschen) machte aus der Konstante `DELETED.1`; dazu `SAVEPOINT`/`ROLLBACK TO`/`RELEASE SAVEPOINT` mit Backtick-Namen. MySQL Connector/J (`com.mysql`) ist bewusst nicht Ziel: er fragt `@@transaction_read_only`, das es bei MariaDB nicht gibt (am Orakel gemessen). 12 neue Tests✅
D17.12cTreiber-Abnahme, Rest**Erledigt (M17): 0 Unterschiede bei 90 Abfragen.** `tools/mariadb/gui_abfragen.py` spielt die Abfragen aus den QUELLTEXTEN von HeidiSQL (`dbconnection.pas`, `dbstructures.mysql.pas`) und DBeaver (`ext.mysql`) gegen einen echten `mariadb:11` und KuonenSQL nach und vergleicht je Abfrage Erfolg/Fehlernummer, Spaltennamen, Zeilenzahl **und** bei `SHOW CREATE TABLE|PROCEDURE|FUNCTION|TRIGGER|EVENT|DATABASE` den **Wortlaut** der Antwort (Definierer, Sortierung und Zeitpunkte herausgerechnet). Verlauf 48 → 9 → **0**. In dieser Runde: `SHOW CREATE PROCEDURE|FUNCTION|TRIGGER` im Wortlaut des Clients (`Catalog/MariaUrtext`, `MariaDb/MariaDbUrtext`: Kopf im Zuschnitt des Servers - `DEFINER`, Namen in Backticks, Merkmale je auf einer Zeile in der Reihenfolge des Servers, Rückgabetyp klein mit Anzeigebreite; Parameterliste und Rumpf verbatim samt Leerraum und Kommentaren; dauerhaft im Katalog, überholt sich selbst, wenn das Objekt neu angelegt wird), dazu `SHOW TRIGGERS` mit Rumpf und Definierer des Clients; **`CHECK`-Bedingungen in `SHOW CREATE TABLE`** (am Spaltenkopf, sonst `CONSTRAINT name CHECK (…)` hinter den Schlüsseln, `CONSTRAINT_1` für unbenannte), `AUTO_INCREMENT=n` in `SHOW CREATE TABLE` und `information_schema.TABLES` (der nächste Wert statt des Startwerts), Nachkommastellen im Standardwert (`DEFAULT 0.000`), **Kommentare an Tabellen und Spalten** (`COMMENT`: als `MS_Description` abgelegt, also auch in SSMS sichtbar; `SHOW CREATE TABLE`, `SHOW FULL COLUMNS`, `information_schema`), `Default` in `SHOW COLUMNS` ist ohne `DEFAULT`-Klausel SQL-NULL; **`!x` und Zeilenwerte** `(a,b)=(c,d)`, `<>`, `<`, `<=`, `>`, `>=`, `<=>`, `IN`, `NOT IN`, `ROW(…)` als Grammatik des gemeinsamen Parsers (`Dialekt.AusrufezeichenIstNicht`, `Dialekt.Zeilenwerte`); `mysql.user` lesbar (fehlte: die Sicht wurde nicht als Systemsicht erkannt), Spaltenkopf ohne Alias im Wortlaut des Clients (`CONCAT(`User`, '@', `Host`)`), `EXPLAIN` mit `db.tabelle` (fand die Tabelle nicht und nannte `db.tabelle`), die Abfrage von `mariadb-dump` auf `information_schema.FILES` (leer statt Fehler). Beim Nachprüfen der Sicherung gefunden und behoben: **ein Schlüssel in Backticks (`KEY `x` (`a`)`, so schreibt ihn `mariadb-dump` immer) wurde nicht erkannt** - jede Sicherung einer Tabelle mit Nebenschlüssel ließ sich nicht wiederherstellen; Routinenköpfe mit `DEFINER=`, `IF NOT EXISTS`, `COMMENT`, `CHARSET` an Parameter und Rückgabetyp; ein `-- Kommentar` vor dem `END` einer Routine verschluckte das `END`; `ALTER TABLE t COMMENT = '…'|ENGINE=…|CHARSET=…`. **`mariadb-dump --routines --triggers` läuft jetzt fehlerfrei durch und lässt sich in eine leere Datenbank zurückspielen** (Tabellen, Schlüssel, `CHECK`, Routinen, Trigger, Daten). **Benannt statt offen:** die GUIs selbst (DBeaver/HeidiSQL am Bildschirm, das Schema `mariadb` in SSMS) sind technisch nicht automatisierbar - sie brauchen eine Anzeige und Mausbedienung; belegt ist, was sie an den Server schicken (die Quelltext-Abfragen oben). `SHOW CREATE VIEW` gibt den (von KuonenSQL lesbaren) Text der Sicht zurück, nicht die vom Server umgeschriebene Form (`select `kunde`.`id` AS `id` …`) - nachgebaut wird der Umschreiber des Servers nicht. `LOCK TABLES` beantwortet OK ohne zu sperren (D17.12, ehrlich benannt: die Engine kennt keine Tabellensperre auf Zuruf des Clients). `TIMESTAMP` und `FLOAT` erscheinen als `datetime`/`double` (siehe D17.6e)✅
D17.13Dialekt-Beschreibung statt Dialekt-Schalter**Erledigt.** Nutzervorgabe: weitere Anschlüsse (Oracle, DB2, SAP MaxDB, Informix …) ohne Umbau des Kerns. Bestandsaufnahme in `docs/dialekte-architektur.md` (rund zwei Dutzend Stellen fragten `Sitzungsschalter.MariaDbDialekt`/`PostgreSqlDialekt` ab). Neu `Dialekte/Dialekt` (Eigenschaften mit T-SQL als Vorgabe: `LimitKlausel`, `WithRecursive`, `SperrklauselAmEnde`, `IfAlsFunktion`, `VorzeichenTypen`, `IntervallDatumsfunktionen`, `VergleichIstWert`, `TeilungDurchNullErgibtNull`, `GanzzahlTeilungNachkommastellen`, `ConcatMitNullErgibtNull`, `DatediffMitZweiArgumenten`, `FunktionsrechtBeimAufruf`, `AutowertWieAutoIncrement`; Methoden für eigene `@@`-Variablen, Katalogsichten und Kennwort-Ableitungen), `MariaDialekt`/`PgDialekt` in ihren Modulen, `Dialektregister` mit Selbstanmeldung je Modul. Die Sitzung hält **einen** Dialekt (`ExecutionEngine.SetzeDialekt`); das Protokoll trägt seine Kennung in Bit 20–27 (MariaDB = 1 = das bisherige Bit 1048576, also rückwärtsverträglich; PostgreSQL-Folgen spielt der Wiederanlauf jetzt auch mit ihrem Dialekt). `Prinzipalverwaltung` ruft keinen MariaDB-Code mehr. Wächter `DialektArchitekturTests`: kein Dialekt beim Namen ausserhalb der Module (Ausnahmeliste leer), Griffe in ein Modul nur mit begründeter Ausnahme (7 Dateien, Liste darf nur schrumpfen), ein fremder Testdialekt mit `LIMIT` übersteht den Neustart✅
D17.13bGemeinsame Nahtstellen der Protokoll-Anschlüsse**Erledigt.** Was MariaDB und PostgreSQL doppelt taten, steht an einer Stelle: `IProtokollanschluss` (Name, Port, Startangaben, eine Verbindung bedienen — die Einstellungen beider Anschlüsse implementieren sie), `Program.RunAnschlussAsync` (Hörer, Annahmeschleife, eigene Sitzung derselben Engine — vorher je Anschluss zwei Methoden), `Protokollanschluss.LiesPort` (Argument vor Umgebung, 1–65535, sonst aus), `Anschlusssitzung` (Anmelden bei der Sitzungsverwaltung, `KILL` von aussen, Abbruch der laufenden Anweisung, Aufräumen — auch für den Ereignis-Zeitgeber ohne Leitung) und `Anmeldevorpruefung` (gesperrt / Betriebssystem / ohne Kennwort / prüfen). Handschlag, Pakete und Anmeldeverfahren bleiben im Modul. **Fund dabei (Sicherheit):** eine Anmeldung `FROM WINDOWS` hat keinen Kennworthash und kam über MariaDB **und** PostgreSQL mit **jedem** Kennwort durch (sie galt als „ohne Kennwort“); jetzt abgewiesen (1045 bzw. `28000`), weil nur TDS das Kennwort im Klartext hat und es dem System vorlegen kann✅
D17.13cCheckliste für einen neuen Anschluss**Erledigt.** `docs/neuer-dialekt.md`: welche Dateien ein neuer Anschluss (Oracle, DB2, SAP MaxDB, Informix …) anlegt (Dialekt-Beschreibung mit Kennung und Selbstanmeldung, `IProtokollanschluss`, Verbindung über `Anschlusssitzung`, Anmeldung nach `Anmeldevorpruefung`, Fehler-/Typcodes), wie Abweichungen beschrieben statt verzweigt werden (vorhandene oder neue Eigenschaft, Methode für Code, Text-Übersetzer nur als benannte offene Scheibe), das Orakel-Vorgehen, die Tests (Parser ohne Leitung, T-SQL-Gegenfall, Wiederanlauf, Robustheit, Sabotage) und die Roadmap. Der Wächter `DialektArchitekturTests` hält die Regel fest; `docs/dialekte-architektur.md` nennt, was bewusst noch im Kern liegt (Sitzungszustand eines Dialekts, gespeicherte MariaDB-Ableitungen, `pg_catalog`, MariaDB-Funktionsbibliothek) samt nächstem Schritt✅
D17.14WordPress-Abnahme**Erledigt (vier Stellen, an denen WordPress 7.1 scheiterte; `MariaDbWordPressTests`, Leitung, Schema wie bei WordPress).** (1) `SELECT SQL_CALC_FOUND_ROWS … LIMIT a, b` ist kein Syntaxfehler mehr, `FOUND_ROWS()` liefert die Zeilenzahl ohne `LIMIT` (die Abfrage läuft dafür ein zweites Mal ohne ihr oberstes `LIMIT`), ohne das Stichwort die Zeilen der letzten Abfrage (`MariaDbGefundeneZeilen`, `Sitzungsschalter.LetzteGefundeneZeilen`). (2) `SELECT DISTINCT … ORDER BY` auf eine nicht ausgewählte Spalte (statt 145): die Spalte läuft verdeckt mit, entdoppelt wird nur über die sichtbaren Spalten. (3) Nicht gruppierte Spalten in Auswahlliste, `HAVING` und `ORDER BY` (statt 8120/8121/8127): der Wert der ersten Zeile der Gruppe, wie MariaDB 11 im Standard-`sql_mode`; mit `ONLY_FULL_GROUP_BY` im Modus bleibt die strenge Prüfung. (2) und (3) hängen am neuen `Dialekt.FreieGruppierungUndSortierung` (nur MariaDB, T-SQL unverändert). (4) `DELETE a, b FROM t a, t b …`: dieselbe Tabelle unter zwei Aliasen (der Alias des Ziels fehlte im `DELETE` je Ziel). Sabotage: jede der vier Wirkungen einzeln zurückgenommen, alle 4 Tests rot.✅
D17.15CMS-Abnahme**Erledigt (fünf Stellen aus Joomla 6, Ghost 6, Drupal 11 und Nextcloud/Redmine/Kanboard; `MariaDbCmsTests`, `FehlerprotokollTests`).** (1) Vorbereitete Anweisungen (`COM_STMT_PREPARE`) kennen alle Katalogbefehle wie das Textprotokoll: `SHOW …` (Spaltenform durch Ausführen mit NULL-Platzhaltern), `SET`, DDL, `CALL` laufen beim EXECUTE zuerst durch die Katalogbefehle, dann durch den T-SQL-Ausführer. (2) `ALTER TABLE … ADD PRIMARY KEY name(spalten)` (knex) und ein nachträglicher Primärschlüssel auf nullbaren Spalten macht sie still `NOT NULL` (`Dialekt.PrimaerschluesselMachtNichtNull`). (3) Impliziter Commit bei DDL/GRANT/REVOKE (`Dialekt.DdlSchreibtImplizitFest`, nur MariaDB): offene Transaktion davor und danach festgeschrieben, `TEMPORARY`-Tabellen ausgenommen, in einer Funktion 1422; Trigger noch nicht erkannt. (4) `KUONENSQL_FEHLERPROTOKOLL=1`: eine Zeile `[SQL-FEHLER]` je Fehler auf stdout (`Fehlerprotokoll`). (5) Ein Bezeichner in Backticks, eckigen Klammern oder Anführungszeichen ist nie ein Schlüsselwort (`Token.Begrenzt`; Regression `key`/`index`/`unique` in `CREATE TABLE`). (6) Zeichenkettenliteral '*' in der Auswahlliste wird nie als Stern erweitert (T-SQL und MariaDB). (7) Binärprotokoll: DATE/DATETIME/TIMESTAMP/TIME im MariaDB-Binärformat, BLOB roh. (8) `SHOW KEYS ... WHERE`, 1064 mit Quelltext ab der Fehlerstelle und Zeile, DDL im Trigger 1422, `KUONENSQL_ANWEISUNGSPROTOKOLL=1` (eine Zeile `[SQL]` je Kommando), Fehlerprotokoll mit `Original=` und sp_execute-Text. (9) `ON DELETE/UPDATE RESTRICT`, `utf8` ist `utf8mb3`, `GET_LOCK` und Verwandte (sitzungsgebunden, Stufen, Freigabe beim Verbindungsende), JSON wie LONGTEXT vergleichbar, `ALTER ADD NOT NULL` ohne Standardwert füllt mit der Typvorgabe, TEXT/MEDIUMTEXT/BLOB ausserhalb der Zeile (keine 8060-Grenze), `ALTER ADD ... AUTO_INCREMENT PRIMARY KEY` inline mit FIRST/AFTER (die Spalte steht wirklich dort), `DROP FOREIGN KEY/INDEX` auf Fehlendes meldet 1091. (10) Kimai: eine Anweisung nur aus einem Kommentar (`#`, `--`, `/* */`) ist OK, nur wirklich Leeres bleibt 1065. Kanboard: ein Index ohne Namen heisst wie seine erste Spalte, bei Kollision `name_2`, `name_3` (UNIQUE, KEY, INDEX, FULLTEXT; `MariaIndexnamen`), `DROP INDEX name` trifft ihn. Redmine: `JOIN … USING (spalten)` und `NATURAL [LEFT/RIGHT] JOIN` (`Dialekt.JoinUsing`, Umschreibung im Parser mit Spalten aus dem Katalog, `*` nennt die Spalte einmal und zuerst). Nicht reservierte Woerter (`end`, `start`, `status`) auch im Ausdruck. BookStack: `RENAME TABLE` (auch `ALTER TABLE … RENAME TO`, mehrere Paare, ueber Datenbanken) nimmt AUTO_INCREMENT-Zaehler, Indizes, Fremdschluessel (ausgehend und eingehend), Trigger (auch der Rumpf eines BEFORE-Triggers und `SHOW CREATE TRIGGER`), Volltextindizes mit, Tabellenrechte bleiben am alten Namen wie bei MariaDB (`CatalogManager.RenameTable`, `ExecutionEngine.TabelleUmbenennen`); beim Datenbankwechsel 1435 bei Triggern, Fremdschluessel ziehen mit und gelten ueber Datenbanken (`ForeignKeyDefinition.ReferencedDatabase`, Kind- und Elternseite, Aktionen laufen in der Datenbank des Kinds; Kind zieht: Schluessel zeigt in die alte Datenbank, Eltern ziehen: der Schluessel folgt, am Orakel gemessen). `REFERENCES db.tabelle` in CREATE/ALTER TABLE legt den Schluessel ueber Datenbanken direkt an (die Datenbank der Kindtabelle faellt weg, wenn sie gleich ist). Falsch gebaute Fremdschluessel (unbekannte Datenbank, Elterntabelle nicht in der Datenbank des Kinds, Zielspalten ohne Index) melden wie MariaDB 1005 errno 150, `DROP DATABASE` einer Elterndatenbank mit fremden Kindern 1451; `PRIMARY KEY indexname (spalten)` verlor bisher still den Schluessel. Zusatz: `DELETE/UPDATE db.tabelle` mit der gewaehlten Datenbank scheiterte mit 1146, jetzt geht es. Offen, benannt: mehrere VARCHAR(4000) in einer Zeile (über 8060 Bytes) scheitern weiter; CALL im Binärprotokoll sendet nur die letzte Ergebnismenge; DDL-Erkennung in Triggern nur für DML-Trigger. Sabotage: je Wirkung einzeln zurückgenommen, Tests rot.✅
D17.16Katalog und Rechte für Admin-Werkzeuge**Erledigt (Adminer, phpMyAdmin; `MariaDbAdminKatalogTests`, Leitung).** (1) Ein Benutzer mit nur `GRANT ALL ON kanboard.*` kommt nicht mehr in `wordpress`: Anmeldung mit Datenbank und `COM_INIT_DB` melden 1044 (`MariaKatalog.DarfOeffnen`, dieselbe Regel wie `USE`); `VIEW ANY DATABASE` zählt dafür nicht (hat jeder über `public`). (2) `SHOW DATABASES`, `information_schema.SCHEMATA/TABLES/COLUMNS` zeigen nur Datenbanken mit Recht, immer `information_schema`, für sa/sysadmin zusätzlich `mysql`, `performance_schema`, `sys` (nur als Namen, ohne eigenen Katalog); `master`/`model`/`msdb`/`tempdb` nie in `SHOW DATABASES`/`SCHEMATA` (in `TABLES`/`COLUMNS` nur, wenn die Sitzung gerade darin steht). (3) `CREATE_TIME` ist die Anlagezeit der Tabelle in ganzen Sekunden (`DATETIME`), `UPDATE_TIME` die der letzten Zeilenänderung (`NULL`, solange nie geändert), `TABLE_ROWS` die echte Zeilenzahl, `DATA_LENGTH` aus den Seiten auf 16 KiB gerundet. **Rest geschlossen:** die Anlagezeit steht in der Datendatei (Version 2, Version 1 wird weiter gelesen, ein Sicherungssatz bleibt im Format 1); `SHOW TABLES/TABLE STATUS/COLUMNS/INDEX FROM <fremde Datenbank>` melden 1044; `mysql`, `performance_schema`, `sys` sind Schemata (`MariaEingebauteSchemata`: `mysql.db/tables_priv/columns_priv/procs_priv/proc/global_priv/func/proxies_priv/user/roles_mapping`, `performance_schema.global_/session_variables/status`, `users/hosts/accounts`, `sys.version/sys_config`) samt `USE`, `SHOW TABLES/COLUMNS/INDEX/TABLE STATUS FROM` und `information_schema.USER_/SCHEMA_/TABLE_/COLUMN_PRIVILEGES` (Verwalter sieht alle, sonst nur eigene Rechte); ein Name ohne Datenbank meldet in einer Sitzung ohne gewählte Datenbank 1046. **Zweiter Rest geschlossen:** nach `USE mysql`/`performance_schema`/`sys`/`information_schema` lösen Namen ohne Datenbank dort auf (auch `SHOW TABLES`, `DESCRIBE`); ohne gewählte Datenbank meldet jede Anweisung mit einem Namen ohne `db.` 1046 (`MariaDbKeineDatenbank` liest den Text Wort für Wort; CTE-Namen, `FROM` in Funktionen, Zeichenketten und Kommentare sind ausgenommen), nie ein stilles `master`; `mysql.proc` zeigt Parameter, Rückgabe, Merkmale, Definierer und Rumpf aus der Urfassung; Rechte auf Spalten (`GRANT SELECT (a, b), INSERT (c) ON db.t`, `REVOKE`, `PermissionManager.Spaltenrechte`, dauerhaft) mit 1143 bei der ersten fehlenden Spalte und 1221 für Rechte ohne Spaltenstufe, Anzeige in `SHOW GRANTS`, `mysql.columns_priv`/`tables_priv.Column_priv`, `information_schema.COLUMN_PRIVILEGES`; ein Benutzer mit nur einem Tabellenrecht darf die Datenbank wählen (MariaDB prüft jedes Recht in der Datenbank, belegt durch Test). **Grenze:** Spaltenrechte gelten für Abfragen, `INSERT` und `UPDATE` in der aktuellen Datenbank (andere Datenbank: nur Tabellenrecht); die Schreibweise der Spaltenzeile in `SHOW GRANTS` folgt der Anleitung, nicht einer Messung am Server. Sabotage: Sichtbarkeitsfilter ausgeschaltet, Test rot.✅
D17.17App-Abnahme Runde 4**Erledigt (Funde der frischen Installation von Redmine, Ghost, BookStack, Kanboard, Snipe-IT, Nextcloud; `MariaDbAppRunde4Tests`).** (1) Redmine: `DROP COLUMN` liess die Typbedingung der Spalte (`TYV__tabelle__spalte` aus `TINYINT(1)`, `UNSIGNED` …) stehen, jedes folgende `UPDATE` der Tabelle scheiterte mit 1054 `Unknown column` (Redmine benennt `mail_notification` in `mail_notification_bool` um und entfernt sie). Jetzt faellt die Typbedingung mit der Spalte. (2) Kanboard: `ALTER TABLE … MODIFY/CHANGE spalte … UNIQUE` (bzw. `PRIMARY KEY`) verwarf den Inline-Schluessel still; MariaDB legt einen weiteren eindeutigen Index an (`name_2`, wenn `name` schon besteht, am Orakel gemessen) bzw. meldet bei vorhandenem Primaerschluessel 1068. Kanboard entfernt danach `name` und `name_2`; fehlte `name_2`, brach die Migration ab und lief bei jedem Aufruf erneut (1091 auf `name`). (3) BookStack: `DROP COLUMN id` liess den Primaerschluessel auf der entfernten Spalte stehen, das folgende `ADD PRIMARY KEY (role_id, …)` meldete 1068. Jetzt wie MariaDB 11.8 (gemessen, `Dialekt.SpalteEntfernenNimmtSchluesselMit`): einspaltige Indizes der Spalte und `CHECK`-Bedingungen nur auf ihr fallen mit, eine Spalte in einem mehrspaltigen Index oder Primaerschluessel meldet 1072, unter einem Fremdschluessel 1553, in einer `CHECK`-Bedingung mit weiteren Spalten 1054 `in 'CHECK'`, die letzte Spalte 1090. (4) Nebenbefund: `information_schema.TABLE_CONSTRAINTS` zeigte die internen Typbedingungen (`TYV__…`) als `CHECK`; jetzt wie `CHECK_CONSTRAINTS` und wie MariaDB nur die eigenen (gemessen). Wie MariaDB, am Orakel belegt, nichts zu tun: Ghost 1068/1061 (knex-migrator legt `migrations_lock` mit Primaerschluessel an und schickt bei jedem Start erneut `add primary key` und `add unique`, MariaDB meldet dasselbe), Snipe-IT 1091 (`*_company_id_foreign` wurde nie angelegt, die Migration loescht sie vorsorglich), Nextcloud 1142 (`mysql.user` ohne Recht). Sabotage: je Wirkung einzeln zurueckgenommen, Tests rot.✅
D17.18App-Abnahme Runde 5**Erledigt (Funde nach frischem Neuaufbau: Redmine, Kanboard, BookStack; `MariaDbAppRunde5Tests`).** (1) Redmine: `RENAME COLUMN` (auch `sp_rename` auf eine Spalte) setzte den AUTO_INCREMENT-/IDENTITY-Zaehler zurueck; das folgende `INSERT` ohne `id` meldete 1062 `Duplicate entry '1' for key 'PRIMARY'`, die Migration brach ab. Der Zaehler bleibt jetzt. (2) BookStack: geklammerte Abfragen als Glied einer Mengenoperation (`(SELECT …) UNION (SELECT …)`, so schreibt Laravel jede Vereinigung) und als Quelle von `INSERT … [(spalten)] (SELECT …)` meldeten 1064. Jetzt auch als ganze Anweisung, in `FROM ((…) UNION (…)) x`, mit eigener Sortierung oder Grenze in der Klammer (gilt nur dort) und mit `ORDER BY`/`LIMIT` hinter der letzten Klammer (gilt fuer das Ganze); T-SQL kennt dieselbe Schreibweise; am Orakel gemessen. (3) Kanboard: ein Fremdschluessel, dessen Spalten kein Index vorn fuehrt, bekam nur einen angezeigten Index (`tabelle_ibfk_N`); MariaDB legt einen echten an, benannt nach `CONSTRAINT`, sonst `FOREIGN KEY indexname`, sonst der ersten Spalte (`user_id_2` bei Kollision), und er bleibt nach `DROP FOREIGN KEY` (gemessen). Kanboards `DROP KEY user_id` scheiterte, die Migration lief bei jedem Aufruf erneut (1091 auf `user_has_notifications_ibfk_1`); die 1050 auf `password_reset` kam von einer zweiten Anfrage, die dieselbe Migration parallel anstiess (mit MariaDB derselbe Wettlauf). `DROP FOREIGN KEY`/`DROP CONSTRAINT` entfernt bei gleichem Namen zuerst den Fremdschluessel, der Index bleibt. Sabotage: je Wirkung einzeln zurueckgenommen, Tests rot.✅
Meilenstein 18: PostgreSQL-Protokoll und -Syntax auf derselben Ablage (175/175)
PunktTitelBeschreibungStand
P18.1Der HandschlagPort 5432 (einstellbar, voreingestellt aus), `StartupMessage` Version 3.0, `SSLRequest` und `GSSENCRequest` (letzteres benannt abgelehnt), `ParameterStatus` (`server_version` 17.x, `client_encoding`, `DateStyle`, `TimeZone`, `integer_datetimes`, `standard_conforming_strings`), `BackendKeyData`, `ReadyForQuery` mit Transaktionszustand, `CancelRequest` über eine zweite Verbindung, `Terminate`✅
P18.2Die Anmeldung`SCRAM-SHA-256` (und `-PLUS` mit Kanalbindung über TLS), `md5`, `password` nur über TLS; gegen dieselben Anmeldungen wie TDS, ohne zweite Benutzertabelle — gespeichert wird, was PostgreSQL selbst hält (Salz, Iterationen, `StoredKey`, `ServerKey`)✅
P18.2bAnmeldung mit `md5` und `SASLprep`ERLEDIGT: `md5` (`Anmeldeverfahren` `md5`, `KUONENSQL_POSTGRESQL_ANMELDUNG=md5`): `AuthenticationMD5Password` mit zufälligem Salz, Antwort `"md5" + hex(md5(hex(md5(Kennwort + Name)) + Salz))` in fester Zeit geprüft; der Wert `md5(Kennwort + Name)` entsteht wie die MariaDB-Ableitungen beim Anlegen/Ändern des Kennworts und beim ersten gelungenen Klartext einer älteren Anmeldung (`Anmeldung.PgMd5`), gegen `pg_authid` von `postgres:17` belegt (`md5-orakel.txt`); wo er fehlt, fällt `md5` wie in `pg_hba.conf` auf SCRAM zurück. **SASLprep** (RFC 4013 samt RFC 3454, `Pgsaslprep`, Tabellen aus dem Python-Modul `stringprep` erzeugt): SCRAM und Klartext prüfen das Kennwort nach SASLprep (Anmeldung mit zerlegtem oder zusammengesetztem `ä` ist dasselbe Kennwort), ein verbotenes Kennwort (Steuerzeichen, gemischte Schreibrichtung, in Unicode 3.2 Unzugeteiltes, U+0340) gilt unverändert; 38 Kennwörter gegen den echten Server (`saslprep-orakel.txt`: aus Salz und Kennwort folgt derselbe StoredKey), dazu Npgsql als fremder Client (md5 und Kennwort jenseits von ASCII). Der Schlüssel nach SASLprep wird nur gespeichert, wenn SASLprep das Kennwort verändert (`Anmeldung.PgSalted`), sonst gilt der Kennworthash. **Grenze:** `md5` bindet an den Anmeldenamen, wie er in der Anmeldung steht (Gross-/Kleinschreibung des Clients muss stimmen; SCRAM tut es nicht)✅
P18.3Einfache Abfragen`Query` mit Mehrfachanweisungen in einer impliziten Transaktion, `RowDescription` mit Typ-OIDs und Grössen, `DataRow` im Textformat, `CommandComplete` mit den richtigen Etiketten (`INSERT 0 3`, `UPDATE 2`, `SELECT 5`), `EmptyQueryResponse`, `ErrorResponse`/`NoticeResponse` mit allen Feldern (Schweregrad, **SQLSTATE**, Meldung, Detail, Hinweis, Position) — die PostgreSQL-Fehlercodes (`23505`, `42P01`, `42703`, `42601`, `22012` …) statt der SQL-Server-Nummern✅
P18.3bFehlerfelder und Fehlercode-TabelleERLEDIGT (Orakel `postgres:17`, `fehler-orakel.txt` 75 Fehler mit allen Feldern, `bedingungen-orakel.txt` 256 Namen): `ErrorResponse` trägt `P` (Position im Text der ganzen Anfrage), `D` (`Key (id)=(1) already exists.`, `Key (pid)=(9) is not present in table "fe_p".`, `Failing row contains (…)` mit Werten auf 64 Zeichen gekürzt), `s`/`t`/`c`/`n`; die Engine teilt die Stelle einer verletzten Einschränkung jetzt neutral mit (`KuonenSqlException.Ort`/`Meldung.Ort`: Tabelle, Spalte, Schlüssel, Zeile, Datentyp), PostgreSQL liest sie in `Pgfehler`. Meldungen wie am Original: `null value in column "id" of relation "t"`, `insert or update on table "k" violates foreign key constraint`, `new row for relation "k" violates check constraint`, `value too long for type character varying(3)`, `integer out of range`, `invalid input syntax for type numeric: "x"` (`date` → `22007`), `missing FROM-clause entry`, `function f(integer, unknown) does not exist`, `column "x" of relation "t" does not exist` (INSERT-Spaltenliste, UPDATE SET). **Fehlernummern → SQLSTATE:** eine Tabelle (`Pgfehler.NummernTabelle`, rund 90 Nummern der Engine) statt der 20 von Hand; der Handler im PL/pgSQL-Rumpf baut seinen `CASE` aus derselben Tabelle. **Alle 245 Bedingungsnamen** von PL/pgSQL (`Pgbedingungen`, erzeugt aus der Antwort des Orakels: `tools/postgres/bedingungen-erzeugen.py`), Namen mit `000` treffen die ganze Klasse, `RAISE data_exception` meldet `22000`. Dazu: eine Transaktion, die erst die Anweisung öffnete (`DO`-Block), endet mit ihrem Fehler (vorher blieb die Verbindung in `25P02`); `Ambiguous column name` (209) meldet auch die Engine bei `FROM a, b` (SQL Server 2025 belegt). **Grenze:** `P` ist die erste Fundstelle des Namens im Text (`Pgfehlerstelle`), genau erst mit dem Parser (P18.5c)✅
P18.3cAnfragetext in PostgreSQL-SyntaxERLEDIGT (`PgRunde11dTests`, 12 Anweisungen samt Fehlerfeldern gegen `postgres:17`; die Syntax selbst übersetzt seit P18.5 `PgUebersetzer`, die Typen der Ausdrücke seit P18.3c/P18.8c): `INSERT INTO t VALUES (1)` füllt die ersten Spalten und gibt den übrigen ihre Vorgabe, zu viele oder zu wenige Werte sind `42601` mit Meldung und Position von PostgreSQL; ein Text aus mehreren Anweisungen, von denen eine mit keinem Befehl beginnt (`SELEC 1`), läuft gar nicht (`syntax error at or near`, Position im ganzen Text); `CASE`, `coalesce` und `greatest` über `text` und `varchar` melden den Typ des ersten Zweigs in der Reihenfolge von PostgreSQL (bei `CASE` der `ELSE`-Zweig zuerst), ein `CASE` heisst wie seine `ELSE`-Spalte. Dabei behoben: das `AS`, das die Spaltenbenennung (P18.12b) einfügt, verdeckte die Typbestimmung von `CASE`/`coalesce` (sie griff nur bei einem selbst geschriebenen Alias)✅
P18.3d`INSERT` mit weniger Werten: temporäre TabellenERLEDIGT (`PgRunde11dTests.Weniger_Werte_gehen_auch_in_eine_temporaere_Tabelle`): nachgeprüft — der Katalog der Sitzung findet `#name` in `tempdb` (`CatalogManager.TempdbFuer`), die Spaltenliste entsteht dort wie bei jeder Tabelle; die bei P18.3c befürchtete Grenze gibt es nicht✅
P18.4Das erweiterte Protokoll`Parse`/`Bind`/`Describe`/`Execute`/`Sync`/`Flush`/`Close`, benannte und unbenannte Anweisungen und Portale, Parameter `$1…$n` mit Typableitung, **Binärformat** je Spalte und Parameter, `Execute` mit Zeilenlimit und `PortalSuspended`, Pipelining; `COPY … FROM STDIN`/`TO STDOUT` (Text, CSV, Binär)✅
P18.4b`COPY` über die Leitung`COPY … FROM STDIN` und `COPY … TO STDOUT` im Text-, CSV- und Binärformat: `CopyInResponse`/`CopyOutResponse`/`CopyData`/`CopyDone`/`CopyFail`, dazu die Anweisung `COPY` selbst (Optionen `FORMAT`, `DELIMITER`, `NULL`, `HEADER`, `QUOTE`, `ESCAPE`) — Grundlage von `pg_dump`/`pg_restore` und `\copy`✅
P18.4d`COPY`: RestpostenERLEDIGT (Orakel `postgres:17`, `r9e-orakel.txt`, 100 Anweisungen, Werkzeug `copy-orakel.py`: Daten hin, `CopyInResponse`, `CopyData`, Felder `D`/`H`/`W` zurück): **`COPY` über das erweiterte Protokoll** (`Execute` liefert `CopyInResponse` und liest die Daten; `Sync` folgt danach), `FORCE_QUOTE (spalten)`/`*` (nur CSV, nur `TO`), `FORCE_NOT_NULL` und `FORCE_NULL` (nur CSV, nur `FROM`, Spalten oder `*`), `DEFAULT 'text'` (Text und CSV, ein angeführtes Feld bleibt Text), `ON_ERROR ignore` mit der `NOTICE` (`2 rows were skipped due to data type incompatibility`) und `LOG_VERBOSITY verbose` (Meldung je Zeile mit Kontext), `WHERE bedingung` (auch mit Fehler `42703` vor dem ersten Byte), `HEADER match` (`22P04` mit Feldnummer), **die Kontextzeile `W`** (`COPY t, line 2, column id: "abc"`, `COPY t, line 1: "zeile"` bei fehlenden oder zu vielen Spalten, `unterminated CSV quoted field` mit der offenen Zeile), die Fehler zwischen den Optionen wie im Original (`22023`, `42601`, `0A000`), `22003` für eine zu grosse Ganzzahl; Dateien und `PROGRAM` des Servers sind **Absicht** benannt abgelehnt (der Port liest keine Serverdateien)✅
P18.4cParametertypen aus dem Zusammenhang, Fehler bei ParseERLEDIGT (Orakel `postgres:17`, `r10a-orakel.txt`, 85 Anweisungen, Werkzeug `parse-orakel.py`: Parse ohne Typen, Describe, Sync): der Typ eines offenen Parameters aus dem Zusammenhang — Vergleich mit einer Spalte in beide Richtungen (`varchar` vergleicht als `text`), `BETWEEN`, `IN (…)`, `= ANY($1)` (Arraytyp), `$1::typ`, `CAST`, Textfunktionen, `LIMIT`/`OFFSET` (`int8`), `INSERT … VALUES`/`SELECT` und `UPDATE … SET` (der eigene Typ der Spalte, `varchar` bleibt `varchar`), `coalesce`, `CASE WHEN`; `$1 IS NULL` ohne weiteren Hinweis und eine Nummer, die im Text fehlt, sind `42P18`. **Fehler schon bei Parse:** Syntaxfehler (der Parser der Engine liest den übersetzten Text; ein unbekanntes erstes Wort ist `42601`), Tabelle, die es nicht gibt (`42P01`), Spalte der Zieltabelle oder der Auswahlliste einer einfachen Abfrage (`42703`), `missing FROM-clause entry` (`42P01`), Ganzzahlspalte gegen eine Textkonstante (`22P02`, `22003`). Die Form von `INSERT/UPDATE/DELETE … RETURNING` liefert jetzt schon `Describe` (über `SELECT liste FROM ziel WHERE 1 = 0`). Eine Funktion, die es nicht gibt, meldet die Übersetzungsprüfung der Engine (`42883`, `ExecutionEngine.PruefeTextOhneAusfuehren`); die Spaltenüberschrift `$1::int + $2::int` ist `?column?`✅
P18.4eParse-Zeitpunkt: der RestERLEDIGT (Orakel `postgres:17`, `tools/postgres/r12f-folge.py` mit `parse-orakel.py`, `PgRunde12fTests`: 66 Anweisungen, je `Parse` + `Describe` + `Sync`, verglichen werden Code, Meldung, Stelle und Hinweis). Was PostgreSQL bei `Parse` meldet, meldet der Port jetzt auch dort, über den Syntaxbaum (P18.5c, `Pganalyse`): fehlende Spalten in `WHERE`, `ON`, `GROUP BY`, `HAVING`, `ORDER BY`, Ausdrücken, Funktionsargumenten, Unterabfragen, `LATERAL`, CTEs, Mengen, `OVER (…)`, `FILTER`, `DISTINCT ON`, `RETURNING`, `INSERT … VALUES`, `UPDATE … FROM`, `DELETE … USING` (`column "x" does not exist`, `column b.x does not exist`), fehlende Tabellen auch mit Schema, `missing FROM-clause entry`, mehrdeutige Spalten (`column reference`, `ORDER BY "id" is ambiguous`), `USING` ohne Spalte, ein Alias zweimal, `FOR UPDATE OF`, Operatoren und Funktionen über den Typen (`operator does not exist: text > integer`, `operator is not unique: date + unknown`, `function sum(text) does not exist`, `function f(integer) does not exist` auch bei einer Spalte als Argument), `argument of WHERE must be type boolean`, Aggregate (verschachtelt, in `WHERE`, Spalte ohne `GROUP BY` — ein gruppierter Primärschlüssel deckt seine Tabelle), Konstanten, die nicht in ihren Typ passen (`22P02` für integer, bigint, numeric, boolean, auch `LIMIT 'x'`), `ORDER BY 5`, `INSERT` mit mehr Zielspalten als Werten, ein unbekannter Typ mit Stelle, `22008` mit dem Hinweis auf `datestyle`. Ein Julianischer Tag vor dem Jahr 1 gehört zu den Daten vor Christus (wandert mit P18.8h nach J26.2)✅
P18.4fParse-Zeitpunkt: Uhrzeit und VerbundERLEDIGT (`PgParsezeitpunktTests`): eine Textkonstante gegen eine Spalte der Art `time` meldet bei `Parse` `22007` (ohne Ziffer und ohne `now`/`allballs`) bzw. `22008` (`hh:mm[:ss]` mit einem Feld ausserhalb seines Bereichs, `24:00:00` gilt); eine fehlende Spalte in der Auswahlliste eines Verbunds oder einer Komma-Liste über Tabellen (`column b.zz does not exist`, `column "y" does not exist`) und neben Funktionsaufrufen; die Eingaben `J2451187` (Julianischer Tag), `040506` und `allballs` für `time`, eine Konstante mit Typ (`date '…'`, `time '…'`, `timestamp '…'`) heisst wie der Typ, und `timestamp with time zone '…'` (wie `without` und `time without time zone`) ist kein Syntaxfehler mehr✅
P18.5Der PostgreSQL-ParserDie dritte Stellung der Dialekt-Weiche aus M17, auf **denselben** Syntaxbaum: `"Bezeichner"` und Kleinschreibung ungequoteter Namen, `$$…$$`-Zeichenketten, `E'…'`, `::`-Umwandlung, `||`, `ILIKE`, `SIMILAR TO`, `~`/`~*`, `IS [NOT] DISTINCT FROM`, `LIMIT`/`OFFSET`/`FETCH`, `RETURNING`, `DISTINCT ON`, `FILTER (WHERE …)`, `ARRAY[…]`, Array-Operatoren, Zeilenwerte, `LATERAL`, `USING`✅
P18.5bRest der PostgreSQL-Schreibweise: gebautGegen `postgres:17` belegt (`syntax-orakel.txt`, 21 Anweisungen): `JOIN … USING (spalten)` (inner/left; die zusammengefasste Spalte ohne Qualifizierer wird die der ersten Tabelle), `LATERAL` als `CROSS APPLY`/`OUTER APPLY` (`, LATERAL`, `CROSS/INNER JOIN LATERAL`, `LEFT JOIN LATERAL … ON true`), `SIMILAR TO`/`NOT SIMILAR TO` mit Konstante (als `REGEXP_LIKE`), `ORDER BY … NULLS FIRST/LAST`, `RETURNING` mit Ausdrücken, Aliassen und Quellspalten (`RETURNING g * 2 AS z, lower(name)`) samt dem Spaltennamen der Funktion✅
P18.5dRest der PostgreSQL-SchreibweiseERLEDIGT bis auf die Skala von `numeric` (P18.5e; Orakel `postgres:17`, `r8a-`, `r8b-`, `r8c-orakel.txt`): **`DISTINCT ON`** (Parser-Eigenschaft `Dialekt.DistinctOn`: die Ausdrücke werden verdeckte Sortierspalten, nach dem Sortieren bleibt je Wertefolge die erste Zeile, vor `LIMIT`; ohne `ORDER BY` wird nach den Schlüsseln sortiert; Fehler `42P10`, wenn `ORDER BY` nicht mit denselben Spalten beginnt), **Zeilenwerte** (`Dialekt.Zeilenwerte` wie bei MariaDB: `(a, b) = (1, 2)`, `<`, `IN ((…), (…))`, `IS [NOT] DISTINCT FROM`, `IS [NOT] NULL`; Fehler `42601` und `42883` mit PostgreSQLs Wortlaut), **NULL-Reihenfolge** (`Dialekt.NullIstGroesster`: NULL ist beim Sortieren, auch im Fenster, der grösste Wert), **`JOIN … USING` und `NATURAL JOIN` in allen Verbundarten** (`RIGHT`/`FULL`: zusammengefasste Spalte `COALESCE`, bares `*` aus den Spalten des Katalogs in der Reihenfolge von PostgreSQL, Fehler `42703`/`42702`), **Gross-/Kleinschreibung** (`Dialekt.GrossKleinUnterscheiden`: `=`, `LIKE`, Sortierung, Gruppierung lesen die CS-Schwester der Datenbanksortierung; `ILIKE` ist `LIKE` mit `Latin1_General_100_CI_AS`), `(-2.5)::int` rundet. Dabei ein Engine-Fehler behoben: eine Gruppierungsspalte aus Text meldete `nvarchar(4000)` statt des Spaltentyps✅
P18.5e`numeric` ohne Angabe: Skala je WertERLEDIGT (Orakel `postgres:17`, `r8e-orakel.txt`): der Port schreibt `numeric` ohne Angabe als `NUMERIC(38, 10)`, `Dialekt.NumericOhneAngabe` lässt die Stellen des Werts stehen (Speichern, Umwandeln, `SUM` in der Skala des weitesten Summanden, `CASE`/`UNION`/`VALUES`, `GROUP BY` ohne die Nullen hinten)✅
P18.5f`numeric` mit mehr als 38 StellenNach M26 verschoben (**J26.2l**): eine Zahl beliebiger Länge wird ein eigener Typ neben `decimal`, gebaut wie die übrigen Typen, die SQL Server 2025 nicht hat (Speicherform, Vergleich, Rechnung, Binärform von `numeric`, Typmodifizierer über 38 Stellen in der `RowDescription`)✅
P18.5gGrenzen des `numeric`-TypmodifizierersERLEDIGT (`PgDdlTests.Numeric_Typmodifizierer_ausserhalb_der_Grenzen`): Stellen 1 bis 1000, Skala -1000 bis 1000, sonst `22023` mit der Meldung von PostgreSQL (`numerictypmodin`), für `::`, Spalten und `CAST`; vorher nahm der Port jede Angabe an✅
P18.5cPostgreSQL im gemeinsamen ParserEin eigener Syntaxbaum statt Text auf Text. Scheiben siehe Untertabelle P18.5c1–c6: der Leser nach der Grammatik von PostgreSQL mit Fehlerstellen (c1), der Baum über dem Text des Clients (c2), die Namensauflösung (c3) und die Typprüfung (c4) darüber, das Typwissen für die Übersetzung (c5) sind gebaut; offen ist c6, die Übersetzung selbst aus dem Baum statt aus den Token — c6 erledigt 2026-10-04✅
P18.6DDL im PostgreSQL-Stil`CREATE TABLE` mit `SERIAL`/`GENERATED … AS IDENTITY`, `CREATE TYPE … AS ENUM`, `CREATE DOMAIN`, `CREATE SEQUENCE` samt `nextval`/`currval`/`setval`, `CREATE SCHEMA`, `CREATE INDEX [CONCURRENTLY] … USING btree/hash/gin`, Teil- und Ausdrucksindizes, `ALTER TABLE` in allen Formen, `CREATE [MATERIALIZED] VIEW`, `COMMENT ON`, `CREATE EXTENSION` (benannte Auskunft, was vorhanden ist)✅
P18.6bDDL: Rest, gebautGegen `postgres:17` belegt (`ddl2-orakel.txt`, 41 Anweisungen in einer Sitzung): berechnete Spalten (`GENERATED ALWAYS AS (…) STORED` → `AS CAST((…) AS typ) PERSISTED`, Typ und Typmodifizierer bleiben), mehrere Änderungen in einem `ALTER TABLE` (eine Anweisung je Änderung), Sequenzen mit PostgreSQLs Vorgaben (`CREATE SEQUENCE s` beginnt bei 1, absteigend bei −1; die Engine begann am Rand des Typs, `nextval` lieferte −9 223 372 036 854 775 808), `nextval` immer `bigint`, `currval`, `setval(name, n [, ist_gerufen])` (als ganze Anweisung; der nächste Wert ist n + Schrittweite), `CREATE TEMP TABLE` (als `#name`, je Sitzung gemerkt, jede spätere Bezugnahme wird umgeschrieben) samt `DROP TABLE` und `ON COMMIT PRESERVE ROWS`✅
P18.6eDDL: RestERLEDIGT (Orakel `postgres:17`, `ddl3-orakel.txt` und `ddl4-orakel.txt`): `ALTER COLUMN … SET/DROP NOT NULL/DEFAULT` (Typ und Vorgabename aus dem Katalog, Fehler 42P01/42703/23502), `DROP INDEX name` ohne Tabelle (42704, `IF EXISTS`), `COLLATE "C"/"POSIX"` (binär), `currval`/`lastval` je Sitzung (55000) als generische Engine-Eigenschaft. **Neu: `CREATE TYPE … AS ENUM`** (Aliastyp mit Typmarke `pg:<OID>`; die Engine prüft die Beschriftung mit `22P02` und ordnet nach der Reihenfolge der Deklaration, in `ORDER BY`, Vergleichen, `min`/`max`; `ALTER TYPE … ADD VALUE [IF NOT EXISTS] [BEFORE|AFTER]`, `RENAME VALUE`, `DROP TYPE [IF EXISTS] [CASCADE]` mit `2BP01`, `enum_first`/`enum_last`/`enum_range`), **`CREATE DOMAIN`** (Grundtyp, `DEFAULT`, `NOT NULL`, benannte `CHECK`-Bedingungen mit `VALUE`; sie gelten je Spalte, in `x::domäne` und bei `ALTER DOMAIN ADD/DROP CONSTRAINT`, `SET/DROP NOT NULL`; Fehler `23514`/`23502` in PostgreSQLs Wortlaut) und **`CREATE MATERIALIZED VIEW`** (Tabelle plus gemerkte Anfrage, `WITH [NO] DATA`, `REFRESH [CONCURRENTLY]` mit `55000` ohne eindeutigen Index, nur lesbar `42809`, ungefüllt lesen `55000`, `DROP TABLE` unter einer Sicht `2BP01`, `DROP MATERIALIZED VIEW [IF EXISTS]`). Die Beschreibungen liegen in erweiterten Eigenschaften der Datenbank (`pg:enum:…`, `pg:domain:…`, `pg:matview:…`), über das Protokoll geschrieben und nach Neustart geprüft (`PgTypenNeustartTests`); die Engine bekam nur die Typmarke am gelesenen Wert, die Ordnung als Nahtstelle `IFremdtyp.Vergleiche` und einen Änderungszähler an den erweiterten Eigenschaften. Benannte Grenzen: die Ausgabespalte einer Gruppierung und der Ausgang von `unnest(enum_range(…))` melden `text` statt der Aufzählung; Arrays von Aufzählungen als Spaltentyp fehlen; `ALTER DOMAIN … SET DEFAULT` gilt für Spalten, die danach entstehen; `ALTER TYPE/DOMAIN … RENAME TO`, `ALTER MATERIALIZED VIEW`, `\dm` (`pg_class.relkind = 'm'`) fehlen (Rest siehe P18.6f, P18.9d)✅
P18.6fDDL: Rest 2ERLEDIGT (Orakel `postgres:17`, `r8g-orakel.txt`, 70 Anweisungen): `CREATE [TEMP] TABLE [IF NOT EXISTS] … AS SELECT/VALUES/TABLE [WITH [NO] DATA]` mit Spaltenliste und Zeilenzahl im Etikett, `TEMP` in allen Formen (`pg_temp.t`, `GLOBAL`/`LOCAL`, `ON COMMIT DROP`/`DELETE ROWS`/`PRESERVE ROWS`), **Ausdrucksindizes** (auch eindeutige; eine verborgene, gespeicherte berechnete Spalte `pg_ix_<index>_<n>`, dafür `ALTER TABLE … ADD spalte AS ausdruck PERSISTED HIDDEN` in der Engine; schlägt der Index fehl, verschwindet die Spalte wieder), `gin`/`gist`/`brin`/`spgist` mit der Typprüfung von PostgreSQL (`42704` mit dem Typnamen) als Merkzeile in `pg_indexes`, `COLLATE "C"`/`"POSIX"`/`"default"` an Spalten, `pg_collation`, `information_schema.columns` mit den PostgreSQL-Typnamen, `DROP TABLE`/`VIEW` mit Abhängigkeiten (`2BP01` mit Detail und Hinweis, `CASCADE` samt `NOTICE drop cascades to …`), `IF [NOT] EXISTS` mit der `NOTICE`, Listen mehrerer Namen bei `DROP INDEX`/`TABLE`✅
P18.6gDDL: benannte Grenzen von P18.6fERLEDIGT bis auf `gin`/`gist` (Orakel `postgres:17`, `r10e-orakel.txt`, 30 Anweisungen): `ALTER TABLE … DROP COLUMN` entfernt mit der Spalte, was auf ihr liegt (Primärschlüssel, Eindeutigkeiten, Fremdschlüssel, Prüfungen, Vorgaben, Indizes, Ausdrucksindizes samt verborgener Spalte); Operatorklassen (`text_pattern_ops`), `NULLS FIRST/LAST` und `DESC` im Index werden angenommen (beides ändert nur die Nutzung, nicht das Ergebnis) und stehen in `pg_get_indexdef`/`pg_indexes` (`CREATE INDEX i ON public.t USING btree (lower(n))`, Merkzeile `pg:idxdef:` je Index); `pg_index.indkey` führt einen Ausdruck als 0; `pg_get_indexdef(oid, 0, true)` lässt `public.` weg; `information_schema.tables`, `table_constraints`, `key_column_usage`, `referential_constraints`, `schemata`, `views`, `sequences` und die `yes_or_no`-Spalten von `columns` in der Form von PostgreSQL (Spalten, Namen als `name`). **Offen bleibt:** `gin`/`gist` sind Merkzeilen ohne Suchwirkung (eine Suche über `@>`, `&&`, `@@` liest die Tabelle: die Ergebnisse stimmen, die Laufzeit nicht), `pg_get_indexdef(oid, spalte, …)` mit einer Spaltennummer, die übrigen `information_schema`-Sichten (`routines`, `triggers`, `check_constraints`, `domains` …), die Merkzeilen `pg:idxdef:` eines mit `DROP TABLE` entfernten Index räumt erst ein neuer Index gleichen Namens ab (sie zählen nur zusammen mit einem Index)✅
P18.6cEngine: vier Fehler aus dem PortAm Orakel SQL Server 2022 gemessen und in der Engine behoben (`EngineFehlerM18Tests`): `SELECT max(1) AS max` behält den ausdrücklichen Alias (nur der unbenannte Funktionsaufruf erscheint beim Client ohne Namen); zwei `IDENTITY`-Spalten in einer Tabelle sind Fehler 2744 (vorher teilten sie sich still einen Zähler); `OUTPUT inserted.x` trägt Genauigkeit und Skalierung der Zielspalte, auch ohne Ergebniszeile (vorher `int`/18 Stellen); **`NEXT VALUE FOR` je Zeile war in 14 Formen (TDS, Engine, PostgreSQL-Weg, 12 288 Zeilen) nicht reproduzierbar** — jede Zeile bekommt ihren Wert, ein Beleg-Test bleibt✅
P18.6dEngine: NEXT VALUE FOR mit OVER (ORDER BY)ERLEDIGT (Orakel SQL Server 2022, `FolgeMitOrdnungTests`): die Werte gehen in der Reihenfolge der Sortierung an die Zeilen, dieselbe Folge mit derselben Klausel teilt den Wert je Zeile, 11727 und 11739 wie beim Orakel; auch in Ausdrücken und in `INSERT … SELECT`✅
P18.6hDDL: `ADD COLUMN IF NOT EXISTS`ERLEDIGT (`PgDdlTests.Add_column_if_not_exists_ist_bei_vorhandener_Spalte_ein_Hinweis`): fehlt die Spalte, wird sie angelegt; gibt es sie, kommt `NOTICE: column "b" of relation "t" already exists, skipping` und das Etikett `ALTER TABLE` ohne Änderung (die Engine prüft ein `ALTER TABLE` schon beim Übersetzen des Stapels, darum läuft der Zweig als `EXEC`)✅
P18.6iDDL: Rest 3ERLEDIGT (Orakel `postgres:17`, `tools/postgres/r12b-folge.py`, 431 Zeilen, `PgRunde12bTests` gleich): **deklarative Partitionierung in der Engine** (`Dialekt.Partitionen`, `ExecutionEngine.Partitionen.cs`): `PARTITION BY RANGE/LIST/HASH`, `PARTITION OF … FOR VALUES FROM … TO …`, `IN (…)`, `WITH (MODULUS, REMAINDER)`, `DEFAULT`, mehrstufig, `MINVALUE`/`MAXVALUE`; Weiterleitung beim Einfügen (`23514 no partition of relation … found for row` samt Detail), Grenzprüfung beim direkten Schreiben, `UPDATE` lässt eine Zeile wandern, `tableoid`, `ONLY`, `ATTACH`/`DETACH PARTITION`, Überlappung `42P17` mit Stelle, Moduli wie `check_new_partition_bound`, Hash wie `compute_partition_hash_value` (lookup3, am Orakel gleich verteilt), Index auf allen Partitionen, `TRUNCATE`/`DROP` über alle; `pg_class.relkind/relispartition/relpartbound`, `pg_partitioned_table`, `pg_inherits`, `pg_get_partkeydef`, `pg_partition_tree`. `LIKE … INCLUDING …`, weitere `serial`-Spalten mit eigener Folge, `ALTER COLUMN … TYPE … USING`. Dazu **feste Spaltennummern** im Kern (`ColumnDefinition.Nummer`: `column_id`/`attnum` bleiben nach `DROP COLUMN`, wie bei SQL Server und PostgreSQL) und der **Rückschreiber** `Pgdeparser` (`CHECK ((n > (0)::numeric))`, `generation_expression`)✅
P18.7DML im PostgreSQL-StilGegen `postgres:17` belegt (`dml-orakel.txt`, 27 Anweisungen mit Etiketten, Spalten-OIDs und Zeilen): `INSERT … ON CONFLICT (spalten) DO NOTHING` und `DO UPDATE SET … [WHERE …]` samt `EXCLUDED` und `RETURNING` (als `MERGE`, mit richtiger Zeilenzahl), `UPDATE … FROM`, `DELETE … USING` (auch mit Alias und `RETURNING t.x, u.y` — die Engine liest jetzt Spalten der Quellen in `OUTPUT`, auch für T-SQL), `WITH RECURSIVE`, `MATERIALIZED`, `WITH … UPDATE/DELETE` (Etikett vom Verb hinter der CTE-Liste), `SELECT … FOR UPDATE/SHARE/NO KEY UPDATE [OF t]` und `MERGE` (durchgereicht, `MERGE n`). `FOR UPDATE` gilt in der PostgreSQL-Sitzung wie bei MariaDB (D17.7b)✅
P18.7bDML: RestERLEDIGT (Orakel `postgres:17`: `dml2-orakel.txt`, `dml3-orakel.txt`, `sperren-orakel.txt`, `notify-orakel.txt`): `FOR UPDATE|SHARE NOWAIT` (`55P03` mit der Tabelle in der Meldung) und `SKIP LOCKED` gegen eine Zeile, die eine zweite Sitzung sperrt; **schreibende CTEs** (`WITH x AS (INSERT|UPDATE|DELETE … RETURNING …)` mit Haupt-`SELECT`/`INSERT`/`DELETE`, mehreren CTEs und lesenden CTEs davor: temporäre Tabelle + `OUTPUT … INTO`, jede Änderung genau einmal, Aufräumen nach der Anweisung); dabei ein Engine-Fehler behoben (`OUTPUT deleted.* INTO` schob nur eine Spalte, Anweisung still zurückgenommen, `AusgabeIntoSternTests`); **`LISTEN`/`UNLISTEN`/`NOTIFY`/`pg_notify`** (NotificationResponse an alle Zuhörer auch den Sender, Kanäle kleingeschrieben, 7999 Byte, in Transaktion erst beim `COMMIT` dedupliziert, `ROLLBACK` verwirft, in offener Transaktion nichts empfangen; mit Npgsql geprüft); **`ON CONFLICT` ohne Ziel, `ON CONSTRAINT name` (`42704`), ohne Spaltenliste, `DO UPDATE SET (a, b) = (…)` und `= (SELECT …)`**; **`MERGE` nach PostgreSQL 17** (`THEN DO NOTHING`, `WHEN NOT MATCHED BY SOURCE|TARGET`, `RETURNING` mit `merge_action()` und Spalten von Ziel und Quelle). Benannte Grenzen: die Hauptanweisung einer schreibenden CTE sieht die Änderungen der CTE schon (PostgreSQL: Stand davor); `sum(int)`/`avg(int)`-Typen stehen in P18.8c; eine eindeutige Spalte erlaubt in der Engine nur ein `NULL` (PostgreSQL mehrere)✅
P18.7cDML: Fensterrahmen `GROUPS` und `EXCLUDE`ERLEDIGT (Orakel `postgres:17`, `r8f-orakel.txt`, 36 Anweisungen): `GROUPS BETWEEN … AND …` (Gruppen gleicher Sortierwerte), `EXCLUDE` mit `CURRENT ROW`, `GROUP`, `TIES` und `NO OTHERS`, `RANGE` mit Abstand im Sortierwert (auch Dezimalzahlen, absteigend, je Partition, Fehler `42P20` bei mehr als einer Sortierspalte) und die Fehler des Rahmens (`UNBOUNDED FOLLOWING` als Anfang, `CURRENT ROW … PRECEDING` u. a.); `Dialekt.FensterrahmenErweitert`, `ExecutionEngine.Fensterrahmen`. Dazu behoben: ein Aggregat über ein Fenster ohne Rahmen rechnete in der PostgreSQL-Sitzung je Zeile statt je Gruppe gleicher Werte (`sum(v) OVER (ORDER BY v)`: 10, 50, 50), `count(*) OVER (… ROWS …)` scheiterte mit `COUNT_BIG()`, `avg(x) OVER w` lieferte eine Zeile; laufende Aggregate nehmen je Zeile nur die neuen Zeilen auf (nicht n²)✅
P18.8FunktionsbibliothekGegen `postgres:17` belegt (`fkt-orakel.txt`, 98 Ausdrücke, Wert **und** Typ-OID): Zeichenketten (`substr`, `strpos`, `btrim`, `chr`, `split_part`, `format` mit `%s`, `||` mit Zahlen, `upper`/`lower`/`replace`/… liefern `text`), Zahlen (`round`/`trunc` mit Nachkommastellen, `power`, `ln`, `log`, `sign`, `div`, `atan2` …), Zeit (`date_trunc`, `extract` als `numeric` samt `dow`/`doy`/`epoch`, `date_part`, `to_char` mit Datumsmustern, `to_date`/`to_timestamp` mit festen Mustern, `make_date`/`make_timestamp`), typisierte Konstanten (`date '…'`, `timestamp '…'`), `Datum ± Zahl`, `Zeitpunkt ± interval 'n einheit'`, `string_agg … ORDER BY`, `bool_and`/`bool_or`, `stddev`/`variance`. Benannte Abweichung: `numeric`-Ausdrücke melden ihre Genauigkeit als Typmodifizierer statt `-1`✅
P18.8bFunktionsbibliothek: Arrays, `jsonb`, `interval`, WahrheitswerteGegen `postgres:17` belegt (`fkt2-orakel.txt`, 101 Anweisungen, Wert **und** OID): `initcap`, `starts_with`, `IS [NOT] DISTINCT FROM`, `IS [NOT] TRUE|FALSE|UNKNOWN`; **Arrays** als Text mit Typmarke (`ARRAY[…]`, `'{1,2}'::int[]`, `a[i]`, `array_length`/`cardinality`/`array_upper`/`array_lower`, `array_to_string`, `string_to_array`, `array_append`/`prepend`/`cat`/`remove`/`position`, `||`, `@>`, `<@`, `&&`, `= ANY(feld)`, `<> ALL(feld)`, `array_agg(… ORDER BY …)`, `unnest` in `FROM` und allein in der Auswahlliste; OIDs `_int4`, `_text`, `_numeric` …); **`jsonb`** (Spaltentyp, `::jsonb`, Normalform wie PostgreSQL: Schlüssel nach Länge, `": "`/`", "`; `->`, `->>`, `#>`, `#>>`, `@>`, `<@`, `?`, `||`, `jsonb_typeof`, `jsonb_array_length`, `jsonb_build_object`/`_array`, `to_jsonb`; `json` mit OID 114); **`interval`** (`interval '…'`, `interval * n`, `interval ± interval`, `Zeitpunkt − Zeitpunkt`, `age(a, b)`, `make_interval(days => 3)`, `extract(day from interval)`; Ausgabe `IntervalStyle=postgres`). Mechanik: Typmarke `pg:<OID>` im Feld `Xmlsammlung` des Spaltentyps (`Types/Pgwerte.cs`), Umwandlung normalisiert in `SqlConvert`, Funktionen `PG_…` im Ausführer (`ExpressionEvaluator.Postgres.cs`). Zwei Engine-Fehler dabei: eine Skalarfunktion um ein Aggregat unter `GROUP BY` (`ABS(SUM(x))`, `UPPER(STRING_AGG(…))`) meldete „Funktion nicht erkannt“ — behoben✅
P18.8cFunktionsbibliothek: RestERLEDIGT (Orakel `postgres:17`: `r9a-orakel.txt`, `r9b-orakel.txt` mit 150 Mustern): **`to_char` für Zahlen** in allen Mustern (`9`, `0`, `,`/`G`, `.`/`D`, `S`, `MI`, `PL`, `SG`, `PR`, `L`, `TH`/`th`, `V`, `EEEE`, `RN`, `FM`, Text in Anführungszeichen, Überlauf `#`; `Pgzahlenformat`), **`to_date`/`to_timestamp` mit freien Mustern** (`Pgdatumsmuster`: Jahr, Monat/`Mon`/`Month`, Tag, `DDD`, `HH12`/`HH24`, `MI`, `SS`, `MS`, `US`, `AM`/`PM`, `Day`, `J`, `Q` …, Fehler `22007`/`22008`), **`format`** mit `%s`, `%I`, `%L`, `%%`, Nummer `%1$s`, Breite und `-`, **`generate_series`** in der Auswahlliste und über `date`/`timestamp` mit Intervall (Schritt 0: `22023`, `(1, 0)` leer), `regexp_match`/`regexp_matches`/`regexp_replace` (`g`, `i`, Gruppen `\1`, `\&`, Beginn und Vorkommen)/`regexp_split_to_array`/`regexp_split_to_table` mit den POSIX-Klassen, `OVERLAY … PLACING`, `trim(BOTH, LEADING, TRAILING … FROM …)`, `substring(s FROM 'muster')`, `translate` mit kürzerem Ersatz, `octet_length`, `age(x)`, `extract`/`date_part` aus `age()`, `starts_with` in der Auswahlliste; `length`/`char_length` melden `int`. Die Laufzeitfunktionen heissen `PG_TO_CHAR_NUM`, `PG_TO_DATE`, `PG_FORMAT`, `PG_REGEXP_*`, `PG_OVERLAY` … (`Pgtextfunktionen`). Typen der Ausdrücke, `date ± Zahl` über Spalten, `7.0 / 2`, `sum(int)` und `::`-Umwandlungen sind gegen das Orakel gemessen (`PgTypenTests`); `CASE`, `COALESCE`, `GREATEST`, `LEAST`, `NULLIF` und `UNION` über `text`/`varchar`/`char` melden den Typ von PostgreSQL (Runde 8, `r8c-orakel.txt`)✅
P18.8dArrays, `jsonb`, `interval`, Datumsgrenzen: RestNach M26 verschoben: Arrays samt Operatorwahl an Ausdrücken ohne sichtbaren Typ und Binärform nach **J26.2c**, `interval` an Spalten und seine Binärform nach **J26.2b**, `jsonb` samt Binärform nach **J26.2f**, `to_char(zahl, muster)` mit einem Muster, das kein Zeichenkettenliteral ist oder keinen Ziffernplatz hat, nach **J26.2l**✅
P18.8jArrays und `jsonb`: Spalten und OperatorenERLEDIGT (`PgRunde11cTests`, alle 26 Anweisungen der Folge `r11c` samt Fehlerfeldern): `FROM t, unnest(t.a)` und `FROM t, jsonb_each(t.j)` sehen die Spalten der Quellen davor (implizit `LATERAL`, `CROSS APPLY`), `unnest(a, b) AS t(x, y)` in `FROM` läuft im Gleichschritt (fehlende Elemente `NULL`), die `jsonb`-Operatoren `-` (Schlüssel, Stelle, Schlüsselfeld), `#-`, `?|` und `?&` (über Konstanten und `jsonb`-Spalten), `||` über eine Spalte mit Array-Typ (Anhängen statt Textverkettung, der Typ aus dem Katalog der Sitzung), `format('%s', true)` gibt `t`, `array_agg(DISTINCT …)` und `jsonb_agg(DISTINCT …)` (sortiert gesammelt, gleiche Nachbarn fallen weg), `jsonb_object_keys` und `jsonb_array_elements[_text]` in der Auswahlliste, `jsonb_each` über eine Spalte meldet `value` als `jsonb` (Engine: eine Spalte aus `CROSS APPLY` behält Typmarke und Sortierung); mehrdimensionale Arrays: `ARRAY[[1, 2], [3, 4]]` (ungleiche Teilfelder `2202E`), `'{{1,2},{3,4}}'::int[]` (nicht rechteckig `22P02`), Spalten `int[][]`, `a[i][j]` (weniger Stellen als Dimensionen: `NULL`), Scheiben `a[2:3]`, `a[:2]`, `a[3:]`, `m[1:1][1:2]`, `array_dims`, `array_ndims`, `array_length`/`array_upper`/`array_lower` je Dimension; mehrere `unnest` in der Auswahlliste laufen im Gleichschritt (die kürzeren mit `NULL` aufgefüllt, wie PostgreSQL ab 10)✅
P18.8e`interval` mit Feldangabe, `to_char(interval)`, `justify_*`ERLEDIGT (Orakel `postgres:17`, `r10b-orakel.txt`, 22 Anweisungen): `INTERVAL '1-2' YEAR TO MONTH`, `'3' DAY`, `'1 2:30' DAY TO MINUTE`, `HOUR TO SECOND` und die übrigen Paare (auch hinter `::interval` und `CAST`; der Text wird nach den Feldern gelesen), `interval / zahl`, `justify_hours`/`justify_days`/`justify_interval` (wie `interval_justify_*`), `to_char(interval, muster)` (`HH24` ohne Begrenzung auf 24, `HH12`, `MI`, `SS`, `MS`, `US`, `YYYY`, `MM`, `DD`, `DDD`, `AM`, `FM`, Vorzeichen je Feld) für Ausdrücke, deren Art man am Text sieht; die Überschrift der Spalte heisst `interval`✅
P18.8f`infinity` und `-infinity` für `date`, `timestamp`, `timestamptz`ERLEDIGT (Orakel `postgres:17`, `r10c-orakel.txt`, 46 Anweisungen): Eingabe (Text, Konstante, Parameter, `COPY`, Binärform), Ausgabe, Vergleich, `ORDER BY`, `MIN`/`MAX`, `+`/`-` mit Tag und `interval` (bleibt unendlich), `date_trunc`, `to_char` (leerer Text), `isfinite`, `epoch`, Umwandlung zwischen den Datumstypen. Die Engine führt die beiden Werte als `DateTime.MaxValue` und `MinValue` plus ein Tick (`Types/Zeitgrenzen.cs`); nur eine Sitzung des PostgreSQL-Anschlusses kennt sie (Schlüssel `pg_zeitgrenzen` im Sitzungskontext) — für SQL Server bleibt jeder Wert, was er ist✅
P18.8g`timetz` (`time with time zone`)ERLEDIGT (Orakel `postgres:17`, `r10d-orakel.txt`, 13 Anweisungen): der Typ 1266 als Text mit Typmarke (wie `interval`), Eingabe mit und ohne Versatz, Ausgabe (`12:30:00+02`, `12:30:15.5-05:30`), die Ordnung von `timetz_cmp` (erst UTC, dann der grössere östliche Versatz als kleinerer Wert: `12:30+02` ist nicht gleich `10:30+00`), `::time`, `::text`, `timetz ± interval` (nur Stunden, Minuten, Sekunden, umlaufend), `extract(timezone from …)`, `current_time`, Spalten dieses Typs✅
P18.8hDatumsgrenzen und `interval`: der RestNach M26 verschoben: Daten vor Christus und Jahre über 9999 (samt `make_date(-43, …)`, `to_char(…, 'BC')`, unendlichen Zeitpunkten) nach **J26.2m**; `interval` (unendlich, Feldangabe als Typmodifizierer der `RowDescription`, `INTERVAL(p) '…' SECOND`, `to_char` über eine Intervall-Spalte) nach **J26.2b**. Die am Orakel gemessenen Werte stehen weiter in `umsetzung.md`, Abschnitt P18.8h✅
P18.8k`to_char` mit der ÄraERLEDIGT (`PgParsezeitpunktTests.To_char_schreibt_die_Aera`): `BC`, `AD`, `B.C.`, `A.D.` und klein geschrieben; im Bereich der Engine (Jahre 1 bis 9999) immer n. Chr. in der Schreibweise des Musters, wie am Orakel (`to_char(d, 'BC')` ist `AD`)✅
P18.8l`extract(era …)` und `date_part('era', …)`ERLEDIGT (`PgParsezeitpunktTests.Extract_era_meldet_wie_PostgreSQL`): `era` ist keine Einheit von `EXTRACT`; wie am Orakel (für `date`) `22023 unit "era" not recognized for type …` mit dem Typ des Werts, beim Auswerten (`PG_EINHEIT_UNBEKANNT`), statt des `0A000` des Ports✅
P18.8m`timetz '…'` und `timetz + interval`ERLEDIGT (`PgParsezeitpunktTests.Timetz_plus_interval_laesst_Tage_und_Monate_weg`): die Konstante `timetz '…'` bzw. `time with time zone '…'` war ein Syntaxfehler (der Typname blieb vor dem `DATEADD` stehen) und geht jetzt als `'…'::timetz`; die Rechnung lässt Tage und Monate weg wie PostgreSQL (`timetz_pl_interval`)✅
P18.9Katalog und Verwaltungsbefehle`pg_catalog` (`pg_class`, `pg_attribute`, `pg_type`, `pg_namespace`, `pg_index`, `pg_constraint`, `pg_proc`, `pg_database`, `pg_roles`, `pg_settings`, `pg_stat_activity` …) und `information_schema` im PostgreSQL-Zuschnitt — so weit, dass die Metadatenabfragen von psql (`\d`, `\dt`, `\df`), Npgsql, pgAdmin und DBeaver dasselbe liefern wie beim Orakel; `SET`/`SHOW`/`RESET`, `search_path`, `EXPLAIN`, `VACUUM`/`ANALYZE` (benannt ohne Wirkung), `GRANT`/`REVOKE`/`CREATE ROLE` auf dieselben Anmeldungen✅
P18.9a`SET`/`RESET`/`SHOW` und Sitzungsfunktionen: WirkungGebaut (teilweise): das Modell aller 385 Einstellungen aus dem Orakel samt Einheiten, Pruefung und `SHOW ALL`; transaktionales `SET`/`SET LOCAL`; `SET ROLE`/`SESSION AUTHORIZATION`/`NAMES`/`SCHEMA`; Wirkung von `default_transaction_read_only` (25006), `bytea_output`, `extra_float_digits`, `standard_conforming_strings`, `client_min_messages`, `default_transaction_isolation`, `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, `idle_session_timeout`, `transaction_timeout`, Startpaket-Parameter samt `options`, `pg_sleep`, `current_setting` zur Laufzeit (`SESSION_CONTEXT`), `search_path` (Lesen, Anlegen, `current_schema()` als `name`, `42P01`/`3F000`). Alles gegen das Orakel. **Seit M18-Abschluss auch gebaut:** `TimeZone` (Regeln aus den TZif-Dateien des Servers, alle 487 Zonen gegen das Orakel), `DateStyle` (Ausgabe und Eingabe), `IntervalStyle` (Ausgabe und Eingabe), `client_encoding` auf der Leitung (`LATIN1`, `WIN1252`, `EUC_JP`, `SQL_ASCII` …, 22021/22P05) — `PgZeitTests`, `PgZeitzonenTests`, `PgKodierungTests`. **Benannt offen:** Datum vor Christus und `infinity` (die Engine hält `date` wie SQL Server von 0001 bis 9999), `timetz`, `interval … YEAR TO MONTH`; `SET ROLE` mit Rechtewirkung steht bei P18.9d✅
P18.9b`pg_catalog`: Typladen und Grundtabellen**Npgsql öffnet ohne `Server Compatibility Mode=NoTypeLoading`** (der Test-Server setzt die Angabe nicht mehr). Gebaut: `pg_type` (195 Zeilen), `pg_proc` (350), `pg_range`, `pg_am`, `pg_language`, `pg_enum` (leer) aus einem echten `postgres:17` (`tools/postgres/katalog-export.py` → `pg_katalog.json.gz`); lebend über die `sys`-Sichten `pg_namespace`, `pg_class`, `pg_attribute`, `pg_index`, `pg_constraint`, `pg_database`, `pg_roles`, `pg_description`, `pg_tables`, `pg_views`, `pg_sequences`, `pg_indexes`; die Spaltentypen `oid` (26), `name` (19), `"char"` (18) über eine Marke am Spaltentyp; ein unqualifiziertes `pg_xxx` bekommt vom Übersetzer `pg_catalog.`. Gegen das Orakel: `katalog-orakel.txt` (25 Abfragen, RowDescription mit Typ-OIDs und Zeilen). Dabei drei Engine-Fehler behoben, die auch T-SQL trafen: abgeleitete Tabelle über `SELECT a.id FROM t a`, `LEFT JOIN` ohne Treffer mit Alias und einer unqualifizierten rechten Spalte✅
P18.9c`pg_catalog`: psql-Metabefehle`psql` aus `postgres:17` gegen KuonenSQL (Docker, `PgPsqlAbnahmeTests`): `\\dt`, `\\d`, `\\d tabelle`/`sicht` (Spalten, Typen, Vorgaben, Indizes, Fremdschlüssel in beide Richtungen), `\\di`, `\\dv`, `\\ds` liefern **dieselbe Ausgabe wie ein echter `postgres:17`** (`Fixtures/psql-orakel.txt`), `\\dn`, `\\l`, `\\du`, `\\dx` laufen. Gebaut (gegen `katalogfkt-orakel.txt` belegt): `x::regclass`/`regnamespace`/`regtype`/`regproc`/`oid` (Zahl oder Name, `'t'::regclass::text` = Name), `pg_get_userbyid`, `format_type`, `pg_get_indexdef`, `pg_get_constraintdef`, `pg_get_expr`, `pg_table_is_visible` u. a. (immer wahr), `pg_size_pretty`, `array_to_string(ARRAY(SELECT …), s)`, `OPERATOR(pg_catalog.~)`, `COLLATE pg_catalog.default`, `::pg_catalog.text`, `UNION ALL VALUES (…)`; die Tabellen `pg_settings` (aus der Sitzung), `pg_stat_activity` (aus `sys.dm_exec_sessions`), `pg_authid`, `pg_user`, `pg_extension`, `pg_attrdef`, `pg_collation`, dazu Spalten in `pg_class`/`pg_database`/`pg_roles`/`pg_constraint`; eine Anfrage, die eine leere Katalogtabelle (Richtlinien, Veröffentlichungen, erweiterte Statistik, Vererbung …) innen verbindet, wird zu einem leeren Ergebnis gleicher Spaltenzahl. Eine Einschränkung ohne Namen heisst wie in PostgreSQL (`t_pkey`, `t_spalte_key`, `t_spalte_fkey`, `t_spalte_check`) — auch in der Fehlermeldung. Dabei zwei Engine-Fehler behoben: eine korrelierte Unterabfrage, die eine Spalte der äusseren Zeile **ohne Präfix** liest, wurde für alle Zeilen nur einmal gerechnet (auch T-SQL), und die Fehlermeldung bei doppeltem Primärschlüssel nannte immer `PK_<tabelle>`✅
P18.9d`pg_catalog`: Rest**Gebaut** (gegen `katalog3-orakel.txt`, `PgKatalog3Tests`): `CREATE`/`ALTER`/`DROP ROLE`/`USER`/`GROUP` mit allen Optionen, `GRANT`/`REVOKE` (Objekt-, Spalten-, Mitgliedschaftsrechte), **`SET ROLE` mit Rechtewirkung**, `has_*_privilege`, `pg_roles`/`pg_auth_members`, `COMMENT ON` mit Inhalt (`pg_description`, `obj_description`, `col_description`), `pg_type`/`pg_enum`/`pg_proc` für Nutzerobjekte, Seriensequenzen als `pg_class`-Zeilen, `VACUUM`/`ANALYZE`/`REINDEX`/`CLUSTER` (benannt ohne Wirkung). **Runde 9** (Orakel `postgres:17`, `r9d-orakel.txt`): `pg_table_size`/`pg_relation_size`/`pg_indexes_size`/`pg_total_relation_size` (Seiten geschätzt nach den Regeln von PostgreSQL, für kleine Tabellen am Orakel geprüft: leere Tabelle 0, TOAST-Index 8192, B-Baum Metaseite und Blatt), `pg_table_is_visible`/`pg_type_is_visible`/`pg_function_is_visible` nach dem `search_path` (auch für verdeckte Namen in einem späteren Schema; `NULL` für eine unbekannte OID), `pg_depend` für Indizes (auto an den Spalten, intern an der Einschränkung), Einschränkungen (Schlüssel, Fremdschlüssel normal an der Zieltabelle und ihrem Schlüsselindex, Prüfbedingungen) und Sichten (`pg_rewrite` an den Spalten der gelesenen Tabellen), `pg_class` zeigt die Hilfstabellen von `master` nicht mehr✅
P18.9e`pg_catalog`: Rest 2ERLEDIGT (Orakel `postgres:17`). **`EXPLAIN`** (`tools/postgres/r12c-folge.py`, `PgRunde12cTests`, `Pgerklaerung`): der Plan der Engine, aus ihrer echten Ausführung abgelesen (Zugriff über Index oder Durchlauf wie `PlaneZugriff`, Fächer- oder Schleifenverbund wie zur Laufzeit, Aggregat, Fenster, Sortierung, Grenze, Mengenoperation, Unterabfrage als `SubPlan`, CTE, Funktion, `VALUES`, `INSERT`/`UPDATE`/`DELETE`), in der Form von PostgreSQL als Text, `JSON`, `YAML` und `XML`, mit `VERBOSE` (Ausgabespalten), `ANALYZE` (führt aus, echte Zeilen und `Rows Removed by Filter`, Fehler wie bei der Ausführung), `GENERIC_PLAN`, `EXECUTE name`, `CREATE TABLE … AS` und den Fehlern der Optionen. Wo die Engine anders plant als PostgreSQL, steht ihr Plan mit einem von sechs benannten Gründen in `r12c-engine.txt` (keine Bitmap-Suche, keine Indexreihenfolge, Verbund nach Laufzeit, `IN`/`EXISTS` je Zeile, CTE nicht gefaltet, Index auch für weite Bereiche). **Begründete Grenze:** die Kostenzahlen (`cost=…`, `Startup/Total Cost`, `Plan Rows/Width`) und die Speicherangabe eines Hash-Knotens folgen dem Kostenmodell von PostgreSQL und seinen `ANALYZE`-Statistiken, die die Engine nicht führt — sie werden geschätzt und im Vergleich gekürzt. **psql `\d+`** (`tools/postgres/psqlplus-orakel.py`, `PgPsqlPlusAbfragenTests`: die 66 Abfragen von `\d+`, `\dt+`, `\di+`, `\dv+`, `\ds+`, `\df+`, `\dn+`, `\du+`, `\dp`/`\z`, `\dT`/`\dT+`, `\d pg_class`, `\d information_schema.tables` — **Zeilen** gleich): Rechte als Feldtext (`array_to_string`/`array_length` über `relacl`/`attacl`/`nspacl`/`proacl`), `ARRAY(SELECT …)` in `ARRAY(SELECT …)` und hinter `||`, `CASE … END` als Operand von `||`, Name eines `CASE` nach seinem `ELSE`, `pg_partition_ancestors … WITH ORDINALITY`, Sichtbarkeit von Folgen, Indizes, Funktionen und Zeilentypen je Schema, Funktionen in ihrem Schema (`pronamespace`), Indizes und Bedingungen der Katalogtabellen, Typen ausserhalb des Suchpfads mit Schema, `rolvaliduntil` als `timestamptz`, die Rechte von `public`; im Kern: Kommaverbund mit Gleichheit im `WHERE` als Fächerverbund, Korrelation in abgeleiteten Tabellen, Typ von `STRING_AGG` ohne Zeilen und hinter einem Alias. Die Regel einer Sicht hängt in `pg_depend` an den Spalten aus ihrem Syntaxbaum (`PG_SICHTBEZUG`, `PgKatalogbezugTests`). Nicht verglichen: die Grösse auf der Platte (`Size`, die Ablage von KuonenSQL ist eine andere) und die Windows-Anmeldungen der Anlage in `\du+`✅
P18.10Funktionen, Prozeduren, TriggerGegen `postgres:17` belegt (`plpgsql-orakel.txt`, 64 Anweisungen in einer Sitzung, dazu die Meldungen mit Stufe): `CREATE [OR REPLACE] FUNCTION/PROCEDURE` in `LANGUAGE plpgsql` und `sql` (Skalar, `RETURNS TABLE`/`SETOF` als Tabellenfunktion mit `RETURN QUERY`/`RETURN NEXT`, `void`), `DO`, `CALL` (auch benannte Argumente), `DROP FUNCTION/PROCEDURE/TRIGGER` (Listen, `IF EXISTS`, 42883), im Rumpf `DECLARE` (auch `%TYPE`/`%ROWTYPE`, `RECORD`), `:=`, `IF`/`ELSIF`, `CASE`, `LOOP`/`WHILE`/`FOR i IN [REVERSE] a..b [BY s]`/`FOR rec IN SELECT`, `EXIT`/`CONTINUE [WHEN]` (auch mit Marke), `SELECT … INTO [STRICT]`, `FOUND`, `GET DIAGNOSTICS … ROW_COUNT`, `PERFORM`, `RAISE` (Stufen, `%`, `USING ERRCODE/MESSAGE`, Fehlerbedingungen), `ASSERT`, `EXCEPTION WHEN … THEN` als Unter-Transaktion (Sicherungspunkt + `TRY … CATCH`), `SQLSTATE`/`SQLERRM`, `CREATE TRIGGER … EXECUTE FUNCTION` (Zeilen- und Anweisungstrigger, `NEW`/`OLD`/`TG_OP`/`TG_*`/`TG_ARGV`, `WHEN`, `BEFORE` mit Zurückschreiben von `NEW` und `RETURN NULL` beim `INSERT`). Läuft auf demselben Ablauf-Ausführer wie T-SQL (`IF`/`WHILE`/`TRY`/Cursor). Dabei zwei Engine-Fehler behoben: eine Skalarfunktion lieferte `RETURNS nvarchar(max)` als `nvarchar(4000)` (auch T-SQL), und `||` kannte nur Namen und Literale als Operanden, keine Variablen✅
P18.10bPL/pgSQL: Rest 1ERLEDIGT (Orakel `postgres:17`, `r9c-orakel.txt`, 100 Anweisungen mit Fehlerfeldern und den Feldern der Meldungen): `RAISE … USING DETAIL`/`HINT` (Fehler und `NOTICE`/`WARNING`, Felder `D` und `H`; ohne Text `USING MESSAGE`), `GET STACKED DIAGNOSTICS` (`MESSAGE_TEXT`, `PG_EXCEPTION_DETAIL`/`HINT`/`CONTEXT`, `RETURNED_SQLSTATE`), `INSERT`/`UPDATE`/`DELETE … RETURNING … INTO [STRICT]` (über `OUTPUT … INTO` in eine Tabellenvariable, `FOUND`, `ROW_COUNT`), `FOREACH x IN ARRAY` (mit `EXIT`, `CONTINUE`, verschachtelt, `NULL` → `22004`), Cursor (`DECLARE c [[NO] SCROLL] CURSOR [(parameter)] FOR …`, `OPEN c[(argumente)]`, `FETCH [NEXT/PRIOR/FIRST/LAST/ABSOLUTE n/RELATIVE n] [FROM] c INTO …` mit `FOUND`, `CLOSE`, `22004` bei einem nicht geöffneten), `EXECUTE … [INTO [STRICT] ziele] [USING werte]` (`$n` durch das Literal des Arguments, die Zeile über `INSERT … EXEC` in eine Tabellenvariable), `EXIT` aus einem benannten Block, `SQLERRM` bei Division durch null wie PostgreSQL. Dabei ein Engine-Fehler behoben: ein Aufruf einer Skalarfunktion bekam keinen eigenen Variablenbereich, zwei Aufrufe in einer Anweisung legten ihre Tabellenvariable doppelt an (134)✅
P18.10cPL/pgSQL: Rest 2**Erledigt (Orakel `postgres:17`, `r10f-orakel.txt`, 84 Anweisungen):** `OUT`/`INOUT`-Parameter einer Funktion (sie ist eine Tabellenfunktion mit einer Zeile: `SELECT * FROM f(3)`, `SELECT d FROM f(3)`, `RETURN` und das Ende geben die Zeile aus), `OUT`/`INOUT`-Parameter einer Prozedur (`CALL p(1, 2, NULL)` liefert die Ausgaben als eine Zeile; Namen und Typen stehen als Kennung `KPGOUT:` im Rumpf), `RAISE … USING SCHEMA`/`TABLE`/`COLUMN`/`CONSTRAINT`/`DATATYPE` (Felder `s`, `t`, `c`, `n`, `d`), `COMMIT`/`ROLLBACK` in einer Prozedur (der Aufruf `CALL` öffnet die Transaktion und schreibt sie am Ende fest, ein Fehler rollt zurück, in einem offenen Block ist es `2D000`; Kennung `KPGTX` im Rumpf), `VARIADIC` am letzten Parameter einer Funktion (`f(1, 2, 3)` packt die Argumente als Feld; Kennung `KPGVAR:` im Rumpf). der Aufruf einer `OUT`-Funktion als Wert (`SELECT f(3)` liefert bei einer Ausgabe den Wert, bei mehreren einen `record` als `(6,x3)` mit der OID 2249; Kennung `KPGOUTF:` im Rumpf), **überladene Funktionen** (die erste behält den Namen, jede weitere mit anderen Argumenttypen heisst `name__typ` und trägt `KPGBASE:`; der Aufruf wählt nach Zahl und Art der Argumente wie PostgreSQL — eine Zeichenkette ohne Typ bevorzugt `text`, `1` geht auch nach `bigint`/`numeric`, `1.5` nicht nach `integer` —, `42883`/`42725` mit dem Hinweis des Originals, `DROP FUNCTION f(typ)` wählt, `pg_proc` führt jede unter ihrem Namen; die Arten kennt der Übersetzer für Konstanten, Umwandlungen und Spalten, bei einem anderen Ausdruck gilt das Argument als passend zu jeder Art), `UPDATE OF spalten` für Zeilentrigger und Anweisungstrigger mit einem Ereignis (`UPDATE(spalte)` der Engine; eine unbekannte Spalte ist `42703`), `refcursor`-Variablen (`DECLARE c refcursor; OPEN c FOR anfrage; FETCH c INTO …; CLOSE c`, auch mehrfach geöffnet), `EXECUTE` übersetzt den Text beim Ausführen als PostgreSQL (`PG_UEBERSETZE`: `LIMIT`, `::`, `string_agg … ORDER BY`; ein Fehler der Übersetzung kommt mit seinem SQLSTATE, `SELECT nix()` ist `42883`).✅
P18.10dPL/pgSQL: Rest 3 (aus P18.10c)Am Orakel gemessen (`tools/postgres/r11b-folge.py`, 72 Anweisungen, jetzt vollständig in der Fixture, und `r12a-folge.py`, 103 Anweisungen). **Echte `BEFORE`- und Zeilentrigger in der Engine** (`Dialekt.Zeilentrigger`, `ExecutionEngine.Zeilentrigger.cs`; T-SQL unverändert, sein Parser kennt weder `BEFORE` noch `FOR EACH ROW`): ein `BEFORE`-Zeilentrigger läuft im Ausführungspfad vor dem Schreiben jeder Zeile mit veränderbarem `NEW` (die Engine liest es zurück, danach erst `NOT NULL`, Schlüssel, berechnete Spalten), sieht die vorigen Zeilen derselben Anweisung schon geschrieben und die eigene nicht, `RETURN NULL` lässt die Zeile aus (nicht geschrieben, nicht gezählt, kein späterer Trigger sieht sie), `RETURNING` zeigt die Zeile nach dem Trigger; `AFTER`-Zeilentrigger laufen je Zeile nach der Anweisung, alte und neue Zeile über die Reihenfolge gepaart — ein `UPDATE`-Trigger braucht keinen Primärschlüssel mehr; `BEFORE … FOR EACH STATEMENT` läuft vor der ersten Zeile. `pg_trigger` (`tgtype`, `tgenabled`, `tgattr`, `tgargs`, `tgqual`, Übergangstabellen), `pg_get_triggerdef`, `information_schema.triggers` (`action_order` je Ereignis), `ALTER TABLE … ENABLE/DISABLE TRIGGER`. `record`-Funktionen mit `RETURN QUERY EXECUTE` bzw. `EXECUTE … INTO r` (Breite und Typen erst beim Aufruf geprüft, `42804` mit Detail), `EXECUTE … USING` mit typisierten Werten, `refcursor` als Ergebnis und Parameter (Portale: `DECLARE … CURSOR [WITH HOLD]`, `FETCH`/`MOVE` in jeder Richtung, `CLOSE [ALL]`, `<unnamed portal n>`, Ende mit der Transaktion, `pg_cursors`, `34000`/`42P03`), Variable und Spalte gleichen Namens auch in Unterabfragen (`42702`), `NEW.x` einer Spalte, die die Tabelle nicht hat, erst beim Ausführen. Dazu im Kern `sys.dm_exec_describe_first_result_set`✅
P18.10ePL/pgSQL: Rest 3, erster TeilERLEDIGT (`PgRunde11bTests`, die 54 Anweisungen der Folge `r11b` samt Fehlerfeldern, die der Port wie das Orakel beantwortet): `FOR rec IN cursor [(argumente)]` öffnet, liest und schliesst einen gebundenen Cursor, `MOVE [richtung] [FROM] c` bewegt ihn (in Hilfsvariablen je Spalte), `COMMIT`/`ROLLBACK` in einem `DO`-Block (wie bei `CALL` einer solchen Prozedur; im Transaktionsblock `2D000`), `SQLERRM` trägt bei Fehlern der Engine den Wortlaut von PostgreSQL (`duplicate key value violates unique constraint`, `invalid input syntax for type integer`, `null value in column … of relation …`); `(f(3)).spalte` und `(f(3)).*` (die Felder des Ergebnisses einer Funktion mit OUT-Parametern), `f(VARIADIC ARRAY[…])` mit ausdrücklichem Feld, `RETURNS record` und `RETURNS SETOF record` (`RETURN QUERY`, `RETURN NEXT r`, `RETURN r`) mit dem Aufruf `FROM f(x) AS t(a int, b int)` (ohne Spaltenliste `42601` mit Position, andere Breite `42804`), eine Variable und eine Spalte gleichen Namens in einer Abfrage ohne Unterabfrage sind beim Ausführen `42702` mit dem Detail von PostgreSQL, `FOREACH x SLICE n IN ARRAY a` (Teilfelder der letzten n Dimensionen, ausserhalb `2202E`; ohne `SLICE` auch über ein mehrdimensionales Feld jedes Element); `REFERENCING OLD TABLE AS ot NEW TABLE AS nt` (die Übergangstabellen sind `deleted`/`inserted`); mehrere Trigger laufen wie bei PostgreSQL nach dem Namen (BEFORE vor Zeilen- vor Anweisungstriggern, Dialekt-Nahtstelle `OrdneTrigger`) und ein `BEFORE`-Trigger sieht, was ein `BEFORE`-Trigger vor ihm an `NEW` geändert hat; `RETURNING` zeigt die Zeile, wie der `BEFORE`-Trigger sie hinterlässt (über die Schlüssel nach der Anweisung gelesen); ein Anweisungstrigger läuft auch bei einer Anweisung ohne Zeile (Engine: ein AFTER-Trigger läuft wie bei SQL Server je Anweisung, auch mit leerem `inserted`/`deleted`, `EngineFehlerP18Tests`)✅
P18.11Der Nachweis, dass es den SQL-Server nicht bremstKurzmessung ohne Dauerlast wie D17.11 (`PgLastMessungTests`, nur mit `KUONENSQL_MESSUNG=1`): 2000 Punktabfragen `WHERE id = ?` über **eine** Verbindung je Protokoll gegen **eine** gemeinsame Engine, kürzeste von fünf Läufen; TDS allein 198 µs, PostgreSQL allein 219 µs; TDS mit gleichzeitiger PostgreSQL-Last 235 µs (Faktor 1,19), PostgreSQL mit TDS-Last 241 µs (Faktor 1,10) — der Faktor kommt aus geteilten Kernen und einer zweiten aktiven Verbindung, nicht aus einer Sperre der Engine. `pgbench` (17.11 aus Docker) läuft ohne Änderung am Client gegen KuonenSQL: `-i` (100 000 Zeilen) und `-S` mit 1 Verbindung 1220 tps, keine fehlgeschlagene Transaktion. Keine Vergleichszahl gegen echtes PostgreSQL (die Reihen sind protokollübergreifend nicht vergleichbar)✅
P18.11bNachweis: gedrosselte LastreiheERLEDIGT (`PgGedrosselteLastTests`, nur mit `KUONENSQL_MESSUNG=1`): das Skript von `pgbench` (TPC-B: drei `UPDATE`, `SELECT`, `INSERT`, `COMMIT`) über **eine** Verbindung, getaktet auf 50 Transaktionen je Sekunde, zehn Sekunden lang, mit Npgsql und Parametern. Geprüft wird die Richtigkeit (die Summen von Konten, Kassen, Filialen und Buchungen stimmen, 500 Buchungen), gemessen die Verzögerung: 500 Transaktionen in 10,1 s (49,6 je Sekunde), Mittel 2,9 ms, 95. Perzentil 3,6 ms, Höchstwert 84 ms✅
P18.11cNachweis: Lastreihe mit vielen VerbindungenNach M35 verschoben (**LL23.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit✅
P18.12Treiber-AbnahmeDie Clients laufen aus **Docker-Abbildern** (`KUONENSQL_DOCKER_ABNAHME=1`) gegen den Port und liefern **Zeile für Zeile dasselbe wie gegen einen echten `postgres:17`** (dasselbe Skript, am Orakel aufgenommen): `psql` (`\\dt`, `\\d`, `\\d tabelle`, `\\di`, `\\dv`, `\\ds`), **psycopg 3** (Parameter, Typ-OIDs, `executemany`, vorbereitete Anweisungen, Fehler mit SQLSTATE, Transaktionen, Sicherungspunkte, `COPY` ein und aus), **node-postgres** (Parameter, benannte Anweisungen, mehrere Anweisungen je Aufruf, Transaktionen), **JDBC** (`PreparedStatement`, Stapel, `DatabaseMetaData`: Tabellen, Spalten, Primärschlüssel). Dabei gefunden und behoben: `SAVEPOINT`/`ROLLBACK TO`/`RELEASE`, `SET TRANSACTION … READ ONLY`, `x::numeric` behält die Stellen eines Literals, ein Zeitpunkt mit Versatz `+00` (pgjdbc) wurde um die Ortszeit des Servers verschoben, `pg_is_in_recovery()`, `pg_catalog.set_config`, `tableoid`, `_pg_expandarray` (pgjdbc-Schlüsselabfrage), Wahrheitswerte in der Auswahlliste (P18.5d)✅
P18.12b`pg_dump` und `pg_restore`Gegen `postgres:17` belegt: die 74 Abfragen von `pg_dump` 17 (Klartext und `-Fc`) liefern dieselben Spalten wie am Original (`PgDumpAbfragenTests`, Fixture `pgdump-orakel.txt` aus dem Protokoll des Servers, `tools/postgres/dump-orakel.py`), und der Rundlauf mit dem Programm selbst gelingt: Klartext `pg_dump | psql` und `pg_dump -Fc` + `pg_restore --exit-on-error` in eine zweite Datenbank, danach stimmen Zeilen, Sichten, Folgen, Primärschlüssel, Fremdschlüssel und der Fremdschlüssel wirkt (`PgDumpAbnahmeTests`, nur mit `KUONENSQL_DOCKER_ABNAHME=1`). Dafür: 39 weitere Katalogtabellen als echte, leere Tabellen mit ihren Spaltennamen (`pg_depend`, `pg_tablespace`, `pg_trigger` …), fehlende Spalten an den vorhandenen, die festen `tableoid`-Werte aus einem echten Server (64 Tabellen), `pg_sequence`, `acldefault` (NULL), `current_schemas` nach dem `search_path` der Sitzung, `pg_get_viewdef`, `quote_ident`/`quote_literal`, `pg_options_to_table`, `unnest` auch in der Auswahlliste, `PREPARE`/`EXECUTE`/`DEALLOCATE`, `LOCK TABLE`, `ALTER … OWNER TO`/`OWNED BY` (ohne Wirkung), `ALTER TABLE ONLY … ALTER COLUMN … SET DEFAULT`, `public.` vor jedem Namen, die Namen der Ergebnisspalten wie PostgreSQL (`FigureColname`: `set_config`, `case`, `x::int` → `x`/`int4`), `SELECT last_value, is_called FROM folge`. Eine Identitätsspalte (`serial`) erscheint dem Katalog als Serienspalte mit eigener Folge `tabelle_spalte_seq` (Relation, `pg_sequence`, Besitz, Vorgabe `nextval(…)`); nach dem Einspielen ist daraus eine Folge mit `DEFAULT (NEXT VALUE FOR …)` geworden, keine Identität✅
P18.12cTreiber-Abnahme: Npgsql mit EF CoreERLEDIGT (`PgEfCoreTests`, `Npgsql.EntityFrameworkCore.PostgreSQL` 10 mit Npgsql 10 gegen den Port): `EnsureCreated` (`GENERATED BY DEFAULT AS IDENTITY`, eindeutiger Index, `numeric(10,2)`, Fremdschlüssel), Einfügen mehrerer Zeilen mit `RETURNING`, Filter, Sortierung, `Skip`/`Take` (`LIMIT @p OFFSET @p` mit demselben Parameter), `Sum`, `Include`, `GroupBy`, `StartsWith`/`Contains`, Ändern, `ExecuteDelete`/`ExecuteUpdate` (`DELETE FROM t AS b`), Verletzung der Eindeutigkeit (`23505`), Transaktion mit Rollback. Dabei fünf Fehler behoben: `pg_class` zeigte die `spt_*`-Objekte von `master` als Tabellen des Nutzers (EF hielt die Datenbank für angelegt), `Describe` eines Portals vor `Execute` meldete für `INSERT … RETURNING` keine Spalten (`unexpected DataRow`), `LIMIT @p OFFSET @p` band bei `Describe` nicht, `DELETE`/`UPDATE` mit Alias am Ziel ohne weitere Quellen, und `COALESCE(SUM(dec), 0)` machte aus `12.50` die Zahl `12` (Fehler der Engine, auch in T-SQL-Sitzungen; `CoalesceAggregatTests`)✅
P18.12dTreiber-Abnahme: `pg_dump`-RestERLEDIGT (Orakel `postgres:17`, `tools/postgres/dump2-orakel.py`, `PgDump2AbfragenTests`: die 85 Abfragen von `pg_dump` 17 für eine Datenbank mit Sicht über Sicht, SQL- und PL/pgSQL-Funktion, Prozedur, Trigger, echtem `IDENTITY`, Rechten und Kommentaren — **Zeilen** gleich, nicht nur Spalten; nicht verglichen werden nur die Werte der TOAST-Ablage und der Einfriernummern, die KuonenSQL nicht führt, und die Typen der Felder, die J26.2 baut): Sichten in PostgreSQLs Schreibweise (`Pgsichten`/`Pgdeparser`: `pg_get_viewdef`, mit Schema, wenn `public` nicht im Suchpfad liegt), `pg_rewrite` und die Abhängigkeiten (`pg_depend`: Zeilentyp, Namensraum, Sprache, Vorgabe, Trigger, Regel), Zeilentypen in `pg_type`, Funktionen und Prozeduren mit Sprache, Flüchtigkeit, Rumpf und Argumenten (`Pgroutinen`: `pg_get_function_arguments`/`identity_arguments`/`result`), Rechte an Funktionen (`proacl`, `REVOKE … FROM PUBLIC`) und Spalten (`attacl`), `acldefault`, `GENERATED … AS IDENTITY` als Identität (`attidentity`, `pg_depend` `i`, keine Vorgabe), `regclass`/`pg_get_expr`/`pg_get_triggerdef` mit Schema bei leerem Suchpfad, `EXECUTE name` mit dem Etikett der Anfrage. Die Werkzeuge mit Oberfläche liegen bei **LL23.2**✅
P18.13App-Abnahme PostgreSQLFremde Programme über den PostgreSQL-Port, Ablauf und Stand je Programm in `docs/app-abnahme.md`; gemessen an PostgreSQL 17, nur der PG-Dialekt (T-SQL unverändert). **Erledigt (Gitea, n8n, Keycloak, pgjdbc; `PgAppAbnahmeTests`, 10 Fälle über Npgsql):** (1) **Datenbanken wie PostgreSQL:** `postgres` und `template1` sind echte, anfangs leere Datenbanken der Engine, die der Port beim ersten Verbinden anlegt (Clients dürfen dort schreiben; die Vorgabe einer Anmeldung auf `master`/`model`/`msdb`/`tempdb` führt nach `postgres`; T-SQL-Sitzungen sehen die beiden nicht in `sys.databases`/`sysdatabases`, `USE` geht), `template0` ist nur eine Zeile in `pg_database` (`datistemplate`, nicht `datallowconn`; Verbindung `55000`), `master`/`model`/`msdb`/`tempdb` zeigt `pg_database` nie und verbinden lässt sich der Port damit nicht (`3D000`); `datdba` ist der echte Besitzer; `ALLOW_CONNECTIONS` und `CONNECTION LIMIT` (`53300`) gelten beim Verbinden. (2) **`CREATE DATABASE`** liest alle Optionen (`OWNER`, `TEMPLATE`, `ENCODING`, `LC_COLLATE`, `LC_CTYPE`, `LOCALE`, `CONNECTION LIMIT`, `IS_TEMPLATE`, `ALLOW_CONNECTIONS`, `TABLESPACE`, `STRATEGY`, `ICU_*`, `LOCALE_PROVIDER`, `OID` mit `=` oder ohne, `WITH`), prüft wie PostgreSQL (`42P04`, `42704`, `42601` unbekannte und doppelte Option, `22023` Kodierung und Sortierung zur Vorlage, `42501`), kopiert bei `template1` und jeder Vorlage außer `template0` deren Inhalt (`TEMPLATE template0` legt leer an; die Vorlage darf keine andere Sitzung haben, `55006`), und der Besitzer wirkt (Anmeldung als Besitzer der Datenbank, die Engine lässt ihm alle Rechte). `ALTER DATABASE` kennt `OWNER TO`, `RENAME TO`, `[WITH] CONNECTION LIMIT/IS_TEMPLATE/ALLOW_CONNECTIONS`, `SET`/`RESET` (gilt für neue Sitzungen vor den Parametern des Startpakets), `DROP DATABASE [IF EXISTS] [(FORCE)]`. (3) **`pg_indexes`** scheiterte mit `XX000` (LEFT/RIGHT), sobald in der Datenbank eine erweiterte Eigenschaft ohne Trenner stand (`COMMENT ON DATABASE`): die Trennerzeichen-Suche ist jetzt abgesichert. (4) **Besitz:** wer ein `TABLE`/`VIEW`/`SEQUENCE`/`FUNCTION`/`PROCEDURE` anlegt, besitzt es (kein `permission denied for table` mehr auf die eigene Tabelle); der Besitzer der Datenbank darf alles (Gitea mit `pgapp`). (5) **Funktionen in `FROM`** (`SELECT * FROM current_schema()`, `version()`, `now()`, `lower(x) AS a(c)`, `pg_catalog.f()`) als Zeile mit einer Spalte, die wie die Funktion oder der Alias heißt; `json[b]_each[_text]`, `json[b]_array_elements[_text]`, `json[b]_object_keys` über `OPENJSON`; eine unbekannte Funktion meldet `42883`. (6) **Temporäre Tabellen:** Schema `pg_temp_N` und `pg_toast_temp_N` (`N` = `pg_backend_pid()`) in `pg_namespace`, die Tabellen in `pg_class` (`relpersistence` `t`), `pg_attribute`, `pg_tables`, `information_schema.tables` (`LOCAL TEMPORARY`) und `columns`; `pg_my_temp_schema()`, `pg_is_other_temp_schema()`, `pg_temp.t` und `pg_temp_N.t` als Namen, `current_schemas(true)` mit dem Schema vorn, `DROP TABLE` auch beim erweiterten Protokoll; `ON COMMIT DROP/DELETE ROWS/PRESERVE ROWS` laufen; `UNLOGGED` zeigt `relpersistence` `u` (`ALTER TABLE … SET [UN]LOGGED` ändert es). `tempdb` ist nie zu sehen. (7) **Keycloak und pgjdbc:** `pg_get_keywords()` (`word`, `catcode`, `barelabel`, `catdesc`, `baredesc`), `to_regclass`/`to_regproc`/`to_regprocedure`/`to_regtype`/`to_regnamespace`/`to_regrole`/`to_regoper`/`to_regoperator`/`to_regcollation` (NULL statt Fehler), `current_catalog`, `getTables` (mit `~ '^pg_'`), `getColumns`, `getPrimaryKeys` und `getIndexInfo` (`indoption`, `pg_get_indexdef(index, spalte, false)`); ein Fehler schon bei `Parse` macht eine ausdrückliche Transaktion unbrauchbar (`25P02`), wie PostgreSQL. **Nachgezogen (zweiter Durchgang, keine Reste):** `pg_get_keywords` ist die Ausgabe von PostgreSQL 17.11 (Fixture `pg17-keywords.txt`, 491 Zeilen, Zeile für Zeile verglichen); `ALTER DATABASE … RENAME TO` benennt dieselbe Datenbank um (Kennung, Katalog, Daten und Besitzer bleiben; `55006` für die aktuelle und für eine mit anderen Sitzungen, auch bei `postgres`/`template1`/`template0`, die jetzt alle drei echte Datenbanken sind); eine Rolle ohne `LOGIN` darf eine Datenbank besitzen (Merkmal `o` in `pg:db:name`, ihre Mitglieder haben die Rechte des Besitzers); `ALTER TABLE/SEQUENCE/VIEW/MATERIALIZED VIEW/FUNCTION/PROCEDURE/SCHEMA/TYPE/DOMAIN … OWNER TO`, `REASSIGN OWNED BY`, `DROP OWNED BY` (Besitzer als Rolle auch ohne `LOGIN`, Mitglieder erben, `pg_class.relowner`, `pg_tables.tableowner`, `pg_namespace.nspowner` zeigen ihn); `pg_temp_N` bleibt ab der ersten temporären Tabelle bis zum Ende der Sitzung; Indizes und Schlüssel temporärer Tabellen stehen in `pg_class`, `pg_index`, `pg_constraint`, `pg_indexes`, `pg_get_indexdef`, `pg_get_constraintdef` und `information_schema` (`CREATE INDEX` und `DROP INDEX` auf eine temporäre Tabelle gehen); `json_each`, `json_array_elements` und `json_object_keys` liefern `json` (114), die `jsonb`-Fassungen `jsonb` (3802), die `_text`-Fassungen und die Schlüssel `text` — und `jsonb` geht im Binärformat mit dem Versionsbyte (Npgsql las es bisher falsch). **Bleibt (Grenzen der Engine, benannt):** die Läufe mit echten Containern (Gitea, n8n, Keycloak, psql, pgjdbc) stehen aus; Rollen und ihre Mitgliedschaften liegen weiter je Datenbank (`pg:role`), der Besitz einer Datenbank durch eine Rolle gilt für Rechte in dieser Datenbank, nicht für Rechte an anderen; ein Name einer temporären Tabelle, der auf `_` endet, verliert es im Katalog (die Engine füllt mit `_` auf); fremde Sitzungen zeigt der Katalog nicht (`pg_temp_M` anderer Sitzungen); wird die Transaktion zurückgerollt, in der die erste temporäre Tabelle entstand, fällt auch die Marke des Schemas weg; `postgres`/`template1`/`template0` fehlen nur in `sys.databases`/`sysdatabases` der T-SQL-Sitzungen (nicht in `sp_databases`/`DB_ID`); das Umbenennen einer Datenbank steht nicht im Protokoll der Engine (es gibt in T-SQL keine Anweisung dafür)✅
P18.14App-Abnahme PostgreSQL Runde 2Keycloak und n8n brachen beim Start ab, gemessen an `postgres:17` (`PgAppAbnahme2Tests`, Orakelfragen U/A/K). **Keycloak erledigt:** `ROLLBACK TO SAVEPOINT` über das erweiterte Protokoll nahm die ganze Transaktion zurück (die Steuerung las den schon übersetzten Text `ROLLBACK TRANSACTION x`), so verschwand die eben angelegte `USER_SESSION_NOTE`; eine fehlende Relation heisst jetzt wie geschrieben samt Schema (`relation "public.user_session_note"`) mit Stelle, auch bei `Parse`. Die 42P01 auf `databasechangeloglock` und `MIGRATION_MODEL` beim allerersten Start zeigt das Orakel genauso. **n8n `uuid-ossp` erledigt:** `CREATE`/`DROP EXTENSION` je Datenbank (`Pgerweiterungen`, Marke `pg:ext:name`, geht mit der Transaktion zurück, weil erweiterte Eigenschaften jetzt zur Transaktion gehören), `pg_extension` und `pg_available_extensions` aus dem Stand, `uuid_generate_v1`/`v1mc`/`v3`/`v4`/`v5`, `uuid_nil`, `uuid_ns_*` erst nach dem Anlegen (davor 42883), v3/v5 Wert für Wert wie das Orakel, die Funktionen in `pg_proc`; `plpgsql` lässt sich entfernen und wieder anlegen (dazwischen `language "plpgsql" does not exist`); `uuid::text` in Kleinbuchstaben. **n8n Advisory Locks erledigt:** alle elf `pg_advisory_*`/`pg_try_advisory_*` in beiden Formen (`bigint` und `integer, integer`), Sitzungs- und Transaktionsbindung, mehrstufig, geteilt und exklusiv, Warten mit `lock_timeout` (55P03), Abbruch (57014) und Ringerkennung (40P01), Warnung `you don't own a lock`, `pg_locks` (`Pghinweissperren`), offene Parameter als `integer` bzw. `bigint`; Describe (FMTONLY) sperrt nichts. `ROLLBACK TO SAVEPOINT` gibt die danach genommenen Transaktionssperren frei. Die Reste stehen in P18.14b (erledigt)✅
P18.14bApp-Abnahme PostgreSQL Runde 2: RestERLEDIGT (`PgAppAbnahme2bTests`, Orakel L01-L34 und D01-D18): (1) `Describe` schiebt keine Folge mehr weiter (unter FMTONLY lassen `NEXT VALUE FOR` und die serial-Folgen den Zähler stehen); (2) eine Skalarfunktion gibt `NULL` im deklarierten Typ zurück, `Describe` nennt bei PL/pgSQL-Funktionen den Typ, den `Execute` liefert; (3) Warteschlange der Beratungssperren wie `LockAcquire`, Ringerkennung über alle Wartekanten; (4) `pg_locks` mit `virtualxid`, `transactionid` (gleich `txid_current()`) und den Relationssperren der Anweisung und der Transaktion samt Indizes (`Pgsperrsicht`), Katalogarten `xid` und `timestamptz`; (5) `DROP EXTENSION` scheitert mit 2BP01 an Spaltenvorgaben, Prüfungen, Sichten und Routinen mit Rumpf im SQL-Standard (`RETURN …`, `BEGIN ATOMIC`, neu übersetzt), `plpgsql` an PL/pgSQL-Routinen, `CASCADE` nimmt sie mit✅
P18.14cKatalogobjekte in `pg_class`ERLEDIGT (`PgRunde11aTests`, 39 Anweisungen gegen `postgres:17`, `EngineFehlerP18Tests`): `pg_class`, `pg_index`, `pg_tables` (jetzt mit `tablespace`), `pg_views` und `information_schema.tables` führen die 415 Relationen von `pg_catalog`, `information_schema` und `pg_toast` mit den OIDs und Merkmalen von PostgreSQL 17 (`Referenzdaten/pg_katalog.json.gz`, `tools/postgres/katalog-klassen.py`); `regclass`/`to_regclass` lösen sie auf (`pg_locks` = 12073, `information_schema.tables` = 13500) und geben sie mit Namen aus; `pg_locks` zeigt zu einer Katalogtabelle ihre Indizes und zu einer Katalogsicht die am Orakel je Sicht gemessene Menge (143 Sichten); `x::oid` meldet `oid`. Dabei gefunden und behoben: ein blosses `NULL` gab einem Zweig von `UNION`/`EXCEPT` den Typ `int` (der Text der anderen Zweige scheiterte, darum lieferte das `UNION ALL` über die drei Systemsichten nichts); `GROUP BY 1` gruppierte nach der Konstanten (eine Gruppe, still falsch), jetzt nach der Spalte an der Stelle (`42P10` ausserhalb der Liste); `SELECT *` auf `pg_catalog` zeigte die Systemspalte `tableoid`; eine blosse Gruppierungsspalte einer Systemsicht verlor ihren Typ (`"char"` wurde `varchar(4000)`)✅
P18.14dKatalogobjekte: die SpaltenERLEDIGT (Orakel `postgres:17`, `tools/postgres/r11i-folge.py`, `PgRunde11iTests`): `pg_attribute` führt die 3126 Spalten der 415 Relationen von `pg_catalog`, `information_schema` und `pg_toast` (samt Systemspalten) als feste Zeilen aus `pg_katalog.json.gz` (`tools/postgres/katalog-spalten.py`); `\d pg_class` zeigt dieselben 33 Spalten mit Typen und `NOT NULL` wie PostgreSQL✅
P18.14eKatalogobjekte: Namen in der AusgabeERLEDIGT (`PgRunde11aTests`): `'tabelle'::regclass` als Eintrag der Auswahlliste zeigt auch bei einer Nutzertabelle den Namen (Typ `regclass`), in einem Vergleich bleibt es die OID; eine skalare Unterabfrage als Spalte heisst wie die Spalte, die sie liefert (`(SELECT rolname …)` heisst `rolname`, wie `FigureColname`); ein Vergleich hinter einer Umwandlung (`x::oid > 0`) heisst `?column?`✅
P18.15Zeilensicherheit PostgreSQLERLEDIGT (`PgZeilensicherheitTests`, `SicherheitsrichtlinienTests`, Orakel `postgres:17` und SQL Server 2025): der Kern kennt neben den Sicherheitsrichtlinien von SQL Server eine zweite Form, die Zeilenrichtlinien je Tabelle (`Zeilenrichtlinie`, Bedingung als Ausdruck über die Zeile, je Anweisung einmal übersetzt). `ALTER TABLE … ENABLE/DISABLE/FORCE/NO FORCE ROW LEVEL SECURITY`, `CREATE POLICY … AS PERMISSIVE/RESTRICTIVE FOR ALL/SELECT/INSERT/UPDATE/DELETE TO rollen USING (…) WITH CHECK (…)`, `ALTER POLICY` (umbenennen, Rollen, Bedingungen), `DROP POLICY [IF EXISTS]`; freizügige mit ODER, einschränkende mit UND, ohne freizügige nichts; Superuser, `BYPASSRLS` und der Besitzer ohne `FORCE` umgehen sie; `UPDATE`/`DELETE` fragen die `SELECT`-Richtlinien nur, wenn sie Spalten lesen (`DELETE` ohne `WHERE` nicht); eine neue Zeile gegen die Prüfung ist `42501 new row violates row-level security policy`. `pg_policy`, `pg_policies`, `pg_class.relrowsecurity/relforcerowsecurity`; `pg_dump` schreibt Richtlinien und Schalter, PostgreSQL 17 spielt sie unverändert ein. Dazu im Kern: die Blockprädikate `AFTER UPDATE`, `BEFORE UPDATE`, `BEFORE DELETE` von SQL Server wirken (bisher nur `AFTER INSERT`)✅
P18.16Vordefinierte Rollen PostgreSQLERLEDIGT (`PgFestrollenTests`, Orakel `postgres:17`): die 15 vordefinierten Rollen von PostgreSQL 17 stehen mit ihren OIDs in `pg_roles`. `pg_read_all_data` ist das Serverrecht `SELECT ALL USER SECURABLES`, `pg_write_all_data` das globale Datenrecht `INSERT, UPDATE, DELETE` auf `*.*` (dasselbe wie MySQL `GRANT … ON *.*`, jetzt im Kern auch beim Schreiben wirksam, `CatalogManager.HatGlobalesDatenrecht`); `GRANT`/`REVOKE` mit Hinweis und Warnung wie am Orakel, `pg_auth_members` zeigt die Mitglieder, beide geben `USAGE` auf jedes Schema, die Zeilensicherheit bleibt. `row_security = off` (pg_dump) meldet `42501 query would be affected by row-level security policy`. Dabei behoben: der Besitzer einer Tabelle wurde für eine Anmeldung ohne Datenbankbenutzer als `dbo` erkannt; ohne `USAGE` meldet der Port jetzt das Schema vor der Tabelle; `has_*_privilege(…)::text` ist `true`/`false` statt `1`/`0`. GRANT auf die übrigen 13 vordefinierten Rollen, `pg_has_role` und Rechtefunktionen auf Katalogtabellen: P18.16b (erledigt)✅
P18.16bRechtefunktionen und übrige vordefinierte RollenERLEDIGT (`PgFestrollenTests`, `SicherheitsrestTests`, Orakel `postgres:17` und SQL Server 2025): `pg_has_role([rolle,] ziel, recht)` mit `MEMBER`, `USAGE` (nur mit `INHERIT`), `SET`, `… WITH ADMIN OPTION` und Kommalisten, 42704/22023 wie am Orakel; `has_table_privilege` auf Relationen von `pg_catalog`/`information_schema` (lesen darf PUBLIC alles ausser 21 gemessenen, `pg_settings` auch ändern). Die übrigen vordefinierten Rollen sind beim ersten `GRANT` Serverrollen des Kerns (in `pg_roles` mit der OID von PostgreSQL; `pg_monitor`, `pg_read_all_stats`, `pg_stat_scan_tables` mit `VIEW SERVER STATE`, `pg_signal_backend` mit `ALTER ANY CONNECTION`), `pg_monitor` enthält die drei anderen (feste Zeilen in `pg_auth_members`), `pg_database_owner` hat keine Mitglieder (XX000). Dabei im Kern behoben: `SUSER_ID(name)` gab für jeden unbekannten Namen 1 statt NULL und für eigene Anmeldungen und Serverrollen nie ihre Kennung✅
P18.17Fehlende Rechenfunktionen PostgreSQLERLEDIGT 10.10.2026 (`PgRechenfunktionenTests`, Orakel `postgres:17`): `sha256`/`sha384`/`sha512`, `num_nulls`/`num_nonnulls`, `gcd`/`lcm` (im Typ der Argumente, numeric auch mit Nachkommastellen), `factorial` (negativ 22003) fehlten (42883). Gefunden mit einer Reihe von 67 gängigen Funktionen gegen das Orakel; alle übrigen Werte stimmten. Rest: PR27.16g✅
P18.5c1Syntaxfehler mit Stelle`Pgsyntaxort`: ein Leser nach der Grammatik von PostgreSQL (`gram.y`) für `SELECT`/`VALUES`/`TABLE`, `WITH`, `INSERT`, `UPDATE`, `DELETE` und Ausdrücke, über den Zeichen des Clients. Lehnen Übersetzer oder Engine eine Anweisung ab (`42601` oder ohne Klasse), nennt er Stelle und Wortlaut wie PostgreSQL (`syntax error at or near "…"`, `at end of input` bis zum Ende des ganzen Textes, nicht beendete Zeichenketten in Hochkommas und Dollar-Marken); was er nicht kennt, lässt die Meldung, wie sie war. Orakel `r12g-folge.py` (`PgRunde12gTests`)✅
P18.5c2Der Baum`Pgbaum`: Anfragen (Auswahl, Mengen, `VALUES`, CTEs, `INSERT`/`UPDATE`/`DELETE` mit `RETURNING`), Quellen (Tabelle, Unterabfrage, `LATERAL`, Funktion mit `WITH ORDINALITY`, Verbund mit `ON`/`USING`/`NATURAL`), Ausdrücke mit der Rangfolge von PostgreSQL (Operatoren, `IS`, `BETWEEN`, `IN`, `LIKE`, Quantoren, Aufrufe mit `FILTER`/`OVER`/`WITHIN GROUP`, Umwandlungen, `CASE`, getippte Konstanten), jede mit ihrer Stelle; tief verschachtelte Eingaben brechen ab, statt den Stapel zu sprengen✅
P18.5c3Namensauflösung`Pganalyse`: Quellen, Bezüge und Korrelation in der Reihenfolge der Analyse von PostgreSQL, mit dem Katalog der Sitzung (Suchpfad, temporäre Tabellen, Sichten, `pg_catalog`, Systemspalten); im Zweifel (eine Quelle ohne bekannte Spalten) meldet sie nichts. Fehler bei `Parse` und im einfachen Protokoll (siehe P18.4e)✅
P18.5c4Typen und AggregateDie Typen der Ausdrücke über dem Baum (Zahlen mit Rangfolge, Text, Zeiten, Wahrheitswerte, Konstanten als `unknown`), Operatoren und Funktionen, die es für diese Typen nicht gibt, Konstanten, die nicht passen, Aggregate an verbotener Stelle und die Gruppierung✅
P18.5c5Typwissen für die ÜbersetzungWas der Übersetzer Text auf Text nicht sah: `date + time` ist ein `timestamp`, ein Wahrheitswert in einer Verkettung `true`/`false` (Spalte und Konstante), `^` die Potenz, `@`/`|/`/`||/` Betrag und Wurzeln, der Typ einer skalaren Unterabfrage ohne Zeilen, der Name eines `CASE` nach seinem `ELSE` (`PgRunde12gTests`: `pg_typeof` über Spalten, Funktionen und Unterabfragen wie das Orakel)✅
P18.5c6Die Übersetzung aus dem BaumDer Übersetzer (`PgUebersetzer`) erzeugt T-SQL-Text aus Token; Ziel ist ein Schreiber auf dem Baum aus c2 mit den Typen aus c4 (Entscheid c6a), der die Durchgänge ersetzt, die heute Namen, Typen und Stellen erraten — ohne neues Verhalten für den Client (jede Pg-Abnahme bleibt grün). Stand 2026-10-04: c6c14–c6c17 ✅, c6c18a–q (Ausdrücke und Quellen einer Auswahl, OFFSET/FETCH-Ausdrücke, ONLY, Katalognamen, temporäre Tabellen, Suchpfad, Vorgabeschema beim Zerlegen), c6d ✅ (DML), c6e1–e3 (Relationen jeder DDL im Leser, TRUNCATE-Trigger, Anweisungsereignis, CREATE TABLE … AS). In `Durchgaenge` bleiben `Woerter` (die Schreibweise der Ausgabe), `Ddl` und `Umwandlungen` (die Formen, die der Leser noch nicht als Struktur führt) — offen c6e4 (Ddl) und c6f (Rumpf), danach `Woerter`/`Umwandlungen` in die Ausgabe. Leistung `Uebersetze` 24,2 → 14,7 µs (Messreihe der Vorgänger); eigene Messreihe 9,9 → 8,6 µs — Stand 2026-10-04 (Fortsetzung): c6d9 (Orakelbefunde LIMIT/RETURNING), c6e4a–e (DDL aus dem Baum, `Ddl` entfällt), c6f1–f5, c6g1–g3 (`Woerter` in der Ausgabe, `Umwandlungen` und `AlleTabellen` aus `Durchgaenge` entfallen). In `Durchgaenge` bleiben vor dem Schreiber nur `Gruppenstellen` (Baum) und `ZeilenSterne` (Wortsuche nach `(x).*`); offen: ZeilenSterne, der Rumpf als Baum, `AlleTabellen` bei Parse/Parametertypen — Stand 2026-10-04 (Abschluss): c6h1 (Orakelbefunde LIMIT float8, Prüfnamen, ADD COLUMN UNIQUE, nextval), c6h2 (`ZeilenSterne` und `Tabellenaliase` aus dem Baum), c6h3 (`AlleTabellen` entfällt), c6h4 (Rumpf als Baum). In `Durchgaenge` steht vor dem Schreiber keine Wortsuche mehr. Leistung `Uebersetze` 13,3 → 13,0/13,6 µs (gleich, `docs/leistung.md` Abschnitt 7)✅
P18.5c6aBefund: die Engine braucht T-SQL-TextErkundet 2026-10-03: einen Baum der Engine (`SelectStatement` …) direkt zu bauen, geht an drei Stellen vorbei, die den T-SQL-Text brauchen — das Protokoll schreibt je ändernden Stapel dessen Text (`WalRecordType.Stapelfolge`, `BeginneStapelImProtokoll`) und spielt ihn beim Wiederanlauf durch den `PrattParser`; Abfragespeicher und `dm_exec_*` lesen `SqlStatement.Quelltext`; Sichten, Funktionen und Trigger liegen als T-SQL-Text im Katalog. **Entscheid des Auftraggebers (2026-10-03):** die gemeinsame Nahtstelle ist ein Schreiber Baum → T-SQL-Text (c2-Baum mit den Typen aus c4) statt der Token-Durchgänge; der Weg in die Engine bleibt der eine Text, Protokoll, Abfragespeicher und Katalog bleiben unverändert; kein textloser Weg in die Engine✅
P18.5c6bVorbedingung: der Baum trägt, was die Übersetzung brauchtGebaut: `Pgbaum` führt die Art einer Menge samt `ALL` (`Mengen.Verknuepfungen`), Richtung und `NULLS FIRST/LAST` einer Sortierung, das Ende jedes Eintrags der Auswahlliste und von `RETURNING` (`Ziel.Ende`), den Namen hinter `(x).feld` und die Seite von `trim` (`Sonst.Wort`), den Namen von `TABLE t`; `Pgsyntaxort` liest dazu `MERGE` (`Mischen`, mit `RETURNING`), die Anweisungen, die eine Anfrage tragen (`CREATE [MATERIALIZED] VIEW … AS`, `CREATE TABLE … AS`, `DECLARE … CURSOR FOR` als `Huelle`, von der Analyse übergangen), `current_schema()` und `numeric(10, -2)`. Neu ist der Leser über den schon zerlegten Wörtern des Übersetzers (`Pgsyntaxort.Lies(woerter)`, Stelle = Index des Worts): damit gibt es den Baum auch an den 17 Stellen ohne Text (`Rumpf`, `Programm`, `Portale`, `SchreibendeCtes`), `@variable`/`#tabelle` stehen dort als Name. Der Gang durch den Baum (`Kinder`, `AlleZiele`) und `FigureColname` (`Pgbaum.Spaltenname`, mit Stärke wie PostgreSQL) gehören jetzt dem Baum; die Analyse nutzt dieselben statt eigener Kopien✅
P18.5c6b2Der Leser für DDL und die übrigen FormenGebaut: `Pgsyntaxort` liest jetzt `SELECT … INTO [TEMP|UNLOGGED] t` (`Auswahl.Into`), `WINDOW w AS (…)` und jedes Fenster als `Fensterdefinition` (Basis, `PARTITION BY`, Sortierung, Rahmen `ROWS/RANGE/GROUPS` mit Grenzen und `EXCLUDE`), `SEARCH`/`CYCLE` und `[NOT] MATERIALIZED`/`RECURSIVE` einer CTE, `ON CONFLICT` mit Ziel, `ON CONSTRAINT`, `DO NOTHING`/`DO UPDATE SET … WHERE` (`Konflikt`), `OVERRIDING SYSTEM|USER VALUE`, `TABLESAMPLE … REPEATABLE` und `ONLY`, `UPDATE … SET (a, b) = (…)`/`ROW(…)`/`(SELECT …)` mit ihren Werten (`Zeilenzuweisung`), `WHERE CURRENT OF`, die Zweige von `MERGE` mit ihrer Form (`Mischzweig`), benannte Argumente, `VARIADIC`, `WITHIN GROUP`, die Typen einer Spaltenliste (`AS t(a int)`) und den Alias von `USING (…) AS j`. Die Analyse kennt die Spalten von `SEARCH`/`CYCLE`. Folge: auch in diesen Anweisungen heissen die Spalten wie bei PostgreSQL (`PgBaumuebersetzungTests`, vorher `?column?`, `CAST`, `Column_1`). **Entscheid (§5, keine Schicht ohne Nutzer):** der Leser für DDL entsteht mit seinem ersten Nutzer in c6e, nicht vorab✅
P18.5c6cScheibe SELECT aus dem BaumOffen (c6c1–c6c13 gebaut, Rest als eigene Zeilen c6c14–c6c18): der Schreiber (`PgUebersetzer.Schreiber`) schreibt aus dem Baum Operatoren, Mustervergleiche, die Formen mit Schlüsselwörtern, grosse Zahlen, FILTER, NULLS FIRST/LAST, LIMIT/OFFSET/FETCH, IS TRUE/FALSE/UNKNOWN, EXTRACT (nach dem Typ aus c4) und die Uhr der Sitzung; dazu Spaltennamen und die Ziele von SELECT … INTO. 14 Token-Durchgänge sind weg (`Potenzen`, `Rechenoperatoren`, `Ilike`, `Regex`, `AehnlichWie`, `LikeMitRueckstrich`, `ZeitzonenWechsel`, `Schluesselwortformen`, `GrosseZahlen`, `Filter`, `NullenOrdnung`, `Begrenzung`, `WahrheitPruefungen`, `Zeitfunktionen`). **Entscheid 2026-10-03 (§5): keine Anker, der Schreiber wächst von vorn** — je Schritt wandert eine Familie an den Anfang, wo der Baum gilt; was der Schreiber an Namen der Engine schreibt, steht roh oder als Wort, das die späteren Durchgänge nicht mehr umschreiben. Befund (c6c11): die Engine nimmt eine Bedingung auch dort, wo T-SQL einen Wert verlangt. Weiter mit c6c14 — c6c14–c6c18 erledigt (2026-10-04)✅
P18.5c6c1SELECT: die Namen der Ergebnisspalten aus dem BaumGebaut: `Spaltennamen` (P18.12b) sucht die Einträge der Auswahlliste nicht mehr in den Wörtern (`AuswahlGrenzen`, `HatAlias`, `Fallname`, `SpaltenAufruf`, `SpaltenRest` …, rund 300 Zeilen), sondern nimmt sie samt Ende aus dem Baum über den Wörtern und benennt sie mit `Pgbaum.Spaltenname` (FigureColname mit Stärke); das gilt für jede Unteranfrage, CTE, `RETURNING`, `MERGE … RETURNING`, `CREATE VIEW/TABLE … AS` und die Anweisungen im Rumpf von PL/pgSQL. Richtiger als vorher (`PgBaumuebersetzungTests`): `CASE … ELSE true END` heisst `case` (vorher `true`), `(timestamp '…')::timestamptz` heisst `timestamptz` (vorher `timestamp`); ein Vergleich alt gegen neu über alle Pg-Abnahmen fand sonst nur verfälschte Eingaben der Robustheitsprüfung. Leistung (`Uebersetze`, 5 typische Anweisungen, kürzeste von 25 Runden): vorher 15,6 µs je Anweisung, nachher 15,7–17,5 µs, davon 3,5 µs der Baum — der Anteil, den die folgenden Scheiben mitbenutzen✅
P18.5c6c2SELECT: GROUP BY mit Stelle aus dem BaumGebaut: `Gruppenstellen` (P18.14c) nimmt Auswahlliste, Posten und Enden aus dem Baum statt aus einer Suche rückwärts nach `SELECT` und einem geratenen Alias ohne `AS` (`OhneAlias`); 163 → 48 Zeilen. Richtiger als vorher: `SELECT (ARRAY[a])[1] v … GROUP BY 1` (der Alias hinter `]` landete in der Gruppe, Syntaxfehler) und `GROUP BY ROLLUP(1)`/`GROUPING SETS ((1), ())` (die Zahl meint auch dort die Stelle — Orakel `postgres:17`, der alte Weg gruppierte nach der Konstanten). Der Baum wird je Anweisung einmal gelesen und nur neu, wenn `Gruppenstellen` oder `ZeilenSterne` die Wörter umgebaut haben✅
P18.5c6c3SELECT: Anfragen an leere Katalogtabellen aus dem BaumGebaut: `LeereAnfrage` (P18.9c) findet die leere Katalogtabelle in den Quellen des Baums samt Verbundart und benennt die Spalten mit Alias, Spalte oder `Ergebnisname` statt die Wörter hinter `FROM` abzusuchen und `AusgabeName` zu raten; der zweite Aufruf in `Katalogfunktionen` entfällt. Richtiger als vorher: eine leere Tabelle links von `RIGHT JOIN` (und in `FULL JOIN`) leert das Ergebnis nicht mehr. Der Baum führt dafür die Art eines Verbunds (`Verbund.Art`)✅
P18.5c6c4Der Schreiber: Operatoren aus dem BaumGebaut: jeder Ausdruck im Baum kennt seine Spanne (`Anfang`/`Ende`, samt Klammern); der Schreiber (`PgUebersetzer.Schreiber`) geht einmal über die Wörter und schreibt jeden Ausdruck mit Regel aus dem Baum, seine Teilausdrücke wieder durch den Schreiber — erste Familie: `^` (`PG_POWER`), `@`, `|/`, `||/`; die Durchgänge `Potenzen` und `Rechenoperatoren` mit ihrer Suche nach Operanden entfallen, `#` wird erst danach zum `^` von T-SQL (ein `^`, das der Leser nicht gelesen hat, ist 42601, kein stilles Oder). Der Schreiber setzt auch die Namen der Ergebnisspalten (`Spaltennamen` fügt nicht mehr selbst ein) und die Ziele von `SELECT … INTO` in PL/pgSQL (`TOP (1) @x = (…)`, vorher in `Rumpf` als T-SQL vor den Durchgängen, wo der Leser es nicht las). Vorbedingung dafür: der Leser kennt jede übrige Anweisung als `Befehl` mit ihren Ausdrücken (`DEFAULT`, `CHECK (…)`, `GENERATED ALWAYS AS (…)`, Indexelemente, `WHERE`, `USING`, `WHEN (…)`, `FOR VALUES`, `CALL`, `RETURN`, Anfragen hinter `AS`/`(`). Richtiger als vorher (`PgBaumuebersetzungTests`): `-2 ^ 2` ist 4 (vorher -4), `2 ^ -1` ist 0.5 (vorher Syntaxfehler), `|/ 16 + 9` ist 5 und `@ -5 + 2` ist 3 (der vorangestellte Operator bindet schwächer als `+`; vorher 13 und 7). Leistung (`Uebersetze`, 5 typische Anweisungen, kürzeste von 25 Runden): vorher 24,5 µs, nachher 25,2 µs✅
P18.5c6c5Der Schreiber: Mustervergleiche aus dem BaumGebaut: `~`, `~*`, `!~`, `!~*` (`REGEXP_LIKE`), `[NOT] LIKE|ILIKE … [ESCAPE]` und `[NOT] SIMILAR TO` schreibt der Schreiber aus dem Baum (der Leser führt dafür die Art samt `NOT` in `Sonst.Wort`); die Durchgänge `Ilike`, `Regex`, `AehnlichWie` und `LikeMitRueckstrich` entfallen. `ILIKE` setzt die Sortierung auf die ganze linke Seite (ein Operator dort in Klammern), der Rückstrich als Fluchtbuchstabe gilt am Muster aus dem Baum. Die Prüfung einer Domäne kommt erst in der DDL-Übersetzung in die Anweisung und geht darum vorher selbst durch den Schreiber (`SchreibeAusdruck`); `Wahrheitswerte` kennt `REGEXP_LIKE` als Bedingung. Rest: `ILIKE ANY/ALL (…)` hat noch keine Spanne im Baum und fällt in `Woerter` auf `LIKE` zurück wie bisher. Richtiger als vorher (`PgBaumuebersetzungTests`): `'a' || 'b' ~ 'ab'` ist wahr (`||` und `~` binden gleich stark, vorher Fehler), `upper('ab') || 'c' NOT ILIKE 'ABC'` ist falsch (vorher wahr), auch in `CHECK`. Leistung: 24,8 µs (vorher 25,2 µs)✅
P18.5c6c6Der Schreiber: Formen mit Schlüsselwörtern aus dem BaumGebaut: `x AT TIME ZONE z` (`PG_AT_TZ`), `trim([LEADING|TRAILING|BOTH] [zeichen] FROM s)` (`PG_TRIM`) und die Formen `substring(s FROM a FOR n)`, `position(a IN b)`, `overlay(s PLACING r FROM a FOR n)` schreibt der Schreiber aus dem Baum (der Leser führt dafür die Trennwörter, `Sonst.Trenner`); die beiden letzten und `substring` in die Form mit Kommas, denn ob daraus Text- oder Bitfolgenfunktionen werden, entscheidet noch der Typ in `Funktionsformen` und der Bibliothek (`Abbildung` kennt dafür `substring` mit zwei Argumenten samt Muster, `position` und `overlay`). Es entfallen `ZeitzonenWechsel`, `Schluesselwortformen`, die Suche nach `IN`/`FROM`/`FOR` in `Funktionsformen` und in `Bitfolgefunktion`. Richtiger als vorher: `substring('hello' FOR 3)` und `substring(s FOR n FROM a)` (vorher Fehler). Rest: die Wahl nach dem Typ (Bitfolge, Intervall bei `extract`) wartet auf die Typen aus c4 im Schreiber✅
P18.5c6c7Der Schreiber: grosse Zahlkonstanten aus dem BaumGebaut: eine Zahlkonstante mit Exponent oder mit mehr Stellen, als decimal fasst, schreibt der Schreiber an ihrem Wert im Baum als Zahl beliebiger Länge (`CAST(N'…' AS bignumeric)`); der Durchgang `GrosseZahlen` über jede Zahl der Anweisung entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: `PgTypNumericFolgeTests`); eine Zahl ausserhalb eines Ausdrucks (Optionen von DDL) wird nicht mehr umgeschrieben✅
P18.5c6c8Der Schreiber: FILTER aus dem BaumGebaut: `agg([DISTINCT] x, …) FILTER (WHERE b)` schreibt der Schreiber am Aufruf im Baum als `agg([DISTINCT] CASE WHEN b THEN x END, …)` (`count(*)`: `THEN 1`); Sortierung im Aufruf und `OVER` bleiben stehen. Der Durchgang `Filter` entfällt; `array_agg … FILTER` filtert weiter `Sammler` (J26.2c), denn dort gälte ein NULL als Element. Weil die Bedingung damit hinter `CASE WHEN` steht statt hinter `WHERE`, macht `Wahrheitswerte` jetzt auch dort aus einer Spalte allein eine Bedingung (`FallBedingungen`). Richtiger als vorher: `string_agg(x, ',' ORDER BY x) FILTER (WHERE …)` (vorher stand das zweite Argument im CASE, Fehler)✅
P18.5c6c9Der Schreiber: NULLS FIRST/LAST aus dem BaumGebaut: jedes Glied von `ORDER BY` mit `NULLS FIRST|LAST` (in der Anfrage, im Fenster, im Aufruf eines Aggregats) schreibt der Schreiber aus dem Baum (`CASE WHEN x IS NULL …, x [DESC]`; der Leser führt das Ende des Glieds). In der Sortierung einer Anfrage meint eine Zahl die Spalte an dieser Stelle und ein blosser Name zuerst eine Ergebnisspalte — die Prüfung auf NULL nimmt dann deren Ausdruck. Der Durchgang `NullenOrdnung` mit seiner Suche nach `ORDER BY` und den Gliedern entfällt. Der Schreiber kennt dafür Ersetzungen, die kein Ausdruck sind. Richtiger als vorher: `ORDER BY x DESC NULLS FIRST` mit einem Alias (vorher unbekannte Spalte) und `string_agg(… ORDER BY a DESC NULLS LAST)` (vorher Fehler)✅
P18.5c6c10Der Schreiber: LIMIT, OFFSET und FETCH aus dem BaumGebaut: die Klauseln `LIMIT`, `OFFSET` und `FETCH FIRST|NEXT … ROWS ONLY` (in jeder Reihenfolge, der Leser führt ihre Spannen) schreibt der Schreiber aus dem Baum als `OFFSET m ROWS FETCH NEXT n ROWS ONLY` an der Stelle der ersten; ein `ORDER BY (SELECT NULL)` nur, wo die Anfrage im Baum keine Sortierung hat. Der Durchgang `Begrenzung` mit seiner Suche nach Zahl und `ORDER BY` entfällt. `FETCH … WITH TIES` ist 0A000 (vorher 42601). Richtiger als vorher: `LIMIT (2)`, `OFFSET (1)` (vorher Fehler). Rest: ein berechneter Ausdruck dort liest die Engine nicht (sie kennt hinter OFFSET/FETCH nur Zahl und Parameter)✅
P18.5c6c11Der Schreiber: IS TRUE/FALSE/UNKNOWN aus dem BaumGebaut: `x IS [NOT] TRUE|FALSE|UNKNOWN` schreibt der Schreiber aus dem Baum (der Leser führt die Art der Prüfung samt NOT in `Sonst.Wort`); ob `x` in T-SQL eine Bedingung ist, sagt sein Knoten (`Bedingung`: Vergleich, AND/OR/NOT, IS/IN/LIKE/BETWEEN, EXISTS, Quantor, Mustervergleich und die Operatoren, die die Übersetzung mit `… = 1` schreibt), nicht mehr die Suche nach Bedingungswörtern hinter dem Ausdruck. Der Durchgang `WahrheitPruefungen` samt `BedingungsAnfang` entfällt. Richtiger als vorher: `2 BETWEEN 1 AND 3 IS TRUE` (IS bindet schwächer als BETWEEN; vorher prüfte es nur die 3). Befund: die Engine nimmt eine Bedingung auch dort, wo T-SQL einen Wert verlangt — die Unterscheidung zeigt sich darum nur an Vorrang und NOT (Sabotage an `IS NOT` rot, an der Liste der Bedingungen nicht). Rest: `<<`/`>>` und `&&` hängen vom Typ ab (Bitschiebung oder Bereich, Überlappung oder tsquery) und gelten als Wert bzw. Bedingung, bis die Typen aus c4 im Schreiber stehen✅
P18.5c6c12Der Schreiber: Typen aus c4, erste Nutzerin EXTRACTGebaut: der Schreiber fragt die Analyse (`Pganalyse.Typen`, die Typen aus c4 je Ausdruck, mit dem Katalog der Sitzung; erst wenn eine Regel einen Typ braucht, dann einmal je Anweisung; bricht die Analyse ab, gelten die Typen bis dahin). Erste Nutzerin: `EXTRACT(feld FROM x)` aus dem Baum — aus einem interval das Feld, aus timetz die Teile der Uhrzeit, sonst zur Laufzeit; der Zweig in `Funktionsformen` mit seiner Suche nach Intervallwörtern und `pgtyp1266` entfällt. Die Analyse kennt dazu `age`, `make_interval` und `justify_*` als interval. Richtiger als vorher: `extract(hour from d)` über die Spalte einer Unterabfrage oder CTE vom Typ interval (vorher als Zeitpunkt gelesen). Leistung: 24,4 µs (die Analyse läuft nur, wo eine Regel einen Typ braucht)✅
P18.5c6c13Der Schreiber: die Uhr der Sitzung aus dem BaumGebaut: `now()` (und `transaction_timestamp()`, `statement_timestamp()`, `clock_timestamp()`), `current_timestamp`, `current_date`, `current_time` und `localtimestamp` schreibt der Schreiber an ihrem Knoten im Baum in der Zone der Sitzung (`PG_NOW`, `PG_TIMETZ_NOW`), ausser in `CREATE`/`ALTER` (dort übersetzt die DDL die Vorgabe); die Spalte heisst wie das Wort. Der Durchgang `Zeitfunktionen` entfällt. Richtiger als vorher: `current_time(0)`, `localtimestamp(2)` (die Genauigkeit gehört zum Knoten; vorher blieb sie hinter dem Aufruf stehen, Fehler)✅
P18.5c6c14Der Schreiber: Literale und Umwandlungen aus dem BaumGebaut: `x::typ`, `CAST(x AS typ)` und `typ '…'` sind ein Knoten (`Umwandlung`), den der Schreiber mit dem Typ der Engine schreibt (`Typ()`; vorher ging `CAST` unverändert an die Engine). Eine Konstante liest er gleich: eine Zeitkonstante nach DateStyle und TimeZone (`Zeitkonstante`), eine als bytea als Binärkonstante, `interval '…' FELDER` in der Form für die Zeitrechnung; `NULL AS x` ist text. Nach dem Typ aus c4 rundet numeric/real/double in eine ganze Zahl (`ROUND`). Die Durchgänge `Zeitliterale`, `Typkonstanten`, `MehrwortTypen`, `ByteaLiterale`, `Nullspalten`, die Längenkorrektur von `CAST(… AS varchar)` und die toten Zweige über `Argumentart`/`Skalierung` entfallen; `Umwandlungen` bleibt nur für Zeilen, Verbünde und `record` (Zeilen) und Anweisungen ohne Baum, die Registertypen behält `Registerumwandlungen`. Richtiger als vorher (`PgBaumuebersetzungTests`): `CAST(2.7 AS int)` ist 3, `n::int` über die Spalte einer Unterabfrage oder CTE rundet, `CAST(ts AS timestamptz)` gilt in der Zone der Sitzung, `CAST('{1,2}' AS int[])`, `timestamptz '…+00'::date` (vorher las Zeitliterale den Text als date), `point '(1,2)'[0]` ist 42601 für jede getippte Konstante. Frage ans Orakel: `CAST(1 AS bit)` bleibt boolean wie im Mitschnitt (PgErweitertTests), `1::bit` ist bit(1). Leistung: 24,2 → 23,5 µs✅
P18.5c6c15Der Schreiber: Verkettung, Felder, jsonb und Zeitrechnung aus dem BaumGebaut (c6c15a–e, Sammler und `ArrayZuTextVorab` in c6c16d/j): Verkettung, Zeit- und Intervallrechnung, Arrays, Index, `ANY`/`ALL` samt `[I]LIKE ANY`, die Operatoren über jsonb, Bereiche, Volltextsuche, Vektoren und Geometrie, Zeilen und Felder eines Verbunds schreibt der Schreiber am Knoten. Die Reste stehen bei ihrer Scheibe: `Aufloesen` und `JsonZeilenInAuswahl` (Zeilenfunktionen als Quelle) in c6c18, `Feldzuweisungen` in c6d, `<<`/`>>`/`&&` nach dem Typ in c6c17✅
P18.5c6c15aDer Schreiber: Verkettung, Zeit- und Intervallrechnung aus dem BaumGebaut: `a || b` und `+ - * /` schreibt der Schreiber am Operator nach den Typen aus c4 (`Verkettung`, `Rechnung`): über tsvector/tsquery, Bitfolgen und Vektoren `KT_OP`, über Arrays und jsonb `PG_CONCAT` (eine Zeichenkette daneben `PG_UNBEKANNT`), über eine Unterabfrage ohne bekannten Typ zur Laufzeit, sonst Text (Wahrheitswerte als true/false, auch eine Bedingung als Operand); Datum ± ganze Zahl, Datum − Datum, Datum + Uhrzeit, Zeitpunkt − Zeitpunkt, Zeitpunkt/timetz ± `interval '…'` und die Rechnung mit Intervallen. Die Durchgänge `Verkettung`, `Zeitrechnung` (samt `DatumsRechnung`, `IstZeitpunkt`, `CastAls`), `Intervalle` und der `||`-Zweig von `Felder` entfallen; `interval '…' FELDER` schreibt die Umwandlung (c6c14) gleich mit Feldern. Ein Operator heisst als Spalte immer `?column?`. `CREATE INDEX` liest die Schlüssel für `pg_get_indexdef` vor dem Schreiber (`IndexKurzschrift`). Richtiger als vorher (`PgBaumuebersetzungTests`): Datumsrechnung über die Spalten einer Unterabfrage oder CTE, `b || 'x'` mit einem Wahrheitswert aus einer Unterabfrage, `'y' || (1 > 2)`, `INTERVAL '1-2' YEAR TO MONTH * 2`. Leistung: die Analyse aus c4 läuft jetzt für jede Anweisung mit Rechnung oder Verkettung (23,5 → 25,9 µs; offen als c6c15e)✅
P18.5c6c15bDer Schreiber: Arrays, ANY/ALL und die Operatoren über jsonb aus dem BaumGebaut: `ARRAY[…]` (auch geschachtelt, der Leser führt das innere Feld jetzt als Knoten), `a[i]`, `a[i][j]`, Scheiben `a[i:j]` (der Leser führt ihre Form), `name[0]`, `x op ANY|ALL (feld)` und die Operatoren über jsonb, Arrays, Bereiche, Netzadressen, Volltextsuche und Vektoren (`-> ->> #> #>> ? ?| ?& #- @> <@ && @@ <-> <<= >>= &< &> -|- !!`, `jsonb - x`) schreibt der Schreiber am Knoten; über geometrischen Werten (Typ aus c4 oder sichtbar) bleibt der Operator für `FeldOperatoren`. Die Durchgänge `Konstruktoren`, `Teilfelder`, `Vergleiche` und der übrige Teil von `FeldOperatoren` entfallen; `Variadische` schreibt `PG_ARRAY(…)`. Klammern um einen geschriebenen Ausdruck bleiben stehen (`Umschlossen`). Neu: `x [NOT] LIKE|ILIKE ANY|ALL (feld)` je Element wie PostgreSQL (`PG_ARRAY_ANY` mit `~~`, `~~*`, `!~~`, `!~~*`, `Pgwerte.Wie`; vorher wurde ILIKE zu LIKE und ANY blieb stehen). Richtiger als vorher (`PgBaumuebersetzungTests`): der linke Operand von `op ANY (…)` ist der ganze Ausdruck (`2 * 2 = ANY (ARRAY[4])`, `j ->> 'a' = ANY (…)`; vorher nur das Wort davor). Leistung: 25,7 µs✅
P18.5c6c15cDer Schreiber: die Operatoren über geometrischen Werten aus dem BaumGebaut: zweistellige (`~= <<| |>> <^ >^ ?- ?| ?-| ?|| ?# ## &<| |&> = <> < > <= >= << >> &< &> && @> <@ # <->`) und vorangestellte (`@-@ @@ ?- ?| #`) Operatoren über geometrischen Werten schreibt der Schreiber als `KT_OP` (ein Vergleich `… = 1`), wenn die Anweisung einen geometrischen Wert kennt (Umwandlung, Funktion oder Spalte einer ihrer Tabellen) und ein Operand nach dem Typ aus c4 oder sichtbar geometrisch ist. `FeldOperatoren` entfällt ganz, ebenso der Zustand `_geoAnweisung`/`_geoTabelle` und die Frage nach Spalten in `IstGeometrie` (die beantwortet c4). Richtiger als vorher (`PgBaumuebersetzungTests`): `a = b`, `a < c`, `a && c` über Spalten einer Unterabfrage vom Typ box (vorher verglich die Engine den Text)✅
P18.5c6c15dDer Schreiber: Zeilen aus dem BaumGebaut: `ROW(…)` und die implizite Zeile `(a, b)` (`PG_ROW`, ausser in einer Liste von `IN` und den Listen der DDL wie `FOR VALUES IN (…)`), `ROW(…)::record`, Zeilenvergleiche Feld für Feld, `IS [NOT] NULL` und `IS [NOT] DISTINCT FROM` über Zeilen, `(x).feld` (`PG_FELD`, `(t).spalte` eines Tabellenalias als `t.spalte`) und ein Tabellenalias als Wert (`PG_ZEILE`) schreibt der Schreiber am Knoten; eine Zeile gegen einen Wert, dessen Typ c4 kennt, ist 42883 (`record = integer`). `ImpliziteZeilen`, `Zeilenvergleiche`, `Feldzugriffe`, `ZeilenVonTabellen` entfallen; `Zeilen` behält die Felder eines Verbunds in INSERT/UPDATE (c6d) und `row_to_json` (c6c16), `(f(x)).feld` einer Funktion mit OUT-Parametern bleibt für `AusgabeWerte` (c6c16). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde8bTests, PgTypVerbundFolgeTests); dazu erkennt der Schreiber jede Zeile, auch wo die Suche in den Wörtern sie nicht fand (`(a, b) IS NOT DISTINCT FROM (…)`)✅
P18.5c6c15eDer Schreiber: Typen ohne die ganze Analyse, wo der Knoten sie selbst sagtGebaut: `Schreiber.Typ` beantwortet eine Konstante, eine Umwandlung, `now()` und — ohne Katalog in einer Anweisung ohne Unteranfrage, CTE oder abgeleitete Quelle — eine Spalte selbst (dieselben Typen, die die Analyse gäbe); erst was davon nicht reicht, fragt die Analyse der ganzen Anweisung. Seit c6c15a lief sie für jede Anweisung mit Rechnung, Verkettung oder Operator über jsonb. Leistung (`Uebersetze`, 5 typische Anweisungen, kürzeste von 25 Runden): 25,7 → 22,2–23,4 µs (Beginn dieser Runde: 24,2 µs). Mit Katalog (Server) läuft die Analyse weiter, wo eine Spalte ihren Typ braucht✅
P18.5c6c16Der Schreiber: Funktionen aus dem BaumGebaut (c6c16a–m): die Funktionen am Knoten `Aufruf` — Bibliothek, Formen der Engine, Überladungen, VARIADIC, OUT-Parameter, Sammler, Rangfenster, Folgen (nextval/currval/lastval), Sitzungsfunktionen, Reihen, Katalogfunktionen samt Registerumwandlungen und COLLATE, current_schemas, OPERATOR(…), format(…, true). Die Reste stehen bei ihrer Scheibe: `Satzfunktionen`, `FunktionenImFrom`, `Katalognamen` und die Tabelle `pg_settings` (`Einstellungen`) sind Quellen und Namen (c6c18), `Sequenzsetzen` (setval als ganze Anweisung) ist c6d✅
P18.5c6c16aDer Schreiber: die Namen der Funktionsspalten aus dem BaumGebaut: die Namen von Funktionen, Sonderwörtern (`current_user`, `current_role`, `current_catalog`, `current_schema` …), Folgenfunktionen (`nextval`, `currval`, `lastval`; `setval` setzt Sequenzsetzen) und `x AT TIME ZONE z` (`timezone`) gibt `Spaltennamen` aus dem Baum; jeder Operator heisst `?column?`. Die Durchgänge `Funktionsnamen` (samt `Namensalias`, `AblaufWoerter`) und `Rueckgabenamen` entfallen — ein Vergleich über alle Pg-Abnahmen fand nur noch die Namen in skalaren Unteranfragen im Rumpf (ohne Belang). Richtiger als vorher (`PgBaumuebersetzungTests`): `AT TIME ZONE` heisst `timezone` (vorher `pg_at_tz`), `current_catalog` und `current_role` heissen wie das Wort✅
P18.5c6c16bDer Schreiber: die Bibliothek der Funktionsnamen am AufrufGebaut: jeden Aufruf schreibt der Schreiber am Knoten (`Funktion`): FILTER, die Formen der Engine (`count` → `COUNT_BIG`, `length` in Zeichen, über Bitfolgen, Geometrie, Volltextsuche) und `Abbildung` samt `NVARCHAR(MAX)` für Text (`Bibliotheksform`, mit den Tabellen der Anweisung für die Hilfen, die nach Spalten fragen); ebenso `substring`/`position`/`overlay`/`trim` in jeder Form (die Formen mit Schlüsselwörtern zuerst in die mit Kommas, c6c6). Eine Funktion als Quelle in FROM nimmt die Bibliothek in `FunktionenImFrom`. Die Durchgänge `Bibliothek` und `Funktionsformen` und die Methode `Gefiltert` entfallen. Dafür trägt ein Wort jetzt `Fertig` (schon in der Form der Ausgabe, `N'…'`): `Woerter` lässt es stehen, `Konstante`/`RohText` lesen beide Formen — so schreibt der Schreiber Zeichenketten der Bibliothek vor `Woerter`. Richtiger als vorher (`PgBaumuebersetzungTests`): die Bibliothek gilt in jeder Stelle, die der Leser liest, auch in der Prüfung einer Domäne (`length(VALUE)` zählte dort Bytes)✅
P18.5c6c16cDer Schreiber: Funktionen des Nutzers am AufrufGebaut: die Auswahl unter überladenen Funktionen (`Ueberladung`, für ein Argument ohne sichtbare Art mit dem Typ aus c4), `VARIADIC` (`Variadisch`) und der Wert einer Funktion mit OUT-Parametern (`Ausgabewert`, `(f(x)).feld` und `(f(x)).*` über `Ausgabefelder`) schreibt der Schreiber am Aufruf; eine Funktion als Quelle in FROM bekommt dafür eine Spanne (der Leser), ihre Bibliothek bleibt `FunktionenImFrom`. Die Durchgänge `Ueberladungen`, `AusgabeWerte`, `Variadische` (samt `InFrom`) entfallen. Richtiger als vorher (`PgBaumuebersetzungTests`): `f('a' || 'b')`, `f(2 + 3)`, `f(length('xy'))` wählen unter `f(int)`/`f(text)` wie PostgreSQL (vorher 42725 „not unique“)✅
P18.5c6c16dDer Schreiber: Feldfunktionen und Sammler am AufrufGebaut: die Namen der PG_-Funktionen der Engine (`FeldFunktionen`, ausser den Array-Funktionen einer Anfrage an den Katalog), `starts_with`, `age(a, b)`, `make_interval` (`Feldfunktion` in `Bibliotheksform`) und die sammelnden Aggregate `array_agg`, `json[b]_agg`, `json[b]_object_agg` samt `DISTINCT`, `ORDER BY` und `array_agg … FILTER` (`Sammelform`) schreibt der Schreiber am Aufruf, auch mit `pg_catalog.` davor; `pg_options_to_table` als Quelle ebenso. Die Durchgänge `Funktionsnamen2` und `Sammler` entfallen, `Felder` behält `Feldzuweisungen` (c6d) und `Aufloesen` (unnest). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgTypArrayFolgeTests)✅
P18.5c6c16eDer Schreiber: OPERATOR(schema.op) und format(…, true) am KnotenGebaut: die Form `OPERATOR(pg_catalog.=)` (pg_dump, psql) schreibt der Schreiber am Operator als das Zeichen (`Operatortext`, `Operatorwort`, auch in `x OPERATOR(pg_catalog.=) ANY (…)`), ein blosses `true`/`false` als Argument von `format` als `t`/`f` (`Formatwahrheit`). Die Durchgänge `Operatorschreibweise` und `FormatWahrheit` entfallen. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDump2AbfragenTests)✅
P18.5c6c16fDer Schreiber: Rangfunktionen ohne Sortierung am AufrufGebaut: `row_number`, `rank`, `dense_rank`, `ntile`, `percent_rank`, `cume_dist` mit einem Fenster ohne `ORDER BY` bekommen am Aufruf `ORDER BY (SELECT NULL)` (T-SQL verlangt die Sortierung, 4112). Richtiger als vorher (`PgUebersetzerTests`): auch mit `PARTITION BY` (vorher nur `OVER ()`). Der Durchgang `Rangfenster` entfällt✅
P18.5c6c16gDer Schreiber: nextval, currval, lastval am AufrufGebaut: `nextval('s')` und `nextval('s'::regclass)` (als bigint, in einer Vorgabe unverpackt, die Folge einer serial-Spalte über `PG_SERIE_NEXTVAL`), `currval` und `lastval` schreibt der Schreiber am Aufruf (`Folgewert`, `Folgenkonstante`), auch mit `pg_catalog.` davor. Der Durchgang `Folgewerte` entfällt; `setval` als ganze Anweisung bleibt `Sequenzsetzen` (c6d). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDdlTests)✅
P18.5c6c16hDer Schreiber: Sitzungsfunktionen am KnotenGebaut: `current_user`, `session_user`, `user`, `current_role`, `current_schema[()]`, `current_catalog`, `current_database()`, `version()`, `pg_backend_pid()` und `current_setting('x'[, true])` schreibt der Schreiber mit der Sitzung am Knoten (`Sitzungswert`), als Quelle in FROM gleich als Zeile mit einer Spalte (`FROM current_schema() AS s(x)`). Der Durchgang `Sitzungsfunktionen` entfällt (das doppelte `pg_is_in_recovery` bleibt in `Katalogfunktionen`). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: RechtemodellPgTests, PgKatalog3Tests)✅
P18.5c6c16iDer Schreiber: Reihen am AufrufGebaut: `generate_series`, `regexp_matches`, `regexp_split_to_table` als Quelle (die Spalte heisst wie der Alias oder die Funktion, `AS g(i)` benennt sie über die Spaltenliste der abgeleiteten Tabelle um) und als einziges Element einer Auswahl ohne Quelle (`SELECT generate_series(…) [AS a]`, samt Namen; `Spaltennamen` lässt sie aus) schreibt der Schreiber am Aufruf (`Reihenkern`). Der Durchgang `Reihen` entfällt samt seiner Datei. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgBaumuebersetzungTests)✅
P18.5c6c16jDer Schreiber: Katalogfunktionen am AufrufGebaut: die Funktionen über den Katalog (`KatalogAusdruck`: `pg_get_userbyid`, `format_type`, `pg_get_indexdef`, `pg_table_is_visible`, `has_*_privilege`, `to_reg*`, `obj_description`, `array_to_string` über `ARRAY(SELECT …)` und über `x || ARRAY(…)` …) schreibt der Schreiber am Aufruf als letzte Form nach der Bibliothek, `pg_catalog.f(x)` ohne Schema; als Quelle in FROM `FunktionenImFrom`. Die Funktionsschleife in `Katalogfunktionen` und der Durchgang `ArrayZuTextVorab` entfallen; `Katalogfunktionen` behält `Bereinige`, `Registerumwandlungen`, `Einstellungen`. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: 42 Tests, u. a. PgDumpAbfragenTests, PgPsqlPlusAbfragenTests)✅
P18.5c6c16kDer Schreiber: COLLATE am KnotenGebaut: der Leser merkt sich den Namen der Sortierung (`Sonst collate`, `Wort`); der Schreiber lässt `COLLATE pg_catalog.default` weg, schreibt `"C"`/`"POSIX"` als `Latin1_General_BIN2` und lässt jede andere stehen. Die Durchgänge `Sortierungen` und der COLLATE-Zweig von `Bereinige` entfallen (die Spalten in DDL schreibt weiter `Ddl`). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDdl3Tests)✅
P18.5c6c16lDer Schreiber: Registerumwandlungen am KnotenGebaut: `x::regclass`, `::regnamespace`, `::regtype`, `::regproc[edure]`, `::oid` (auch `::pg_catalog.regclass`) schreibt der Schreiber an der Umwandlung (`Registerumwandlung`): eine Konstante als OID oder als Name (`'t'::regclass::text`, ganzer Eintrag der Auswahlliste), feste OIDs der Katalogtabellen, `'english'::regconfig::oid`, `nextval|currval|setval('f'::regclass)` als Text. `Katalognamen` erkennt die fremde Klasse von `pg_depend` an der festen OID. Die Durchgänge `Registerumwandlungen`, `Bereinige` und die Hülle `Katalogfunktionen` entfallen (es bleibt `Einstellungen`), `Textkonstante` ist `Literal`. Richtiger als vorher: `f(a, 't'::regclass, b)` ist die OID (vorher als Name gelesen, weil ein Komma davor stand). Rot über die Sabotage gezeigt: PgRunde11aTests, PgKatalogFunktionenTests✅
P18.5c6c16mDer Schreiber: current_schemas am AufrufGebaut: `current_schemas(true|false)` schreibt der Schreiber am Aufruf mit dem search_path der Sitzung (`Suchschemas`); `Einstellungen` behält nur die Tabelle `pg_settings` (eine Quelle, c6c18). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: RechtemodellPgRestTests, PgAppAbnahmeTests)✅
P18.5c6c17aDer Schreiber: Wahrheitswerte am KnotenGebaut: die Spalte als Bedingung (`Spaltenbedingung`: hinter `WHERE`, `HAVING`, `ON CONFLICT … WHERE`, in `CASE WHEN` und `FILTER`, als Operand von `AND`/`OR`/`NOT`, auch `true`/`false`) und der Eintrag der Auswahlliste, der geschrieben eine Bedingung ist, als `CASE WHEN … THEN 1 WHEN NOT … THEN 0 END` (`Wahrheitswert`, eine Hülle mit Vorrang vor der Regel des Knotens) schreibt der Schreiber; `EXCLUDE … WHERE` geht über ihn. Die Durchgänge `Wahrheitswerte` und `FallBedingungen` samt `NurSpalten` entfallen. Richtiger als vorher (`PgWahrheitTests`): eine Spalte in Klammern (`WHERE (aktiv) OR NOT (gesperrt)`). Rot über die Sabotage gezeigt: PgWahrheitTests✅
P18.5c6c17bDer Schreiber: Typen der Arme einer MengeGebaut: die Zeichentypen der Arme von `UNION`/`INTERSECT`/`EXCEPT` (`Mengentypen`, P18.8c) bestimmt der Schreiber aus dem Baum (die Arme ohne Klammer, je Arm die Tabellen seiner Quellen, `Zweigart` am Knoten, `Spaltenzweig` geteilt) und schreibt sie als Hülle um den Eintrag; die Hülle (`Huelle`) ist allgemein (auch die Bedingung als Wert). Der Teil `Mengentypen` von `Ausdruckstypen` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde8cTests)✅
P18.5c6c17cDer Schreiber: Zeichentypen von CASE, COALESCE, GREATEST, LEAST, NULLIFGebaut: den Typ eines Eintrags `CASE`/`COALESCE`/`GREATEST`/`LEAST` über Zeichenausdrücke und `NULLIF(varchar, 'x')` (`Textergebnis`, P18.8c) bestimmt der Schreiber am Knoten mit den Tabellen der Quellen der Auswahl und schreibt ihn als Hülle (char(n)-Zweige mit `RTRIM`); er hat Vorrang vor der Bedingung als Wert. Der Teil `Textergebnis` von `Ausdruckstypen` samt der Wort-Fassung von `Zweigart` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde8aTests, PgRunde8cTests, PgRunde11dTests)✅
P18.5c6c17dDer Schreiber: Typen der AggregateGebaut: `sum`, `avg`, `min`, `max` nach der Art ihres Arguments (`Aggregattypen`, P18.3c: sum(int) bigint, sum(bigint) numeric, avg als `PG_NUMERIC_DIV` samt Fenster, avg(real) double, min/max(varchar) text, min/max über macaddr und Bereiche 42883) schreibt der Schreiber am Aufruf, mit `Argumentart` am Knoten und den Tabellen der Quellen der Auswahl; zwei Hüllen um denselben Knoten schachteln sich (`_aktiv`, vorher lief das in einen Stapelüberlauf). Der Teil `Aggregattypen` von `Ausdruckstypen` entfällt, `Spaltenart` ist geteilt. Richtiger als vorher (`PgBaumuebersetzungTests`): `sum(int)` in einer Unterabfrage der Auswahlliste ist bigint (vorher zweimal umgewandelt, numeric). Rot über die Sabotage gezeigt: PgRunde12gTests, PgTypenTests, PgRunde8fTests✅
P18.5c6c17eDer Schreiber: Typen der KonstantenGebaut: die Konstanten eines Eintrags der Auswahlliste (Text als `NVARCHAR(MAX)`, eine ganze Zahl über int als bigint/numeric, auch mit Vorzeichen), die Ergebnisse jedes `CASE` darin und die Argumente von `COALESCE`/`NULLIF`/`GREATEST`/`LEAST`/`min`/`max` (`Konstantentypen`) schreibt der Schreiber am Knoten; ein Eintrag, der schon als Ganzes einen Typ bekommt (Arm einer Menge, Zeichentyp, min/max über varchar), behält seine Konstanten (`_typisiert`). `(VALUES …)` als Quelle bekommt seine Typen je Spalte über eine Hülle über der Spanne (`Huelle` mit Spanne, `WerteZeilen` über den geschriebenen Zeilen), die `VALUES` von `ON CONFLICT` in `Upsert`. Die Teile `Element` und `WerteZeilen` von `Ausdruckstypen` entfallen. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: 8 Tests, u. a. PgTypenTests, PgDump2AbfragenTests)✅
P18.5c6c17fSpaltenarten aus dem BaumGebaut: je Spalte der Hauptauswahl Ausdruck oder Spalte (`Spaltenarten`, für den Typmodifizierer von numeric in der RowDescription) kommt aus dem Baum vor dem Schreiber (die Auswahl, der erste Arm ohne Klammer, die Auswahl von INSERT … SELECT; `CAST(… AS numeric(p, s))` als ganzer Eintrag zählt als Spalte), eine leere Katalogabfrage hat nur Ausdrücke; `Durchgaenge` setzt sie am Ende. Die Suche nach dem ersten SELECT in den Wörtern entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde10bTests, PgTypenTests, PgRunde9aTests, PgRunde12gTests, PgTypIntervallFolgeTests; die Gegenrichtung wirkt nur bei DECIMAL-Spalten der Engine, die der Weg über PostgreSQL nicht anlegt)✅
P18.5c6c17gDer Schreiber: Rechnungen der Auswahlliste, Fehler über Aufzählungen und jsonGebaut: real neben int/numeric/double als FLOAT, die Division mit numeric als `PG_NUMERIC_DIV` (`Zahlenausdruecke`, `Teilung`), die Rechnung mit einer Aufzählung (42883, J26.2d, oberste Ebene des Eintrags) und der Vergleich mit json (42883, J26.2o, im Eintrag und in ON/WHERE/GROUP BY/HAVING/ORDER BY) schreibt bzw. meldet der Schreiber am Knoten; eine Zahl mit Exponent schreibt er schon selbst. Der Durchgang `Ausdruckstypen` entfällt ganz (es bleiben die Arten der Spalten und Werte, die der Schreiber fragt, und `Listenelemente` für `TabelleAus`, c6e). Ein Knoten ohne Spanne bricht die Prüfung nicht mehr ab. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgTypenTests, PgTypVektorJsonUuidFolgeTests). Leistung `Uebersetze` 17,4 µs✅
P18.5c6c17Der Schreiber: Bedingung als Wert und Typen der Auswahlliste aus dem BaumGebaut (c6c17a–g): `Wahrheitswerte` (die Spalte als Bedingung, die Bedingung als Wert) und `Ausdruckstypen` (Mengen, Zeichentypen, Aggregate, Konstanten, VALUES, Rechnungen, Spaltenarten, Fehler über Aufzählungen und json) schreibt der Schreiber am Knoten mit den Tabellen der Quellen; beide Durchgänge entfallen. `<<`/`>>`/`&&` nach dem Typ trägt schon der Operator mit dem Typ aus c4 (c6c15), auch unter `IS TRUE|FALSE` (Test in `PgBaumuebersetzungTests`)✅
P18.5c6c18aDer Schreiber: die SperrklauselGebaut: der Leser führt je `FOR UPDATE|NO KEY UPDATE|SHARE|KEY SHARE [OF …]` die Spanne, ob sie teilt und ob `NOWAIT` folgt (`Abfrage.Sperren`, auch `NOWAIT SKIP LOCKED`, das der Schreiber wie PostgreSQL mit 42601 meldet); der Schreiber schreibt sie in der Anweisung als `FOR UPDATE`/`FOR SHARE` und nennt bei `NOWAIT` die erste Tabelle der Quellen für die Meldung. Der Durchgang `Sperrlesen` entfällt. Richtiger als vorher: auch hinter `WITH … SELECT` (vorher nur, wenn die Anweisung mit SELECT begann). Rot über die Sabotage gezeigt: PgSperrenTests✅
P18.5c6c18bDer Schreiber: WITH RECURSIVE und MATERIALIZEDGebaut: der Leser führt die Wörter `RECURSIVE` und `[NOT] MATERIALIZED` einer CTE als Spannen (`Cte.Fremdwoerter`), der Schreiber lässt sie weg (in jeder Anfrage mit CTEs, auch einer inneren) und meldet eine schreibende CTE, die `SchreibendeCtes` nicht übersetzt hat, mit 0A000. Der Durchgang `Cte` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDmlTests, PgBaumuebersetzungTests, PgDumpAbfragenTests, PgDump2AbfragenTests)✅
P18.5c6c18cDer Schreiber: LATERALGebaut: der Leser führt den Anfang jeder Quelle (`Quelle.Anfang`, auch das Wort `LATERAL`), das erste Wort eines Verbunds und die Stelle von `ON` (`Verbund.Verbindung`, `OnStelle`); der Schreiber schreibt `, LATERAL (…)` und `[INNER|CROSS] JOIN LATERAL (…)` als `CROSS APPLY`, `LEFT JOIN LATERAL (…) ON true` als `OUTER APPLY` und meldet die übrigen Formen wie bisher (0A000, 42601 ohne Alias). Der Durchgang `Seitlich` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgSchreibweiseTests)✅
P18.5c6c18dDer Schreiber: VALUES in einer MengeGebaut: `… UNION|EXCEPT [ALL] VALUES (…), (…)` schreibt der Schreiber je Zeile als `SELECT …`, verbunden mit derselben Verknüpfung (`Mengenwerte`), die Konstanten der Zeilen typisiert wie eine Auswahlliste, und die Zeilen zählen in `Mengentypen` als Arme. Der Durchgang `UnionMitWerten` entfällt. Richtiger als vorher (`PgBaumuebersetzungTests`): `UNION VALUES` bleibt ohne Doppelte (vorher wurde jede Zeile `UNION ALL SELECT`, das Ergebnis hatte Doppelte), `EXCEPT VALUES` geht (vorher Fehler). Rot über die Sabotage gezeigt: PgBaumuebersetzungTests✅
P18.5c6c18eDer Schreiber: USING und NATURALGebaut: der Leser führt das Ende jeder Quelle und die Spanne von `USING (…) [AS x]`; der Schreiber schreibt je Kette die Bedingung `ON links = rechts AND …` (statt USING, hinter der rechten Quelle bei NATURAL), die zusammengefasste Spalte je Verbundart (links, rechts, `COALESCE` bei FULL) für jeden unqualifizierten Namen der Anweisung, ein bares `*` als Spaltenfolge von PostgreSQL und die Fehler 42703/42702/0A000 wie bisher (`Verbundspalten`, `Kette`; temporäre Tabellen als `#name`). Der Durchgang `VerbundMitUsing` entfällt samt seiner Kettenleser. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde8aTests, PgRunde8bTests)✅
P18.5c6c18fAbgelehntes im SchreiberGebaut: ein `ARRAY[`/`ARRAY(`, das der Schreiber nicht geschrieben hat, meldet er in der Schleife, die er am Ende ohnehin über die Wörter macht (dort steht schon `^`/`#`), mit 0A000. Der Durchgang `Abgelehntes` und der tote Helfer `Verbund` entfallen. Rot über die Sabotage gezeigt: PgUebersetzerTests (neue Zeile `SELECT ARRAY(SELECT 1)`)✅
P18.5c6c18gDer Schreiber: Funktionen als QuelleGebaut: eine Funktion als Quelle einer Auswahl, die keine andere Regel schreibt, schreibt der Schreiber samt Alias und Spaltenliste an der Quelle (`Funktionsquellen`, `Quellfunktion`): `(SELECT f(x) AS spalte) AS alias` über die Bibliothek und die Katalogfunktionen, die json-Zeilenfunktionen über OPENJSON (hinter einem Komma `CROSS APPLY`), ts_debug, pg_get_keywords, pg_partition_tree/-ancestors und den Satz einer Funktion mit `RETURNS SETOF record` (`AS x(a int, …)`, 42601/42804 wie bisher); der Leser liest dazu `AS (a int, b text)` ohne Namen. Die Durchgänge `Satzfunktionen` und `FunktionenImFrom` (samt `Vorfahren`) entfallen. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde11bTests, PgRunde12aTests)✅
P18.5c6c18hDer Schreiber: unnest und json-ZeilenfunktionenGebaut: `unnest(feld)` und `jsonb_object_keys`/`json[b]_array_elements[_text]` als Eintrag der Auswahlliste schreibt der Schreiber am Aufruf als Wert des Elements und hängt die Quelle (`OPENJSON`) hinter die Quellen der Auswahl (`CROSS APPLY`, ohne Quelle `FROM`), mehrere im Gleichschritt (`Auswahlzeilen`); dazu kann er spät einfügen (`EinfuegeZuletzt`: der Inhalt entsteht beim Schreiben, nach den Aufrufen davor). `unnest` als Quelle (samt `WITH ORDINALITY`, Spaltennamen, mehreren Feldern, hinter einem Komma `CROSS APPLY`) schreibt `Quellfunktion` (`Unnestquelle`); ein `unnest` an anderer Stelle meldet er mit 0A000. Die Durchgänge `Aufloesen` und `JsonZeilenInAuswahl` entfallen samt ihren Wortsuchern. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: 12 Tests, u. a. PgRunde11cTests, PgTypArrayFolgeTests)✅
P18.5c6c18iDer Schreiber: pg_settingsGebaut: die Tabelle `pg_settings` (auch `pg_catalog.pg_settings`) als Quelle einer Auswahl schreibt der Schreiber am Namen als Tabelle der Einstellungen der Sitzung (`Einstellungstabelle`). Der Durchgang `Einstellungen` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgKatalogFunktionenTests, PgDumpAbfragenTests, PgDump2AbfragenTests)✅
P18.5c6c18jKorrektur: `CAST(x AS bit)` ist die BitfolgeAm Orakel (postgres:17) gemessen: `pg_typeof(CAST(1 AS bit))` ist `bit` (Wert 1), wie `1::bit`, kein boolean. Die Ausnahme im Schreiber (`Umwandelbar`: CAST … AS bit bleibe der Wahrheitswert der Engine) entfällt. Ihr Grund waren zwei Tests aus P18.3/P18.4, die T-SQL-Typnamen über die Leitung schickten (`datetime2`, `uniqueidentifier`, `varbinary`); der Mitschnitt des Orakels (Binärwert `0,0,0,1,1`) gehört zu boolean — die Tests fragen jetzt `CAST(1 AS boolean)`, `CAST(1 AS bit)` meldet OID 1560, Grösse -1, Typmodifizierer 1. Rot über die Sabotage gezeigt: PgAbfrageTests, PgTypBitfolgeTests (neu: Wert, `CAST(5 AS bit(4))` = 0101, pg_typeof)✅
P18.5c6c18kAusdrücke hinter OFFSET und FETCHGebaut: die Engine liest hinter `OFFSET` und `FETCH NEXT` jeden Ausdruck (Rechnung, Variable, Unterabfrage — SQL Server 2025 erlaubt das) und wertet ihn einmal je Ausführung aus (`SelectStatement.OffsetAusdruck`/`FetchAusdruck`, `Zeilenzahl`), mit den Fehlern des Orakels: negativ 10742/10744, keine ganze Zahl 10743/1060, NULL 10743/1014; `FETCH NEXT 0` bleibt wie die feste 0 (D17.5b) ohne Zeile (SQL Server: 10744). Der Schreiber schreibt hinter `LIMIT`/`OFFSET` einen Ausdruck als `(COALESCE(x, alle|0))` und beide Klauseln in der Folge des Texts (die Namen einer Unterabfrage darin gingen vorher durcheinander); 10742/10744 meldet der Port als 2201X/2201W wie PostgreSQL. Vorher war jeder Ausdruck dort ein Syntaxfehler. Rot über die Sabotage gezeigt: OffsetAusdruckTests (neu), PgBaumuebersetzungTests✅
P18.5c6c18lDer Schreiber: ONLYGebaut: `ONLY` vor einer Relation schreibt der Schreiber aus dem Baum (`NurElter`): eine Quelle einer Auswahl über einer partitionierten Tabelle wird leer (`(SELECT * FROM t WHERE 1 = 0) AS t`), sonst fällt `ONLY` weg — an den Quellen, im Ziel von UPDATE/DELETE/MERGE, hinter `TABLE` und in den Relationen von `TRUNCATE`, `LOCK` und `ALTER TABLE`, die der Leser dafür liest (`Relationsliste`, auch `IF EXISTS`; `Unbestimmt.Stelle`). Der Durchgang `NurElter` entfällt samt `LetzteKlausel`; ebenso der tote Helfer `Listenelemente`. Richtiger als vorher (`PgBaumuebersetzungTests`): `MERGE INTO ONLY t`, `DELETE … USING ONLY u`. Rot über die Sabotage gezeigt: PgRunde12bTests, PgBaumuebersetzungTests✅
P18.5c6c18mDer Schreiber: der Stand einer Sequenz für pg_dumpGebaut: `SELECT last_value, is_called FROM [schema.]folge` (so liest pg_dump eine Sequenz) erkennt der Schreiber am Baum und schreibt die Abfrage an `sys.sequences`/`sys.identity_columns` (`Folgenstand`; die Namen gefaltet wie bei PostgreSQL). Der letzte Durchgang aus `Felder.cs` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDumpAbfragenTests, PgDump2AbfragenTests, PgRunde8dTests)✅
P18.5c6c18nVorgabeschema beim ZerlegenGebaut: `public.name` → `name` geschieht schon beim Zerlegen (`Zerlege` ruft `Vorgabeschema`), für jede Anweisung, jedes Stück eines Rumpfs und jeden Typnamen gleich; der Durchgang in `Durchgaenge` entfällt. Der Leser sieht damit nie `public .` (ein Name im Vorgabeschema hat für den Baum kein Schema). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgSuchpfadTests, PgAppAbnahme2Tests)✅
P18.5c6c18oDer Schreiber: KatalognamenGebaut: ein Katalogname ohne Schema als Quelle (auch hinter `TABLE`) bekommt `pg_catalog.` vom Schreiber, `information_schema.columns` usw. wird `pg_catalog.pg_is_…`, und welche Fassung von `pg_depend` pg_dump braucht (leer bei einer fremden Klasse, schmal bei `deptype 'e'/'x'` ohne andere Arten), liest er am Baum statt mit regulären Ausdrücken über dem Text (`Katalognamen`, `Abhaengigkeiten`; `Tabellen` geteilt mit den schreibenden CTEs). Der Durchgang `Katalognamen` entfällt. Gemessen über alle Pg-Tests: jede Anweisung mit einem Katalognamen hat einen Baum (auch die von psql und pg_dump). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: 123 Tests; die pg_depend-Fassung PgDump2AbfragenTests)✅
P18.5c6c18pDer Schreiber: temporäre Tabellen und SuchpfadGebaut: der Leser führt die Relationen jeder übrigen Anweisung (`Kopfrelationen`: `TRUNCATE`, `LOCK`, `ALTER|DROP [MATERIALIZED] TABLE|VIEW|SEQUENCE`, `COMMENT ON`, `GRANT|REVOKE … ON`, `CREATE TABLE|VIEW|SEQUENCE` als neu, `REFERENCES`, `ON` eines Index/Triggers/einer Richtlinie; `Pgbaum.Relationsverweis`) und die Stelle von `SELECT … INTO`; der Schreiber schreibt jeden Namen einer Relation aus dem Baum, wie die Engine ihn kennt (`Relationsnamen`/`Relationsname`): eine temporäre Tabelle der Sitzung als `#t` (auch `pg_temp.t` und als Qualifizierer, `DROP` nimmt sie aus der Sitzung), sonst nach dem `search_path` (gelesen im ersten Schema mit dem Namen, sonst 42P01 in Abfragen; angelegt im ersten Schema, sonst 3F000; eine Quelle ohne Alias behält ihren Namen als Alias). `UPDATE … FROM`, `DELETE … USING` und `ONLY` über einer partitionierten Tabelle nehmen denselben Namen. Der Durchgang `Suchpfad` und der Verweisteil von `Temporaere` entfallen (es bleibt der Kopf von `CREATE TEMP TABLE`). Richtiger als vorher: `DROP TABLE a, b` qualifiziert auch `b`, `SELECT … INTO neu` legt im ersten Schema an (vorher 42P01). Rot über die Sabotage gezeigt: 10 Tests, u. a. PgDdlRestTests, PgAppAbnahmeTests, RechtemodellPgRestTests✅
P18.5c6c18qDer Schreiber: CREATE TEMP TABLEGebaut: der Leser führt den Kopf von `CREATE [GLOBAL|LOCAL] TEMP TABLE|VIEW|SEQUENCE [IF NOT EXISTS] name` und die Spanne von `ON COMMIT …` (`Befehl.Kopf`, `Tabellenkopf.Objekt`/`CommitAnfang`); der Schreiber schreibt `CREATE TABLE #t …`, merkt die Tabelle samt `ON COMMIT` in der Sitzung, meldet `IF NOT EXISTS` über eine vorhandene mit der NOTICE und eine temporäre Sicht oder Sequenz mit 0A000 (`TemporaerAnlegen`). Der Durchgang `Temporaere` entfällt ganz. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: 13 Tests, u. a. RechtemodellPgRestTests, PgAppAbnahmeTests, PgRunde11dTests)✅
P18.5c6c18Der Schreiber: Quellen, Verbunde und Namen aus dem BaumGebaut (c6c18a–q): Sperrklausel, CTE-Wörter, LATERAL, VALUES in Mengen, USING/NATURAL, Abgelehntes, Funktionen als Quelle, unnest und json-Zeilenfunktionen, pg_settings, die Korrektur `CAST(x AS bit)`, Ausdrücke hinter OFFSET/FETCH, ONLY, der Stand einer Sequenz, Vorgabeschema beim Zerlegen, Katalognamen, temporäre Tabellen und Suchpfad, CREATE TEMP TABLE. Es bleibt `Woerter` (Namen falten, Zeichenketten, Wörter, die T-SQL reserviert) — es wirkt auf die Ausgabe jeder Anweisung und geht mit `Ddl`/`Umwandlungen` (c6e4) in die Ausgabe — Rest erledigt in c6g1 (`Woerter` in der Ausgabe) und c6g2 (`Umwandlungen` im Schreiber)✅
P18.5c6d1Der Schreiber: Ziele von SET mit Index und FeldGebaut: der Leser führt je Ziel von `SET` die Glieder hinter der Spalte (`Pgbaum.Setzung`: `.feld`, `[index]` als Ausdruck im Baum, Stelle von `=`); der Schreiber schreibt `a[i][j] = x` als `a = PG_ARRAY_SET(a, x, i, j)` (Felder und jsonb) und `a.feld = x` einer Verbundspalte als `a = PG_FELD_SETZE(a, N'feld', x)` (`Setzziele`), in `UPDATE`, `ON CONFLICT DO UPDATE` und den Zweigen von `MERGE`. Die Durchgänge `Feldzuweisungen` (samt der Weiche `Felder`, es bleibt `Folgenstand` für pg_dump) und der UPDATE-Teil von `Verbundteilfelder` entfallen. Richtiger als vorher (`PgBaumuebersetzungTests`): ein Element setzen in ON CONFLICT und MERGE, ein Index mit Rechnung oder Unterabfrage. Rot über die Sabotage gezeigt: PgTypArrayFolgeTests, PgTypJsonbFolgeTests, PgTypVerbundFolgeTests✅
P18.5c6d2Der Schreiber: Felder in der Spaltenliste von INSERT, row_to_jsonGebaut: der Leser führt die Spalten der Liste von `INSERT`, die ein Feld eines Verbunds nennen (`Einfuegen.Felder`); der Schreiber schreibt `INSERT INTO t (id, a.strasse, a.nr) VALUES …` je Verbundspalte als einen Wert aus ihren Feldern (`PG_FELD_SETZE(…CAST(NULL AS typ)…)`, `Felderlisten`/`Feldwerte`; die Werte der Zeilen in der Folge des Texts geschrieben, eine Zeile mit falscher Länge 42601 wie PostgreSQL statt einer Ausnahme), `row_to_json` geht über die Liste der Funktionen der Engine. Der Durchgang `Zeilen` entfällt samt `Verbundteilfelder` und `Zeilenteile`. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgTypVerbundFolgeTests, je für die Felder und für row_to_json)✅
P18.5c6d3Der Schreiber: weniger Werte als Spalten, ON CONFLICT als MERGEGebaut: der Leser führt den Anfang der Quelle eines `INSERT` und von `ON CONFLICT` samt Setzliste; der Schreiber löst den Namen der Zieltabelle selbst auf (`Relation`: temporäre Tabelle der Sitzung als `#t`, Suchpfad, `public`), gibt einem `INSERT … VALUES` mit weniger Werten die ersten Spalten als Liste und meldet zu viele/zu wenige Werte mit 42601 (`Einfuegespalten`), und schreibt `INSERT … ON CONFLICT` als `MERGE` (`Upsert`/`Mischung`: Quelle, Setzliste und Bedingung in der Folge des Texts geschrieben, Konfliktziel aus Spalten, `ON CONSTRAINT` oder jeder Eindeutigkeit, dieselben Fehler 0A000/42704/42601). Die Durchgänge `Einfuegespalten` und `Upsert` entfallen. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDmlTests, PgDml3Tests, PgBaumuebersetzungTests; PgRunde11dTests für die Spaltenliste)✅
P18.5c6d4Der Schreiber: MERGE … THEN DO NOTHINGGebaut: der Leser führt je Zweig von `MERGE` die Stellen von `WHEN`, `THEN` und das Ende; der Schreiber lässt `WHEN … THEN DO NOTHING` weg und schliesst mit seiner Bedingung die folgenden Zweige derselben Art aus (`AND NOT (…)`, ohne Bedingung entfallen sie; `Mischzweige`/`Nichts`, Kopf, Bedingung und Aktion in der Folge des Texts geschrieben). Der Durchgang `MergeZweige` samt `DannStelle` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDml3Tests, PgEinstellungenTests)✅
P18.5c6d5Der Schreiber: RETURNING als OUTPUTGebaut: der Schreiber schreibt `RETURNING …` einer Datenänderung als `OUTPUT …` (`Ausgabe`): die Spalten der Zieltabelle (ohne Qualifizierer, mit Name oder Alias, `*`, `t.*`) aus `inserted`/`deleted`, bei MERGE und `INSERT … ON CONFLICT` je nach `$action` samt Namen, `merge_action()` als `$action`, `INTO` einer schreibenden CTE. Neu kann er umstellen: die Klausel wird in der Folge des Texts geschrieben und rückt danach an ihre Marke vor `VALUES`/`SELECT`/`FROM`/`WHERE`/`USING` (`_umstellung`). Der Durchgang `Rueckgabe` entfällt samt `SpaltenVorsatz`, `Zielnamen`, `MergeAusgabe`. Richtiger als vorher (`PgBaumuebersetzungTests`): eine Unterabfrage in RETURNING bleibt, wie sie ist (vorher bekam jeder Name darin `inserted.`). Rot über die Sabotage gezeigt: 30 Tests, u. a. PgUebersetzerTests, PgDmlTests, PgEfCoreTests✅
P18.5c6d6Der Schreiber: UPDATE … FROM, DELETE … USING, Alias am ZielGebaut: der Schreiber nennt das Ziel von `UPDATE … FROM` und `DELETE … USING` selbst in den Quellen (`UPDATE x SET … FROM t AS x, a`, `DELETE x FROM t AS x, a`) und ein Ziel mit Alias ohne weitere Quellen im `FROM` vor `WHERE` oder am Ende (EF Core `ExecuteDelete`/`ExecuteUpdate`; `Zielquelle`, nach der Ausgabe eingefügt, damit `OUTPUT` vor `FROM` steht). Die Durchgänge `Quellen` und `ZielMitAlias` entfallen. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDmlTests, PgEfCoreTests)✅
P18.5c6d7Der Schreiber: SELECT setval(…)Gebaut: `SELECT [pg_catalog.]setval('s', n [, true|false]) [AS x]` als ganze Anweisung schreibt der Schreiber aus dem Baum (`Folgesetzen`: `ALTER SEQUENCE … RESTART` über die Schrittweite der Folge, `PG_SERIE_SETVAL` für die Folge einer serial-Spalte; n auch mit Vorzeichen), `setval` an anderer Stelle meldet er mit 0A000. Der Durchgang `Sequenzsetzen` entfällt samt `FolgenOhneKatalog` (`pg_catalog.nextval` schreibt schon `Folgewert`). Neuer Test für den Weg von pg_dump (`pg_catalog.setval`, `DEFAULT pg_catalog.nextval(…)`). Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgDdlRestTests, PgRunde8dTests, PgRunde12bTests, PgAppAbnahme2bTests)✅
P18.5c6d8Schreibende CTEs aus dem BaumGebaut: der Leser führt je CTE die Stellen von Name, `AS`, Klammer und Ende; `SchreibendeCtes` zerlegt die Anweisung über den Baum (welche CTE schreibt, ihre `RETURNING`-Liste, das Ziel samt Alias) und ersetzt die Verweise auf eine schreibende CTE an den Quellen des Baums (alle Anfragen ausser den schreibenden, auch hinter `USING` und als Quelle von `MERGE`). Die Wortsuche (`CteZiel`, `CteVerweiseErsetzen`, `Tabellenstelle`, `Klauselwoerter`) entfällt. Richtiger als vorher (`PgBaumuebersetzungTests`): `DELETE … USING cte` (vorher 42P01), ein Alias ohne `AS` am Ziel. Rot über die Sabotage gezeigt: PgDml2Tests✅
P18.5c6d9Orakelbefunde: Anzahl in LIMIT/OFFSET, Unterabfrage in RETURNINGAm Orakel (postgres:17) gemessen: `LIMIT 2.5` liefert 3 Zeilen — die Anzahl ist bigint, numeric rundet halb weg von null (Konstante schon im Schreiber, ein Ausdruck vom Typ numeric/real/double als `CAST(ROUND(x, 0) AS BIGINT)`; vorher 1060); negativ nach dem Runden 2201W/2201X, NULL alle bzw. 0. Eine Unterabfrage in `RETURNING` sieht den Stand vor der Anweisung (`INSERT … RETURNING (SELECT count(*) FROM t)` liefert 0): liest sie das Ziel, legt der Übersetzer davor eine Kopie an (`SELECT * INTO #pgvorN_ FROM ziel`, abgeräumt nach der Anweisung) und die Unterabfrage liest die Kopie (`RueckgabeVorDerAnweisung`). Nebenbei: `UPDATE t x … RETURNING ausdruck` ohne WHERE setzte den Spaltennamen hinter das eingefügte FROM✅
P18.5c6dDer Schreiber: DML aus dem BaumGebaut (c6d1–c6d8): Ziele von SET mit Index und Feld, Felder in der Spaltenliste von INSERT, weniger Werte als Spalten, ON CONFLICT als MERGE, MERGE … DO NOTHING, RETURNING als OUTPUT (mit Umstellung), UPDATE … FROM/DELETE … USING samt Alias am Ziel, `SELECT setval(…)`, schreibende CTEs. Die Durchgänge `Feldzuweisungen`, `Zeilen`, `Einfuegespalten`, `Upsert`, `MergeZweige`, `Rueckgabe`, `Quellen`, `Sequenzsetzen` entfallen; der Schreiber löst den Namen der Zieltabelle selbst auf (`Relation`). Leistung `Uebersetze` (eigene Messreihe, 5 Anweisungen, kürzester von 30 Läufen) 9,9 → 8,7 µs✅
P18.5c6e1Leser für DDL: die Relationen einer Anweisung, erster Nutzer TRUNCATEGebaut: `Pgbaum.Befehl` führt die Relationen, die eine Anweisung nennt (`Relationen`: Name, Stelle, `ONLY`), der Leser liest sie für `TRUNCATE [TABLE] [ONLY] t [*], …` (`Relationsliste`); erster Nutzer ist der Schreiber, der die Prozeduren der TRUNCATE-Trigger davor/danach einfügt (`Kuerzungstrigger`, die Tabellen über `Relation` aufgelöst). Der Durchgang `Kuerzungstrigger` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgRunde11bTests)✅
P18.5c6e2Der Schreiber: das Ereignis für AnweisungstriggerGebaut: vor `INSERT`/`UPDATE`/`DELETE` auf einer Tabelle mit einem Anweisungstrigger für mehrere Ereignisse legt der Schreiber das Ereignis im Sitzungskontext ab (`Anweisungsereignis`, die Zieltabelle aus dem Baum über `Relation`; `INSERT … ON CONFLICT` bleibt wie bisher ohne). Der Durchgang `Anweisungsereignis` entfällt. Ein Umzug ohne neues Verhalten (rot über die Sabotage gezeigt: PgPlpgsqlTests, PgRunde11bTests)✅
P18.5c6e3CREATE TABLE … AS aus dem BaumGebaut: der Leser führt den Kopf von `CREATE [TEMP] TABLE … AS` (`Pgbaum.Tabellenkopf`: Name samt Stelle, Spaltenliste, `TEMP`, `IF NOT EXISTS`, `ON COMMIT`, Spanne der Anfrage, `WITH NO DATA`) und das Ende jeder Auswahlliste (`Auswahl.ListeEnde`); der Schreiber schreibt daraus `SELECT … INTO t …` (`TabelleAus`: Kopf und Schluss weg, äussere Klammern weg, `TABLE x` und `VALUES` als `SELECT *`, die Spaltenliste als Namen — `Spaltennamen` lässt diese Einträge aus —, `TRUNCATE` für `WITH NO DATA`, `IF OBJECT_ID … ELSE PRINT` für `IF NOT EXISTS`, die temporäre Tabelle als `#t` samt `ON COMMIT` in der Sitzung, eine neue Tabelle im ersten Schema des Suchpfads). Die Durchgänge `TabelleAusAbfrage`/`TabelleAusAbfrageBauen` entfallen (samt `_tabelleAus`). Richtiger als vorher (`PgBaumuebersetzungTests`): eine Spaltenliste über `VALUES` (vorher 42601). Rot über die Sabotage gezeigt: PgRunde8gTests, PgBaumuebersetzungTests✅
P18.5c6e4aDROP aus dem BaumGebaut: der Leser führt die Form von `DROP objekt [CONCURRENTLY] [IF EXISTS] name, … [CASCADE|RESTRICT]` (`Pgbaum.Entfernen`: Art, Kopf, Namen mit Spanne, Stelle von CASCADE/RESTRICT, auch vor einem `;`); der Schreiber (`Schreiber.Ddl`, eine Regel `Ddlschrift`, die ihre Spanne selbst schreibt und die Teile durch die übrigen Regeln schickt) lässt CASCADE/RESTRICT weg, schreibt `DROP INDEX` über den Katalog (`IndexEntfernen`), `DROP SEQUENCE|SCHEMA IF EXISTS` mit der Notiz, eine Liste als eine Anweisung je Name (jetzt auch `MATERIALIZED VIEW`) samt den eigenen Folgen einer Tabelle. `DropWennVorhanden`, `DropIndexOhneTabelle` entfallen; vorher blieb CASCADE vor einem `;` stehen (`DROP INDEX … CASCADE;` war ein Syntaxfehler)✅
P18.5c6e4bCREATE SEQUENCE und CREATE SCHEMA aus dem BaumGebaut: der Leser führt `CREATE SEQUENCE [IF NOT EXISTS] name [option …]` (`Pgbaum.Folgendefinition`: Name mit Spanne, je Option Art, Spanne und Stelle des Werts) und `CREATE SCHEMA [IF NOT EXISTS] name` (`Schemadefinition`); der Schreiber schreibt die Folge mit den Vorgaben von PostgreSQL (MINVALUE 1 bzw. MAXVALUE -1, BY/WITH, NO MINVALUE|MAXVALUE entfällt, der Typ hinter AS) und das Schema mit der Notiz. `CreateSequence`, `CreateSchema` entfallen. Vorher prüfte `CREATE SEQUENCE IF NOT EXISTS s.f` nur den Namen ohne Schema (eine gleichnamige Folge in public übersprang die Anlage); jetzt die Relation samt Schema, wie PostgreSQL✅
P18.5c6e4cCREATE INDEX aus dem BaumGebaut: der Leser führt `CREATE [UNIQUE] INDEX [CONCURRENTLY] [IF NOT EXISTS] name ON [ONLY] t [USING m] (…) rest` (`Pgbaum.Indexdefinition`: Name, Tabelle, Methode, je Schlüssel Spanne, Ende des Kerns, Operatorklasse, DESC, NULLS; Anfang des Rests, `TABLESPACE`); der Schreiber schreibt Spalten- und Ausdrucksindizes (verborgene Spalte), die Merkzeilen und `pg_get_indexdef` aus dem Baum (`Indexkurzschrift`). `IndexKopf`, `IndexKurzschrift` (der Vorlauf vor dem Schreiber), `Indexschluessel`, `CreateIndex` entfallen. Neu: `TABLESPACE pg_default` (pgAdmin) entfällt, ein anderer Tablespace ist 42704 wie bei PostgreSQL (vorher still übergangen)✅
P18.5c6e4dCREATE TABLE aus dem BaumGebaut: der Leser führt `CREATE [TEMP|UNLOGGED] TABLE [IF NOT EXISTS] name (…)` (`Pgbaum.Tabellendefinition`: Einträge mit Spanne — Spalten mit Typspanne, Typname und Klauseln samt CONSTRAINT-Namen, Ausdruck von DEFAULT/CHECK/GENERATED, Identität mit ihren Folgenoptionen, Sortierung; Einschränkungen mit Spalten, EXCLUDE mit Methode, Paaren und Bedingung; LIKE mit Optionen —, `PARTITION OF`/`PARTITION BY`, `WITH (…)`, `ON COMMIT`, `TABLESPACE`); der Schreiber schreibt Spalten, Namen der Einschränkungen, LIKE, EXCLUDE, Domänen und Identitäten aus dem Baum. `CreateTable`, `Einschraenkungsnamen`, `SpaltenAus`, `Spaltentypen` (Wörter) entfallen; `Wie` und `Ausschluss` nehmen die Struktur. Neu: `START n`/`INCREMENT n` ohne WITH/BY in `GENERATED … AS IDENTITY (…)` (pgAdmin) gelten, `TABLESPACE pg_default` entfällt (ein anderer 42704)✅
P18.5c6e4eALTER TABLE aus dem Baum, der Durchgang Ddl entfälltGebaut: der Leser führt `ALTER TABLE [IF EXISTS] [ONLY] name aktion, …` (`Pgbaum.Tabellenaenderung`/`Tabellenaktion`: ADD COLUMN mit der Spaltendefinition aus c6e4d, ADD [CONSTRAINT] mit der Einschränkung, DROP COLUMN/CONSTRAINT, ALTER COLUMN SET/DROP DEFAULT, SET/DROP NOT NULL, TYPE … USING, RENAME, SET LOGGED, ENABLE/DISABLE TRIGGER, ATTACH/DETACH PARTITION, je mit Spanne); der Schreiber schreibt eine Anweisung je Aktion. `AlterTable`, `AlterSpalteEigenschaft`, `Spalte`/`Identitaet` (Wörter), `OhneKaskade`, `Eintraege`, `IstAusschluss` und der Durchgang `Ddl` selbst entfallen (`Name`/`Folgt` bleiben bis c6f im PL/pgSQL-Weg). Neu: eine Einschränkung ohne Namen in ALTER TABLE ADD bekommt den Namen von PostgreSQL (`t_a_check`, `t_b_key`; vorher `CK__…`/`UQ__…` der Engine)✅
P18.5c6eScheibe DDL aus dem BaumGebaut (c6e1–c6e3): der Leser führt die Relationen jeder übrigen Anweisung (`Befehl.Relationen`) und den Kopf von `CREATE [TEMP] TABLE … [AS]`; aus dem Baum kommen die TRUNCATE-Trigger, das Ereignis für Anweisungstrigger, `CREATE TABLE … AS`, ONLY, die Namen (c6c18l–q). Offen c6e4: die Struktur von `CREATE/ALTER TABLE` (Spalten, Einschränkungen, Optionen), `CREATE INDEX`, `CREATE SEQUENCE`, `DROP` für `Ddl` und die Umwandlungen ausserhalb des Baums (`Umwandlungen`) — Stand 2026-10-04 (c6e4a–e): die Struktur jeder DDL-Anweisung (DROP, CREATE SEQUENCE/SCHEMA/INDEX/TABLE, ALTER TABLE) liest der Leser (`Pgbaum.Ddlform`), der Schreiber schreibt sie; der Durchgang `Ddl` ist weg✅
P18.5c6fScheibe PL/pgSQL aus dem BaumOffen: die Anweisungen im Rumpf bekommen den Baum über den Wörtern schon (`@variable` steht dort als Name; c6c1–c6c3 gelten auch im Rumpf). Offen ist der Rumpf selbst als Baum (Blöcke, Variablen, Schleifen, Cursor, Ausnahmen) und die Variablenersetzung (`@…`) im Schreiber statt in den Token; `Rumpf`, `Programm`, `Trigger`, `Portale` über den Schreiber; danach ist der Token-Weg leer und fällt weg Stand 2026-10-03 (c6c4): die Ziele von `SELECT … INTO` setzt der Schreiber (`TOP (1) @x = (…)`), die Anfrage geht in der Form von PostgreSQL durch den Leser; offen bleiben die Variablen (`Ersetze` → Namen im Schreiber, dann kennt auch die Analyse ihre Typen) und Rumpf/Programm/Trigger/Portale — Stand 2026-10-04 (c6f1–c6f5): Variablen, Portale, CREATE TRIGGER, CREATE FUNCTION/PROCEDURE, DO, CALL, DROP FUNCTION, Sicherungspunkte aus dem Baum. Offen: der Rumpf selbst als Baum (Blöcke, Deklarationen, Anweisungen; heute ein Leser über den Wörtern in `Rumpf`) samt der Breite von RETURN QUERY (`RueckgabeAbfrageBreite`/`AbfrageBreite` zählen noch Kommas) — Stand 2026-10-04 (c6h4): der Rumpf als Baum (`Rumpfleser`, `Pgbaum.Plblock`) und die Breite von RETURN QUERY aus dem Baum; erledigt✅
P18.5c6f1PL/pgSQL: Variablen aus dem BaumGebaut: welche Namen einer eingebetteten Anweisung Variablen sind, sagt ihr Baum (`Rumpf.Einsetzen`): ein Bezug `x` → `@x`, `rec.f` → `@rec__f` (42703 bzw. im Trigger erst beim Ausführen), `tg_argv[n]` → das Argument, `$n` → der Parameter; Spaltenlisten, SET-Ziele, Aliase und Funktionsnamen sind keine Bezüge. Mehrdeutig (42702 mit Detail, r11b-orakel) ist ein Bezug, den eine Quelle einer Anfrage, in der er steht, als Spalte führt (`Sichtbereiche`: alle Quellen der Anfrage, auch hinter einem Komma, bei UPDATE/DELETE samt Ziel). `Ersetze`, `Spaltenziele`, `SpaltenImBereich` (Wortsuche nach Spaltenstellen und Bereichen) entfallen. Vorher sah die Wortsuche in `SELECT … INTO` und hinter einem Komma keine Quelle (die Variable gewann still)✅
P18.5c6f2Portale aus dem BaumGebaut: `DECLARE name … CURSOR [WITH HOLD] FOR anfrage` liest der Leser als Hülle mit Kopf (Name, NO SCROLL, WITH HOLD, ein unbekanntes Wort vor CURSOR), `FETCH|MOVE [richtung] [FROM|IN] name` und `CLOSE name|ALL` als `Pgbaum.Portalaktion` (Richtung, Anzahl samt Vorzeichen, Name); `Portal` schreibt aus dem Baum, `Portalname`/`Vorzeichenzahl` (Wortsuche) entfallen. Nebenbei: der Leser setzte nach einem fehlgeschlagenen Ausdruck am Ende einer übrigen Anweisung hinter das Ende (`FETCH WHERE … WHERE` warf eine ArgumentOutOfRangeException, jetzt nie hinter das Ende)✅
P18.5c6f3CREATE TRIGGER aus dem BaumGebaut: der Leser führt `CREATE [OR REPLACE] TRIGGER …` (`Pgbaum.Ausloeserdefinition`: Name, Zeitpunkt, Ereignisse samt UPDATE OF, Tabelle, Übergangstabellen aus REFERENCING, ROW/STATEMENT, Spanne von WHEN, Funktion, Stellen der Argumente; ein Syntaxfehler mit Stelle); `TriggerAnlegen` schreibt daraus. Die Übergangstabelle ersetzt `Rumpf.Einsetzen` nur, wo sie eine Quelle ist (`FROM neu` → `FROM inserted AS [neu]`); vorher ersetzte eine Wortsuche jedes Wort gleichen Namens im Rumpf (auch eine Spalte der Liste von INSERT)✅
P18.5c6f4CREATE FUNCTION/PROCEDURE aus dem BaumGebaut: der Leser führt `CREATE [OR REPLACE] FUNCTION|PROCEDURE …` (`Pgbaum.Routinendefinition`: Name, Parameter mit Modus, Name, Typspanne und Vorgabe — ein Name nur, wenn der Rest bis zum Ende des Parameters ein Typ ist —, Rückgabe samt Spalten von RETURNS TABLE, Sprache, Stelle des Rumpfs, Rumpf im SQL-Standard, Merkmale); `FunktionAnlegen` und `Parameterliste` nehmen die Struktur. Vorher las `RETURNS NULL ON NULL INPUT` hinter `RETURNS typ` den Typ null (Fehler); jetzt ist die Funktion strikt wie bei PostgreSQL✅
P18.5c6f5DO, CALL, DROP FUNCTION, Sicherungspunkte aus dem BaumGebaut: der Leser führt `DO [LANGUAGE l] 'rumpf'` (`Anonymform`, die Sprache auch dahinter), `SET TRANSACTION`/`SAVEPOINT`/`RELEASE`/`ROLLBACK [WORK|TRANSACTION] TO` (`Transaktionsform`) und `DROP FUNCTION|PROCEDURE|TRIGGER` (`Entfernen.Routinen`: Name, Typspannen der Argumente wie bei CREATE, `ON` tabelle); `CALL` nimmt Aufruf, Argumente und ihre Namen aus dem Baum. `Name`/`Folgt` (Wortsuche) entfallen ganz. Vorher war ein Argumentname in `DROP FUNCTION f(a integer)` für die Wortsuche ein Typ, und `ROLLBACK WORK|TRANSACTION TO SAVEPOINT` nahm der Anschluss für ein ganzes ROLLBACK (`Pgbefehle`)✅
P18.5c6g1Die Schreibweise in der AusgabeGebaut: der Durchgang `Woerter` entfällt aus `Durchgaenge`; die Schreibweise von T-SQL (N'…', […], TRUE/FALSE, ILIKE, reservierte Namen, Kleinschreibung) setzt erst die Ausgabe je Wort (`Schreibweise`, von `Ausgabe` gerufen; `Woerter` bleibt als Helfer für Text, den der DDL-Schreiber und die Domänen selbst bauen). Die Durchgänge danach (`AlleTabellen`, `Umwandlungen`) sehen die Wörter des Texts. Leistung `Uebersetze` (eigene Messreihe, Release, 5 Anweisungen, kürzester von 30) 18,3 → 18,4 µs (gleich)✅
P18.5c6g2Umwandlungen ganz im SchreiberGebaut: der Schreiber schreibt jetzt auch die Umwandlung einer Zeile (`ROW(…)::typ`), eines Verbunds (`'(…)'::adr`, auch als Feld) und den Text hinter einer Registerumwandlung (`x::regclass::text`) am Knoten (`Umwandelbar`); der Durchgang `Umwandlungen` (Wortsuche nach dem Operanden vor `::`, `OperandAnfang`) und `AlleTabellen` in `Durchgaenge` entfallen. Bleibt nach dem Schreiber ein `::` stehen (eine Anweisung, die der Leser nicht liest), ist das 42601. In den Tests erreichten den alten Durchgang zuletzt nur noch diese Formen und Wortsalat der Robustheitsprobe (Sonde über den gefilterten Lauf). Leistung `Uebersetze` 18,4 µs (gleich)✅
P18.5c6g3Die Tabellen der Anweisung im Schreiber aus dem BaumGebaut: Überladungen und Bibliotheksformen fragen nach der Art einer Spalte über `Schreiber.Tabellenverzeichnis` (die Quellen jeder Anfrage und die Ziele von INSERT/UPDATE/DELETE/MERGE samt Alias aus dem Baum) statt über `AlleTabellen` (Wortsuche nach FROM, JOIN, UPDATE, INTO). Sabotage (keine Tabelle) → drei Orakelfolgen rot (Intervalle, Bitfolgen, Bereiche). `AlleTabellen` bleibt nur noch für die Prüfung bei Parse und die Parametertypen (Wörter ohne Schreiber)✅
P18.5c6h1Orakelbefunde postgres:17: LIMIT float8, Prüfnamen, ADD COLUMN UNIQUE, nextvalGebaut: `LIMIT 2.5::float8` rundet halb zur Geraden (2 Zeilen, `3.5` → 4; der Schreiber rundet einen Ausdruck vom Typ numeric/real/double precision über `PG_NUM('ROUND', …)` wie `round()` von PostgreSQL am Wert, numeric bleibt halb weg von null). Eine Prüfung ohne Namen heisst nach der einen Spalte, die sie nennt (Bezüge aus dem Baum), sonst `<tabelle>_check` (vorher jedes Wort der Klammer, `t_a_and_a_length_check`). `ALTER TABLE t ADD COLUMN b int UNIQUE` legt `t_b_key` an: die Engine verwarf `ADD spalte typ [CONSTRAINT n] UNIQUE|PRIMARY KEY|CHECK (…)` still, jetzt je ein Schritt danach (Orakel SQL Server 2025: UQ und C angelegt). `SELECT nextval('s'), nextval('s')` → 1|2 über den Dialektschalter `FolgewertJeAufruf` in `Sequenzwerte.Hole`; `NEXT VALUE FOR` zweimal in einer Zeile bleibt in T-SQL derselbe Wert (Orakel 1|1, Test). Sabotage je Befund rot✅
P18.5c6h2ZeilenSterne und Tabellenaliase aus dem BaumGebaut: `(x).*` eines Verbunds wird weiter vor dem Schreiber die Liste `(x).f, …` (damit `SELECT (x).* INTO a, b` und die Namen gelten), aber Stellen und Verbund sagt der Baum: der Knoten `feld *`, der Typ aus der Umwandlung, aus der Spalte einer Quelle (`Tabellenverzeichnis`) oder aus dem Feld eines Verbunds; vor `GROUP BY n`, das wie bei PostgreSQL die Spalten danach zählt. Die Aliase der Quellen für Zeilenwerte (`Tabellenaliase`) kommen ebenfalls aus dem Baum. Entfallen: die Wortsuche nach `) . *`, `VerbundVonAusdruck`, `OeffnetAusdruck`, `AusdrucksWoerter`, `KeinZeilenalias`, `Rohname`. Vorher sah die Wortsuche keine Quelle hinter einem Komma: `SELECT (a).* FROM t1, t2` und `SELECT z FROM t1, t2 z` waren 42601. In `Durchgaenge` steht vor dem Schreiber kein Wortdurchgang mehr (nur `Gruppenstellen` und `ZeilenSterne`, beide aus dem Baum). Sabotage (Spaltentyp, nur erste Quelle) → rot✅
P18.5c6h3AlleTabellen aus dem Baum (Parse, Parametertypen)Gebaut: das Verzeichnis der Tabellen einer Anweisung (`Tabellenverzeichnis`: Quellen jeder Anfrage und Ziele von INSERT/UPDATE/DELETE/MERGE samt Alias aus dem Baum) gilt jetzt auch für die Parametertypen, die Prüfung der Konstanten bei Parse, die Spalten von Zeitfeldern und die Frage nach geometrischen Spalten; `AlleTabellen` (Wortsuche nach FROM, JOIN, UPDATE, INTO) entfällt samt drei toten Helfern (`JsonbSpalte`, `Unsichtbar`, `FeldSpalte`). Vorher nahm die Wortsuche `trim(both ' ' FROM spalte)` als Quelle: `` bekam den Typ der gleichnamigen Tabelle (text statt integer), die Prüfung bei Parse ebenso. Sabotage (kein Baum) → der neue Test und die r10a-Orakelfolge rot✅
P18.5c6h4Der PL/pgSQL-Rumpf als Baum, die Breite von RETURN QUERYGebaut: ein Leser des Rumpfs (`Rumpfleser`) liest die Gliederung vor dem Schreiben als Baum (`Pgbaum.Plblock`: Marke, Deklarationen als Spannen, Anweisungen, Ausnahmezweige; `Planweisung`: Block, IF/ELSIF/ELSE und CASE als Zweige, LOOP/WHILE/FOR/FOREACH mit Kopf und Rumpf, jede einfache Anweisung als Spanne bis zu ihrem `;`). `Rumpf` schreibt Blöcke, Zweige, Schleifen und Ausnahmen aus dem Baum (`Halt`, das Mitlesen von END IF/END LOOP/END CASE beim Schreiben und das Feld `_marke` entfallen) und liest eine einfache Anweisung ab ihrem ersten Wort; die eingebetteten Anfragen und Ausdrücke gehen wie bisher durch den Baum der Anweisung. Die Breite einer `RETURNS SETOF record`-Funktion kommt aus dem Baum: die Anweisungen `RETURN QUERY`, `SELECT … INTO r`, `FOR r IN …` aus dem Rumpf, die Spalten aus dem Baum der Anfrage samt `*` und `t.*` (vorher Kommas gezählt: `RETURN QUERY SELECT * FROM t` hatte eine Spalte, 42804 beim Aufruf); `AbfrageBreite` entfällt. Sabotage (Stern = eine Spalte) → drei Fälle rot✅
P18.5c6h5Wahrheitswert als Text: `true`/`false`**Erledigt (04.10.2026), gegen `postgres:17` gemessen (`SELECT true::text` ergibt `true`; `b || 'x'` ergibt `truex` und `format('%s', true)` `t`, Fixtures r12g/r11c):** `b::text`, `CAST(b AS varchar(n))`, `(1 > 2)::text`, `attnotnull::text` ergaben `1`/`0` — die Umwandlung in einen Texttyp schreibt einen Wahrheitswert (Konstante, Bedingung, Typ `boolean` aus c4) als `true`/`false` (`AlsText`), `format` nimmt `t`/`f` jetzt auch für Spalten und Bedingungen (vorher nur Konstanten). T-SQL unberührt. Sabotage: Umwandlung ohne `AlsText` → 3 Tests rot.✅
Meilenstein 19: Der Prüfstand — Beispiele, Handbuch, Installation, Roadmap (12/15)
PunktTitelBeschreibungStand
U19.1Die HülleEigenes Webprojekt in der Lösung (`tools/Kuonen.KuonenSQL.Playground`, Blazor Server), Startseite mit Übersicht aller Bereiche (die noch nicht gebauten als Platzhalter mit „kommt mit U19.x"), Verbindung zum Server (Adresse und Anmeldung einstellbar, Vorgabe der lokale Server), Hell/Dunkel, schmal bis breit bedienbar✅
U19.2Die Konsole je SpracheEingabe, Ausführen, Ergebnismengen als Tabelle, Meldungen und Fehler mit Nummer, Laufzeit — je Sprache über ihr **echtes Protokoll**: T-SQL über TDS (`Microsoft.Data.SqlClient`, derselbe Treiber wie SSMS/EF Core), MariaDB über den MariaDB-Draht (eigener Client auf den öffentlichen Paket-Bausteinen der Kernbibliothek), Redis über RESP (eigener Draht-Client). GraphQL und PostgreSQL sind noch nicht gebaut und sagen das mit dem Stand aus der Roadmap, ohne einen Verbindungsversuch; ein zugebauter Port (MariaDB voreingestellt aus) meldet sich als „Port aus"✅
U19.3Der BeispielkatalogBeispiele je Sprache und Thema (T-SQL: Tabellen, CRUD, Joins, Transaktionen, JSON, Vektoren, Sicherheit; MariaDB: was D17.1-D17.8 können — AUTO_INCREMENT/Typen, `IF NOT EXISTS`/`CREATE OR REPLACE`, `ON DUPLICATE KEY UPDATE`, `INSERT IGNORE`/`REPLACE`, `LIMIT`, `RETURNING`, `ALTER`/`RENAME`, JSON/ENUM; Redis: Zeichenketten, Hashes, Listen, Mengen, sortierte Mengen, Ablauf), jedes mit Erklärung, Code und hinterlegtem Ergebnis, mit einem Klick in der Konsole ausführbar. **Wächter:** `BeispielkatalogTests` führt den ganzen Katalog im Testlauf gegen einen im Test gestarteten Server (TDS/MariaDB-Draht/RESP) und verlangt das hinterlegte Ergebnis✅
U19.4Das Handbuch je AbfragespracheEine Seite je Sprache (T-SQL, MariaDB-SQL, PostgreSQL, Redis, GraphQL): Syntax der Anweisungen, Datentypen, Funktionen, Fehlernummern — jeder Abschnitt mit ausführbarem Beispiel aus U19.3 und dem Stand je Punkt aus der Roadmap (was geht, was nicht, was benannt abgelehnt wird). **Wächter:** jeder in der Roadmap als fertig geführte Punkt hat einen Handbuchabschnitt✅
U19.5InstallationDocker (`docker run`, das Compose-File, Volumes, Umgebungsvariablen, Lizenzdatei, TLS-Zertifikat, welche Ports man freigibt), die Einzeldatei für Windows/Linux/macOS, das Anmelden beim ersten Start. **Wächter:** die Befehle auf der Seite stimmen mit `docker/` und den Umgebungsvariablen des Servers überein✅
U19.6Zugriff: Werkzeuge und PortsEine Tabelle Port → Protokoll → Werkzeuge: 1433 (SSMS, sqlcmd, Azure Data Studio, DBeaver, `Microsoft.Data.SqlClient`, EF Core), 3306 (`mariadb`-CLI, HeidiSQL, MySqlConnector), 5432 (psql, pgAdmin, Npgsql), 6379 (`redis-cli`, StackExchange.Redis), GraphQL (Insomnia, Banana Cake Pop), REST; je Werkzeug die Verbindungszeichenfolge zum Kopieren und ob der Port voreingestellt an oder aus ist. Die Portangaben kommen aus derselben Stelle, an der der Server sie liest✅
U19.7Version und RoadmapDie **laufende** Fassung (Produktversion aus der Programmdatei, nicht die feste `AssemblyVersion` — die Lehre aus Kuonen.PDFTools M1415), Datum und Commit des Baus, die Liste der Änderungen je Version aus dem git-Verlauf; dazu die Roadmap als Seite: Meilensteine mit Stand und Fortschritt, aufklappbar bis auf die einzelnen Punkte, erzeugt aus `docs/roadmap.md` selbst, damit Seite und Datei nie auseinanderlaufen✅
U19.8Im Docker-AbbildDer Prüfstand als zweiter Einstiegspunkt im selben Abbild, eigener Dienst im Compose-File, eigener Port; im Browser gegen einen frisch gestarteten Container abgenommen✅
U19.9SPARQL-Endpunkt (RDF-Sicht)**Nutzerwunsch 29.09.2026, Idee aus dotNetRDF.** Ein eigener Endpunkt nach *SPARQL 1.1 Protocol* (HTTP, wie GraphQL in M16 über die gemeinsame Annahmeschleife), der Tabellen und die Graph-Tabellen von SQL Server (`AS NODE`/`AS EDGE`, `MATCH`) als RDF zeigt: Abbildung Zeile → Subjekt-URI, Spalte → Prädikat, Fremdschlüssel/Kante → Verweis (nach W3C *Direct Mapping*). `SELECT`/`ASK`/`CONSTRUCT`/`DESCRIBE`, Ergebnisse als SPARQL-JSON/XML und Turtle/N-Triples; übersetzt auf denselben Syntaxbaum und Ausführer wie T-SQL, dieselben Berechtigungen (S12.17). **Kein** fremdes Paket im Kern — dotNetRDF (MIT) höchstens als Orakel im Test (dieselbe Abfrage über dessen In-Memory-Store gegen dieselben Tripel). Dazu **SPARQL 1.1 Update** (vom Nutzer am 29.09.2026 bestätigt): `INSERT DATA`, `DELETE DATA`, `DELETE/INSERT … WHERE`, `DELETE WHERE` — abgebildet auf `INSERT`/`UPDATE`/`DELETE` der zugrunde liegenden Tabellen in **einer** Transaktion je Anfrage, mit denselben Berechtigungen und Einschränkungen (Fremdschlüssel, CHECK) wie T-SQL; ein Tripel, das sich auf keine Spalte abbilden lässt, wird benannt abgelehnt statt still verworfen. Setzt M16 (Endpunkt-Muster) voraus **Stand:** fertig: `/sparql[/<db>]` am HTTP-Port des GraphQL-Endpunkts (dieselbe Anmeldung, Rechte S12.17, Zeitgrenze, Gleichzeitigkeit), RDF-Sicht nach dem W3C-Direct-Mapping (Zeile/Spalte/Fremdschlüssel, Graphknoten und -kanten), SELECT/ASK/CONSTRUCT/DESCRIBE mit BGP, FILTER, OPTIONAL, UNION, MINUS, [NOT] EXISTS, BIND, VALUES, Aggregate, ORDER/LIMIT/OFFSET, übersetzt auf T-SQL für denselben Ausführer (kein zweiter); SPARQL-JSON/XML, CSV/TSV, Turtle/N-Triples; Update (INSERT/DELETE DATA, DELETE/INSERT WHERE, DELETE WHERE) in einer Transaktion, nicht Abbildbares benannt abgelehnt; eigener Parser ohne Paket (Tiefe 64, 20 000 Knoten, 512 Zweige). Geprüft an den Beispielen der W3C-Empfehlungen (Direct Mapping: genau die 12 Tripel) und eigenen Tabellen (53 Tests). Container: Port 4000 im Compose, mit `KUONENSQL_GRAPHQL_PORT=4000` an; Prüfstand-Zugriffstabelle nennt /sparql. Herleitung in docs/umsetzung.md.✅
U19.10Produktseite und Installation als Dienst**Nutzerwunsch 13.10.2026.** Die Startseite stellt das Produkt vor (Zugänge mit Ports aus den Konstanten, Merkmale, Messwerte aus L6.1/L6.2, Fortschritt aus der Roadmap), neues Design hell/dunkel bis Handybreite. Die Installation führt Schritt für Schritt durch Docker (fertiges Abbild, benanntes Volume, compose.yaml) und als Dienst unter Linux (systemd), macOS (launchd) und Windows (WinSW); die Dienstdateien liegen in dist/dienst und stehen eingebettet auf der Seite. Unterwegs gefunden: die komprimierte Einzeldatei stürzt unter macOS bei jedem zweiten Start ab (Laufzeit .NET 10.0.6), darum dort unkomprimiert; das Bauskript lief nur unter Windows.✅
U19.10aWindows-Dienst ohne FremdwerkzeugDer Server meldet sich selbst bei der Dienstverwaltung von Windows an (heute über WinSW, U19.10). Braucht Win32-Aufrufe hinter OperatingSystem.IsWindows() - mit AGENTS.md §3 abzuwägen.⬜
U19.10bPostgreSQL in der KonsoleDer Reiter PostgreSQL der Konsole des Prüfstands spricht das Protokoll noch nicht (der Server schon, M18); wie bei MariaDB über die Bausteine der Kernbibliothek.⬜
U19.10cEinzeldateien je Fassung veröffentlichenDie Einzeldatei je Plattform (win/linux/osx, x64/arm64) mit jeder Fassung an der GitHub-Veröffentlichung, geschützt wie das Abbild (signiert, verschleiert, Lizenzprüfung) - heute nur aus dem Quellstand, ungeschützt.⬜
U19.11Gemeinsame Webseite mit dem Studio**Nutzerwunsch 15.10.2026.** Der Prüfstand zieht ohne Konsole in die Webseite des MultiSQLManager (`manager/src/Kuonen.MultiSQLManager.Webseite`, https://multisql.kuonen.org): Installation, Zugriff, Handbuch, Beispiele, Roadmap und Versionen von Server und Studio auf einer Seite im Design der Webseite (statisch, ohne Skript). Das Server-Abbild, `docker/docker-compose.yml` und `dist/dienst/docker/compose.yaml` enthalten keinen Prüfstand mehr; die Wächter (Beispielkatalog, Handbuch, Installation, Zugriff, Produkt, Dienst) laufen weiter im Server-Testprojekt (`tests/…/Webseite/`). Spec `docs/manager/superpowers/specs/2026-10-15-gemeinsame-webseite-design.md`.✅
U19.12Eine Ordnerstruktur für Server und Studio**Nutzerwunsch 15.10.2026.** `src/Global` (Xml, SqlTypes, Licensing, Data.Gemeinsam), `src/Server`, `src/Manager`, `tests/Server`, `tests/Manager`; Doku, Skripte und Docker des Studios unter `docs/manager`, `dist/manager`, `docker/manager`, die Erweiterung unter `vscode-erweiterung`; der Ordner `manager/` ist aufgelöst. `Kuonen.SQL.slnx` zeigt die Struktur in Visual Studio 2026 und VS Code. Die Patch-Zahl zählt weiter nur Server-Commits; Wächter `StrukturTests` und `ProjektmappeTests`. Spec `docs/manager/superpowers/specs/2026-10-15-ordnerstruktur-design.md`.✅
Meilenstein 20: Hochverfügbarkeit — Übernahme und lesbare Kopien (8/8)
PunktTitelBeschreibungStand
H20.1Übernahme auf denselben Dateien (Aktiv/Passiv)Mietvertrag `besitz.lease` im Datenordner (Besitznummer, Inhaber, Herzschlag; Lesen-Prüfen-Schreiben unter der Sperrdatei `besitz.lease.sperre`): die wartende Instanz übernimmt, wenn der Herzschlag eine volle Dauer stillsteht oder der Inhaber beim Beenden freigibt, spielt das WAL ein und öffnet erst dann die Ports. Fencing: vor jedem WAL-Schreiben, Kürzen und Kontrollpunkt prüft die Instanz Frist (Dauer/2 seit der letzten Erneuerung) und Besitznummer in der Datei; eine abgesetzte Instanz schreibt keinen Satz mehr und fährt herunter. `--uebernahme[=ms]` bzw. `KUONENSQL_UEBERNAHME`/`KUONENSQL_UEBERNAHME_DAUER_MS` (). Belegt: `MietvertragTests`, `UebernahmeProzessTests` (zwei Serverprozesse, harte Beendigung der aktiven)✅
H20.1bNetzlaufwerk für die Übernahme messenGemessen mit Docker statt zwei Rechnern (`dist\ha-netzlaufwerk.ps1`: Datei-Server aus `debian:bookworm-slim` mit Samba und NFSv4 des Kerns, zwei KuonenSQL-Container, je Seite ein eigener NFS-/SMB-Client): Sperre, Herzschlag, `docker kill` mitten im Schreiben (alle bestätigten Zeilen da) und Netztrennung samt Wiederverbinden (ein Schreiber) auf NFSv4.2 und SMB 3.1.1. Gefunden und behoben: NFS las den Vertrag über den offenen Griff aus dem Zwischenspeicher (zwei Schreiber gemessen) → unter der Sperre je Zugriff neu öffnen; .NET-`FileShare.None` sperrt auf SMB nicht (`EACCES`) → eigenes `flock`; lange gehaltene Sperrdatei liess die wartende Instanz abstürzen → sie wartet weiter (, )✅
H20.2Lesbare Kopien über den WAL-Strom**Gebaut (2026-10-04):** eigenes Replikationsprotokoll über TCP (`KUONENSQL_KOPIEN_PORT` auf der Hauptinstanz, `KUONENSQL_KOPIE_VON`/`_ANMELDUNG`/`_KENNWORT`/`_ADRESSE`/`_ZERTIFIKAT` auf der Kopie; nur BCL, jede Länge begrenzt, Anmeldung mit Kennwort aus sysadmin samt Anmeldebremse, TLS mit dem Serverzertifikat und Fingerabdruck-Prüfung). Erstbefüllung aus einem Abbild (Kontrolldatei, Datendateien, Protokoll bis zu einer Stelle ohne laufenden Stapel), danach fortlaufend; nach einem Unterbruch weiter ab der letzten bestätigten Stromposition. Verworfene Stapel (PR27.1b) erreichen keine Kopie. Die Kopie ist nur lesbar: 3906 wie am Orakel gemessen (Schwere 16, Zustand 1), `#`-Tabellen gehen. `ApplicationIntent=ReadOnly` wird per ENVCHANGE Routing (Typ 20 hinter dem LOGINACK, MS-TDS 2.2.7.9) reihum an eine verbundene Kopie geleitet. `sp_kuonensql_befoerdern` macht eine Kopie zur Hauptinstanz (offene Transaktionen der alten zurück, Kontrollpunkt, dann schreibbar). Asynchron. `LesbareKopienTests` (10)✅
H20.2bKopien: automatische Übernahme und synchroner Modus**Gebaut (2026-10-05):** synchroner Modus (`KUONENSQL_KOPIE_SYNCHRON`: ein schreibender Stapel endet erst mit der Quittung jeder synchronisierten Kopie; nach `KUONENSQL_KOPIEN_FRIST_MS`, Vorgabe 10 s wie `SESSION_TIMEOUT`, fällt eine säumige heraus). Automatische Übernahme über einen Zeugen (`KUONENSQL_ZEUGE_PFAD`, Mietvertrag und Besitznummer aus H20.1): die Hauptinstanz hält dort den Vertrag und führt die synchronisierten Kopien; schweigt sie eine Dauer, befördert sich eine aufgeführte Kopie (`KUONENSQL_KOPIE_UEBERNAHME`) mit Besitznummer + 1, die alte schreibt keinen Satz mehr. Die übrigen Kopien versuchen `KUONENSQL_KOPIE_VON` reihum, holen aus dem Vorrat der beförderten nach und nehmen am Laufwechsel zurück, was die alte offen ließ. `LesbareKopienTests`, `UebernahmeProzessTests` (zwei Serverprozesse, Zeuge)✅
H20.2cKopien: Reste**Gebaut (2026-10-05):** Wiederaufnahme über Neustarts: die Stromposition zählt die Protokollbytes über Kürzen und Neustarts (`Strombasis` in der Kontrolldatei); nach einem Neustart der Hauptinstanz macht die Kopie bis zum Ende des vorigen Laufs weiter (verpasste Sätze hält der Kontrollpunkt für bekannte Kopien bis zur Haltefrist im Protokoll) und nimmt am `Laufwechsel` zurück, was jener offen ließ; die Kopie setzt nach ihrem Neustart bei ihrem letzten Kontrollpunkt fort (Stand im Strom in der Kontrolldatei, nur ohne offene Folge). Schreibschutz auch für Systemprozeduren, die den Katalog ohne Anweisung ändern (`sp_addrolemember`, erweiterte Eigenschaften, `sp_rename`, Serverprozeduren, CDC, Änderungsstrom: 3906). Lesen auf der Kopie wie auf der sekundären Replika unter SNAPSHOT (nachspielende Sitzungen halten Zeilensperren samt bestätigtem Bild). `LesbareKopienTests` (20 mit H20.2b/d). Die Lastmessung steht als H20.2e✅
H20.2dKopien: Stapel vor der Erstbefüllung**Gebaut (2026-10-05):** die Erstbefüllung nimmt die Stapelsperre erst an einer Stelle, an der keine offene Transaktion mit einem schon festschreibenden ersten Glied steht (`HaeltGemischteFolge`, wie beim unscharfen Kontrollpunkt), sonst wartet sie bis zu 30 s und die Kopie versucht es erneut. Vorher stand die erste Zeile auf der Kopie doppelt. `LesbareKopienTests.Ein_Stapel_Der_Festschreibt_Und_Offen_Laesst_Reicht_Nicht_Ueber_Die_Erstbefuellung`✅
H20.2eKopien: Leselast messen**Gemessen (2026-10-07):** `tools/kopienlast` startet Hauptinstanz und zwei Kopien und misst je 30 s Leser direkt und mit `ApplicationIntent=ReadOnly` (reihum weitergeleitet, 4/4 bzw. 8/8). 8 Leser: 13 970 → 19 211 Lesungen/s (+38 %), 16 Leser: 13 621 → 22 797/s (+67 %); Median 0,51 → 0,39 ms bzw. 1,11 → 0,60 ms; Kopie im Median 4–5 ms hinter der Hauptinstanz (höchstens 27 ms), Schreiben unverändert 50/s (`docs/leistung.md` §16). Befund: eine Instanz allein skaliert nicht von 8 auf 16 Leser — H20.2f✅
H20.2fLeselast: Nebenläufigkeit einer InstanzEine Instanz liest mit 16 Lesern nicht mehr als mit 8 (13 621 gegen 13 970 Lesungen/s, Median 1,11 statt 0,51 ms) und nutzt dabei rund drei von 32 Kernen (H20.2e, `tools/kopienlast`): den Engpass suchen (Sperre je Anweisung, gemeinsamer Puffer, Netzschicht?) und vorher/nachher messen. **Erledigt 08.10.2026:** der Engpass waren Monitore, die jedes Lesen nahm - die Tabelle selbst (jetzt Lese-/Schreibsperre), dazu Zähler und Verzeichnisse mit `ConcurrentDictionary.Count`/`.Values` je Anweisung (`docs/leistung.md` §17). Über TDS 16 Leser 13 621 → 26 433 Lesungen/s, 8 Leser 13 970 → 16 057/s; ohne Netz 16 Fäden 8 632 → 25 744/s. Test `TabellensperreTests`.✅
Meilenstein 26: Jeder Zugang gleichwertig — parallel auf derselben Datenbank (40/40)
PunktTitelBeschreibungStand
J26.1Tabellen und Schema über jeden Zugang**Erledigt (04.10.2026):** Kreuztest-Matrix `ObjektKreuzTests` — 33 Änderungsarten (Tabelle anlegen/umbenennen/löschen, Spalte hinzufügen/umbenennen/Typ/Vorgabe/löschen, Index anlegen/eindeutig/umbenennen/löschen, Primär-, Fremd-, Eindeutig- und Prüfschlüssel samt Löschen, Sicht anlegen/ändern/umbenennen/löschen, Prozedur, Funktion, Trigger, Folge anlegen/ändern/umbenennen/löschen, Schema) je über T-SQL, MariaDB und PostgreSQL angelegt, in den beiden anderen im Katalog gesehen (`sys.*`, `information_schema`, `pg_catalog`) und benutzt bzw. als Schlüssel wirksam. Behoben: `DROP CONSTRAINT` des Primärschlüssels liess ihn unter erzeugtem Namen wieder entstehen; Zeilen- und BEFORE-Trigger aus PostgreSQL liefen beim Schreiben über T-SQL/MariaDB nicht (NullReference) — sie tragen die Form ihres Dialekts; Trigger aus PostgreSQL heissen in T-SQL/MariaDB wie in PostgreSQL (`Anzeigename`, Suche über beide Formen), Trigger aus T-SQL/MariaDB stehen in `pg_trigger`/`information_schema.triggers`; PostgreSQL `CREATE OR REPLACE VIEW`, `ALTER INDEX/VIEW/SEQUENCE … RENAME TO`; MariaDB-Folgen (`NEXTVAL`, `LASTVAL`, `PREVIOUS VALUE FOR`, Vorgaben von `CREATE SEQUENCE`, `TABLE_TYPE SEQUENCE`, `SHOW FULL TABLES`); `sp_rename`/`RENAME TABLE` einer Folge✅
J26.1aMariaDB: Folge in `information_schema.TABLES`**Erledigt (04.10.2026), gegen `mariadb:11.8` gemessen** (`TABLE_TYPE=SEQUENCE, ENGINE=InnoDB, VERSION=10, ROW_FORMAT=Fixed, TABLE_ROWS=1, AVG_ROW_LENGTH=16384, DATA_LENGTH=16384, INDEX_LENGTH=0`; `SHOW FULL TABLES` → `SEQUENCE`): einzig `ROW_FORMAT` wich ab (`Dynamic` → `Fixed`), alles andere stimmte schon. Test `Folge_in_Tables_wie_MariaDB`; Sabotage: anderer Wert → rot.✅
J26.2Datentypen über die Zugänge: die gemeinsamen Typen**Gebaut in M17 und M18:** jeder Typ, den SQL Server 2025 hat, ist in allen drei Zugängen dieselbe Spalte der gemeinsamen Ablage; MariaDB und PostgreSQL übersetzen ihre Namen auf den nächsten SQL-Server-Typ (`INT`, `int4`, `VARCHAR`, `text`, `DATETIME`, `timestamp`, `uuid` …) und melden ihn auf ihrer Leitung in ihrer Form zurück. Die Typen, die SQL Server 2025 **nicht** hat, und der Kreuztest je Typ stehen als Scheiben in der Untertabelle J26.2a bis J26.2p.✅
J26.3Datenbank-Einstellungen**Erledigt (04.10.2026):** einzige Wahrheit ist die Datenbank der Engine (`sys.databases`). **Sortierung:** `ALTER DATABASE … COLLATE` aus T-SQL zeigt MariaDB als Standardsortierung (`information_schema.SCHEMATA`, `@@collation_database`, `SHOW CREATE DATABASE`; binär `utf8mb4_bin`, CS `utf8mb4_uca1400_as_cs`, CI `…_as_ci`/`…_ai_ci`, die Vorgabe des Servers als `utf8mb4_uca1400_ai_ci`) und PostgreSQL als `datcollate` (binär `C`); MariaDBs `CREATE/ALTER DATABASE [name] … COLLATE` (auch ohne Namen) setzt die Sortierung der Engine (`_bin` → `Latin1_General_100_BIN2_UTF8`, `_cs` → `…_CS_AS_SC_UTF8`, `_ci` → `…_CI_AI_SC_UTF8`); PostgreSQLs `CREATE DATABASE … LC_COLLATE 'C'` ist binär; neue Spalten jedes Zugangs vergleichen danach. **Optionen:** `SET READ_ONLY`/`READ_WRITE` wirkt jetzt (vorher ohne Wirkung) und weist Änderungen über jeden Zugang ab (3906, MariaDB 1290, PostgreSQL 25006). **Ohne Gegenstück** (bleiben im eigenen Zugang): Kompatibilitätsgrad (T-SQL), `ALTER DATABASE … SET param` (PostgreSQL, gilt für neue PostgreSQL-Sitzungen, in T-SQL als erweiterte Eigenschaft sichtbar). `DatenbankEinstellungKreuzTests` (4)✅
J26.4Gleichzeitiges Arbeiten über die Protokolle**Erledigt (04.10.2026):** `GleichzeitigKreuzTests` (18) mit gleichzeitigen Sitzungen auf TDS, MariaDB und PostgreSQL derselben Engine: eine Zeilensperre hält das `UPDATE` des anderen Zugangs auf (alle sechs Paare, eine andere Zeile bleibt frei), Lesesperren (T-SQL `REPEATABLE READ`, MariaDB/PostgreSQL `SELECT … FOR UPDATE`) halten Schreiber anderer Zugänge auf, eine gegenseitige Blockade über zwei Ports wird erkannt und genau ein Opfer in der Form seines Zugangs gemeldet (1205/1213/40P01), Unbestätigtes sieht kein anderer Zugang, Bestätigtes sofort. Sperren, Transaktionen und Blockadeerkennung gehören der Engine — es war nichts zu bauen; Sabotage (Warten der Sperrverwaltung ausgeschaltet) macht 11 der Fälle rot✅
J26.5Werkzeuge im Wechsel**Erledigt (04.10.2026):** `WerkzeugWechselTests` (9: sechs Richtungen und GraphQL): SSMS (Skript des Tabellenentwurfs über TDS), phpMyAdmin/Adminer (`CREATE TABLE … AUTO_INCREMENT … ENGINE = InnoDB` über MariaDB) und pgAdmin (`CREATE TABLE IF NOT EXISTS public.… GENERATED BY DEFAULT AS IDENTITY`, `OWNER to`) legen je eine Tabelle an; jedes andere Werkzeug liest sie mit seinen Katalogabfragen (SSMS `sys.tables`/`sys.all_columns`/`sys.indexes`, phpMyAdmin `information_schema.TABLES`/`COLUMNS` mit `COLUMN_TYPE`, `COLUMN_KEY`, `EXTRA`, pgAdmin `pg_class`/`pg_attribute`/`pg_constraint`) — Name, Spalten im eigenen Typnamen, Primärschlüssel, Zähler (T-SQL IDENTITY und AUTO_INCREMENT erscheinen in pgAdmin als serial mit eigener Folge) — und füllt sie über den Zähler der Tabelle; Ändern und Löschen deckt die Matrix aus J26.1; die GraphQL-Schnittstelle (ohne DDL) fragt die Tabelle jedes Werkzeugs sofort ab (Schema neu nach jeder Katalogänderung). Der Rundgang an den Oberflächen von Hand gehört zu den Läufen (LL23.2)✅
J26.6Ein Einstellungsmodell für alle Zugänge — Kern und erste geteilte Einstellung**Erledigt (04.10.2026):** `Catalog/Servereinstellungen` ist der eine Speicherplatz der Servereinstellungen (eingestellt/laufend je Schlüssel, dauerhaft über den Kontrollpunkt, alte Datendateien lesbar); `sp_configure`/`sys.configurations` laufen darüber. Erste Alias-Gruppe mit Wirkung: Höchstzahl der Verbindungen = T-SQL `user connections` (0 = Grenze von KuonenSQL, 32 767) = MariaDB `max_connections` (10–100 000) = PostgreSQL `max_connections` (1–262 143); gesetzt über `sp_configure`+`RECONFIGURE`, `SET GLOBAL` oder `ALTER SYSTEM`, gelesen über `sys.configurations`, `@@GLOBAL.`/`SHOW VARIABLES`, `SHOW`/`pg_settings`; ein Wert über 32 767 wird der grösste mögliche. Wirkung über jeden Zugang mit seiner Meldung (17809 / 1040 08004 / FATAL 53300). Tests `EinstellungKreuzTests` (Kreuz, Wirkung, Neustart). Rest J26.6a–J26.6f.✅
J26.6aServerfakten aus der Engine über jeden Zugang**Erledigt (05.10.2026):** `Catalog/Serverfakten` ist die eine Quelle; der Host meldet je Zugang Port, TLS und TLS-Pflicht. T-SQL `sys.dm_tcp_listener_states`, `xp_instance_regread` (TcpPort, ForceEncryption), `CONNECTIONPROPERTY('local_tcp_port')`/`sys.dm_exec_connections` (Endpunkt der Verbindung); MariaDB `@@port`, `@@datadir`, `@@basedir`, `@@have_ssl`, `@@require_secure_transport`, `@@max_allowed_packet` (256 MiB, die echte Grenze), `@@version_comment` mit eigener Version; PostgreSQL `port`, `listen_addresses`, `ssl`, `data_directory`, Konfigurationsdateien leer. Ein Zugang ohne Hörer zeigt Port 0 und kein TLS. Tabelle in `umsetzung.md`, Test `ServerfaktenTests`.✅
J26.6bWeitere gleichbedeutende serverweite Einstellungen als Aliasse**Erledigt (05.10.2026):** Einstellungen des Kerns mit eigenem Schlüssel (dauerhaft, `einstellungenKern`), Umrechnung je Name einmal aufgelöst (Faktor, Stellen, Rand → nächster Wert), je Zugang ein `FrozenDictionary`. Neue Gruppen mit Wirkung: Anweisungszeitgrenze MariaDB `max_statement_time` (s, 6 Stellen) = PostgreSQL `statement_timeout` (ms) — MariaDB bricht mit 1969 ab; Leerlauf in einer Transaktion MariaDB `idle_transaction_timeout` (s) = PostgreSQL `idle_in_transaction_session_timeout` (ms) — MariaDB schliesst die Verbindung. MariaDB-Sitzungen übernehmen den globalen Wert beim Anmelden (`SET SESSION`, `DEFAULT`), PostgreSQL liest ihn ohne eigenes `SET`; `ALTER SYSTEM` prüft wie PostgreSQL; `pg_settings.setting` in der Grundeinheit mit `unit`. Nicht gleichbedeutend (begründet in `umsetzung.md`): `lock_wait_timeout`, `wait_timeout`/`idle_session_timeout`, `connect_timeout`/`authentication_timeout`, `read_only`. Test `EinstellungAliasTests`.✅
J26.6b1Messung der neuen Aliasse am MariaDB-Referenzcontainer**Erledigt (05.10.2026):** gemessen und eingearbeitet — `@@max_statement_time` DOUBLE mit 6 Stellen, `@@idle_transaction_timeout` LONGLONG; `SLEEP()` unter `max_statement_time` endet mit 1969 (vorher 1); nach `idle_transaction_timeout` schliesst der Server ohne Paket (Client 2006), wie KuonenSQL. Test `EinstellungAliasTests`.✅
J26.6cDialekteigene Variablen im Kern**Erledigt (05.10.2026):** jeder Dialekt meldet seine Variablen im Modulinitialisierer an (`mariadb.<name>`, `postgresql.<name>`, eigene Vorgabe, dauerhaft), nicht zusammengelegt; MariaDB alle 703 Systemvariablen des Referenzcontainers (`systemvariablen.tsv`, Umgebungswerte ersetzt), PostgreSQL jeder Parameter von `pg_settings`. Umgestellt mit Wirkung für neue Sitzungen: MariaDB `transaction_isolation`/`tx_isolation`, `innodb_lock_wait_timeout`, `sql_mode` (`SET GLOBAL` ändert die Sitzung nicht mehr), `autocommit`, `time_zone`; PostgreSQL `lock_timeout`, `default_transaction_isolation`, `DateStyle`/`IntervalStyle`/`TimeZone` und jeder vom Anschluss gelesene Parameter. Übrige „ohne Wirkung“ (setzbar, lesbar, dauerhaft); 1238 nur lesbar, 1231 ungültig, 1229 nur global; `SHOW VARIABLES` vollständig und geordnet; Sichten nur mit eigenen Namen; Modell einmal aufgelöst, MariaDB-Anmeldung kopiert nur Geändertes. Test `EinstellungDialektTests`.✅
J26.6c1MariaDB: Bereiche und Aufzählungswerte der Systemvariablen**Erledigt (05.10.2026):** gemessen und in `systemvariablen.tsv` aufgenommen; Zahlen in den Bereich gelegt und auf die Schrittweite abgerundet mit Warnung 1292, Aufzählungen und `SET` je Teil geprüft (1231, 42000), `innodb_lock_wait_timeout` 0–100 000 000 (0 = nicht warten); eine Zeitgrenze über 24 Tagen bringt die Verbindung nicht mehr zum Absturz. Test `EinstellungDialektTests`.✅
J26.6dFremde Variablen über Dialektpräfix und Gesamtsicht**Erledigt (05.10.2026):** jede Einstellung jedes Zugangs mit Präfix `mssql.`, `mariadb.`, `postgresql.` (die Zeilen von `sys.configurations` sind jetzt T-SQL-Namen im Modell). T-SQL `sp_kuonensql_einstellung [@name [, @wert]]` (lesen wie `sp_configure`, setzen sofort; `ALTER SETTINGS`, unbekannt 15123, ungültig 15129); PostgreSQL `SHOW "mariadb.x"`, `current_setting`, `SET`/`set_config`/`RESET` (setzen den globalen Wert, nur Superuser), `ALTER SYSTEM SET "mssql.user connections"`; MariaDB `` SET GLOBAL `postgresql.x` ``, `` @@GLOBAL.`mssql.x` ``; geprüft vom Zielzugang. Gesamtsicht `sys.kuonensql_einstellungen` / `kuonensql.einstellungen` (dialekt, name, gruppe, typ, minimum, maximum, einheit, wert, vorgabe, wirkung, nur_lesen, nur_global, zugang_aktiv) aus jedem Zugang, lesbar für jeden; Funktion `KUONENSQL_EINSTELLUNG('präfix.name')` liest beim Ausführen (auch `current_setting` liest jetzt den globalen Wert). Einen Zugang ohne Hörer stellt nur sysadmin ein. Eigene Sichten bleiben sauber. Test `EinstellungFremdTests`.✅
J26.6eAlles über SSMS 22 einstellbar**Erledigt (05.10.2026):** geprüft mit SMO, derselben Bibliothek, mit der SSMS 22 die Dialoge füllt und speichert (Testpaket `Microsoft.SqlServer.SqlManagementObjects`, nur in den Tests): „Servereigenschaften“ (Information, Settings, UserOptions, Server, jede Zeile von `Configuration`) und „Datenbankeigenschaften“ (Database, DatabaseOptions) laden ohne Fehler — ausgenommen nur, was SMO auch am Orakel (SQL Server 2025, nur gelesen) nicht liefert (Azure-, AG-, Host-Angaben); Speichern auf der Seite „Verbindungen“ schreibt in den gemeinsamen Wert (MariaDB/PostgreSQL `max_connections`). `sp_configure 'präfix.name', wert` + `RECONFIGURE` stellt Zahlen und Schalter jedes Zugangs ein (Grenzen, eingestellt/laufend), Text über `sp_kuonensql_einstellung`. Die Seite „Optionen“ speichert jetzt (`ALTER DATABASE … SET ANSI_NULLS …`, zwölf Ein/Aus-Optionen, `sys.databases`, dauerhaft, in Sicherungen). Bewusst ohne fremde Zeilen in `sys.configurations` (Nutzervorgabe: eigene Sichten nur mit eigenen Namen). Test `SsmsEinstellungenTests`.✅
J26.6fFehler der Einstellungen je Dialekt vollständig**Erledigt (05.10.2026):** MariaDB 1228 (`SET GLOBAL` einer reinen Sitzungsvariable, Bereich `SESSION ONLY` gemessen), 1232 (Zeichenkette für eine Zahl) neben 1193, 1227, 1229, 1231 (42000), 1238; PostgreSQL 55P02 für interne Parameter bei `ALTER SYSTEM`, ein `postmaster`-Parameter gilt erst nach dem Neustart (`SHOW` zeigt bis dahin den alten Wert, `pg_settings.pending_restart` = t, dauerhaft); T-SQL `RECONFIGURE` mit 5808 (`allow updates`) und 5807 (Wiederherstellungsintervall über 60 Minuten, mit `WITH OVERRIDE` erlaubt) neben 15123, 15129, 15247. Test `EinstellungFehlerTests`.✅
J26.6gGenerierte Spalten über MariaDB und MySQL**Erledigt (05.10.2026):** typ [GENERATED ALWAYS] AS (ausdruck) [VIRTUAL/STORED/PERSISTENT] mit NULL/NOT NULL, UNIQUE, COMMENT, INVISIBLE in CREATE TABLE und ALTER TABLE … ADD (auch AFTER/FIRST): die Spalte rechnet mit ihrem vereinbarten Typ, Spalten und CHECK dahinter gehen nicht mehr verloren; INSERT ohne Spaltenliste zählt sie mit, DEFAULT (MariaDB auch NULL) rechnet, ein eigener Wert ist MySQL 3105 / MariaDB 1906; PERSISTENT bei MySQL 1064; SHOW CREATE TABLE und EXTRA nennen Art und INVISIBLE. NULL/NOT NULL an der Generierung bei MariaDB 1064; Dezimalzahlen runden in ganzzahlige Spalten (1.6 → 2). Beide Orakel gemessen (MySQL 8.4, MariaDB 11.8). Test MariaDbGenerierteSpaltenTests.✅
J26.2aNetzadressen**Erledigt (02.10.2026), gegen `postgres:17` (Fixture `j26a-orakel.txt`, 84 Anweisungen, Zeile für Zeile gleich) und `mariadb:11.8` gemessen:** PostgreSQL `inet`, `cidr`, `macaddr`, `macaddr8` und MariaDB `INET4`, `INET6` als eigene Typen im Modul `Erweiterungstypen` (T-SQL-Name, Methoden `a.Masklen()`, `inet::Parse`, `sys.types`/`sys.assembly_types`, UDT über TDS). Ein- und Ausgabeform und Ordnung wie PostgreSQL bzw. MariaDB (IPv4 in `INET6` wird abgebildet), Funktionen von PostgreSQL (`host`, `masklen`, `network`, `broadcast`, `netmask`, `hostmask`, `family`, `abbrev`, `set_masklen`, `text`, `inet_same_family`, `inet_merge`, `trunc`, `macaddr8_set7bit`) und seine Operatoren (enthalten in, enthält, überlappt, bitweises Und, Oder, Nicht, Addition und Subtraktion), Binärform mit den Fehlern 08P01 und 22P03, `pg_type` mit `regproc`-Namen. MariaDB: `INET6_ATON`, `INET6_NTOA`, `IS_IPV4`, `IS_IPV6`, `IS_IPV4_COMPAT`, `IS_IPV4_MAPPED`, `HEX`, CAST ohne Wert als NULL mit 1292, nicht strenger Modus, 1292 und 4078 mit Spalte. Kreuztest sechs Typen in allen sechs Richtungen samt der drei Kataloge✅
J26.2b`interval`**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26b-orakel.txt`, Zeile für Zeile gleich):** `interval` als eigener Typ im Modul `Erweiterungstypen` (Monate, Tage, Mikrosekunden getrennt; T-SQL-Name, `sys.types`/`sys.assembly_types`, UDT über TDS). Ordnung und Gleichheit wie `interval_cmp` auch in `DISTINCT`, `GROUP BY`, `UNION`, `VALUES` und Index; Eingabe nach `DecodeInterval` (SQL-Standard-Formen, doppelte Felder, Überlauf 22008), unendliche Werte, Rechnen mit Zeitpunkten auch an Spalten (`date ± interval` ist `timestamp`), `*`, `/`, `avg` wie PostgreSQL 17, `justify_*`. Feldangabe und Genauigkeit als Typmodifizierer (`YEAR TO MONTH`, `INTERVAL(p)`, `interval '…' SECOND(0)`, `CAST(… AS interval DAY TO SECOND(3))`) in `RowDescription`, `pg_attribute`, `format_type`, `information_schema`; Binärform; `to_char`, `extract`, `date_part` über eine Intervall-Spalte. MariaDB kennt keinen Intervall-Spaltentyp: dort als Zeichenkette mit erweiterten Metadaten. Kreuztest in allen sechs Richtungen samt der drei Kataloge✅
J26.2cArrays**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26c-orakel.txt`, Zeile für Zeile gleich):** jedes Array als eigener Typ je Elementtyp (`_int4`, `_text` … 26 Typen, Modul `Erweiterungstypen`) statt Text mit Marke: mehrdimensional und mit Untergrenzen ungleich 1, Ordnung und Gleichheit wie `array_cmp`, Typmodifizierer des Elements (`varchar(3)[]`) und `attndims`; Operatoren (`@>`, `<@`, `&&`, Verkettung über Dimensionen, Elementzugriff, Ausschnitte, `SET a[i] = x`) auch an Ausdrücken ohne sichtbaren Typ, Binärform je Elementtyp, `unnest`, `array_agg` (auch über Arrays und mit `FILTER`), `ANY`/`ALL`, `array_positions`, `array_fill`, `trim_array`, `array_replace`; in T-SQL mit Methoden und als UDT über TDS, in MariaDB als `longtext`; Kreuztest in allen sechs Richtungen samt der drei Kataloge✅
J26.2dAufzählungen und Mengen**Erledigt (02.10.2026), gegen `postgres:17` (Fixture `j26d-orakel.txt`, Zeile für Zeile gleich) und `mariadb:11.8` (orakel-fragen-6) gemessen:** PostgreSQL `CREATE TYPE … AS ENUM` ist ein eigener Typ je Deklaration (in T-SQL ein Benutzertyp mit dem Namen der Aufzählung in `sys.types`): Ordnung nach `enumsortorder` (float4 wie `AddEnumLabel`, `BEFORE`/`AFTER` als Mitte), `ADD VALUE [IF NOT EXISTS] BEFORE/AFTER`, `RENAME VALUE` (auch in Arrays), `enum_range`/`enum_first`/`enum_last` (leere Aufzählung 55000), Arrays von Aufzählungen (`stimmung[]`, Ordnung wie `array_cmp`), kein Rechnen (42883), `pg_type`/`pg_enum`/`pg_attribute`/`information_schema`, `DROP TYPE` mit abhängigen Spalten und Arrays. MariaDB `ENUM`/`SET` sind je Spalte vereinbart und haben keinen Namen: Schreibweise der Vereinbarung (Gross-/Kleinschreibung, Leerzeichen am Ende), Zahlwert (`e + 0` ist INT, `s + 0` die Bits), Ordnung nach der Vereinbarung, Vergleich mit Text als Text, `FIND_IN_SET`, `ALTER TABLE … MODIFY` (ändert bei 1265 nichts, der Leerwert bleibt), `information_schema`/`SHOW COLUMNS` mit `enum(…)`/`set(…)`, Leitung mit `ENUM_FLAG`/`SET_FLAG` und der Länge der längsten Form✅
J26.2q`timestamptz[]` je Sitzungszone**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26q-orakel.txt`, Zeile für Zeile gleich):** die Elemente von `timestamptz[]`, `date[]`, `timestamp[]`, `time[]`, `interval[]` erscheinen in TimeZone, DateStyle und IntervalStyle der Sitzung (Ausgabe, `::text`, Elementzugriff, `unnest`, `array_to_string`); gespeichert bleibt die Normalform, eine Eingabe ohne Versatz liest die Zone der Sitzung✅
J26.2rMariaDB `ENUM`/`SET` als Typ in jedem Zugang**Erledigt (03.10.2026):** jede Vereinbarung wird ein benannter Aliastyp `<tabelle>_<spalte>_enum` bzw. `_set` über `nvarchar(n)`, Mitglieder und Kennung stehen in seiner Typmarke (`Catalog/Aufzaehlungstypen`). T-SQL nennt ihn in `sys.types`/`TYPE_NAME` und kann Spalten damit anlegen, PostgreSQL führt ein ENUM als Aufzählung (`pg_type` `typtype = 'e'`, `pg_enum`, `format_type`, `pg_typeof`, `information_schema` `USER-DEFINED`), ein SET als Domäne über `character varying(n)`; MariaDB zeigt weiter `enum(…)`/`set(…)`, auch für eine in T-SQL angelegte Spalte. Schreibweise der Vereinbarung, Zahl als Nummer bzw. Bits, Ordnung nach der Nummer, Vergleich mit Text als Text, `MIN`/`MAX` nach Text in jedem Zugang wie am Orakel (`j26d`); ein ungültiger Wert ist `22P02` (ENUM) bzw. `23514` (SET) ausserhalb von MariaDB, dort 1265 bzw. ohne strengen Modus der Leerwert. `MODIFY` legt einen neuen Typ an, ein Typ ohne Spalte verschwindet mit der Anweisung, die ihn freigibt✅
J26.2eBereichstypen**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26e-orakel.txt`, Zeile für Zeile gleich):** `int4range`, `int8range`, `numrange`, `tsrange`, `tstzrange`, `daterange` und die Mehrbereiche (`int4multirange` …) als eigene CLR-Typen: Grenzen offen und geschlossen, Normalform diskreter Bereiche, Operatoren (enthält, überlappt, links von, angrenzend, Vereinigung, Schnitt, Differenz), `lower`, `upper`, `isempty`, `range_merge`, `range_agg`, Binärform auf der Leitung, Ausschluss-Einschränkung mit `&&` (`EXCLUDE USING gist`, Fehler `23P01`, `pg_constraint`)✅
J26.2f`jsonb`**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26f-orakel.txt`, Zeile für Zeile gleich):** `jsonb` als eigener CLR-Typ (Kennung 194) neben dem `json` von SQL Server 2025: eigener Leser mit den Fehlern von `jsonb_in` samt Einzelheit, Normalform (Schlüssel geordnet, doppelte entfernt, Zahlen wie `numeric`), Ordnung und Gleichheit wie `jsonb_cmp`, Operatoren auch an Literalen ohne Typ, `jsonb_insert`, `jsonb_extract_path[_text]`, Subskript lesen und zuweisen, Umwandlung nach Zahl und Wahrheitswert, Binärform (Version 1), GIN-Index samt `jsonb_path_ops`; dazu `now()` fest je Transaktion und die Typkennungen frei von denen der Systemtypen✅
J26.2gVolltextsuche als Typ**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26g-orakel.txt`, Zeile für Zeile gleich):** `tsvector` und `tsquery` als eigene CLR-Typen (Kennungen 204/205, Arrays 206/207) mit Ein- und Ausgabeform, Sortierung und Gleichheit wie PostgreSQL; der Standard-Zerleger (Port von `wparser_def.c`, 23 Wortarten), Snowball-Englisch (Porter2) samt Stoppwörtern, die Konfigurationen `simple` und `english` (`regconfig`, `default_text_search_config`), `to_tsvector`, `to_tsquery`, `plainto_tsquery`, `phraseto_tsquery`, `websearch_to_tsquery`, `@@`, `&&`, `||`, `!!`, `<->`, `ts_rank`, `ts_rank_cd`, `setweight`, `strip`, `length`, `numnode`, `querytree`, `ts_delete`, `ts_filter`, `ts_headline`, `ts_lexize`, `ts_debug`, `tsvector_to_array`, `array_to_tsvector`, `unnest`, GIN-Index; Binärform wie `tsvectorsend`/`tsquerysend`. Die übrigen Sprachen: J26.2g2✅
J26.2g2Volltextsuche: weitere Sprachen**Erledigt (03.10.2026), gegen `postgres:17` gemessen (Fixture `j26g2-orakel.txt`, 658 Zeilen, Zeile für Zeile gleich):** alle 28 Snowball-Sprachen von PostgreSQL 17 (`arabic` … `yiddish`) mit ihren Stammformern (Snowball 2.2, mechanisch aus den erzeugten C-Dateien übersetzt, `tools/postgres/snowball-csharp.py`) und den 15 Stoppwortlisten; `hindi` und `russian` lesen ASCII-Wörter englisch wie bei PostgreSQL; die Kataloge `pg_ts_config`, `pg_ts_dict`, `pg_ts_parser`, `pg_ts_template`, `pg_ts_config_map`; dazu `unnest … WITH ORDINALITY` und `oid::regproc` als Name; Herkunft in `THIRD-PARTY-NOTICES.md`✅
J26.2hBitfolgen (PostgreSQL)**Erledigt (02.10.2026), gegen `postgres:17` gemessen (Fixture `j26h-orakel.txt`, Zeile für Zeile gleich):** `bit(n)` und `bit varying(n)` als eigene CLR-Typen `bitstring`/`varbit` (Kennungen 200/201, Arrays 202/203; T-SQLs `bit` bleibt der Wahrheitswert): Eingabe `B'0101'`, `X'1F'`, `'0101'`, Länge bei `CAST` und Zuweisung, Umwandlung von und nach Ganzzahlen, `&`, `|`, `#`, `~`, Schieben, Verkettung, `get_bit`, `set_bit`, `bit_count`, `length`, `octet_length`, `bit_length`, `substring`, `position`, `overlay`, `bit_and`/`bit_or`/`bit_xor`, Ordnung, Kataloge, Binärform✅
J26.2h2MariaDB `BIT(n)` als Bitfolge**Erledigt (03.10.2026), gegen `mariadb:11.8` gemessen (`orakel-fragen-9.sql`, 35 Anweisungen, Werte und Fehler gleich):** MariaDBs `BIT(n)` ist derselbe Typ `bitstring(n)` wie PostgreSQLs `bit(n)` (vorher `bigint` mit Prüfbedingung): Zuweisung aus Zahl, Byte-Literal und den Bytes eines Texts, zu viele Bits `1406` (ohne strengen Modus der Höchstwert mit `1264`), Rechnen, Vergleichen und `BIT_AND`/`BIT_OR`/`BIT_XOR` als Zahl, `HEX`/`BIN`/`OCT`/`BIT_COUNT` der Zahl, `CONCAT`/`LENGTH`/`CAST AS CHAR` der Bytes, Leitung `MYSQL_TYPE_BIT` mit `UNSIGNED_FLAG` und den Bytes, `BIT(0)` → `bit(1)`, `BIT(65)` → `1439`, ungültige `b'…'`/`x'…'` → `1064`, `0b102` ein Bezeichner, `CAST(10 AS BINARY(1))` ist `'1'`; Kataloge (`bit(n)`, Genauigkeit n); eine PostgreSQL-Spalte `bit(n)` erscheint in MariaDB als `bit(n)`. Der Wortlaut von 1054 je Klausel und die Spaltenmetadaten der Ausdrücke: J26.2h3✅
J26.2h3MariaDB: Meldung 1054 je Klausel, Metadaten der Ausdrücke über `BIT`**Erledigt (03.10.2026), gegen `mariadb:11.8` gemessen (`orakel-fragen-10.sql` samt `--column-type-info`, dazu der Teil J26.2k aus `orakel-fragen-11`, alles gleich):** `1054 Unknown column 'x' in '…'` nennt die Klausel (`SELECT`, `WHERE`, `ORDER BY`, `GROUP BY`, `HAVING`, `ON`, `SET`, `INSERT INTO`) und den Namen, wie er dasteht (`a.nichtda`); jeder Ausdruck der Auswahlliste trägt das Maß von MariaDB auf der Leitung (neue Nahtstelle `Dialekt.Ausdrucksmass`, `MariaDb/MariaAusdrucksmass`): `+`/`-`/`*` mit Stellen und Vorzeichen der Operanden (`b + 0` `LONGLONG` 11 ohne Vorzeichen, `y + 0` `LONG` 5, `5 + 0` `LONG` 3 `NOT_NULL`), die Bitoperatoren und `BIT_AND`/`OR`/`XOR` `LONGLONG` 21, `SUM` `NEWDECIMAL` mit 22 Stellen mehr, `MAX`/`MIN` über `BIT` als `BIT`, `CAST(… AS SIGNED/UNSIGNED)`, `BIN`/`OCT`/`HEX` im Zeichensatz 224 (mit 39 Nachkommastellen außerhalb einer Gruppierung), `CONCAT(b)` binär, `BINARY` nur ohne `GROUP BY`; `GROUP_CONCAT(b)` verkettet die Bytes; `CREATE TABLE … AS SELECT b, b + 0` legt `bit(10)` und `bigint(11) unsigned` an✅
J26.2iGeometrische PostgreSQL-Typen**Erledigt (03.10.2026), gegen `postgres:17` gemessen (Fixture `j26i-orakel.txt`, 215 Zeilen, Zeile für Zeile gleich):** `point`, `line`, `lseg`, `box`, `path`, `polygon`, `circle` als eigene CLR-Typen (Kennungen 208–214, Arrays 215–221, `box[]` mit `;`), übertragen aus `geo_ops.c`: Ein- und Ausgabe mit den Fehlern von PostgreSQL, alle Operatoren aus `pg_operator` (Abstand, enthält, überlappt, Schnittpunkt, nächster Punkt, Verschieben, Drehstrecken, Lage, parallel, senkrecht, Flächenvergleich) samt `42883` für ein Paar, das es nicht gibt, alle Funktionen aus `pg_proc` (`area`, `center`, `diameter`, `length`, `npoints`, `polygon(n, circle)` …), die Umwandlungen aus `pg_cast` (sonst `42846`), keine Ordnung (`ORDER BY`/`DISTINCT` sind `42883`), gist/spgist-Index, Binärform; in T-SQL als Methoden und `CAST(… AS geometry)` in beide Richtungen✅
J26.2jVerbundtypen**Erledigt (03.10.2026), gegen `postgres:17` gemessen (Fixture `j26j-orakel.txt`, 174 Zeilen, Zeile für Zeile gleich bis auf die OIDs eigener Typen):** `CREATE TYPE … AS (…)` (auch leer), `ALTER TYPE … ADD/RENAME ATTRIBUTE`, `DROP TYPE` mit den abhängigen Feldern und Spalten im Detail, der Zeilentyp jeder Tabelle (`SELECT t FROM tab t`, `t::text`, `(t).id`, `row_to_json(t)`); Werte `ROW(…)` und die implizite Zeile `(a, b)` als `record` mit der Form ihrer Feldtypen, Ein- und Ausgabe wie `record_in`/`record_out` (Anführungszeichen, Rückstrich, NULL, Leerzeichen; `malformed record literal` mit den Details von PostgreSQL), Feldzugriff `(x).feld` verschachtelt und mit Index, `(x).*`, Vergleich Feld für Feld (`record_cmp`, anonyme Zeilen ausgeschrieben wie `make_row_comparison_op`, `IS [NOT] NULL`, `IS DISTINCT FROM`, `42601 unequal number of entries`), `INSERT … (a.feld)` und `UPDATE … SET a.feld`, Arrays von Verbunden, `pg_type` (c/C, `typrelid`, Array b/A), `pg_class` (relkind c), `pg_attribute`, `format_type`, `information_schema.columns`/`attributes`; Binärform `record_send`/`record_recv` (Npgsql `MapComposite` und `object[]`); in T-SQL ein Aliastyp unter seinem Namen mit einer Methode je Feld (`a.strasse()`)✅
J26.2kGanzzahlen jenseits der SQL-Server-Typen**Erledigt (03.10.2026), gegen `mariadb:11.8` gemessen (orakel-fragen-11 samt `--column-type-info`, jede Antwort gleich):** `MEDIUMINT`, `MEDIUMINT UNSIGNED`, `SMALLINT UNSIGNED`, `INT UNSIGNED`, `BIGINT UNSIGNED` und `YEAR` als benannte Aliastypen über einer numerischen Ablage (`mediumint`, `mediumint_unsigned`, `smallint_unsigned`, `int_unsigned`, `bigint_unsigned`, `year`; Kennungen 222–227 in `sys.types` jeder Datenbank, in PostgreSQL Domänen in `pg_type` und `information_schema.columns.domain_name`), damit `AUTO_INCREMENT`, die Ordnung eines Index und `SUM`/`AVG` Zahlen bleiben; Werte ohne Vorzeichen rechnen bis 2^64-1 (`DIV`, Vorzeichenwechsel), ein Überlauf ist 1690 mit dem Ausdruck (`` in '`db`.`t`.`iu` - 1' ``), `YEAR` liest `'0'` als 2000, `MAKEDATE` zweistellige Jahre; Leitung INT24/LONG/LONGLONG/YEAR mit `UNSIGNED`/`ZEROFILL`; jeder Zugang prüft den Bereich auch für eine Spalte aus T-SQL oder PostgreSQL✅
J26.2l`numeric` über 38 Stellen**Erledigt (03.10.2026), gegen `postgres:17` gemessen (Fixture `j26l-orakel.txt`, 124 Zeilen, gleich):** PostgreSQLs `numeric` ist der eigene Typ `bignumeric` (Kennung 228, OID 1700) neben `decimal` — übertragen aus `numeric.c` (Ziffern zur Basis 10 000, Anzeigestellen, `select_div_scale`, die Näherungen von `sqrt`, `exp`, `ln`, `log`, `power`), bis 131 072 Stellen vor und 16 383 nach dem Komma, NaN und ±Unendlich samt Ordnung; Typmodifizierer `numeric(p, s)` auch mit negativer Skala und Skala über den Stellen (`22003 numeric field overflow` mit dem Detail von PostgreSQL, `22023` für ungültige Angaben mit Stelle), Konstanten mit Exponent oder über `bigint`, `^`, `%`, `div`, `mod`, `round`/`trunc`/`ceil`/`floor`, `scale`/`min_scale`/`trim_scale`, `width_bucket`, Umwandlungen in Ganzzahl und Gleitkomma mit den Meldungen von PostgreSQL, `sum`/`avg`/`stddev`/`variance` genau wie `numeric_stddev_internal`, `to_char` über jede Länge, Binärform `numeric_send`/`numeric_recv`; in T-SQL ein CLR-Typ mit Methoden (`x.Sqrt()`, `x.Round(2)`), in MariaDB `decimal(p,s)`, wo er passt; nimmt P18.5f und den Rest aus P18.8d auf✅
J26.2mDatum und Zeit vor Christus und über 9999**Erledigt (03.10.2026), gegen `postgres:17` gemessen (Fixture `j26m-orakel.txt`, 126 Zeilen, gleich):** `date`, `timestamp` und `timestamptz` über den ganzen Bereich von PostgreSQL (24. November 4714 v. Chr. bis 5874897 bzw. 294276) samt `infinity`: eine **zweite Ablage** der Datumstypen der Engine (`Types/Weitzeit`, Ticks seit dem 1. Januar 1 als 128-Bit-Zahl, in der Zeile acht Bytes im negativen Bereich, den `DateTime` nie belegt), Eingabe in jedem `DateStyle` mit `BC`/`AD` und langen Jahren samt den drei Meldungen der Grenzen, Ausgabe in ISO, SQL, German und Postgres, Ordnung/`min`/`max`/Index, Rechnen mit Tagen und Intervallen (auch hinüber: `'9999-12-31'::date + 1`), `age`, `EXTRACT`/`date_part` mit jedem Feld wie `timestamp_part_common` (±Infinity), `date_trunc` (Jahrhundert v. Chr.), `to_char` als Übertragung von `DCH_to_char` (`BC`, `B.C.`, `Y,YYY`, `CC`, `Day` mit Füllung …), `to_date`/`to_timestamp` mit `BC` und negativem Jahr, `make_date(-43, …)`, Binärform in beiden Richtungen; T-SQL und MariaDB lesen solche Werte als Text (`CONVERT`, `CAST … AS CHAR`, Textprotokoll) und ordnen sie richtig — als `date` über TDS oder im Binärformat von MariaDB gibt es für sie keine Bytes (Meldung statt falschem Datum); nimmt den Rest aus P18.8h auf✅
J26.2n`Kuonen.Global.SqlTypes.dll` für Clients**Erledigt (03.10.2026):** neues Projekt `src/Kuonen.Global.SqlTypes` (netstandard2.0, damit es auch .NET Framework und SSMS laden; einziger Verweis `Microsoft.SqlServer.Server`, keine Kernbibliothek) mit je einer Klasse für jede Klasse, die KuonenSQL auf der Leitung nennt (23: `SqlInet` … `SqlBigNumeric`), je mit `SqlUserDefinedType(Format.UserDefined)`, `IBinarySerialize` (liest die Normalform in UTF-8), `INullable`, `ToString`, `Parse`, `Null`; mit ihr liefert `Microsoft.Data.SqlClient` `GetValue` als Objekt der Klasse, dessen Text gleich den Bytes ist; ein Wächter verlangt zu jedem eigenen Typ der Engine seine Klasse und die Assembly-Identität `Version=1.0.0.0`✅
J26.2o`vector`, `json`, `uuid`**Erledigt (03.10.2026), gegen `postgres:17` mit pgvector (Fixture `j26o-orakel.txt`, 111 Zeilen, gleich) und `mariadb:11.8` (`orakel-fragen-11.sql`, Teil J26.2o samt `--column-type-info`, gleich) gemessen:** die drei Typen, die SQL Server 2025 selbst hat, bilden aus PostgreSQL und MariaDB auf sie ab. **uuid:** die Schreibweisen von `uuid_in` (Klammern, Bindestriche nach jedem zweiten Byte, ohne Bindestriche) mit `22P02` und Stelle, `uuid_extract_version`/`uuid_extract_timestamp`, MariaDBs `UUID` mit Namen in Katalog und Leitung, `CAST(… AS UUID)` auch ohne Bindestriche. **json:** PostgreSQL meldet `json = json` wie dort (`42883` an der Stelle des Operators, auch in `WHERE`); MariaDBs `JSON` ist der `json` der Engine, bleibt aber wie geschrieben, zeigt sich als `longtext … CHECK (json_valid(…))`, meldet `4025`, `JSON_EXTRACT` als `LONG_BLOB format=json`. **vector:** pgvector über `vector(n)` der Engine (Regeln des Dialekts über die neue Nahtstelle `IVektorregeln`): `CREATE EXTENSION vector`, Ein- und Ausgabe samt allen Meldungen, Typmodifizierer, `<->`, `<#>`, `<=>`, `<+>`, `+`, `-`, `*`, Verkettung, Ordnung, `sum`/`avg`, die Funktionen, Umwandlungen von und nach Arrays, Binärform, `hnsw`/`ivfflat`, Kataloge; MariaDBs `VECTOR(n)` als Bytes mit `VEC_FromText`/`VEC_ToText`/`VEC_DISTANCE*` (4206 ohne Vektorindex, `CREATE VECTOR INDEX … DISTANCE=`), `1292` mit den Bytes; Kreuztest aller drei Typen über sechs Richtungen. `halfvec` und `sparsevec` stehen wie nach `CREATE EXTENSION` in `pg_type`; die Typen selbst sind J26.2o2✅
J26.2o2pgvectors `halfvec` und `sparsevec`**Erledigt (03.10.2026), gegen `pgvector/pgvector:pg17` gemessen (Fixture `j26o2-orakel.txt`, 145 Zeilen, gleich):** `halfvec(n)` ist in T-SQL `vector(n, float16)` wie SQL Server 2025 (`sys.columns.vector_base_type` 1, zwei Bytes je Stelle), `sparsevec(n)` ein eigener Typ der Engine (Kennung 229, Klasse `SqlSparsevec`). Ein- und Ausgabe samt allen Meldungen (float16-Bereich, doppelte und ungültige Nummern, NaN), Typmodifizierer, Abstände, Rechnen mit Rundung auf float16 und Überlauf, `l2_norm`/`l2_normalize`/`subvector`/`binary_quantize` in der Art ihres Arguments, Umwandlungen zu `vector`, `halfvec`, `real[]` und aus Feldern, `avg`/`sum`, Ordnung, Spalten, `pg_type`/`pg_attribute`/`format_type`, `hnsw`; Binärform `halfvec_send`/`sparsevec_send` in beide Richtungen. Nebenbei: `vector::text` in PostgreSQL ist jetzt die Ausgabe von pgvector statt der Textform von SQL Server✅
J26.2pKreuztest der gemeinsamen Typen**Erledigt (03.10.2026):** `TypGemeinsameKreuzTests` fährt 14 Typen (`int`, `bigint`, `smallint`, `decimal`, `float`, `real`, `char`, `varchar`, `text`, Binärdaten, `date`, `time`, `timestamp`, Wahrheitswert) je über alle sechs Richtungen (angelegt, geschrieben, gelesen über je einen anderen Zugang), gelesen in der Textform des Lesers. Dabei drei echte Fehler behoben: ein unbestimmtes PostgreSQL-Literal in eine Binärspalte geht jetzt durch `byteain` (`'\x00ff10'` sind drei Bytes, nicht die UTF-16-Bytes des Texts — auch in einer eigenen `bytea`-Spalte), MariaDB `FLOAT` ist einfach genau (`real`, wie MariaDB 4 Bytes) und ein `real` steht bei MariaDB mit sechs signifikanten Stellen statt `1.500000000000000e+000`; dazu ein Wettlauf beim Aufräumen der Kreuzumgebung. Die einzige Abweichung ist benannt: MariaDB `BOOLEAN` ist `TINYINT(1)`, PostgreSQL liest dort `1` statt `t`✅
J26.2sOrtszeit (LMT) sekundengenau**Erledigt (03.10.2026):** ein Versatz mit Sekunden (Zürich bis 1853 `+00:34:08`) wurde auf Minuten gerundet, weil `DateTimeOffset` nicht mehr hält — Ausgabe (`Pgzeitstil.Zeitpunkt`), `AT TIME ZONE`, `timestamptz::timestamp`/`::time` und die Wanduhr eines Texts ohne Versatz lagen um bis zu 30 Sekunden daneben. Jetzt rechnet `Pgzone` Wanduhr und Versatz sekundengenau (`Wanduhr`, `WanduhrVersatz`), der Zeitpunkt selbst bleibt exakt, und der Dialekt liefert die Wanduhr der Sitzung für die Umwandlung (`Dialekt.Wanduhr`). `PgOrtszeitTests` (8 Fälle, z. B. `timestamptz '1850-01-01 00:00:00+00'` → `1850-01-01 00:34:08+00:34:08`); am Orakel bestätigt: `ortszeit-orakel.txt` (`postgres:17`, 70 Zeilen, Zeile für Zeile gleich, auch Amsterdam, Monrovia, `to_char … OF`, `date_trunc`, `DateStyle SQL`)✅
J26.2t`CONVERT(varchar, float/real, Stil)` wie SQL Server**Erledigt (03.10.2026), gegen SQL Server 2025 gemessen (`orakel-fragen.sql`, 30 float- und 18 real-Werte je Stil 0–3, `FloatStilTests` mit 192 Fällen + Überlauf, alle gleich):** Stil 3 stimmte schon (`1.0000000000000001e-001` ist auch die Antwort von SQL Server, die Vermutung `0.10000000000000001` war falsch), ebenso Stil 1 und 2. Drei echte Abweichungen behoben: Stil 0 schreibt wissenschaftlich wie `%g` ohne Nullen in der Mantisse (`1e+015`, `1.5e-007` statt `1.00000e+015`); eine Konstante unter der kleinsten normalen Zahl (`4.9e-324`) liest SQL Server als 0; ein zu kurzes Ziel ist Fehler 232 mit dem Wert auf sechs Nachkommastellen statt 8115. Die Warnung, die SQL Server zur Konstante 0 schickt, gibt es nicht (der Lexer hat keinen Weg für Hinweise)✅
Meilenstein 27: Produktionsreife (219/235)
PunktTitelBeschreibungStand
PR27.1Absturz-PrüfstandEin eigener Prüfstand beendet den Server-Prozess hart (`Process.Kill`, ohne Herunterfahren) an zufälligen Stellen während gemischter Schreiblast über alle drei Zugänge, startet ihn neu und prüft nach der Wiederherstellung: jede bestätigte Transaktion ist da, keine unbestätigte, Indizes und Zeilen stimmen überein (`DBCC CHECKDB`), Folgen und Identitäten laufen weiter. **Bestanden:** mindestens 10 000 Abstürze ohne einen Befund; jeder Befund wird ein eigener Fehler mit Test. **Gebaut 04.10.2026:** Absturzstellen als Testhaken (`WalLogManager.Absturzprobe`, im Server `KUONENSQL_ABSTURZ=stelle:n` → `Process.Kill`): `protokoll.halb` (zerrissener Schreibvorgang), `protokoll.geschrieben`, `kontrollpunkt.dateien`/`.kontrolldatei`/`.umbenannt`, `kuerzen.neu`. `DBCC CHECKDB` prüft jetzt wirklich (Zeilenzahl gegen die Seiten, Schlüsselverzeichnisse in Folge, doppelte Primärschlüssel → 8951/8958). `AbsturzPruefstandTests`: jede Stelle mehrfach, mit offener Transaktion über den Kontrollpunkten, Rücknahmen, Identität und Folge. `tools/absturz` (TDS ×3, PostgreSQL ×2, MariaDB ×2 auf einer Datenbank, abwechselnd `Process.Kill` und Absturzstelle): 10 Abstürze, 9431 bestätigte Transaktionen, 0 Befunde, Wiederanlauf ≤ 1,63 s. Der Lauf über 10 000 Abstürze: PR27.1a.✅
PR27.1bFund: gescheiterter Stapel kam nach dem Neustart zurück**Erledigt 04.10.2026:** ein Stapel ausserhalb einer Transaktion steht vor seiner Ausführung im Protokoll; scheiterte jede seiner ändernden Anweisungen an etwas, das beim Wiederanlauf nicht wieder eintritt (Sperre 1222, Deadlock, Abbruch), stand er nach dem Neustart gelungen da. Jetzt sagt ein Satz `Verworfen` dem Wiederanlauf, ihn zu überspringen (`AbsturzPruefstandTests.Ein_An_Einer_Sperre_Gescheiterter_Stapel…`). Rest: PR27.1c.✅
PR27.1cTeilweise gescheiterter Stapel im Protokoll**Erledigt 04.10.2026:** jede Ausführung einer Anweisung im äusseren Stapel bekommt eine laufende Nummer (Schleifen, Prozeduren, Trigger mitgezählt; ein nach Sperrkonflikt wiederholter Versuch zählt neu). Scheitert eine an etwas, das beim Wiederanlauf nicht wieder eintritt (1222, 1205, 701, 3960/3961, Abbruch), steht hinter ihrem Glied ein Satz `Verworfen` mit Stapel, Nummer, Quelltextstelle und Fehler; der Wiederanlauf wirft dort denselben Fehler statt auszuführen – `@@ERROR`, `ERROR_NUMBER()`, CATCH-Zweig und Identität laufen wie beim ersten Mal, ausserhalb wie innerhalb einer Transaktion (1205/3960 nehmen sie zurück). Deterministische Fehler (2627 …) treten beim Abspielen von selbst wieder auf. Der Vermerk steht dauerhaft, bevor der Client die Antwort sieht. Tests `AbsturzPruefstandTests.Ein_Teilweise_Gescheiterter_Stapel…` (5 Fälle). Rest: PR27.1d.✅
PR27.1dFund: eindeutiger Schlüssel trotz offenem DELETE vergebenNebenbefund aus PR27.1c: Sitzung A löscht in offener Transaktion die Zeile mit `k = 1` (UNIQUE), Sitzung B fügt `k = 1` ohne Warten ein, A nimmt zurück – danach steht `k = 1` doppelt da. Am Orakel wartet B auf die Schlüsselsperre (1222 mit `LOCK_TIMEOUT 0`). **Erledigt 04.10.2026:** ein eindeutiger Index (nicht der Primärschlüssel, der schon Sperrschlüssel war) prüft nach der Suche ohne Treffer die alten Bilder der Zeilen, die offene fremde Transaktionen gelöscht oder umgeschlüsselt haben, und wartet auf deren Ausgang – für `INSERT` und `UPDATE` des Schlüssels, Einschränkung und Index, mit und ohne Primärschlüssel: 1222 mit `LOCK_TIMEOUT 0`, nach ROLLBACK 2627/2601, nach COMMIT gelingt es; READ_COMMITTED_SNAPSHOT ebenso, SNAPSHOT nach COMMIT 3960 – alles wie am Orakel gemessen. Ein Doppelgänger, an dem die fremde Transaktion nur andere Spalten geändert hat, meldet 2627 sofort wie SQL Server (MariaDB/PostgreSQL warten weiter). Ohne fremde Schreiber auf der Tabelle keine Belegung; die Suche geht nur über die fremden Sperren. Tests `EindeutigerSchluesselSperrTests` (11 Fälle).✅
PR27.2Prüfsummen auf Protokoll und DatendateienJeder WAL-Satz und jede Seite der Datendateien trägt eine Prüfsumme (CRC32C). Beim Lesen erkannt: halb geschriebene Sätze am Protokollende (abschneiden, wie SQL Server), beschädigte Seiten mitten im Bestand (Fehler 824 wie SQL Server, nie still falsche Daten). **Bestanden:** Tests mit gezielt verfälschten Bytes an jeder Stelle; der Absturz-Prüfstand (PR27.1) läuft mit zerrissenen Schreibvorgängen. **Erledigt 04.10.2026:** jeder Protokollsatz trägt hinter seiner Länge eine CRC32C (Kopf „KUOP“, ältere Protokolle „KUOG“ bekommen sie mit ihrer nächsten Datei), jede Datendatei ist in 8-KB-Seiten mit CRC32C, Seitennummer und Endmarke geteilt (Version 3, 1/2 weiter lesbar); CRC32C mit SSE4.2/ARM64-Befehl (`Storage/Crc32C`, mit Kafka geteilt). Ein zerrissener Satz am Protokollende wird beim Öffnen abgeschnitten (vorher landeten neue Sätze dahinter und gingen beim Lesen verloren), ein Schaden mitten im Protokoll meldet 9004, eine beschädigte Seite 824, ein falscher Dateikopf 5172/5173. Tests `PruefsummenTests` (jedes Byte des Protokolls, Kopf und jede Seite der Datendatei), `Crc32CTests`; Kosten je Satz 19 ns neben 4 ms fsync (`ProtokollsatzBenchmarks`). Zerrissene Schreibvorgänge im Prüfstand: PR27.1.✅
PR27.3Kontrollpunkt unter Dauerlast**Kern mit P15.9d gebaut (03.10.2026):** der unscharfe Kontrollpunkt kommt neben offenen Transaktionen zustande, das Protokoll bleibt begrenzt (`KontrollpunktUnterLastTests`); offen hier nur noch die Messung unter echter Dauerlast und der Wiederherstellungszeit. Baut auf P15.9d auf: ein Kontrollpunkt, der auch bei ständig offenen Transaktionen zustande kommt (bestätigtes Bild aus der Sperrverwaltung schreiben, Protokoll nur bis zur Abzugsstelle kürzen, Dateien ausserhalb der Stapelsperre schreiben). **Bestanden:** unter 16 Verbindungen Dauerlast wächst das Protokoll nicht über eine feste Grenze; die Wiederherstellungszeit nach einem Absturz bleibt unter 10 s. **Bauteil fertig 04.10.2026:** die Wiederanlaufzeit nach nebenläufiger Last mit selbst ausgelösten Kontrollpunkten ist im Test begrenzt (`KontrollpunktUnterLastTests`, < 10 s; Sabotage: Hintergrund-Kontrollpunkt aus → rot). `tools/absturz` misst mit `[dauerminuten]` 16 Verbindungen über TDS/PostgreSQL/MariaDB, jede Sekunde die Protokollgrösse, danach `Process.Kill`, Wiederanlaufzeit und Prüfung; Probelauf 30 s: 18 965 Transaktionen, Protokoll höchstens 187 KB, Wiederanlauf 3,45 s, 0 Befunde. Der Lauf über Stunden: PR27.3a.✅
PR27.3bDurchsatz unter Dauerlast: Rücknahme ohne Neubau der Schlüsselverzeichnisse**Befund aus PR27.3a (05.10.2026):** bestätigte Transaktionen je Minute fielen von 39 000 auf etwa 2 000 bei 200 000 Zeilen, das Protokoll blieb unter 201 KB. **Ursache gemessen** (einfädig, eine Verbindung bzw. 16 Sitzungen verschränkt im Prozess): nicht der Kontrollpunkt (60 ms bei 10 000, 156 ms bei 200 000 Zeilen; das kleine Protokoll kommt vom `CHECKPOINT` alle 25 Transaktionen in `tools/absturz`), sondern die Rücknahme: das Zurücknehmen eines INSERT (`DeleteAt`), einer Änderung an Ort und Stelle (`UeberschreibeAt`) und eines DELETE (`RestoreAt`) liess alle Schlüsselverzeichnisse der Tabelle veralten, die nächste Suche (schon die Schlüsselprüfung des nächsten INSERT) baute sie aus der ganzen Tabelle neu - unter der Tabellensperre, bei jeder siebten Transaktion. **Behoben:** alle drei tragen aus, um bzw. ein wie ein gewöhnliches Löschen/Ändern/Einfügen. 100 Rücknahmen (INSERT+UPDATE): 200 000 Zeilen 14 342 ms → 70 ms, 10 000 Zeilen 722 ms → 52 ms (`RuecknahmeSchluesselverzeichnisTests`; Sabotage je Weg: Austragen/Umtragen/Eintragen weggelassen → rot, alter Neubau → Zeitgrenze rot). Messreihe in `docs/leistung.md` §8.✅
PR27.3cWiederanlauf beschleunigen**Fertig 07.10.2026:** der Kontrollpunkt nach dem Wiederanlauf läuft im Hintergrund und nur, wenn Protokoll eingespielt wurde (ohne jeden Kontrollpunkt sofort); scheitert er an der Stapelsperre, folgt der nächste Versuch nach dem nächsten Stapel (`KontrollpunktAusstehend`); `Dispose` wartet auf ihn und schreibt nach einem Absturz nichts mehr. Laden: 64-KB-Puffer, Datensätze ohne Belegung je Zeile mit Längengrenze (beschädigte Datei → Fehler); Aufbau der Schlüsselverzeichnisse nach dem Laden ohne Tupel und ohne Sortierung bei geordneten Zeilen. Gemessen (`docs/leistung.md` §15): 500 000 Zeilen Instanz bereit 646 → 102 ms, Vorgänger bei 1–6 Mio. Zeilen 30 s → 8,2 s. `WiederanlaufHintergrundTests` (rot gesehen: Hintergrund, nur bei Rest, doppelter Absturz, Wiederholung; Sabotage je Punkt).✅
PR27.4Datenbestand grösser als der ArbeitsspeicherKlären und belegen, wie sich der Server verhält, wenn die Tabellen mehr Platz brauchen als der Arbeitsspeicher: entweder Seiten auslagern (Puffer-Pool mit Verdrängung) oder eine benannte Obergrenze mit sauberem Fehler (wie SQL Server 701/1105) statt Absturz. **Bestanden:** Lauf mit einem Bestand vom Doppelten des zugeteilten Speichers (Containergrenze), kein Absturz, kein Datenverlust. **Erledigt 04.10.2026 (benannte Obergrenze):** KuonenSQL hält den Bestand im Arbeitsspeicher; ein Stapel, der ihn wachsen lassen kann, beginnt nur unter der Bestandsgrenze (ein Drittel des Speichers, den der Prozess nutzen darf – Arbeitsspeicher oder Containergrenze; der Kontrollpunkt zieht ein Abbild bis zum Doppelten), sonst 701 wie SQL Server; Löschen, `TRUNCATE` und `DROP` gehen immer. Geprüft wird vor dem Protokoll, nicht je Zeile: ein mittendrin am Speicher gescheiterter Stapel käme beim Wiederanlauf anders heraus (gemessen). Reicht der Speicher für einen Kontrollpunkt nicht, bleibt der alte Stand gültig. Tests `BestandsgrenzeTests`; `tools\absturz\bestand-ueber-speicher.ps1` (feste Grenze 192 MB, Versuch mit 384 MB): 701 bei 62 MB, kein Absturz, nach dem Neustart jede Zeile da. Eine Auslagerung über den Arbeitsspeicher hinaus (Puffer-Pool) ist nicht gebaut.✅
PR27.6Geübte WiederherstellungAutomatischer Ernstfall: Sicherung (`BACKUP DATABASE`, `pg_dump`, `mysqldump`) während Last, Server und Datenverzeichnis vernichten, auf frischem Server zurückspielen, Bestand und Prüfsummen gegen den Stand zur Sicherungszeit vergleichen; dazu Wiederherstellung auf einen Zeitpunkt (Protokollsicherung). **Bestanden:** alle drei Wege und der Zeitpunkt stimmen bytegleich. **Gebaut 04.10.2026:** Fund der Übung: `BACKUP DATABASE` nahm die Änderungen offener Transaktionen anderer Sitzungen mit – jetzt zieht die Sicherung wie der Kontrollpunkt den bestätigten Stand unter der exklusiven Stapelsperre (3023, wenn sich eine Transaktion nicht abbilden lässt; 3021 in einer eigenen Transaktion wie am Orakel). Test `WiederherstellungsuebungTests` (Sicherung unter Last, Datenordner vernichtet, frischer Server, Bestand gleich dem bestätigten Stand, `CHECKDB` sauber). Skript `tools\absturz\wiederherstellung-ueben.ps1` (BACKUP, pg_dump, mariadb-dump während offener Transaktion, Server und Ordner vernichtet, jeder Weg zurück, Summen verglichen); geübt mit `-OhneDocker`: BACKUP/RESTORE gleich. Reste: PR27.6a, PR27.6b.✅
PR27.6aÜbung über pg_dump und mariadb-dump (Lauf für Hauptchat/Nutzer)`powershell -File tools\absturz\wiederherstellung-ueben.ps1 -Zeilen 100000` (Wegwerf-Container `postgres:17`, `mariadb:11`). **Bestanden:** alle drei Wege „gleich“, Rückgabewert 0. **Erledigt 05.10.2026:** 100000 Zeilen, Stand `100000/190001900000/54950150505` auf allen drei Wegen gleich, 97 s, Rückgabewert 0 (`tools/absturz/messung-2026-10-05-dump.txt`). Funde: (1) mit dem leeren `search_path` von `pg_dump` scheiterte `CREATE TABLE public.konto` mit 3F000 – `public.` fiel schon beim Zerlegen weg, der Name galt dann als unqualifiziert; jetzt hängt ein mit Schema genannter Name nicht am Suchpfad (wie PostgreSQL 17: nur unqualifiziertes Anlegen scheitert), Test `PgLeererSuchpfadTests` (CREATE TABLE, OWNER, COMMENT ON, Folge, OWNED BY, SET DEFAULT nextval, Sicht, setval, ALTER TABLE ONLY, CREATE INDEX, Fremdschlüssel, COPY). (2) `pg_init_privs` war leer, darum schrieb `pg_dump` für 142 Relationen von `pg_catalog` `REVOKE`/`GRANT`, und `psql` brach bei `pg_catalog.pg_aggregate` ab (die Übung sah den Rückgabewert nicht); jetzt führt `pg_init_privs` die Anfangsrechte der Katalogrelationen, Test `PgAnfangsrechteTests`; die Übung zählt einen Fehler von `pg_dump`/`psql`/`mariadb-dump`/`mariadb` als Befund. `PgDumpAbnahmeTests` (Klartext und eigenes Format) grün. Rest: PR27.6m.✅
PR27.6mEingebautes Schema `redis` im pg_dump`pg_dump` schreibt das eingebaute Schema des Redis-Wegs mit (`CREATE SCHEMA redis`, `ALTER SCHEMA redis OWNER TO sa`); das Zurückspielen in KuonenSQL geht, ein echtes PostgreSQL bekäme aber ein leeres Schema. Wie ein Schema einer Erweiterung auslassen (`pg_depend` Art `e` o. ä.), dasselbe für `graphql`. **Bestanden:** die Klartextsicherung enthält nur die Nutzerschemas. **Erledigt 10.10.2026:** die eingebauten Schemas gehören der eingebauten Erweiterung `kuonensql` (Kennung 13585 unter 16384 wie `plpgsql`, `pg_depend` Art `e`), `pg_dump` lässt Erweiterung und Schemas aus (Abnahme mit `pg_dump` 17).✅
PR27.6bWiederherstellung auf einen Zeitpunkt**Erledigt 04.10.2026:** `BACKUP LOG` sichert die Stapelfolgen der Datenbank seit dem Ende der Kette (LSN = Nummer des Protokollsatzes, Marke unter der exklusiven Stapelsperre; die Kette hält das Protokoll ab dem ersten Glied jeder offenen Folge, auch über einen Neustart). `RESTORE LOG … WITH STOPAT` (auch `@variable`), `STOPATMARK`/`STOPBEFOREMARK` [`AFTER`], `NORECOVERY`/`RECOVERY`, `RESTORE DATABASE x WITH RECOVERY` ohne Gerät; `BEGIN TRAN name WITH MARK`; Protokollende mit `BACKUP LOG … NORECOVERY` wie der SSMS-Dialog. Fehler und Meldungen wie am Orakel: 4208, 4214, 4305, 4326, 4335, 4338, 3217, 3153, 4322, 4329, 3117; 4035/3014. `msdb`: `backupset` (65 Spalten, Kette in first/last/checkpoint/database_backup_lsn, `begins_log_chain`), `backupfile` (26), `backupmediafamily` (9), `restorehistory` (13, mit Haltepunkt), `logmarkhistory`; `RESTORE HEADERONLY` mit denselben LSN. Tests `ZeitpunktWiederherstellungTests`. Rest: PR27.6c.✅
PR27.6cRest Zeitpunkt-Wiederherstellung**Erledigt 05.10.2026:** (1) `STOPATMARK`/`STOPBEFOREMARK = 'lsn:n'` wie am Orakel (bis einschliesslich bzw. ohne die Folge mit LSN n, mitten in einer Transaktion davor; 4334, 4335 Zustand 2, 4329); Fund: `logmarkhistory.lsn` war 0, wenn die markierte Transaktion im selben Stapel begann. (2) `RESTORE LOG` übergeht das Schreiben in eine andere Datenbank still statt 3906 (kein falscher CATCH-Zweig); neuer Protokollsatz `Datenbanken` (19): eine Folge, die die Datenbank nur über Prozedur, Trigger oder dynamisches SQL erreichte, kommt in `BACKUP LOG`. (3) Der `msdb`-Verlauf (`backupset`, `backupfile`, `backupmediafamily`, `restorehistory`, `logmarkhistory`) geht in die Datendatei; `BACKUP` und `RESTORE` fordern danach einen Kontrollpunkt an (auch ein Absturz gleich danach verliert nichts). (4) SSMS-Dialog ohne Klicken: `tools\ssms-compat\Wiederherstellungsdialog.ps1` plant mit SMO aus SSMS 22 wie der Dialog; seine 19 Abfragen (am Orakel mit einer eigenen Ereignissitzung mitgeschnitten) laufen fehlerfrei mit den Spalten des Orakels. Funde dabei: `backupset.position` und Mediensatz beim Anhängen (`NOINIT`/`INIT`/`FORMAT`) wie am Orakel; nach einer Wiederherstellung schliesst die nächste Protokollsicherung an ihren Stand an (vorher 4305 beim zweiten Dialog). Tests `ZeitpunktWiederherstellungTests` (6 neue Fälle). Reste: PR27.6g, PR27.6h, PR27.6i.✅
PR27.6gHandtest Dialog „Datenbank wiederherstellen“ (Nutzer)In SSMS 22 gegen den Container: Quelle „Datenbank“, Zeitachse (letzte Sicherung und ein Zeitpunkt), Protokollende mit NORECOVERY, zweimal hintereinander; dazu „Medium“ mit einer Sicherungsdatei. **Bestanden:** kein Fehler, Bestand wie erwartet, `msdb`-Verlauf nach Containerneustart noch da.⬜
PR27.6hWiederherstellungspfade (recovery fork)**Erledigt 05.10.2026:** wie am Orakel gemessen beginnt jede Wiederherstellung mit `RECOVERY` einen neuen Pfad; `sys.database_recovery_status` zeigt ihn, den vorigen und den Abzweig. Die erste Protokollsicherung danach führt `first_recovery_fork_guid`/`last_recovery_fork_guid`/`fork_point_lsn` (auch in `RESTORE HEADERONLY`); endete die Wiederherstellung mitten in einer Protokollsicherung, beginnt sie an deren Anfang, enthält den alten Pfad bis zum Abzweig und trägt `begins_log_chain` – wie am Orakel lässt sie sich gleich nach der Vollsicherung zurückspielen, und SMO plant den verworfenen Verlauf nicht mehr ein (am Testserver geprüft: F → L2 → L3). Ein Satz vom falschen Pfad oder hinter dem Abzweig: 4330. Test `Zeitpunkt_Wiederherstellung_Beginnt_Einen_Neuen_Pfad_Wie_Am_Orakel`. Rest: PR27.6j.✅
PR27.6iÄnderungen nach `BACKUP DATABASE` im selben Stapel**Erledigt 05.10.2026:** die Voll- oder Differenzsicherung merkt sich die Stelle im Stapel; die nächste Protokollsicherung nimmt dessen Folge mit (das Protokoll bleibt bis dahin stehen), beim Zurückspielen läuft sie erst ab der `BACKUP`-Anweisung – Änderungen davor stehen schon in der Vollsicherung. Test `Aenderungen_Nach_Der_Vollsicherung_Im_Selben_Stapel_Kommen_In_Die_Kette` (auch mit Kontrollpunkt dazwischen).✅
PR27.6jZugriff auf eine Datenbank in RESTORING**Erledigt 05.10.2026:** wie am Orakel gemessen meldet jeder Weg in eine Datenbank in der Wiederherstellung 927 (Klasse 14, Zustand 2): dreiteilige Namen (lesen und ändern), `USE`, Sichten, Synonyme, Prozeduren, `db.sys.…`, `OBJECT_ID`. Erst wenn die Anweisung läuft, bricht den Stapel ab, CATCH fängt ihn nur eine Ebene tiefer; `sys.databases`, `sys.master_files`, `DATABASEPROPERTYEX` antworten weiter. Fund: ein Synonym auf eine andere Datenbank suchte sein Ziel in der eigenen (5313) – jetzt dort. Tests `WiederherstellungZugriffTests` (je Weg ein Fall).✅
PR27.6kSchlüssel beim Abspielen**Erledigt 05.10.2026:** Befund bestätigt – Wiederanlauf, `RESTORE LOG` und die lesbare Kopie prüften beim Abspielen Primärschlüssel und eindeutige Indizes nicht; eine damals gescheiterte und gefangene Anweisung fügte still einen doppelten Schlüssel ein, der CATCH-Zweig lief nicht. Jetzt wird auch beim Abspielen geprüft: die Anweisung geht genau so aus wie beim ersten Lauf (2627/2601, derselbe CATCH-Zweig). Ein ungefangen scheiternder Stapel steht auf der Fehlerausgabe (`[ABSPIELEN] …`) statt still übergangen zu werden. Begründung in `docs/umsetzung.md`. Tests `AbspielenSchluesselTests`, `LesbareKopienTests` (Kopie).✅
PR27.6lCHECK und Fremdschlüssel beim Abspielen**Erledigt 05.10.2026:** Wiederanlauf, `RESTORE LOG` und Kopie übergingen beim Abspielen `CHECK`, Fremdschlüssel, Kantenbedingungen, die `ALTER TABLE`-Prüfung und 3726 – eine gefangene 547 kam still durch, und `ON DELETE CASCADE` lief beim Abspielen gar nicht. Die Annahme „gleiche Verweise in der Reihenfolge des Festschreibens“ hielt nicht, weil der Fremdschlüssel unbestätigte Zeilen anderer Sitzungen sah (Kind auf eine später zurückgenommene Eltern-Zeile blieb stehen); jetzt wartet er wie am Orakel gemessen auf deren Ausgang (1222 mit `LOCK_TIMEOUT`), und alle Prüfungen laufen beim Abspielen wie beim ersten Lauf. Vorbehalt Stapel ohne Transaktion, die auf eine Sperre warten: PR27.1e. Tests `AbspielenSchluesselTests`, `LesbareKopienTests`.✅
PR27.6dSicherheit beim Abspielen gesicherter Stapel**Erledigt 04.10.2026:** Wiederanlauf und `RESTORE LOG` führen gesicherte Stapel erneut aus; was damals ausserhalb der Datenbank wirkte, geschieht nicht ein zweites Mal (`ExecutionEngine.Abspielsperre`, Wege im Code ermittelt, Liste in `docs/umsetzung.md`). Still übergangen (Anweisung gilt als gelungen, kein falscher CATCH-Zweig): REST-Ruf, `xp_create_subdir`, Ablaufverfolgung, `sp_start_job`, `sp_testlinkedserver`; Fehler 3906 statt einer zweiten Anfrage: jede Verbindung zu einem Verbindungsserver (`OPENQUERY`, viergliedrig, `OPENROWSET`/`OPENDATASOURCE`) und .NET-Code mit `EXTERNAL_ACCESS`/`UNSAFE`. Nur bei `RESTORE LOG` still übergangen: Anmeldungen, Serverrollen und -rechte, `sp_configure`/`RECONFIGURE`, Ressourcenkontrolle, Ereignissitzungen, Trigger `ON ALL SERVER`, `KILL`, serverweite DBCC, Verbindungsserver, vertraute Assemblies, Agent-Aufträge, Sicherungsverlauf; der Änderungsstrom (Kafka) merkt nichts vor. Dateien lesen bleibt erlaubt (gleiche Freigaben). Tests `AbspielsperreTests` (15 Fälle), `ClrAssemblyTests`. Rest: PR27.6e.✅
PR27.6eRest Abspielsperre**Erledigt 04.10.2026:** (1) `RESTORE DATABASE` übernimmt die Einstellungen der Datenbank wie am Orakel gemessen – datenbankweite Konfiguration samt `PREVIEW_FEATURES`, Snapshot/RCSI, `PARAMETERIZATION`, ADR, Änderungsverfolgung, `READ_ONLY` u. a.; `TRUSTWORTHY` und `DB_CHAINING` stehen in der Kopie auf OFF. `ALTER DATABASE SCOPED CONFIGURATION` und `ALTER DATABASE CURRENT|quelle SET …` wirken beim Abspielen auf die Zieldatenbank; der Änderungsstrom-Fall läuft jetzt ohne Handgriff. (2) Fund: der Wiederanlauf nach einem Absturz schrieb `BACKUP` erneut in die Sicherungsdatei – jetzt übergeht jedes Abspielen `BACKUP`. (3) Je Aussenweg ein eigener Test, `OPENROWSET`/`OPENDATASOURCE` mit `Ad Hoc Distributed Queries` = 1 dazu; jede Sabotage einzeln rot. Tests `WiederherstellungEinstellungenTests`, `AbspielsperreTests`, `AbspielsperreFernTests`.✅
PR27.6fProtokollende mit NORECOVERY übersteht den Absturz**Erledigt 04.10.2026:** seit PR27.6e übergeht der Wiederanlauf `BACKUP`, damit ging der Zustand von `BACKUP LOG … WITH NORECOVERY` verloren (nach einem Absturz vor dem nächsten Kontrollpunkt ONLINE statt RESTORING, Test rot). Jetzt setzt das Abspielen die Datenbank dabei in RESTORING, ohne Datei; jeder Fehler einer Sicherung steht als `Verworfen` im Protokoll, damit eine gescheiterte Sicherung beim Wiederanlauf nicht als gelungen gilt. Test `AbspielsperreTests.Protokollende_Mit_Norecovery_Ueberlebt_Den_Absturz` (beide Teile einzeln sabotiert).✅
PR27.6nSicherung in der eigenen Syntax jeder Datenbank, über die Routinen von T-SQL**Vorgabe des Auftraggebers (07.10.2026):** die Sicherung von KuonenSQL wird nach vorne in jeden Dialekt geführt: sie lässt sich mit der **Abfragesyntax der jeweiligen Datenbank in ihrer Version** auslösen, so wie ein Client sie an das Original schickt, und läuft dann über dieselben Routinen wie `BACKUP`/`RESTORE` in T-SQL (eine Sicherungsdatei, ein Verlauf in `msdb.dbo.backupset`, dieselbe Protokollkette und Zeitpunkt-Wiederherstellung aus PR27.6b, dieselben Rechte). Kein zweiter Sicherungsweg im Dialektmodul (Alles im Kern): der Dialekt erkennt nur Befehl und Fassung, übersetzt Namen und Antwort. **Befehle je Datenbank und Version, jeder am Orakel der Version gemessen (Antwort, Fehler, Rechte, Wirkung):** (1) **MySQL** 8.0.17 bis 9.x: `CLONE LOCAL DATA DIRECTORY [=] 'pfad'` = Vollsicherung aller Datenbanken in den Ordner (bestehender Ordner: Fehler wie am Orakel), `CLONE INSTANCE FROM …` = Wiederherstellung/Übernahme von einer anderen Instanz, `LOCK INSTANCE FOR BACKUP`/`UNLOCK INSTANCE` (ab 8.0) und `FLUSH TABLES WITH READ LOCK` = Sicherungssperre des Kerns; Fortschritt und Verlauf in `performance_schema.clone_status`/`clone_progress`, `mysql.backup_history`/`backup_progress`; Binärprotokoll (`SHOW BINARY LOGS`/`SHOW BINARY LOG STATUS` ab 8.2, `SHOW MASTER STATUS` davor, `PURGE BINARY LOGS`, `mysqlbinlog --read-from-remote-server`) aus der Protokollkette für die Zeitpunkt-Wiederherstellung. (2) **MariaDB** 10.4 bis 11.8: `BACKUP STAGE START/FLUSH/BLOCK_DDL/BLOCK_COMMIT/END` (ab 10.4.1), `BACKUP LOCK tabelle`/`BACKUP UNLOCK` (ab 10.4.2), `FLUSH TABLES … FOR EXPORT`, `FLUSH TABLES WITH READ LOCK` = Sicherungssperre; MariaDB hat keinen SQL-Befehl, der selbst eine Sicherung schreibt (das macht der Datei-Kopierer `mariadb-backup`, der InnoDB-Dateien liest und ausgenommen bleibt): **Entschieden (07.10.2026):** `BACKUP STAGE START … END` schreibt eine Kernsicherung, wenn eine Einstellung ein Sicherungsziel nennt (sonst nur die Sperre wie am Orakel); daneben gilt (4). (3) **PostgreSQL** 13 bis 17: `pg_backup_start(label, fast)`/`pg_backup_stop(wait)` ab 15, `pg_start_backup`/`pg_stop_backup` bis 14 (samt exklusivem Modus bis 14), `BASE_BACKUP` im Replikationsprotokoll in alter (bis 14) und neuer Optionsschreibweise (ab 15, `TARGET`, `COMPRESSION`, `MANIFEST`), ab 17 `UPLOAD_MANIFEST` und `BASE_BACKUP (INCREMENTAL)` = differenzielle Sicherung des Kerns; `pg_basebackup` erhält die Sicherungsdatei des Kerns (vorher messen, was es prüft), `pg_stat_progress_basebackup`, `pg_switch_wal`/`pg_create_restore_point` auf die Protokollkette (benannter Punkt = `STOPATMARK`). **Wiederherstellung in PostgreSQL-Syntax (Wunsch des Auftraggebers 07.10.2026):** PostgreSQL stellt nicht per SQL wieder her, sondern über Einstellungen und Neustart; hier lösen dieselben Einstellungen die Kern-Wiederherstellung aus: `ALTER SYSTEM SET restore_command`/`recovery_target_time`/`recovery_target_name`/`recovery_target_lsn`/`recovery_target_xid`/`recovery_target_inclusive`/`recovery_target_action` (je Datenbank oder Instanz, Gültigkeit und Fehler wie am Orakel) und danach `SELECT pg_reload_conf()` bzw. `pg_promote()` = `RESTORE … STOPAT/STOPATMARK … WITH RECOVERY`, `recovery_target_action = 'pause'` = `NORECOVERY`/`STANDBY`; `pg_is_in_recovery()`, `pg_last_wal_replay_lsn()`, `pg_get_wal_replay_pause_state()`, `pg_wal_replay_resume()` zeigen und steuern den Stand; `pg_restore`/`psql` einer `pg_dump`-Datei bleibt die logische Wiederherstellung (PR27.6a). Wo ein Ziel (Datei, Datenbankname) in PostgreSQL keine Schreibweise hat, gilt (4) als Funktion/Prozedur im Schema `kuonensql` (`CALL kuonensql.backup_database(…)`, `CALL kuonensql.restore_database(…)`), also in PostgreSQL-Schreibweise. (4) **Was die Datenbank selbst nicht kennt** (`BACKUP DATABASE … TO DISK`, `RESTORE … STOPAT/NORECOVERY` mit Ziel und Zeitpunkt) gibt es in jedem Dialekt als KuonenSQL-Erweiterung in dessen Schreibweise; Verlauf `msdb` in jedem Katalog lesbar (OR31 später: `ALTER DATABASE BEGIN/END BACKUP`). **Fassung:** welche Schreibweise gilt, folgt der Version, die der Zugang meldet (heute MySQL 8.4, MariaDB 11.8, PostgreSQL 17); ältere Schreibweisen werden angenommen, wo das Original der gemeldeten Version sie noch kennt, sonst derselbe Fehler wie dort. Der Assistent *MySQL Enterprise Backup* sucht das Programm `mysqlbackup` auf dem Rechner des Clients: dafür PR27.6o. Abnahme: Sicherung über jeden Zugang in dessen Syntax, Wiederherstellung über einen anderen (z. B. `CLONE` am MySQL-Port, `RESTORE` in SSMS, `pg_basebackup` am PostgreSQL-Port) ergibt dieselbe Datenbank. **Teil 1 erledigt 08.10.2026:** die Sicherungssperre im Kern (`Catalog.Sicherungssperre`, Arten DDL und Schreiben, mehrere Halter, endet mit der Sitzung), gesetzt über MySQL `FLUSH TABLES WITH READ LOCK`/`UNLOCK TABLES`, `LOCK INSTANCE FOR BACKUP`/`UNLOCK INSTANCE` und MariaDB `BACKUP STAGE BLOCK_DDL`/`BLOCK_COMMIT`/`END` (am Orakel mit zwei Sitzungen gemessen: 1205 nach der Frist, 1223 für die haltende Sitzung; Test `SicherungssperreTests`). **Teil 2 erledigt 21.10.2026:** auf die Sicherungssperre wartet eine Sitzung `lock_wait_timeout` (an beiden Orakeln gemessen, nicht `innodb_lock_wait_timeout`; Dialektschalter `Sicherungsfrist`), MariaDB `BACKUP STAGE` prüft die Reihenfolge wie `kq-ref-mariadb` (4146 ohne `START`, 4147 rückwärts, 4149 unbekannt, 4145 `FLUSH TABLES WITH READ LOCK` in einer Stufe, 1223 `START` unter eigenem FTWRL; nur eine Sitzung zugleich im Lauf, eine zweite wartet bis zur Frist, 1205) . Offen: T-SQL `SUSPEND_FOR_SNAPSHOT_BACKUP` auf dieselbe Sperre, die übrigen Eingänge (CLONE, Binärprotokoll, PostgreSQL `pg_backup_start`/`BASE_BACKUP`/`recovery_target_*`, `CALL kuonensql.backup_database`). **Datenbank löschen trotz Verbindungen erledigt 08.10.2026:** T-SQL `SET SINGLE_USER`/`RESTRICTED_USER`/`MULTI_USER` mit `WITH ROLLBACK IMMEDIATE`/`AFTER n`/`NO_WAIT` (trennt über `Sitzungsverwaltung.TrenneIn`, Anzeige in `sys.databases`, 924), PostgreSQL `DROP DATABASE … WITH (FORCE)` über dieselbe Routine, MySQL/MariaDB löschen eine benutzte Datenbank wie das Orakel (die Sitzung bekommt danach 1049). Nachgezogen: `RESTRICTED_USER` nach Besitz und Rolle (923), Anmeldung mit belegter bzw. gesperrter Startdatenbank 4060, ohne Klausel/`NO_WAIT` warten bis zur Sperrfrist (5061), Modus übersteht den Neustart.⬜
PR27.6oEigenes Sicherungswerkzeug, auch unter dem Namen `mysqlbackup`**Entscheid des Auftraggebers (07.10.2026):** ein eigenes Kommandozeilenwerkzeug **KuonenSQLBackup** (`KuonenSQLBackup`, Entscheid zum Namen 07.10.2026) (Windows, Linux, macOS; ohne fremde Abhängigkeiten, Einzeldatei wie der Server) sichert und stellt über einen beliebigen Zugang wieder her und ruft dabei nur die Kernsicherung aus PR27.6n auf (keine eigene Datenkopie, keine Dateien des Servers). Zusätzlich wird es über einen Alias als `mysqlbackup` (`mysqlbackup.exe`) bereitgestellt, damit der Assistent *MySQL Enterprise Backup* in MySQL Workbench / MySQL Shell für VS Code es findet und benutzt (Installer bzw. `dist`-Skript legt den Alias an den Ort, den Workbench prüft - im Nutzerfund `<basedir>\bin\mysqlbackup.exe`, sonst der in den Einstellungen gewählte Pfad). **Erst messen** (Mitschnitt der Aufrufe des Assistenten mit einem Stellvertreterprogramm, das Argumente und Umgebung nur aufzeichnet; das Enterprise-Programm wird nicht heruntergeladen, Handbuch der Befehlszeile ist öffentlich): welche Unterbefehle und Optionen Workbench schickt (`--version`, `backup`/`backup-to-image`, `--backup-dir`, `--backup-image`, `--with-timestamp`, `--incremental`, `--compress`, `--encrypt`, `validate`, `list-image`, `copy-back-and-apply-log`, Benutzer/Kennwortdatei), welche Ausgabe und welche Rückgabewerte es erwartet (Versionszeile, Fortschritt, „mysqlbackup completed OK!“) und was es danach in `mysql.backup_history`/`backup_progress` liest. Umsetzen in diesem Umfang: Voll-, differenzielle und Protokollsicherung, Prüfen, Auflisten, Wiederherstellen (auch auf einen Zeitpunkt), Planung über den Agenten des Kerns, wo der Assistent einen Zeitplan anlegt. Die Version meldet sich als KuonenSQL (kein Vortäuschen von Oracle-Erzeugnissen in Texten oder Markenzeichen); nur Name und Befehlszeile des Alias folgen der Erwartung des Clients. Abnahme: der Assistent in Workbench durchläuft alle vier Schritte, die Sicherung erscheint in `msdb.dbo.backupset` und in SSMS, die Wiederherstellung über SSMS ergibt dieselbe Datenbank. **Schritt 1 erledigt 08.10.2026:** Projekt `src/Kuonen.KuonenSQL.Backup` (Programm `KuonenSQLBackup`, Einzeldatei ohne Abhängigkeiten; Native AOT scheitert hier am fehlenden `vswhere`, darum gekürzte Einzeldatei, 11 MB), `--version`, Mitschnitt jedes Aufrufs in `aufrufe.log` neben dem Programm; `dist\mysqlbackup-einrichten.ps1 [-Mitschnitt] [-Entfernen]` legt es als `KuonenSQLBackup.exe` und `mysqlbackup.exe` nach `C:\app\bin` (der Assistent sucht `<basedir>\bin\mysqlbackup.exe`, der Container meldet `basedir = /app/`). **Schritt 2 erledigt 08.10.2026:** die Einrichtung des Assistenten läuft vollständig durch (gemessen am Backend der MySQL Shell von Workbench gegen einen eigenen Server, Prozessstarts über einen Audit-Haken, siehe umsetzung.md): KuonenSQLBackup auch als `mysql_config_editor` (Anmeldepfade, Kennwort über die Pseudokonsole) und `mysql` (eigener MySQL-Protokollclient); im Kern globale MySQL-Rechte im einen Rechtemodell (`SELECT ON *.*` = `CONNECT ANY DATABASE` + `SELECT ALL USER SECURABLES`, am Orakel gemessen und durchgesetzt; übrige auf `MYSQL::[*.*]`), Rechte auf Tabellen der eingebauten Schemata, Kontoauswahl nach Adresse wie das Orakel, Kennwort mit Maskierkommentaren, Lauscher für IPv6 und IPv4, `/app/data` nur im Container. Offen: die Sicherung selbst (`mysqlbackup backup-to-image` usw. über die Kernsicherung, Aufrufe des Assistenten bei „Backup now“ messen), Wiederherstellen, Zeitplan; `mysql` muss über den Suchpfad erreichbar sein (`-ImSuchpfad`). **Nachtrag 08.10.2026 (Nutzertest im Container):** im Assistenten `mysqlbackup@%` eingeben (die Verbindung kommt aus dem Docker-Netz); `basedir` lässt sich wie `--basedir` beim Original beim Start setzen (`KUONENSQL_BASISORDNER`, für Windows-Clients `C:\app\`), sonst schreibt der Assistent `/app\bin\mysqlbackup.exe` in `backups.cnf` und sein Hilfsprogramm lehnt sie ab (`invalid_config`). **Schritt 3 (Teil 1) erledigt 08.10.2026:** Aufruf einer Sicherung gemessen (mit einem Übersetzer vor `schtasks` nur im Testordner, denn Workbench speichert unter deutschem Windows kein Profil: es erwartet die englische Meldung von `schtasks`): `mysqlbackup --defaults-file=<home>/<profil>.cnf --login-path=… --with-timestamp --backup-dir=… --show-progress=file:<home>/.backup-progress --progress-interval=1 backup`, Ausgabe nach `<home>/logs/*.log`. Im Kern `BACKUP DATABASE`/`BACKUP LOG … TO CLIENT` (KuonenSQL): dieselbe Sicherungsdatei wie `TO DISK` als Ergebnis (`Teil`, `Daten` zu 1 MB), Verlauf in msdb mit Gerät `CLIENT`. **Teil 2 erledigt 08.10.2026:** `mysqlbackup backup` sichert über `TO CLIENT` jede Benutzerdatenbank nach `--backup-dir` (mit `--with-timestamp` in einen Unterordner, je Datenbank `kuonensql/<db>.bak`), liest Optionsdatei und Anmeldepfad, schreibt die Fortschrittsdatei; der Assistent meldet „Completed OK“ mit Grösse (gemessen: er findet den Ordner an der Zeile `MAIN INFO: Backup directory created: '…'`, am Erfolg an „mysqlbackup completed OK!“). Test `KuonenSqlBackupTests` (Sicherung über einen Zugang im selben Prozess, Wiederherstellung, Verlauf). **Teil 3 erledigt 08.10.2026:** Wiederherstellen vom Client: im Kern `RESTORE … FROM CLIENT = 'name'` (der Zugang holt die Datei vor der Ausführung in einen Zwischenstand, MySQL über das Paket 0xFB wie `LOAD DATA LOCAL`, Deckel 256 GB, nach der Abfrage gelöscht; ohne geschickte Datei 3201), `mysqlbackup copy-back-and-apply-log`/`copy-back` stellt jede Sicherung aus `<backup-dir>/kuonensql` mit `WITH REPLACE` wieder her; der Client schickt nur genau die angeforderte Datei. **Teil 4 erledigt 09.10.2026:** die Metadaten im Ordner `meta` (gemessen, siehe umsetzung.md): die Inhaltsanzeige des Assistenten zeigt Version, Zeiten und Wiederherstellungskette, „Restore“ im Assistenten stellt über `copy-back-and-apply-log` wieder her (am Backend geprüft: geänderter Wert kommt zurück); KuonenSQLBackup als Native AOT (2.6 MB), die Namen als feste Verknüpfungen. **Teil 5 erledigt 09.10.2026:** „Verify“ (`validate`, über `RESTORE VERIFYONLY FROM CLIENT`, eine verfälschte Datei meldet der Kern), „Apply Log“ (`apply-log`, ohne Anmeldung: eine Kernsicherung ist schon stimmig) und die inkrementelle Sicherung (Workbench kennt nur full/incremental; `--incremental --incremental-base=history:last_backup --incremental-backup-dir=…`): Differenz des Kerns zur letzten Vollsicherung von mysqlbackup, gefunden über die Beschreibung im Verlauf des Servers; die Kette im Assistenten (gemessen: `start_lsn` = `end_lsn` der Basis + 1) zeigt Full + Incremental, „Restore“ spielt Basis (`NORECOVERY`) und Differenz ein (am Backend geprüft). Nebenbefund behoben: `CREATE TABLE IF NOT EXISTS db.t` am MySQL-Zugang meldete beim zweiten Mal 1050 statt des Hinweises. **Teil 6 erledigt 09.10.2026:** `mysql.backup_history` (26 Spalten nach dem MEB-Handbuch) und `mysql.backup_progress` (leer) im Katalog des MySQL-Zugangs, die Zeilen aus dem Sicherungsverlauf des Kerns: ein Lauf von KuonenSQLBackup ist eine Zeile (DIRECTORY, FULL/INCREMENTAL, Ordner als Ziel), jede andere Sicherung aus T-SQL/SSMS ebenfalls (IMAGE, FULL/DIFFERENTIAL/INCREMENTAL), `server_uuid` wie `@@server_uuid`. **Teil 7 erledigt 09.10.2026:** `FROM CLIENT` über TDS und PostgreSQL: beide Protokolle können keine Datei beim Client anfordern, die Sicherung kommt darum als Parameter - `RESTORE … FROM CLIENT = @sicherung` (TDS `varbinary(max)`, auch als Datenstrom; PostgreSQL `$1` als `bytea`), im Kern für jeden Zugang; `BACKUP … TO CLIENT` liefert seine Zeilen auch im erweiterten Protokoll von PostgreSQL (Describe nennt die Form), `BACKUP`/`RESTORE` dort unübersetzt bis auf `$n`. Offen: Tabellenliste der Inhaltsanzeige - gemessen 09.10.2026: sie hängt nicht an Dateien in der Sicherung, sondern am Manifest in `meta/backup_content.xml` (das MEB-Handbuch: daran erkennt ein Werkzeug, welche Tabellen in einer Sicherung sind); Workbench sucht darin einen Wurzelschlüssel, unter dem `backup_content` liegt, dessen Name sich von aussen nicht ermitteln liess (159 Kandidaten ohne Treffer, kein öffentliches Beispiel) - braucht eine echte `backup_content.xml` von MEB zum Messen; bis dahin zeigt der Assistent jede Tabelle als „Table will be deleted“ (nur Anzeige, die Wiederherstellung stellt alle Tabellen her). Zeitplan (Workbench speichert unter deutschem Windows kein Profil - Fehler von Workbench; der Zeitplan liegt beim Client in `schtasks`/`cron`, nicht im Server). Stand 21.10.2026: beide Reste brauchen Marc - eine echte `meta/backup_content.xml` von MySQL Enterprise Backup (nicht frei erhältlich) bzw. den Handtest des Zeitplans und die Abnahme (vier Schritte im Assistenten, Sicherung in SSMS sichtbar, Wiederherstellung über SSMS).⬜
PR27.6o2`USE` in `EXEC('…')` gilt nur bis zum Ende von `EXEC`Nebenbefund aus PR27.6o (09.10.2026): über TDS steht nach `EXEC('USE zz_use'); SELECT DB_NAME()` die Sitzung danach in `zz_use`; am Orakel (SQL Server 2025, 09.10.2026) liefert `EXEC('USE tempdb'); SELECT DB_NAME()` `master`: der Wechsel wirkt nur bis zum Ende von `EXEC`. **Erledigt 09.10.2026:** am Orakel gemessen (EXEC und `sp_executesql`, verschachtelt, nach Fehler im TRY, Erben des Aufruferkontexts, keine Meldung „Changed database context“) und nachgebildet: beide Wege kehren am Ende in die Datenbank des Aufrufers zurück.✅
PR27.6o1Differenzsicherung ohne Vollsicherung wie SQL Server ablehnenNebenbefund aus PR27.6o (09.10.2026): `BACKUP DATABASE … WITH DIFFERENTIAL` ohne vorige Vollsicherung legt im Kern eine Differenz an; SQL Server lehnt sie mit 3035 ab. Am Orakel messen (Meldung, Zustand, auch nach `RESTORE`/`COPY_ONLY`) und nachbilden; KuonenSQLBackup wählt die Differenz schon nur mit Basis. **Erledigt 09.10.2026:** am Orakel gemessen und nachgebildet - 3035 ohne Basis (auch mit COPY_ONLY, auch für msdb, auch nach Wiederherstellung aus einer Vollsicherung mit COPY_ONLY), Basis nach einer Vollsicherung oder Wiederherstellung einer Vollsicherung; master nur voll (3024), tempdb nie (3147, Zustand 3); dazu allgemein: CATCH und @@ERROR sehen den zuletzt gemeldeten Fehler (3013 bzw. 4916).✅
PR27.6p`pg_dump`/`pg_dumpall`/`pg_restore` vollständigAlle Abläufe der üblichen Anleitung (Nutzerhinweis 07.10.2026, linuxize.com „PostgreSQL Backup and Restore with pg_dump and pg_restore“) laufen gegen den PostgreSQL-Zugang wie gegen PostgreSQL 17, je Rundlauf am Orakel `postgres:17` verglichen (Zeilen, Objekte, Rechte, Rückgabewert 0). Bisher belegt (PR27.6a, `PgDumpAbnahmeTests`): Klartext `pg_dump | psql` und `pg_dump -Fc` + `pg_restore --exit-on-error`. **Erledigt 09.10.2026:** `COPY … TO/FROM 'datei'` auf dem Server (Text, CSV, Binär; nur Superuser bzw. `pg_write_server_files`/`pg_read_server_files`, nur in den freigegebenen Ordnern des Kerns, Fehler 42602/58P01/42501 wie `postgres:17`; `PROGRAM` bleibt 0A000). **Erledigt 10.10.2026 (Lauf mit `pg_dump`/`pg_restore`/`pg_dumpall` 17):** `-t`/`-T`/`-n`/`-N` mit Mustern, `--schema-only`/`--data-only`, `-Fd`/`-Ft`/`-Fc -Z 9`, `pg_restore -t`, `--clean --if-exists` (`ALTER TABLE IF EXISTS`, `DROP CONSTRAINT IF EXISTS` in eigenem Schema), `--single-transaction --no-owner --no-privileges`, `-C`, `pg_dumpall` und `-g` (`pg_parameter_acl`), `createdb -T template0`; dazu: ein nachträglicher Primärschlüssel macht seine Spalten NOT NULL (42P16 bei doppeltem), `ALTER TABLE s.t OWNER TO` in eigenem Schema, `DROP SCHEMA … CASCADE` wie PostgreSQL, das Schema `redis` teilte die Kennung mit dem ersten eigenen Schema (pg_dump liess es aus). Ebenso `pg_dump -Fd -j 4` und `pg_restore -j 4` (`pg_export_snapshot`, `SET TRANSACTION SNAPSHOT` über die Momentaufnahmen des Kerns: jede Arbeitsverbindung liest denselben Stand). Rollen in `pg_dumpall` tragen den SCRAM-Prüfwert (aus dem Kennworthash; ein übernommener `PASSWORD 'SCRAM-SHA-256$…'` meldet sich über SCRAM an, am echten PostgreSQL belegt); `ALTER ROLE r` bzw. `ALTER ROLE ALL` (auch `IN DATABASE d`) mit `SET`/`RESET` als Sitzungsvorgaben des Kerns (gelten beim Anmelden, `pg_db_role_setting`, `pg_dumpall` schreibt sie wie am Orakel); (8) ältere `pg_dump` 13–16 lehnen einen Server 17 ab wie PostgreSQL selbst (`server version mismatch`, am Orakel gleich). **Erledigt 10.10.2026 (Sicherungsrolle und Auswahl, `PgDumpAbnahmeTests`):** eine Rolle mit `pg_read_all_data` (P18.16) sichert die ganze Datenbank, die Zeilensicherheit bleibt (`row_security = off` meldet wie am Orakel); `pg_restore -l`/`-L` mit gekürzter Liste, `--data-only --disable-triggers` (PostgreSQL schaltet mit `DISABLE TRIGGER ALL` auch die Fremdschlüssel ab: im Kern `NOCHECK` und beim Einschalten `WITH NOCHECK CHECK`), `createdb`/`dropdb`; dabei behoben: eine Triggerfunktion in einem eigenen Schema (`EXECUTE FUNCTION app.tf()`) wurde nicht gefunden und als `public.tf()` zurückgeschrieben, `pg_get_triggerdef` schrieb `public.app.b`, `pg_get_constraintdef` liess das Schema der Zieltabelle eines Fremdschlüssels weg (`app.k`, bei leerem Suchpfad `public.k`, am Orakel gemessen). Bewusst anders als PostgreSQL bleibt: eine neue Datenbank gibt PUBLIC kein `CONNECT` (S12.19f), die Sicherungsrolle braucht `GRANT CONNECT ON DATABASE`. **Erledigt 21.10.2026 (Verbindung zu PR27.6n):** ein Lauf von `pg_dump` (Programmname `pg_dump`, liest in einer Transaktion aus einer Momentaufnahme; die Verbindungen von `-j` sind ein Lauf, auch `pg_dumpall` je Datenbank) steht im Sicherungsverlauf des Kerns als logische Sicherung (`msdb.dbo.backupset` Art `X`, ausserhalb der Protokollkette, Gerät 7 mit dem Werkzeug als Name), ein `pg_restore` als logische Wiederherstellung (`msdb.dbo.restorehistory` samt Satz für die Quelle); ein abgelehntes `pg_dump` 16 hinterlässt nichts (Abnahme mit `pg_dump`/`pg_restore` 17 und 16). Dieselbe Regel für `mysqldump`/`mariadb-dump` und MySQL Shell: PR27.6r.✅
PR27.6qSicherungswege des MySQL-Handbuchs (Kapitel 9)Alle Wege aus dem Handbuch MySQL 8.4, Kapitel 9 „Backup and Recovery“ (Nutzerhinweis 07.10.2026), laufen am MySQL-Zugang wie am Orakel `kq-ref-mysql` und, wo sie eine Sicherung bilden, über die Kernsicherung aus PR27.6n. Bisher belegt (MY30.7): `mysqldump` 8.4 Rundlauf (WordPress-Dump Byte für Byte). (1) **`mysqldump`-Optionen** (9.4) **erledigt 21.10.2026** (Lauf mit `mysqldump`/`mysql` 8.4 aus `mysql:8.4`, jeder Schritt am Orakel `kq-ref-mysql` gleich): `--single-transaction`, `--routines`/`--events`/`--triggers`, `--no-data`, `--where`, `--databases`, `--flush-logs`, `--lock-all-tables` (FTWRL über die Sicherungssperre), `--tab=dir` (Zeilen über `INTO OUTFILE` in `secure_file_priv`), Einspielen mit `mysql < dump.sql` und `SOURCE`; `--dump-replica` endet ohne Kopie wie am Orakel mit 1200 (`STOP REPLICA [SQL_THREAD]` mit Hinweis 3084, `START REPLICA` 1200, vorher 1064) . Offen davon: `--source-data` braucht das Binärprotokoll aus (4) (heute wie ein Server ohne `log_bin`: „Binlogging on server not active“), `--all-databases`: PR27.6s. (2) **Textsicherung** (9.2/9.4.3) **erledigt 09.10.2026:** `SELECT … INTO OUTFILE/DUMPFILE` über die Ausfuhr des Kerns (`Execution.Textausfuhr`, nur in freigegebene Ordner, nie über eine bestehende Datei), Dateien Byte für Byte wie am Orakel (Escape-, Anführungs- und NULL-Regeln, `OPTIONALLY`, `LINES STARTING/TERMINATED BY`, `CHARACTER SET`), Fehler 1086/1290/1172/1083; zurück wie bisher mit `LOAD DATA [LOCAL] INFILE`. Offen: Zeilen fester Breite (`FIELDS TERMINATED BY ''`, hier 1235) und `mysqlimport` als eigener Lauf. (3) **Tabellendateien** (9.2): `FLUSH TABLES t WITH READ LOCK`, `FLUSH TABLES t FOR EXPORT` und `ALTER TABLE t DISCARD/IMPORT TABLESPACE`, `IMPORT TABLE FROM 'datei.sdi'`: Ausfuhr und Einfuhr einzelner Tabellen über die Kernsicherung (eine Tabelle sichern und in eine andere Datenbank oder Instanz einspielen), statt InnoDB-Dateien. (4) **Inkrementell über das Binärprotokoll und Zeitpunkt-Wiederherstellung** (9.5): `FLUSH [BINARY] LOGS` schliesst ein Protokollstück ab (= `BACKUP LOG` des Kerns), `SHOW BINARY LOGS`/`SHOW BINARY LOG STATUS`/`SHOW BINLOG EVENTS`, `PURGE BINARY LOGS TO`/`BEFORE`, `binlog_expire_logs_seconds`; `mysqlbinlog --read-from-remote-server --start-datetime/--stop-datetime/--start-position/--stop-position … | mysql` liefert die Änderungen aus der Protokollkette (Replikationsbefehl `COM_BINLOG_DUMP`, Ereignisse im Zeilenformat, so weit `mysqlbinlog` sie lesen muss); Abnahme: Vollsicherung, Änderungen, `FLUSH LOGS`, versehentliches `DROP TABLE`, Wiederherstellung bis kurz davor über `mysqlbinlog --stop-datetime` ergibt den Stand davor. (5) **Sicherung von einer Kopie** (9.2, 19.4.1): über eine lesbare Kopie (H20.2) mit denselben Befehlen; die Replikationsangaben gehören dazu. (6) **Tabellenpflege** (9.6) **erledigt 09.10.2026:** `CHECK`/`REPAIR`/`OPTIMIZE`/`ANALYZE TABLE` über die Pflege des Kerns (`ExecutionEngine.PflegeTabelle`: Konsistenzprüfung wie `DBCC CHECKDB`, `UPDATE STATISTICS`, `ALTER INDEX ALL … REBUILD`), Ergebnistabelle und Wortlaut je Server wie an beiden Orakeln (fehlende Tabelle/Datenbank, Sicht, Optionen); offen davon nur `CHECKSUM TABLE` (am Orakel eine CRC über das Zeilenformat, beide Server gleich) ; der PostgreSQL-Weg ist ebenfalls erledigt (`ANALYZE`, `VACUUM [FULL] [(ANALYZE)]`, `REINDEX TABLE` mit Tabellen über dieselbe Pflege, Fehler und Warnungen wie `postgres:17`). (7) **Planung, Komprimierung, Verschlüsselung** (9.3): wie die Kernsicherung (Agentenaufträge, `WITH COMPRESSION`, `ENCRYPTION`).⬜
PR27.6r`mysqldump`/`mariadb-dump`/MySQL Shell im SicherungsverlaufRest aus PR27.6p (21.10.2026): ein Lauf von `mysqldump`, `mariadb-dump` und MySQL Shell `util.dumpInstance`/`dumpSchemas` erscheint wie `pg_dump` als logische Sicherung im Verlauf des Kerns (`msdb.dbo.backupset` Art `X`), `util.loadDump` als logische Wiederherstellung. Anders als `pg_dump` sichert eine Verbindung mehrere Datenbanken nacheinander (`--all-databases`, `--databases`) und liest ohne `--single-transaction` unter `LOCK TABLES` statt aus einer Momentaufnahme: erkannt am Programmnamen (Verbindungsattribut `program_name`, vorher messen, was jedes Werkzeug schickt) und je Datenbank, deren Tabellen die Verbindung las; ein Satz je Datenbank. Kernroutine: `ExecutionEngine.BeendeLogischenLauf`. **Erledigt 21.10.2026 (`mysqldump`, `mariadb-dump`):** beide schicken `program_name=mysqldump` (am Lauf gemessen); die Sitzung merkt sich jede gewählte Datenbank (`Sitzungsdaten.GewaehlteDatenbanken`), am Ende steht je Datenbank ausser den Systemdatenbanken ein Satz Art `X` (sieben Läufe auf `mdp` = sieben Sätze, `--databases mdp mdq` je einer), ohne Nachlauf (der gilt nur für Wiederherstellungen) . MySQL Shell: PR27.6t.✅
PR27.6tMySQL Shell `util.dumpInstance`/`loadDump` im SicherungsverlaufRest aus PR27.6r (21.10.2026): MySQL Shell meldet sich für jede Arbeit gleich (`program_name` `mysqlsh`), ein Lauf von `util.dumpInstance`/`dumpSchemas` lässt sich darum nicht am Namen erkennen. Erst messen (Abbild `mysql:8.4` enthält `mysqlsh`), woran ein Ausspielen bzw. `util.loadDump` erkennbar ist (Abfolge, Sitzungsvariablen, `FLUSH TABLES WITH READ LOCK`/`LOCK INSTANCE FOR BACKUP`, mehrere Arbeitsverbindungen), dann wie `mysqldump` je Datenbank ein Satz Art `X` bzw. eine logische Wiederherstellung (`ExecutionEngine.BeendeLogischenLauf`).⬜
PR27.6s`mysqldump --all-databases` mit der Systemdatenbank `mysql`Rest aus PR27.6q (1), am 21.10.2026 gemessen: `mysqldump --all-databases` bricht am MySQL-Zugang ab (`Couldn't read status information for table columns_priv`, danach `show create table` 1146 `Table 'master.columns_priv' doesn't exist`) - die Systemtabellen von `mysql` (MY30.7q: `user`, `db`, `tables_priv` …) antworten auf `SELECT`, aber nicht auf `SHOW TABLE STATUS`/`SHOW CREATE TABLE` in `mysql`. Umsetzen wie am Orakel `kq-ref-mysql` (vorher messen, welche Tabellen `mysqldump` 8.4 aus `mysql` sichert und wie, samt Protokolltabellen nur als Struktur): Struktur und Zeilen aus dem Kern liefern und beim Einspielen derselben Datei die Systemtabellen nicht ersetzen, sondern Konten und Rechte über den Kern übernehmen (`CREATE USER`/`GRANT` wie MY30.7q). Abnahme: `mysqldump --all-databases` und `mysql < dump.sql` in einen frischen Server ergeben dieselben Datenbanken, Konten und Rechte.⬜
PR27.7Fuzzing der drei ProtokolleZufällig und gezielt verfälschte Pakete an TDS, MariaDB- und PostgreSQL-Port (vor und nach der Anmeldung, auch mit TLS): der Server stürzt nie ab, hängt nie, gibt keinen fremden Speicher aus, jede Verbindung wird sauber beendet. **Erledigt 04.10.2026:** eigener deterministischer Fuzzer (`ProtokollFuzzingTests`, je Protokoll vor/nach der Anmeldung und über TLS, im Testlauf ~9000 Fälle in Sekunden), Funde als Tests (`FuzzingFundeTests`, `SchachteltiefeTests`) und behoben: PostgreSQL-Leser griff hinter das Ende, jeder Serverfehler wird `XX000`/4804 an diesen Client; Längenangaben belegen keinen Speicher vor den Daten, vor der Anmeldung 128 KiB (TDS, MariaDB); **Stapelüberlauf durch tiefe Schachtelung beendete den Prozess** — jetzt 191/8631 wie am Orakel (`Stapelwache`); unbeobachtete Ausnahme der TDS-Vorauslesung. Langer Lauf: `dist\fuzzing.ps1 -Sekunden 28800` (3 Zeilen × 8 h = 24 h je Protokoll), Rest in PR27.7a/b.✅
PR27.7bSchachteltiefe wie am OrakelSQL Server rechnet 1000 Klammern und 3000 Kettenglieder; KuonenSQL meldete 191/8631 schon früher (Stapel des Verbindungsfadens). **Erledigt 04.10.2026** (`SchachtelgrenzenTests`, `docs/umsetzung.md` PR27.7b): Grenzen je Form am Orakel auf die Ebene genau gemessen und nachgebaut — Klammern/Vorzeichen 1015, ABS 1013, NOT 1006, IF 1008, BEGIN 504, Unterabfrage 168 (191); Rechen-/Verkettungsketten 3029, AND 8870 Glieder (8631 für den ganzen Stapel vor der ersten Anweisung), OR ohne Grenze; CASE/IIF/COALESCE/CHOOSE/NULLIF ab der 11. Ebene 125 (Zustand 4/2/1). Ein tiefer Stapel beginnt, solange er nur gelesen ist, auf einem eigenen Faden mit 256 MiB Stapel neu; inneres dynamisches SQL wechselt allein. Stapelwache bleibt. Gemischte Formen ±1 Ebene. Lesen +1,5 % (13,2 → 13,4 µs, kürzeste von 7 Läufen, Release), Stapel unverändert.✅
PR27.8SicherheitsprüfungAnmeldung (SCRAM, mysql_native/caching_sha2, SQL-Anmeldung), Rechtemodell über alle Zugänge (S12.19), Ausbruch aus der Datenbank (Dateizugriff, `xp_*`, CLR, `COPY … PROGRAM`, `LOAD DATA LOCAL`), Kennwortablage, TLS-Einstellungen: systematisch gegen eine Prüfliste, jeder Befund mit Test. **Erledigt 04.10.2026** (Prüfliste in `docs/umsetzung.md`, Tests `Sicherheit/`): Anmeldebremse für alle Zugänge (10 Fehlversuche je Adresse und Name, dann 1 s bis 5 min); `KUONENSQL_SA_PASSWORD` und Warnung für `sa` ohne Kennwort; `xp_cmdshell`/`xp_subdirs`/`xp_create_subdir`/`xp_readerrorlog` 229, `sp_readerrorlog` 27219, `sp_configure` mit Wert und `sp_addlinkedserver` 15247, `DBCC CHECKDB` 7983, `BULK INSERT` 4834 wie am Orakel; Injektion über den Namen einer temporären PostgreSQL-Tabelle behoben; TLS-Pflicht für alle drei Zugänge (TDS ENCRYPT_REQ, MariaDB 3159, PostgreSQL 28000); Kennwörter in keinem Protokoll. Rest PR27.8a.✅
PR27.8aRest der Sicherheitsprüfung(1) MariaDB `SET GLOBAL` ohne SUPER → 1227, PostgreSQL `ALTER SYSTEM` ohne Superuser → 42501; (2) `pg_authid` nur für Superuser (42501), `sys.sql_logins`/`sys.server_principals` zeigen einer gewöhnlichen Anmeldung nur sich und `sa`; (3) `OPENROWSET(BULK …)` ohne `ADMINISTER BULK OPERATIONS` → 4834; (4) `KUONENSQL_SA_PASSWORD` in `docker-compose` durchreichen. **Erledigt 04.10.2026** (`SicherheitsrestTests`, `docs/umsetzung.md` PR27.8a): `OPENROWSET(BULK)` 4834 wie `BULK INSERT`; `sys.sql_logins`/`sys.server_principals` am Orakel vermessen (sich, `sa`, `public`, feste und eigene Rollen; alles mit sysadmin/securityadmin/`ALTER ANY LOGIN`/`VIEW ANY [SECURITY] DEFINITION`, einzeln mit `VIEW DEFINITION ON LOGIN::x`); MariaDB `SET GLOBAL`/`SET @@GLOBAL.` (auch in einer Liste) 1227; PostgreSQL `ALTER SYSTEM` 42501/25001/42704, `pg_authid` 42501; `KUONENSQL_SA_PASSWORD` in `docker-compose.yml`/`.env.beispiel`/README, leer = unverändert; Nebenbefund `ALTER … ADD MEMBER x END TRY` verschluckte das `END`. Rest PR27.8b.✅
PR27.8bTexte der Rechtefehler am Referenzcontainer bestätigenMariaDB 11.8 und PostgreSQL 17 über `orakel-fragen.sql` gemessen. **Erledigt 04.10.2026** (`SicherheitsrestTests`): MariaDB `SET GLOBAL` verlangt je Variable das gemessene Recht (`max_connections` CONNECTION ADMIN, `read_only` READ_ONLY ADMIN, sonst SUPER, 1227); PostgreSQL `ALTER SYSTEM` (auch `RESET ALL`, unbekannter Parameter 42501 für Nicht-Superuser, 25001 zuerst) bestätigt. Rest PR27.8c.✅
PR27.8cMariaDB: globale Einzelrechte`GRANT|REVOKE SUPER/CONNECTION ADMIN/READ_ONLY ADMIN ON *.*` als Serverrechte im einen Rechtemodell (ALTER SETTINGS, ALTER ANY CONNECTION, ALTER SERVER STATE), sichtbar in `SHOW GRANTS`/`USER_PRIVILEGES`/`sys.server_permissions`, `SET GLOBAL` prüft sie — **erledigt 04.10.2026** (`SicherheitsrestTests`). Rest PR27.8d.✅
PR27.8dMariaDB: unbekannte Systemvariable 1193`SET GLOBAL gibts_nicht = 1` ist bei MariaDB 11.8 1193 (HY000) vor der Rechteprüfung. **Erledigt 04.10.2026** (`SicherheitsrestTests`): 703 Namen vom Referenzcontainer (11.8.9, `information_schema.SYSTEM_VARIABLES`) eingebettet (`MariaDb/Referenzdaten/systemvariablen.txt`), 1193 für jeden vor der Rechteprüfung, auch als Listenglied; Rechte-Reihenfolge in SHOW GRANTS/USER_PRIVILEGES bestätigt. Das gemeinsame Einstellungsmodell aller Zugänge folgt unter M26.✅
PR27.9Abnahme je Ziel-ProgrammJedes Programm, das produktiv laufen soll, besteht seine Abnahme in `docs/app-abnahme.md` (Installation, jede Seite, Aktualisierung auf die nächste Fassung), mit `KUONENSQL_FEHLERPROTOKOLL=1` ohne Eintrag. **Erledigt 04.10.2026 (Gerüst und erste Abnahmen):** `dist\app-abnahme.ps1` (eigener Testserver, Programm aus `tools\app-abnahme\<name>\`, Installation, Anmeldung, Seiten, Zählung der SQL-Fehler, Aufräumen); WordPress ✅ (26 Seiten samt Verwaltung) und Gitea ✅ (9 Seiten und API) ohne SQL-Fehler. Rest PR27.9a.✅
PR27.9aWeitere Programme und AktualisierungDefinitionen für die vom Nutzer gewählten Programme, Gitea mit Anmeldung, Aktualisierung auf die nächste Fassung. **Erledigt 04.-06.10.2026:** Adminer (MariaDB und PostgreSQL), phpMyAdmin, Nextcloud 34→35, Joomla, Drupal 10→11.4, WordPress 6.9→7.1, Gitea mit Anmeldung ohne SQL-Fehler; `app-abnahme.ps1` mit JSON-/PUT-Schritten, Bearer-Marke und Dauer der Aktualisierung; Keycloak und n8n definiert, n8n-Migrationen ohne SQL-Fehler (14 Fehler im PostgreSQL-Port behoben, siehe `docs/app-abnahme.md`). Reste PR27.9b.✅
PR27.9bKeycloak, n8n und Gitea-Aktualisierung abnehmen**Erledigt 21.10.2026** (`distpp-abnahme.ps1`, Abbilder Keycloak 26.8.0, n8n 2.41.5, Gitea 1.27 → latest): **Keycloak ✅** Start im Container 113 s (Liquibase samt Schema, vorher über 15 min), 25/25 Seiten, 0 SQL-Fehler; behoben: `pg_catalog.generate_series(1, 32) pos(n)` in FROM, abgeleitete Tabelle mit Spaltenliste hinter Komma/JOIN (auch T-SQL, vorher 102), `pg_constraint.conindid`/`confupdtype`/`confdeltype` eines Fremdschlüssels (pgjdbc `getImportedKeys`). **n8n 🔶** Migrationen und Start 105 s, 17/17 Seiten (Einrichtung, Ablauf, Marke, Zugang, Variablen, Einblicke …); behoben: DROP COLUMN nahm fremde CHECK-Bedingungen mit (`LIKE '%[name]%'` war eine Zeichenklasse), `VALUES (DEFAULT, …) … ON CONFLICT`, `INSERT INTO t AS a (…)` (Alias, Parametertypen ohne OID), `FOR UPDATE SKIP LOCKED` über Parse (`relation "skip"`), `(a IS NULL) <> (b IS NULL)`, `GROUP BY` nach Alias der Auswahl, `pg_stat_all_tables`/`pg_stat_user_tables`. Rest: PR27.9c. **Gitea ✅** Aktualisierung 1.27 → latest in 43 s (vorher über 20 min), 39/39 Seiten, 0 SQL-Fehler. Tests `PgAppAbnahmeTests` (neun neue), `KommaverbundTests`; Sabotage je Punkt rot.✅
PR27.9cn8n: schreibende CTE mit UpsertRest aus PR27.9b: n8n fasst seine Ablaufstatistik zusammen mit `WITH batch AS (DELETE … RETURNING …), agg AS (SELECT … FROM batch GROUP BY …), upsert AS (INSERT … SELECT … FROM agg ON CONFLICT (…) DO UPDATE SET … RETURNING …, ("xmax" = 0) AS inserted) SELECT … FROM upsert JOIN agg` - der Port meldet 42601 bei `ON` (Upsert nur auf oberster Ebene, schreibende CTEs fehlen); einziger verbliebener SQL-Fehler der Abnahme (Hintergrundarbeit, keine Seite betroffen). Dazu gefunden: `pg_indexes.indexdef` eines Teilindex ohne `WHERE …`, `information_schema.columns` ohne die Sichten von `pg_catalog`, `ALTER TABLE … DROP COLUMN` braucht bei n8n 1–1,5 s je Spalte. Ziel: n8n ✅ (Sollwerte postgres:17)⬜
PR27.10Speicherleck-PrüfungVor allen Lastläufen (Nutzerwunsch 03.10.2026). Ein Prüflauf wiederholt jede Arbeitsart in Runden: Anmelden und Abmelden über TDS, MariaDB, PostgreSQL und Redis, vorbereitete Anweisungen, Cursor, Transaktionen mit Rücknahme, temporäre Tabellen, Auslagerung, gestromte Ergebnisse, abgebrochene Abfragen, Sitzungen ohne sauberes Ende. Nach jeder Runde voller GC; gemessen werden verwalteter Heap, Working Set, Handles, Threads, offene Dateien und die Zahl lebender Objekte je Typ (GC-Abzug). **Bestanden:** nach einer Einschwingphase wächst keine dieser Grössen über die Runden (Steigung innerhalb der Messstreuung, kein Typ mit stetig wachsender Zahl); jeder Fund wird ein Fehler mit Test. Läuft mit begrenzter Rundenzahl in der Testsuite und als langer Lauf im Werkzeug. **Erledigt 04.10.2026:** `SpeicherleckTests` (elf Arbeitsarten über TDS, MariaDB, PostgreSQL, Redis; Heap, Working Set, Handles, Fäden, Sitzungen je Runde), langer Lauf `dist\speicherleck.ps1 -Runden 1000`. Fund: die Merker je Unterabfrage hielten jede gelesene Anweisung fest (2,6 KiB je Ausführung) — jetzt schwach an der Anweisung. Rest PR27.10a.✅
PR27.10aLanger Leck-Lauf`dist\speicherleck.ps1 -Runden 1000` vom Hauptchat oder Nutzer, dazu `dotnet-gcdump` am Anfang und Ende (Zahl lebender Objekte je Typ); Auslagerung (Sortierung/Gruppierung über die Speichergrenze) als weitere Arbeitsart. **Erledigt 05.10.2026:** dist\speicherleck.ps1 -Runden 1000 bestanden (13 min, Release): verwalteter Heap 180945 → 176102 → 178566 → 181044 KiB (Runde 1/100/500/1000), Working Set 501 → 463 MiB, Handles 544 → 525, Fäden 31 → 22, Sitzungen 9 - kein stetiges Wachstum. Rohdaten tools/absturz/messung-2026-10-05-leck.txt. dotnet-gcdump und Auslagerung als Arbeitsart: PR27.10b.✅
PR27.10bLeck-Lauf: Auslagerung und ObjektzählungERLEDIGT 12.10.2026 (`SpeicherleckTests`, `LeckFundeTests`): Arbeitsart `auslagerung` (Gruppieren, Sortieren, Ergebnisspool über einen Pool mit 2 % von 128 MB, mit Nachweis der Dateischreibungen); `dist\speicherleck-objekte.ps1` zählt die lebenden Objekte je Typ mit `dotnet-gcdump` in einem Wegwerf-Container (nichts auf dem Rechner installiert). Funde behoben: die Zugriffsstatistik behielt jede gelöschte (temporäre) Tabelle, der Merker eigener Aliasse wuchs mit jedem temporären Namen✅
PR27.18hSpeicher: Fensterfunktion über ein riesiges KreuzproduktERLEDIGT 14.10.2026 (`HinabgereichtesTopTests`, `ArbeitsgrenzeTests`): ein TOP ohne Sortierung, Bedingung, Verbund, Gruppierung und Aggregat aussen geht in die abgeleitete Tabelle hinab (1000 × 1000: 6 GB Belegung und 5,5 s → unter 20 MB). Braucht das Fenster jede Zeile, fragt jede Sammelstelle (gefilterte Zeilen, Fensterprojektion, abgeleitete Tabelle) je 65 536 Zeilen die Arbeitsgrenze (drei Viertel des Prozessspeichers): darüber 701 an genau diesen Client statt Prozessende; der Wiederanlauf scheitert an derselben Stelle (vermerkter Fehlschlag)✅
PR27.18iPostgreSQL: `pg_prepared_statements`Die Sicht fehlt (42P01); psql, pgAdmin und Treiber lesen damit die vorbereiteten Anweisungen der Sitzung samt `parameter_types` **Erledigt 13.10.2026:** die Sicht mit SQL- und Protokoll-Vorbereitungen wie postgres:17 (Typfelder als Text); dabei PREPARE über Parse (Npgsql) lauffähig gemacht.✅
PR27.18jGitea: zeitweiser Fehler um `include_issues` während der MigrationRest aus PR27.16d: in einzelnen Läufen der Aktualisierung 1.27 → 1.28 scheiterte eine Anweisung um die Spalte `include_issues`, während die Migration die Tabelle umbaut (Wettlauf zwischen Migration und Hintergrundarbeit); im letzten Lauf nicht aufgetreten. Ziel: mit `KUONENSQL_FEHLERPROTOKOLL=1` mehrere Läufe, Ursache klären, Verhalten wie PostgreSQL (dort sperrt `ALTER TABLE` die Tabelle bis zum Ende der Transaktion) **Erledigt 14.10.2026:** Ursache ist Gitea selbst - die Benachrichtigung zum Ticket aus 1.27 läuft in 1.28 vor der Migration (Anweisungsprotokoll: Fehler bei Zeile 1886, ALTER bei 3685), PostgreSQL meldet dasselbe; die Abnahme führt ihn unter Erwartet. Dabei gebaut: Aufbausperre für CREATE/ALTER/DROP/TRUNCATE TABLE und CREATE INDEX bis zum Ende der Transaktion, andere Sitzungen warten (am Orakel PostgreSQL gemessen, `AufbausperreTests`). Rest PR27.18l✅
PR27.18kMietvertrag: Sperrdatei unter LastIm verteilten Volltest (14.10.2026) scheiterte MietvertragTests.Wartende_Instanz_Uebernimmt_Nicht_Solange_Der_Herzschlag_Laeuft_Aber_Sofort_Nach_Freigabe einmal: die Erneuerung bekam IOException auf besitz.lease.sperre (von einem anderen Prozess geöffnet), dann BesitzVerlorenException - die Instanz schreibt bis zur nächsten Erneuerung nichts. Allein dreimal grün. Klären, ob die Erneuerung bei einer belegten Sperrdatei innerhalb der Frist erneut versuchen muss, statt den Besitz aufzugeben **Erledigt 14.10.2026:** ja - die Erneuerung (jetzt alle Dauer/8) wartet auf die Sperrdatei, solange der Vertrag gilt, und versucht es nach einem Fehlschlag gleich wieder (`MietvertragTests.Eine_kurz_belegte_Sperrdatei_kostet_den_Besitz_nicht`, alt rot, neu grün)✅
PR27.18lROLLBACK nimmt ALTER TABLE und DROP TABLE zurückGefunden bei PR27.18j: `BEGIN TRAN; ALTER TABLE t ADD c int; ROLLBACK` lässt die Spalte stehen, `BEGIN TRAN; DROP TABLE t; ROLLBACK` die Tabelle verschwunden (beides in tempdb und einer eigenen Datenbank; CREATE TABLE wird zurückgenommen). Am Orakel SQL Server 2025 und PostgreSQL 17 ist danach der alte Aufbau samt Zeilen wieder da ERLEDIGT 14.10.2026 (DdlRuecknahmeTests): AufbauUndoAction je ALTER TABLE, DROP TABLE, TRUNCATE, CREATE/DROP INDEX und sp_rename (Tabelle, Spalte, Index) - Katalogstand, Identität und Zeilenablage kommen beim ROLLBACK zurück, auch bis zu einem Sicherungspunkt, in tempdb und nach einem Absturz. Rest PR27.18m✅
PR27.18mROLLBACK nimmt Sichten, Module und Sequenzen zurückRest aus PR27.18l: am Orakel stehen nach BEGIN TRAN; DROP VIEW/PROCEDURE/FUNCTION/SEQUENCE …; ROLLBACK alle vier wieder da, ALTER VIEW und sp_rename einer Sicht ebenso; KuonenSQL lässt sie gelöscht. Dazu der Volltextindex einer per ROLLBACK zurückgeholten Tabelle ERLEDIGT 14.10.2026 (DdlRuecknahmeTests): Objektbild je CREATE/ALTER/DROP von Sicht, Prozedur, Funktion, CREATE/DROP SEQUENCE und sp_rename von Sicht/Sequenz; der Volltextindex kommt mit der Tabelle zurück; ein zurückgenommenes ALTER VIEW löscht die Sicht nicht mehr. ALTER SEQUENCE … RESTART ungemessen (Assertion 3624 am Orakel)✅
PR27.11Verkleinern, Sichern und Zurückspielen geprüftVor allen Lastläufen (Nutzerwunsch 03.10.2026), ergänzt PR27.6 um die Breite der Befehle. **Verkleinern:** `DBCC SHRINKDATABASE` und `DBCC SHRINKFILE` (mit Zielgrösse, `NOTRUNCATE`, `TRUNCATEONLY`, `EMPTYFILE`) geben wirklich Platz frei, die Grössen in `sys.database_files`/`sp_spaceused` stimmen danach, kein Datensatz geht verloren, auch während anderer Sitzungen und mit offenen Transaktionen; Meldungen und Ergebnisspalten wie am Orakel SQL Server 2025. **Sichern und Zurückspielen:** voll, differenziell, Protokoll, `COPY_ONLY`, `CHECKSUM`, `COMPRESSION`; `RESTORE VERIFYONLY/HEADERONLY/FILELISTONLY`, `WITH MOVE/REPLACE/NORECOVERY/RECOVERY/STOPAT`, Kette voll + differenziell + Protokolle, beschädigte oder fremde Sicherungsdatei wird abgewiesen; dazu `pg_dump`/`pg_restore` und `mysqldump`/Einspielen über die anderen Ports. **Bestanden:** nach jedem Weg Bestand, Schema, Rechte, Prüfsummen und `DBCC CHECKDB` gleich dem Ausgangsstand; Meldungen wie am Orakel; jeder Fund ein Fehler mit Test. **Stand 03.10.2026, SSMS-Funde:** (1) „Datei verkleinern“ meldete 2583 — behoben: alle SMO-Formen (`SHRINKFILE` mit Ziel + `NOTRUNCATE`/`TRUNCATEONLY`, `EMPTYFILE`, `(1, 8)`, `WAIT_AT_LOW_PRIORITY`, `NO_INFOMSGS`; `SHRINKDATABASE` mit Prozent + Option) liefern Menge, Meldungen 5201/7993/9006 und Fehler 2556/2560/2583 wie am Orakel, `NOTRUNCATE` gibt nichts frei, `sys.database_files` zeigt die neue Grösse (`DbccVerkleinernTests`). (2) „Sichern“ schlug den Windows-Pfad `C:\Program Files\…\Backup\olp2.bak` vor (3201 im Container) — behoben: `InstanceDefaultBackupPath`/`BackupDirectory` und `InstanceDefaultDataPath`/`DefaultData`/`DefaultLog` sind die echten Ordner des Servers (im Container `/app/data/backup` bzw. `/app/data/`, im Volume), der Gegenstrich von SSMS gilt unter Linux als Trenner, relative Namen landen im Sicherungsordner, fehlender Ordner 3201 Zustand 1 wie am Orakel (`SicherungsordnerTests`, Ende-zu-Ende über TDS). **Stand 04.10.2026 (Ablage-Agent):** Verkleinern 11a–d, Dateikonflikte 11e, beschädigte Sicherung 11f, Kette mit `NORECOVERY` 11g erledigt; Sicherung unter Last zieht den bestätigten Stand (PR27.6). Reste: `STOPAT` (PR27.6b), `pg_dump`/`mariadb-dump`-Lauf (PR27.6a).✅
PR27.11aRest aus den SSMS-Funden**Erledigt 03.10.2026 („Datenbank wiederherstellen“ auf bestehende `olp2`):** 3159 nur bei laufender Protokollkette (SIMPLE überschreibt ohne `REPLACE`), 3154 für fremde Sicherung, 3234 für fremdes `MOVE`, `STATS = n` meldet „n percent processed.“ (Sicherung und Wiederherstellung), `log_reuse_wait`/`_desc` = 0/`NOTHING`, Protokolldatei in `sys.master_files` heisst `<name>_log.ldf` statt `mastlog.ldf` (daher stimmt der `MOVE`-Vorschlag von SSMS), Tests `WiederherstellenBestehendeTests`. Rest in PR27.11d.✅
PR27.11bSMO „Datenbank-Eigenschaften“ / „Datenbank verkleinern“: `SpaceAvailable`SMO (`PostProcessDatabaseInsideAttribs.GetColumnData`) brach mit „Die angegebene Umwandlung ist ungültig“ ab, weil `DbSize`/`SpaceUsed` NULL waren. **Behoben:** `sys.allocation_units` liefert je Partition (`container_id = partition_id`) `total_pages`/`used_pages`/`data_pages` (IN_ROW_DATA, im Systemkatalog auch LOB_DATA), `sys.partitions` enthält zusätzlich den Systemkatalog einer frischen Datenbank (am Orakel gemessen), die Seitenzahlen der Benutzertabellen folgen der Zeilenzahl; `ISNULL(SUM(bigint), 0)` und `SUM` über leerer Menge behalten den Typ des Aggregats (bigint statt int). Tests `SmoSpeicherplatzTests`.✅
PR27.11cRest aus PR27.11b**Erledigt 04.10.2026:** `sys.allocation_units` zählt die Seiten der Ablage, auf denen die Zeilen einer Tabelle liegen (Systemkatalog ohne Seiten), `sp_spaceused` ohne Argument liefert wie am Orakel zwei Mengen aus `sys.database_files`/`sys.allocation_units`, eine Unterabfrage neben einem Aggregat über leerer Menge behält ihren Wert. Tests `VerkleinernResteTests`, `VerkleinernAblageTests`, `SmoSpeicherplatzTests`.✅
PR27.11dRest Sichern/Verkleinern nach den SSMS-Funden**Erledigt 04.10.2026 („Verkleinern verkleinert nichts“):** `sys.database_files`/`sys.master_files`/`FILEPROPERTY` zeigen die echte Datendatei (nie unter der Belegung) und das echte gemeinsame Protokoll; `SHRINKDATABASE`/`SHRINKFILE` schreiben einen Kontrollpunkt (Datei dicht neu, Abbruch verliert nichts), `NOTRUNCATE` gibt nichts frei, 9006 nur wenn das Protokoll über dem Ziel bleibt; das Protokoll wird nach jedem Kontrollpunkt gekürzt, FULL mit Sicherungskette hält es bis `BACKUP LOG`; `model` auf FULL (neue Datenbanken FULL, halten das Protokoll erst nach der ersten Vollsicherung); 3013 als Fehler Klasse 16 hinter dem eigentlichen Fehler mit dessen Zeile. Ende-zu-Ende-Test mit Neustart: `VerkleinernAblageTests`. Rest in PR27.11e.✅
PR27.11eDateien je Datenbank bei RESTORE1834/3156/3119 fehlen: KuonenSQL führt je Datenbank eine Datendatei und ein gemeinsames Protokoll, darum gibt es keinen Dateikonflikt, den `RESTORE … WITH MOVE` melden könnte; erst mit eigenen Dateien je Datenbank prüfbar. **Erledigt 04.10.2026:** `RESTORE DATABASE` plant mit Ablage zuerst die Orte der Dateien (aus der Sicherung oder `MOVE`, gegen die Pfade in `sys.master_files`): zwei Dateien am selben Ort 3176, ein Ort einer anderen Datenbank je Datei 1834 und 3156, dann 3119 und 3013 – Texte, Zustände und Reihenfolge am Orakel gemessen (sechs Fälle, auch `REPLACE` auf eine andere Datenbank). Ohne Ablage keine Dateien, also keine Konflikte. Tests `WiederherstellenDateienTests`.✅
PR27.11fBeschädigte oder abgeschnittene Sicherung abgewiesen**Erledigt 04.10.2026:** die Sicherungsdatei (Version 2) trägt hinter dem Kopf Länge und CRC32C ihres ganzen Inhalts; `RESTORE VERIFYONLY` und jedes `RESTORE` weisen eine verfälschte Datei mit 3189 („Damage to the backup set was detected.“) ab, einen falschen Kopf mit 3241 – vorher spielte eine verfälschte Nutzlast still andere Daten zurück. Version 1 bleibt lesbar. Test `SicherungTests.Jedes_Verfaelschte_Byte_Der_Sicherung_Wird_Abgewiesen` (Sabotage: Prüfung aus → rot).✅
PR27.11gKette voll + differenziell + Protokoll mit NORECOVERY**Erledigt 04.10.2026 (am Orakel gemessen):** `RESTORE … WITH NORECOVERY` lässt die Datenbank im Zustand `RESTORING` (`sys.databases.state` 1), `USE` meldet dann 927 (Schweregrad 14, Zustand 2); eine Differenz- oder Protokollsicherung setzt nur eine solche Wiederherstellung fort, sonst 3117 – vorher scheiterte die zweite Stufe der Kette, wie SSMS sie schickt, an 3159. `RECOVERY` macht sie wieder `ONLINE`. Test `WiederherstellungsuebungTests.Die_Kette_…` (mit `COMPRESSION`, `CHECKSUM`, `STATS`, `COPY_ONLY`).✅
PR27.12aKS1: sql_variant als RPC-Parameter**Erledigt 05.10.2026:** Befund aus Kuonen.MultiSQLManager (`docs/kuonensql-gegenstueck.md`, KS1): `sp_executesql` mit `sql_variant` (TYPE_INFO 0x62) scheiterte mit 8016. Basistyp, Merkmale und Daten werden wie ein eigener Parameter gelesen und als `CAST(… AS <Basistyp>)` eingesetzt; dazu meldet `SELECT @v` ohne `FROM` eine `sql_variant`-Variable als `sql_variant`. Tests `TdsGegenstueckBefundeTests` (SqlClient, verfälschte Werte); Herkunft `docs/umsetzung.md` PR27.12.✅
PR27.12bKS1: UDT als RPC-Parameter**Erledigt 05.10.2026:** `geography`, `geometry`, `hierarchyid` als RPC-Parameter (TYPE_INFO 0xF0: Datenbank, Schema, Typname als B_VARCHAR, ohne Höchstlänge und Assemblyname, Wert als PLP – gemessen im Gegenstück an SQL Server 2025) statt 8016; eingesetzt als `CAST(0x… AS <typ>)`. Test `TdsGegenstueckBefundeTests.UDT_…` (SqlClient, verfälschte Werte); `docs/umsetzung.md` PR27.12b.✅
PR27.12cKS1: ENVCHANGE 10 bei XACT_ABORT**Erledigt 05.10.2026:** nimmt ein Fehler unter `XACT_ABORT` die Transaktion zurück, meldet die Antwort ENVCHANGE 10 mit dem Deskriptor des Beginns (wie im Gegenstück an SQL Server 2025 gemessen); eine Antwort ohne Rahmenprotokoll meldet Beginn und Ende der Transaktion jetzt überhaupt, die Transaktionsanforderung trägt den Deskriptor der Engine. Test `TdsGegenstueckBefundeTests.XACT_ABORT_…`; `docs/umsetzung.md` PR27.12c. Rest: Stelle des ENVCHANGE in dieser Antwort am Orakel nachmessen (PR27.12f).✅
PR27.12dKS1: RAISERROR … WITH NOWAIT sofort**Erledigt 05.10.2026:** die Meldung bzw. der Fehler von `RAISERROR … WITH NOWAIT` geht auf der obersten Ebene des Stapels sofort zum Client (Senke leert den ganzen Puffer, auch als kurzes Paket an einer Tokengrenze), nicht erst am Stapelende (Gegenstück: SQL Server 2025 nach 7 ms, KuonenSQL nach 3102 ms); ein Abbruch im folgenden WAITFOR trägt den Fehler. Antwort byte-gleich wie ohne NOWAIT. Tests `TdsGegenstueckBefundeTests` (Zeitpunkt, Bytes, Cancel); `docs/umsetzung.md` PR27.12d.✅
PR27.12eKS1: RESETCONNECTION mit ENVCHANGE 10 und 18**Erledigt 05.10.2026:** nach RESETCONNECTION beginnt die Antwort wie im Gegenstück an SQL Server 2025 gemessen mit ENVCHANGE 18 (beide Werte leer), davor ENVCHANGE 10 mit dem Deskriptor, wenn eine Transaktion offen war (nicht bei SKIPTRAN). Test `TdsGegenstueckBefundeTests.RESETCONNECTION_…` (SqlClient mit Pool, byte-genau); `docs/umsetzung.md` PR27.12e.✅
PR27.12fKS1: Rest – am Orakel nachmessen**Erledigt 05.10.2026:** am Orakel (14335) mit dem Mitschnitt (jetzt mit Paketgrenzen) gemessen und angeglichen: ein Fehler, der den Stapel beendet (XACT_ABORT, THROW), steht im Rahmenprotokoll – die DONE von SET/BEGIN TRAN davor, ENVCHANGE 10 zwischen ERROR und dem letzten DONE; ein Fehler nach dem Spaltenkopf schliesst mit `DONE(ERROR)` ohne Zählung und ohne zweites DONE; RAISERROR mit curCmd 0x00F6, Schweregrad 10 als Klasse 0, unter XACT_ABORT kein Stapelabbruch; NOWAIT schickt die Anweisung samt ihrem DONE im Paket ohne EOM; RESETCONNECTION stimmte; DONEPROC zwischen RPCs 0x0081; verschachtelte Aufrufe (auch im SQL-Stapel) mit DONEINPROC ohne RETURNSTATUS; Transaktionsanforderung mit curCmd 0x00FD. Tests `TdsGegenstueckBefundeTests` (Sollfolgen vom Orakel); `docs/umsetzung.md` PR27.12f.✅
PR27.12gKS1: mehrere RPCs in einer Nachricht (DbBatch)**Erledigt 05.10.2026:** mehrere RPCs einer Nachricht (BatchFlag 0xFF, [MS-TDS] 2.2.6.6) laufen nacheinander, jede Antwort endet mit DONEPROC, alle vor der letzten mit DONE_MORE; ein Fehler beendet nur seinen RPC, NoExecFlag 0xFE und abgeschnittene Trenner sind 8016. Tests `TdsGegenstueckBefundeTests.Mehrere_RPCs_…` (SqlClient `SqlBatch`) und verfälschte Trenner; `docs/umsetzung.md` PR27.12g.✅
PR27.12hKS1: verschachteltes EXEC in einem RPC des DbBatch**Erledigt 05.10.2026:** in der Antwort eines RPC schliessen verschachtelte Prozeduren (EXEC, gefangener Abbruch, Fehler des fremden Servers) mit DONEINPROC, DONEPROC nur am Ende des RPC ([MS-TDS] 2.2.7.6/2.2.7.7); `EXEC sp_executesql` in einem `SqlBatch` bricht SqlClient nicht mehr ab. Im SQL-Stapel bleibt das gemessene DONEPROC. Test `TdsGegenstueckBefundeTests.Mehrere_RPCs_…`; `docs/umsetzung.md` PR27.12h. Orakelmessung in PR27.12f.✅
PR27.12iKS1: RETURNSTATUS eines fehlgeschlagenen Aufrufs**Erledigt 05.10.2026:** am Orakel gemessen und angeglichen: eine Prozedur ohne RETURN meldet nach einem eigenen Fehler −(höchste Klasse − 10) (8134 → −6, 2627 → −4, Klasse 11 → −1; auch gefangen; RETURN gilt vor; Fehler innerer Prozeduren zählen nicht), `sp_executesql` das @@ERROR seiner letzten Anweisung (8134, 50000, 2627, sonst 0); @@ERROR nach dem EXEC wie am Ende des Aufrufs, nach leerem CATCH 0. Test `TdsRueckgabestatusTests` (auch RPC-ReturnValue)✅
PR27.12jKS1: Fehler 245 nimmt die Transaktion zurück**Erledigt 05.10.2026:** am Orakel gemessen: ein Konvertierungsfehler (245, 241, 8114, 8169) beendet den Stapel und nimmt die offene Transaktion zurück (ENVCHANGE 10), auch ohne XACT_ABORT, in `sp_executesql` und mit #temp; im TRY ist sie unbestätigbar (XACT_STATE -1, am Ende 3998). 8134, 220, THROW lassen sie offen (geprüft). Test `TdsKonvertierungsabbruchTests`✅
PR27.13K2b: gespiegelte TDS-Dateien**Erledigt 05.10.2026:** `TdsHeader.cs`, `TdsMessageType.cs` und `TdsTlsHandshakeStream` tragen den Vermerk GESPIEGELT (Gegenstück in Kuonen.MultiSQLManager `src/Kuonen.Data.Tds/Protokoll/`); `ConfigureAwait(false)` an allen fünf `await` des Handshake-Stroms (K2a Punkt 7 dort). Test `TdsTlsHandshakeStromTests` (kein Verklemmen im blockierten Kontext); `docs/umsetzung.md` PR27.13.✅
PR27.14Abspielreihenfolge nach Sperrwarten**Gebaut (2026-10-05):** ein Stapel ohne Transaktion steht vor seiner Ausführung im Protokoll; wartete er auf die Sperre einer offenen Transaktion, stand deren Folge (erst beim COMMIT geschrieben) hinter ihm, und Wiederanlauf, Kopie und `RESTORE LOG` spielten ihn vorher ab (Test: 2 statt 25). Hat er vor der wartenden Anweisung nichts festgeschrieben, wird er nach dem Warten verlegt (Verworfen + derselbe Text neu). `AbspielreihenfolgeTests`. Rest: PR27.14a✅
PR27.14aAbspielreihenfolge: gemischter Stapel**Gebaut (2026-10-05):** hat der Stapel vor dem Warten schon festgeschrieben (`INSERT …; UPDATE …` ohne Transaktion, das zweite wartet), wird er an der wartenden Anweisung geteilt: das alte Glied endet beim Abspielen vor ihr (Vermerk Stapelende), der Rest steht als neues Glied hinter dem COMMIT der anderen - davor die skalaren Variablen mit ihrem Wert beim Warten (`DECLARE @x int = CONVERT(int, 6)`), aufgefüllt auf dieselben Stellen und Zeilen. Die Kopienquelle schickt solche Vermerke vor ihrem Glied. Wiederanlauf, Kopie und `RESTORE LOG` gleich dem ersten Lauf (Test: 2 statt 26). Ohne Warten kein Mehraufwand. `AbspielreihenfolgeTests`, `LesbareKopienTests`. Rest: PR27.14b✅
PR27.14bAbspielreihenfolge: Grenzen der TeilungERLEDIGT 11.10.2026 (`AbspielreihenfolgeTests`, `LesbareKopienTests.Kopie_Haelt_Einen_Unteilbaren_Stapel_An_Seinem_Wartepunkt`): statt den Stapeltext zu teilen, vermerkt der erste Lauf die wartende Anweisung (Wartepunkt) und schreibt beim Weitermachen einen Satz `Weiter`; Wiederanlauf, `RESTORE LOG` und Kopie spielen den Stapel in eigener Sitzung und halten ihn am Wartepunkt an, bis sie den Satz erreichen (`Wartepunkte`). Gilt auch beim Warten in Prozedur, Trigger, IF/WHILE/TRY, mit Tabellenvariablen, `#temp`, Cursorn und SET-Schaltern; die Textteilung von PR27.14a entfällt (ihre Vermerke liest das Abspielen weiter). Rest: PR27.18g✅
PR27.18gProtokollsicherung mitten im Warten eines StapelsERLEDIGT 14.10.2026 (`SicherungDoneTests`): der beschriebene Fall tritt nicht ein - `BACKUP LOG` auf oberster Ebene wartet auf die Stapelsperre eines wartenden Stapels und gibt nach 5 s 3023 (am Orakel gelingt sie, Rest PR27.18g2). Dabei gefunden und behoben: eine Sicherung oder Wiederherstellung hinter einer anderen Anweisung liess den Client bis zur Zeitüberschreitung warten (kein DONE, Meldungen hinter dem letzten DONE). Jetzt wie am Orakel je Meldung ein DONE (BACKUP DATABASE 0x00E4, LOG 0x00EB, RESTORE DATABASE 0x00E5, LOG 0x00EF, VERIFYONLY 0x0179 nach 3262), CREATE/ALTER/DROP DATABASE 0x00CB/0x00D7/0x00CC, MODIFY NAME meldet 5021 statt 5022; ein Netz im Schreiber schliesst jede Antwort mit einem DONE✅
PR27.18g15060 bei SINGLE_USER ohne andere VerbindungERLEDIGT 14.10.2026 (`SicherungDoneTests.Einzelbenutzer_meldet_5060_nur_mit_einer_anderen_Verbindung`): am Orakel gemessen meldet `SET SINGLE_USER|RESTRICTED_USER WITH ROLLBACK IMMEDIATE|AFTER n` 5060 nur, wenn eine andere Verbindung in der Datenbank ist (auch untätig), erst Zustand 2 (0 %), dann 1 (100 %); jetzt ebenso✅
PR27.18g2BACKUP LOG während ein Stapel wartetAm Orakel gelingt `BACKUP LOG` in 291 ms, während ein anderer Stapel auf eine Sperre wartet; hier wartet die Sicherung auf die Stapelsperre und gibt nach 5 s 3023. Ziel: die Protokollsicherung ohne die Stapelsperre, ihr Ende vor dem Glied des frühesten offenen Stapels (Register der offenen Stapel), die Kürzung respektiert es - dann trifft der Fall der alten PR27.18g ein. ERLEDIGT 14.10.2026 (AbspielreihenfolgeTests.Eine_Protokollsicherung_Waehrend_Ein_Stapel_Wartet…): BACKUP LOG nimmt die Stapelsperre nicht mehr; ein Protokollregister (je Sitzung das Glied eines laufenden Stapels ohne Transaktion und die früheste Stelle, die sie im Protokoll braucht) wird zusammen mit dem Schreiben jedes Glieds gepflegt; die Sicherung endet vor dem frühesten offenen Glied einer anderen Sitzung, hält das Protokoll ab deren frühester Stelle (auch über einen Kontrollpunkt), die nächste nimmt den Stapel mit. Neben einem wartenden Stapel unter 3 s statt 3023 nach 5 s; Voll + Log 1 + Log 2 = Endzustand, Voll + Log 1 = Stand vor dem Stapel. Abweichung: SQL Server nähme die schon festgeschriebenen Anweisungen des wartenden Stapels in Log 1 auf, hier stehen sie erst in Log 2 (der Stand von Log 1 ist früher, aber widerspruchsfrei)✅
PR27.15aKS1/K3o: INFORMATION_SCHEMA.COLUMNS (Zeichensatz, bit, sql_variant)**Erledigt 05.10.2026:** Befund 4 aus Kuonen.MultiSQLManager (`docs/kuonensql-gegenstueck.md`, K3o): `COLLATIONPROPERTY(…, 'sqlcharsetname')` nennt den Zeichensatz nach der Codeseite (`iso_1`, `utf8`, `cp<Seite>`), `ODBCSCALE(104, …)` (bit) ist NULL, `COLUMNPROPERTY` misst `sql_variant` mit 0 Zeichen und 0 Bytes – alles am Orakel gemessen. `timestamp` war schon `NOT NULL`. Test `TdsGegenstueckSchemaTests.INFORMATION_SCHEMA_COLUMNS_…`✅
PR27.15bKS1/K3o: SPARSE und COLUMN_SET im Katalog**Erledigt 05.10.2026:** Befund 3 (`ColumnSetColumns`): `SPARSE` und `COLUMN_SET FOR ALL_SPARSE_COLUMNS` werden in `CREATE TABLE` und `ALTER TABLE … ADD` gelesen und stehen in `sys.columns.is_sparse`/`is_column_set` und `COLUMNPROPERTY(…, 'IsSparse'/'IsColumnSet')`; `SPARSE NOT NULL` bzw. mit `IDENTITY` ist Fehler 1731 (am Orakel gemessen). Test `TdsGegenstueckSchemaTests.SPARSE_und_COLUMN_SET_…`✅
PR27.15cSpaltengruppe als XML**Erledigt 05.10.2026:** am Orakel mitgeschnitten: `SELECT *` liefert statt der dünn besetzten Spalten die Spaltengruppe (COLMETADATA 0x040B, xml-Spalten allgemein 0x000B), ihr Wert ist je nicht leere Spalte ein Element (Base64, ISO-Zeit, float wissenschaftlich); `INSERT`/`UPDATE` über die Gruppe verteilen das XML (1911, 360), eine geänderte Spalte ändert die Gruppe. Test `TdsGegenstueckSpaltenschemaTests.Die_Spaltengruppe_…`✅
PR27.15dKS1/K3o: Tabellentypglieder und Funktionsrückgabe**Erledigt 05.10.2026:** Befunde 2 und 5 (`StructuredTypeMembers`, `ProcedureParameters`): `COLUMNPROPERTY` kennt die Glieder eines Tabellentyps (Länge, Bytes, Ordnung, NULL) und den Rückgabewert einer Skalarfunktion (Name leer, Ordnung 0); die Vorgabe eines Glieds steht in `sys.default_constraints` (Elternobjekt die Typtabelle, `is_ms_shipped` 1 wie am Orakel), `OBJECT_DEFINITION(default_object_id)` liefert sie. Test `TdsGegenstueckSchemaTests.Glieder_eines_Tabellentyps_…`✅
PR27.15eKS1/K3o: VIEW_COLUMN_USAGE**Erledigt 05.10.2026:** Befund 1 (`ViewColumns`): `sys.sql_dependencies` führt für eine Sicht je gelesener Tabellenspalte (Auswahl, WHERE, …) eine Zeile mit `referenced_minor_id`, `is_selected` 1, beim `*` alle Spalten mit `is_select_all` 1 – wie am Orakel; daraus füllt sich `INFORMATION_SCHEMA.VIEW_COLUMN_USAGE`. Test `TdsGegenstueckSchemaTests.VIEW_COLUMN_USAGE_…`✅
PR27.15fKS1/K3o: ASSEMBLYPROPERTY**Erledigt 05.10.2026:** Befund 6 (`UserDefinedTypes` scheiterte mit „Die Funktion 'ASSEMBLYPROPERTY' wird nicht erkannt.“): die Funktion liest aus dem CLR-Namen der Assembly die Fassungsteile (`int`), `CultureInfo` (bei neutral NULL), `PublicKey` (Token als `varbinary`), `SimpleName`, `Architecture`, `CLRName`; Unbekanntes NULL, wie am Orakel. `Microsoft.SqlServer.Types` trägt jetzt Fassung 17.0.0.0 wie SQL Server 2025. Test `TdsGegenstueckSchemaTests.ASSEMBLYPROPERTY_…`✅
PR27.15gKS1/K3o: berechnete Spalte und timestamp in COLMETADATA**Erledigt 05.10.2026:** Befund 7 (`Eine_berechnete_Spalte`): eine berechnete Tabellenspalte trägt in COLMETADATA 0x0020 (berechnet, nicht beschreibbar) statt 0x0008. Nebenbefund am Orakel: eine `timestamp`-Spalte ging als `nvarchar(4000)` mit dem Wert als Text (`'0x…01'`) hinaus; jetzt `binary(8)` mit Benutzertyp 80, Merkmale 0x0000, Wert die acht Bytes – auch im Verbund. Tests `TdsGegenstueckSpaltenschemaTests.Eine_berechnete_Spalte_…`, `…Eine_timestamp_Spalte_…`✅
PR27.15hMerkmale der Tabellenspalten bei Verbund und UNION**Erledigt 05.10.2026:** am Orakel mitgeschnitten: im Verbund tragen Tabellenspalten ihre Merkmale, die NULL-Seite eines äusseren Verbunds wird nullbar; UNION meldet 0x0000/0x0001 je nach Nullbarkeit der Zweige, EXCEPT die des ersten Zweigs; Rechnen (`n + 1`) ist nullbar; `timestamp` bleibt über mehrere Zeilen `timestamp`, UNION/DISTINCT entfernen doppelte Bytewerte (vorher nach Verweis verglichen). Test `TdsGegenstueckSpaltenschemaTests.Verbund_und_Mengenoperation_…`✅
PR27.15iKS1/K3o: SET NO_BROWSETABLE (KeyInfo)**Erledigt 05.10.2026:** Befunde 8–10 (`KeyInfo_…`): unter `SET NO_BROWSETABLE ON` schickt ein `SELECT` TABNAME (Tabellen wie geschrieben, `dbo.t` zweiteilig) und COLINFO (Tabelle, Schlüssel 0x08, umbenannt 0x20 mit Tabellennamen, Ausdruck 0x04) und hängt bei Zeilenabfragen die nicht genannten Primärschlüssel- und `timestamp`-Spalten verdeckt an (0x18/0x10, mit Werten); Gruppierung/DISTINCT ohne Anhang, `COUNT(*)` allein nur COLINFO – alles am Orakel mitgeschnitten, auch Verbund, `*` und FMTONLY. Tests `TdsGegenstueckSpaltenschemaTests.NO_BROWSETABLE_…` (3)✅
PR27.15jKS1/K3o: EXEC unter SET FMTONLY**Erledigt 05.10.2026:** Befund 11 (`Eine_Prozedur_mit_SchemaOnly`): unter `SET FMTONLY ON` geht `EXEC` einer eigenen Prozedur ihren Rumpf durch – jede Abfrage beschreibt ihre Menge (COLMETADATA, DONEINPROC ohne Zeilen), ein `IF` beide Zweige, DML zählt 0 und läuft nicht, RETURNSTATUS 0 –, wie am Orakel mitgeschnitten (vorher: nur RETURNSTATUS). Test `TdsGegenstueckSpaltenschemaTests.Unter_FMTONLY_beschreibt_EXEC_…`✅
PR27.15kRETURN nach einer Menge: DONEINPROC**Erledigt 05.10.2026:** am Orakel mitgeschnitten: `RETURN` mit Wert schliesst wie eine Zuweisung (`DONEINPROC 0x0011 cmd 0x00C1` mit einer Zeile, unter FMTONLY keiner), ohne Wert bleibt `0x00DB`; der RETURNSTATUS im SQL-Stapel trägt jetzt den Rückgabewert (war immer 0), `EXEC('…')` nach Fehler -6; unter `SET NOCOUNT ON` fällt in Modulen und RPCs jedes DONEINPROC ohne Menge weg. Test `TdsModulrahmenTests.RETURN_mit_Wert_…`✅
PR27.15lKS1/K3o: SET in Modulen gilt nur bis zu ihrem Ende**Erledigt 05.10.2026:** Befund 12 (`SET FMTONLY ON` in `sp_executesql` blieb stehen – danach lief nicht einmal mehr `CREATE TABLE`): was eine Prozedur, ein Trigger, `sp_executesql` oder `EXEC(…)` mit SET ändert (FMTONLY, NO_BROWSETABLE, @@OPTIONS samt NOCOUNT/XACT_ABORT/ANSI_…, DATEFORMAT, TEXTSIZE, LOCK_TIMEOUT, LANGUAGE, Isolationsstufe), gilt nach ihrem Ende nicht mehr – am Orakel gemessen; nur T-SQL (MariaDB/PostgreSQL behalten ein SET). Test `TdsGegenstueckSpaltenschemaTests.SET_in_sp_executesql_…`✅
PR27.15mKS1: ExecuteNonQuery einer Prozedur mit innerem INSERT**Erledigt 05.10.2026 (schon gleich):** am Orakel und an KuonenSQL mit SqlClient gemessen: `ExecuteNonQuery` (einzeln, über `EXEC` und im `SqlBatch`) zählt 3, im DbBatch je Befehl 3, 3, 0, zusammen 6 – der Befund (-1) ist mit PR27.12g/h behoben; Test `TdsGegenstueckSpaltenschemaTests.Eine_Prozedur_mit_innerem_INSERT_…` hält es fest✅
PR27.15nDbBatch: DONE_RPCINBATCH (0x80)**Erledigt 05.10.2026:** nachgemessen: 0x0081 hatte PR27.12f schon angeglichen; dazu meldet ein Text-Befehl (RPC `sp_executesql`) jetzt wie am Orakel @@ERROR nach seiner letzten Anweisung als RETURNSTATUS (war 0). Test `TdsModulrahmenTests.DbBatch_…`✅
PR27.15oKS1/K3o: ATTENTION in einer grossen gesammelten Menge**Erledigt 05.10.2026:** Befund 13 (`Cancel` mitten in 2 Mio. Zeilen erst nach 2,2–5 s bestätigt): eine fertig gesammelte Antwort geht in Stücken zu 16 Paketen hinaus; liegt dazwischen ein Attention vor, endet sie an der nächsten Tokengrenze (`TdsAntwortleser.Tokengrenze`) mit DONE_ATTN. Gemessen mit SqlClient, `TOP (200000) … CROSS JOIN`, 220 Byte je Zeile: Abbruch vorher nach 318–919 ms mit allen 199 995 Zeilen, jetzt nach 3–21 ms mit 587–1179. Test `TdsGegenstueckAbbruchTests.Ein_Abbruch_mitten_in_…`✅
PR27.15pVerbund mit TOP stroemen**Erledigt 05.10.2026:** ein Ausdruck mit festem Kopf (Typ fester Länge oder eigene Facetten wie `varchar(max)`) strömt jetzt direkt statt über den Spool: `TOP (200000)` im Kreuzprodukt mit `REPLICATE(…)` erste Zeile nach 3–36 ms statt 2320–2814 ms, mit `b.x + 1` 6–12 ms statt 672–804 ms; Zeilen vor einem späteren Fehler sind wie am Orakel beim Client. Test `TdsGegenstueckSpaltenschemaTests.Ein_Ausdruck_mit_festem_Kopf_…`✅
PR27.15q208 in EXEC('…') beendet nur das EXEC**Erledigt 05.10.2026:** am Orakel mitgeschnitten: ein Übersetzungsfehler (208, 207, Syntax, 137) beendet nur das Modul, in dem er entstand (Prozedur, `EXEC('…')`, `sp_executesql` mit RETURNSTATUS = Nummer), der Aufruf schliesst mit `DONEPROC 0x0003 cmd 0x00E0`, der Stapel läuft weiter; ebenso 2812 und der RPC einer Prozedur; Fehler in Blöcken (Prozedurrumpf, IF, WHILE) jetzt wie am Orakel gerahmt. Test `TdsModulrahmenTests.Ein_Uebersetzungsfehler_im_Modul_…`✅
PR27.15rORDER-Token und Spaltengruppe nach ALTER**Erledigt 05.10.2026:** am Orakel mitgeschnitten: jede Abfrage mit `ORDER BY` schickt nach COLMETADATA ORDER mit der Nummer je Sortierglied (Name, Alias, `ORDER BY 2` oder dieselbe Tabellenspalte; 0 für `n + 1` oder eine Spalte ausserhalb der Liste), auch bei Verbund, GROUP BY, UNION, VALUES, Systemsicht und ohne FROM; eine Konstante fällt weg, ohne anderes Glied fehlt das Token. `ALTER TABLE … ADD … SPARSE` neben der Spaltengruppe bleibt aus dem Stern, eine Gruppe neben vorhandenen dünn besetzten Spalten ist 1734. Tests `TdsGegenstueckSpaltenschemaTests.ORDER_BY_…`, `….Eine_spaeter_hinzugefuegte_…`✅
PR27.15sSystemsichten im Verbund strömen**Erledigt 05.10.2026:** eine Abfrage auf Systemsichten mit gemessenen Merkmalen strömt wie eine auf Tabellen; der Kopf kommt vor der ersten Zeile aus `MitSichtmerkmalen` ohne Zeilen (`Strommerkmale`). `sys.all_objects a CROSS JOIN sys.all_objects b` mit `TOP (200000)`: erste Zeile 383 → 18 ms (kürzeste von drei warmen Läufen); Zeilen vor einem späteren Fehler sind wie am Orakel beim Client (`docs/leistung.md` Abschnitt 11). Test `TdsGegenstueckSpaltenschemaTests.Eine_Systemsicht_im_Verbund_…`✅
PR27.15tMerkmale ohne Tabelle, mit Gruppierung, abgeleitetERLEDIGT 11.10.2026 (`SpaltenmerkmaleAbgeleitetTests`, 24 Abfragen, jede Sollzeile vom Orakel SQL Server 2025 mitgeschnitten): ohne FROM wie mit (Konstante 0x0020 fester Typ, `@@SPID`/`@@ROWCOUNT`/`@@TRANCOUNT`/`@@MAX_PRECISION` … fest, `@@IDENTITY`/`@@VERSION`/`@@DATEFIRST` nullbar); Gruppierung: die gruppierte Spalte behält 0x0008/0x0009, ein gruppierter Ausdruck und jedes Aggregat (auch mit OVER, auch ohne GROUP BY, vorher 0x0021) 0x0001, ROLLUP macht alles nullbar; abgeleitete Tabelle, CTE und VALUES reichen die Merkmale ohne „berechnet“ durch (0x0008, Konstante 0x0000, Ausdruck 0x0001), auch verschachtelt; die Tabellenspalte neben einer Systemsicht 0x0008/0x0009, eine Sichtspalte auf der NULL-Seite eines LEFT JOIN nullbar; `is_sparse` stimmte schon. Nebenbei am Orakel gemessen und angeglichen: CAST/CONVERT immer nullbar (vorher fest über NOT-NULL-Spalten), `NEWID()`/`DB_NAME()` nullbar, `ISNULL(a, b)` nur nullbar, wenn beide es sind, `SCOPE_IDENTITY()` numeric(38,0) (vorher 18). Der Kopf einer Abfrage auf Systemsichten, die vor der ersten Zeile scheitert, kommt aus derselben Abfrage ohne Zeilen (vorher `name` als int). Sabotage: abgeleitete Tabellen ausgeschaltet macht 6 Fälle rot✅
PR27.17oRest aus PR27.15t: decimal als eigener TDS-TypERLEDIGT 11.10.2026 (`DecimalNumericTypTests`, 17 Abfragen, jede Sollzeile vom Orakel SQL Server 2025 mitgeschnitten): decimal geht als DECIMALN 0x6A, numeric und jedes Zahlliteral als NUMERICN 0x6C (Spalte, CAST/CONVERT/TRY_CAST, Variable, SUM/AVG/MAX, POWER behalten die Schreibweise); eine Rechnung wird numeric, sobald eine Seite es ist; ein Zweig (CASE, IIF, COALESCE, UNION, VALUES) nimmt die Schreibweise des ersten decimal-Zweigs (vorher färbte jedes numeric ab, VALUES verlor sie); `@@IDENTITY`/`SCOPE_IDENTITY()`/`IDENT_CURRENT` numeric(38,0); die Systemsichten führen ihre 63 numeric-Spalten (LSN usw., am Orakel aus `sys.all_columns`: 66 numeric, 7 decimal, keine davon nachgebaut) als numeric, ebenso die LSN in Sicherungsauskunft und DBCC; der Vorlagengenerator unterscheidet beide. Sabotage: Zweigregel zurück auf „numeric färbt ab“ macht 6 Fälle rot✅
PR27.17pRest aus PR27.17o: PARSE und Merkmale einzelner ZahlfunktionenERLEDIGT 11.10.2026 (`FunktionsmerkmaleTests`, 13 Abfragen mit rund 100 Spalten, jede Sollzeile vom Orakel SQL Server 2025 mitgeschnitten): `ROUND`, `FLOOR`, `CEILING`, `SIGN` und die `*FROMPARTS`-Funktionen sind nullbar, wenn ein Argument es ist (über NOT-NULL-Spalten und Konstanten fest, auch float), `ABS` bleibt nullbar; `PI()`, `CONCAT`, `CONCAT_WS` nie nullbar; `CASE`/`IIF` nullbar, wenn ein Zweig es ist oder ELSE fehlt; `+spalte` (auch `+(+spalte)`) trägt die Merkmale der Spalte (0x0008/0x0009); `PARSE`/`TRY_PARSE` in decimal liefern numeric; `STUFF` misst an der festgelegten Länge der Spalte (`STUFF(v, 1, 1, 'xyz')` über varchar(10) ist varchar(12)). Sabotage: CASE-Regel entfernt macht 2 Fälle rot✅
PR27.17qRest aus PR27.17p: CONCAT-Länge und GleitkommarechnungERLEDIGT 11.10.2026 (`FunktionsmerkmaleTests.Verkettung_und_Gleitkomma_wie_am_Orakel`, 6 Abfragen vom Orakel SQL Server 2025 mitgeschnitten): CONCAT/CONCAT_WS zählen je Argument eine feste Breite nach dem Typ, auch für einen getypten NULL-Wert (bit 1, tinyint 4, smallint 6, int 12, bigint 24, decimal/numeric 41, float/real 23, money, Datum und uniqueidentifier 40; vorher int-artig 12, float 24, Datum nach dem Wert); eine float-Rechnung aus float- und Ganzzahlseiten (`f * 2`, `f / 2`, `id * 1e0`, `PI() * 2`, `1e0 + 1`) ist nur nullbar, wenn eine Seite es ist, mit real oder decimal und `-f` bleibt sie nullbar. Sabotage: float-Regel ausgeschaltet macht 2 Fälle rot✅
PR27.17rRest aus PR27.17q: Typ einer NULL-ZelleERLEDIGT 11.10.2026 (`FunktionsmerkmaleTests.Verkettung_und_Gleitkomma_wie_am_Orakel`, Fall `CONCAT(n, 'x')` vom Orakel): eine NULL-Zelle der Ablage trägt den Typ ihrer Spalte (`MemoryTable.Leerwert`, je Spalte einmal gebaut und gemerkt), `CONCAT(n, 'x')` über einer NULL-Zelle einer int-Spalte ist varchar(13) wie am Orakel (vorher varchar(1)). Gemessen (200 000 NULL-Zeilen, Release, kürzeste von 12): Lesen aller Spalten 276 gegen 272 ms, `COUNT(*) … IS NULL` 163 gegen 177 ms, `SUM`/`MAX` 135 gegen 161 ms - im Rahmen der Schwankung dieser Maschine, kein Einschachteln (SqlValue ist eine Struktur). Sabotage: alter Leerwert macht den Fall rot✅
PR27.15uNullbarkeit der Auswahl (T-SQL)ERLEDIGT 10.10.2026 (`NullbarkeitDerAuswahlTests`, Orakel SQL Server 2025): `SELECT … INTO` und `sys.dm_exec_describe_first_result_set` leiten gleich ab — eine Konstante (auch `-1`) und NEXT VALUE FOR nie NULL, eine Spalte wie in ihrer Tabelle (auch über `*`/`t.*`, auf der NULL-Seite eines äusseren Verbunds nullbar), das Verketten von Text und das Rechnen mit Gleitkomma aus nie-NULL-Teilen nie NULL, das Rechnen mit ganzen und exakten Zahlen nullbar (Überlauf, sogar `1 + 1`), `GETDATE()` und `ISNULL(x, 0)` nie NULL, `CAST`, `COALESCE`, `-spalte` und die übrigen Funktionen nullbar. Bisher legte `SELECT … INTO` jede Spalte nullbar an, und die Beschreibung kannte nur Konstanten und eine einzelne Tabelle✅
PR27.15vPARSE mit Kultur und CRYPT_GEN_RANDOM (T-SQL)ERLEDIGT 10.10.2026 (`ParseKulturTests`, Orakel SQL Server 2025): `PARSE`/`TRY_PARSE(text AS typ USING kultur)` liest Zahlen (Tausender- und Dezimalzeichen der Kultur) und Zeitwerte (Formen der Kultur, auch ausgeschriebene Wochentage und Monate); ein anderer Zieltyp ist 10761, Unlesbares 9819 bzw. bei TRY_PARSE NULL, eine unbekannte Kultur 9768. `CRYPT_GEN_RANDOM(laenge[, saat])` liefert Kryptozufall der verlangten Länge (1 bis 8000). Bisher brach `USING` mit einem Syntaxfehler ab✅
PR27.15wEckige Klammern in Namen und TextliteralenERLEDIGT 10.10.2026 (`KlammernImNamenTests`, Orakel SQL Server 2025, `mariadb:11`, `mysql:8.4`): T-SQL liest `]]` in `[…]` als `]` (`[zz_t[1]]]`), damit gehen auch `` `t[1]` `` und `` `a]b` `` an den Zugängen MariaDB und MySQL (vorher 1064 bzw. 102); `SHOW CREATE VIEW` und `VIEW_DEFINITION` bei MariaDB lassen Textliterale stehen (vorher wurde aus dem Literal `[1]` ein `` `1` `` und aus `N` vor einem verdoppelten Hochkomma nichts, ein Dump stellte andere Werte wieder her); `VIEW_DEFINITION` bei MariaDB ist die Abfrage statt des ganzen T-SQL-Texts✅
PR27.15xReste aus PR27.15w: Klammern im NamenERLEDIGT 11.10.2026 (`KlammernImNamenRestTests`, Orakel SQL Server 2025): `[zz_t[1]]]` heisst `zz_t[1]` (`]]` in Klammern ist `]`, eine Klammer mitten im Namen gehört zum Namen) - vorher entfernte `NormalizeTableName` jede Klammer, `CREATE TABLE dbo.[zz_t[1]]]` legte `zz_t1` an und `OBJECT_ID` traf die falsche Tabelle; ein ungeklammerter Teil mit Klammer (`OBJECT_ID('zz_t[1]')`, `'[zz_t1]x'`) bezeichnet nichts (NULL). Der MariaDB-Teil steht in PR27.17j, Namen mit Punkt in Klammern in PR27.17k✅
PR27.17jMariaDB: VIEW_DEFINITION und SHOW CREATE VIEW in der Form des OrakelsERLEDIGT 11.10.2026 (`MariaSichttextTests`, Orakel `kq-ref-mariadb` mariadb 11.8): MariaDB zeigt den Text einer Sicht umgeformt wie das Orakel (vorher der Rohtext): Spalten mit Tabelle (in VIEW_DEFINITION mit Datenbank), Aliasse in Backticks, Klammern nur nach Vorrang, `MOD`, eine Spalte als Bedingung `is true`, `!exists(…)`, `!(x in (select …))`, De Morgan, ` not like`, `lcase`/`ucase`/`octet_length`/`current_timestamp()`, `exists(… limit 1)`, im ORDER BY ein Alias als Ausdruck und eine Stellennummer als Zahl, TRUE als 1; 27 gemessene Sichten gleich (VIEW_DEFINITION und SHOW CREATE VIEW). Dazu: `CREATE OR REPLACE VIEW` (auch ALGORITHM/DEFINER/SQL SECURITY/IF NOT EXISTS) ergab vorher den ganzen Text als Rumpf✅
PR27.17lDROP VIEW/PROCEDURE/FUNCTION mit Liste, 3701 wie SQL ServerERLEDIGT 11.10.2026 (`LoeschlisteTests`, Orakel SQL Server 2025 und `kq-ref-mariadb`): `DROP VIEW a, b`, `DROP PROCEDURE a, b`, `DROP FUNCTION a, b` (vorher 102, am MariaDB-Zugang 1064 auch mit IF EXISTS; dort jetzt 4092 als Hinweis bzw. Fehler wie am Orakel); ein fehlendes Objekt ist 3701 mit Schweregrad 11 und State 5 (Index State 7, vorher 16/1), Name wie geschrieben, die Liste geht weiter. Offen: DROP SEQUENCE eines fehlenden Objekts ist in T-SQL hier 208 (am Orakel 3701), `sqlcmd -b` bricht hier nach dem ersten Namen ab, weil Kuonen je Name eine eigene Anweisung meldet (am Orakel eine) - beides PR27.17m✅
PR27.17mDROP SEQUENCE mit Liste, 3701 wie SQL ServerERLEDIGT 11.10.2026 (`FolgenlisteTests`, `SequenzTests`, Orakel SQL Server 2025 und `kq-ref-mariadb`): `DROP SEQUENCE a, b` (vorher 102, am MariaDB-Zugang 1064); eine fehlende Folge ist 3701 mit Schweregrad 11, State 5 (vorher 208), die übrigen werden gelöscht; am MariaDB-Zugang 4091 „Unknown SEQUENCE: 'db.a,db.b'“ als Hinweis mit IF EXISTS, sonst als Fehler 42S02, am MySQL-Zugang weiter 1064. Sabotage: die alte 208 zurück macht `FolgenlisteTests` rot✅
PR27.17nFehler einer DROP-Liste in einem DONEERLEDIGT 11.10.2026 (`LoeschlisteTests`, Mitschnitt): `DROP TABLE/VIEW/PROCEDURE/FUNCTION/SEQUENCE a, b` ist eine Anweisung wie am Orakel - jedes fehlende Objekt meldet 3701, die übrigen werden gelöscht, alle ERROR stehen vor einem DONE mit DONE_ERROR (vorher je Name ein DONE). Sabotage: der Zweig in `ExecuteBlock` aus macht `LoeschlisteTests` rot✅
PR27.17kNamen mit Punkt in KlammernERLEDIGT 11.10.2026 (`NamenMitPunktTests`, Sollwerte vom Orakel SQL Server 2025): `dbo.[zz.x]` ist die Tabelle `zz.x` in dbo und steht neben `zz.x` (Tabelle x im Schema zz); ebenso Sichten und Prozeduren, `INSERT`/`UPDATE`/`DROP`, dreiteilige Namen, `OBJECT_ID('dbo.[zz.x]')`, `OBJECT_NAME`, `sys.tables`/`sys.objects`, INFORMATION_SCHEMA. Intern steht der Punkt eines geklammerten Namensteils als U+FDD0 (ein Unicode-Nichtzeichen für den eigenen Gebrauch, `CatalogManager.Namenspunkt`), so trennen alle Stellen weiter richtig am Punkt; nach aussen wird er wieder ein Punkt (`Klartext` in den Systemsichten, `OBJECT_NAME`, Meldungen). Konten (`'u'@'127.0.0.1'`), Server und Datenbanken behalten ihren Punkt. Sabotage: Punkt im Parser nicht umgesetzt macht 2 Fälle rot✅
PR27.17z`sys.objects.type` als char(2)Am Orakel ist `type` in `sys.objects` char(2): `o.name + o.type` ergibt `zz.pP ` (mit Leerzeichen am Ende), hier `zz.pP`; ebenso der Sortierungskonflikt 451 zwischen `name` (Latin1_General_CI_AS_KS_WS) und `type` ohne `COLLATE` **Erledigt 13.10.2026:** Textwerte der sys-Sichten in Länge und Sortierung ihrer Vorlage (aus den Referenzdaten), dazu SQL_VARIANT_PROPERTY Collation des Werts; 451 offen (PR27.17z1).✅
PR27.17z1Sortierungskonflikt 451 und Rangfolge der SortierungenAm Orakel meldet `o.name + o.type` (Datenbanksortierung gegen Latin1_General_CI_AS_KS_WS) Fehler 451; dazu braucht ein Wert die Rangfolge seiner Sortierung (ausdrücklich, Spalte, Vorgabe/Literal) - KuonenSQL unterscheidet Spalte in Datenbanksortierung und Literal nicht. Dasselbe gilt für 468 bei Vergleichen gegen eine Spalte in Datenbanksortierung. ERLEDIGT 14.10.2026 (SortierungsrangTests): Rangfolge ausdrücklich > implizit (jede Zeichenspalte, auch in der Datenbanksortierung, und die festen Katalogsortierungen der Systemsichten) > Vorgabe; zwei verschiedene implizite in +, CASE, COALESCE, IIF und CONCAT ergeben einen Wert ohne Sortierung - varchar 457 sofort (CONCAT nicht), als Ausgabespalte 451, in Vergleich, LEN, UPPER, LOWER und IS NULL 4191; ein Vergleich, REPLACE und CHARINDEX zweier verschiedener impliziter 468 Zustand 9. Geprüft einmal je Anweisung vor dem Lesen (auch bei leerer Tabelle), nur in T-SQL und nur, wenn eine Quelle eine eigene Sortierung hat✅
PR27.17z2Sortierungsrang: übrige WegeERLEDIGT 14.10.2026 (`SortierungsrangTests`, 54 Fälle am Orakel gemessen): UNION/EXCEPT/INTERSECT 468/9, UNION ALL 457 bzw. 451, LIKE/NOT LIKE, BETWEEN (untere/obere Grenze), IN mit Unterabfrage, NULLIF, UPDATE SET/WHERE, die Textfunktionen, die die Sortierung behalten (LEFT … STUFF), QUOTENAME/FORMAT als nvarchar, Sichten und Inline-Tabellenfunktionen beim Anlegen; `UNION ALL … ORDER BY 1` scheitert nicht mehr mit 50000; die Laufzeitprüfung meldet 468 mit Zustand 9. Alle 54 Fälle gleich✅
PR27.15yDATETRUNC, TRIM mit LEADING/TRAILING, Namen von CURRENT_DATEERLEDIGT 10.10.2026 (`DatetruncTrimTests`, Orakel SQL Server 2025): `DATETRUNC` liefert den Typ des Arguments (`date`, `time`, `datetime`, `smalldatetime`, `datetime2(p)`, `datetimeoffset` mit Versatz; vorher immer `datetime2`), kennt `week` nach `DATEFIRST`, `iso_week`, `dayofyear`, `microsecond` und meldet 9810, wenn der Typ den Teil nicht trägt, und 8116 für eine Zahl; `TRIM(LEADING/TRAILING/BOTH zeichen FROM text)` (vorher 102), ohne Zeichen 156; `CURRENT_DATE`, `CURRENT_TIMESTAMP`, `CURRENT_USER`, `SESSION_USER`, `SYSTEM_USER` erscheinen ohne Spaltennamen wie am Orakel✅
PR27.15zODBC-Escapes `{fn …}`, `{d …}`, `{t …}`, `{ts …}`, `{oj …}`, `{escape …}`, `{call …}`, `{guid …}`ERLEDIGT 10.10.2026 (`OdbcEscapeTests`, Orakel SQL Server 2025 über sqlcmd, das die Escapes unverändert schickt): ein Durchlauf vor dem Lexer (`OdbcEscapes`) macht T-SQL daraus — die ODBC-Funktionen unter ihrem T-SQL-Namen (`UCASE`, `LOCATE`, `IFNULL`, `REPEAT`, `INSERT` …), Datumsteile über `DATEPART`/`DATENAME`, `CONVERT(wert, SQL_…)` mit den Typen des Orakels (`SQL_DECIMAL` ist `decimal(38,4)`, `SQL_DATE` `datetime`), `TIMESTAMPADD`/`TIMESTAMPDIFF` mit `SQL_TSI_…`, `CURDATE`/`CURTIME` als Text, `{d}`/`{ts}` als `datetime`, `{t}` die Uhrzeit am heutigen Tag, `{call p(a)}` als `EXEC p a`; Literale, Kommentare und Namen in Klammern bleiben, Escapes dürfen verschachtelt sein. Vorher 102✅
PR27.16aKatalogsichten linear in der Zahl der Objekte**Gebaut 05.10.2026:** Befund App-Abnahme (Gitea/xorm, ~100 Tabellen: `pg_class` 0,5–2 s, `pg_index` 1–2 s). Eine Systemsicht (sys, information_schema, pg_catalog, MySQL-Katalog) wird je lesender Anweisung einmal aufgebaut (`Serverinstanz.Systemsichtaufbauten`); korrelierte Unterabfragen lesen über `ZeilenKontext` statt Variablenkopie und nur das Fach ihrer Gleichheit nach aussen; Verbünde über Systemsichten nutzen deren Fächer und lagern nicht aus, WHERE-Glieder einer Quelle sieben vor dem Verbund, Kommaverbünde rücken nach Gleichheit; innere Abfragen geben ihre Zuteilung zurück; gruppenweise Unterabfragen mit mehreren Schlüsseln, STRING_AGG und Verbund; `CatalogManager.FindTableById` (18 Stellen). 100 Tabellen: `pg_class` 2 274 → 111 ms, `pg_index` 4 895 → 45 ms, xorm Spalten 250 s → 1,2 s; 500 Tabellen linear (`docs/leistung.md` Abschnitt 10). Tests `PgKatalogLeistungTests` (Aufbauten bei 5 und 50 Tabellen, gleiche Ergebnisse), Sabotage ohne Puffer rot.✅
PR27.16bKatalog: Describe des PG-Anschlusses ohne zweite Ausführung**Erledigt 05.10.2026:** Describe eines Portals, das Zeilen liefert, führt es einmal aus; Execute liefert die gemerkten Zeilen (wie bei `RETURNING`). `pg_class` baut 13 statt 26 Sichten auf; 500 Tabellen: xorm Spalten 3 014 → 1 385 ms, `pg_class` 527 → 196 ms (`docs/leistung.md` Abschnitt 12). Test `PgKatalogLeistungTests.Die_Beschreibung_eines_Portals_…`✅
PR27.16cKatalog: Konstante je Zeile der pg-Sichten**Erledigt 06.10.2026:** Profil je Auswahlglied statt Vermutung. Die festen Zeilen von `pg_class`, `pg_attribute` (4 300), `pg_index`, `pg_constraint` wurden je Anfrage ausgewählt, vereinigt und umgetypt - jetzt hängt `Pgkatalog.Form` die fertigen, geteilten Werte an (`pg_attribute` ohne Tabellen 172 → 7 ms); der Ergebnistyp grosser CASE (> 60 Knoten) über die Zeilen eines Verbunds wird bis zu vier Stände lang gemerkt statt je Zeile ermittelt (`information_schema.columns` bei 500 Tabellen 860 → 200 ms, Zähler `Serverinstanz.Typermittlungen` 113 statt 4 372); einfaches CASE mit ≥ 6 ganzzahligen WHEN schlägt nach; `PG_ACL` sucht den Besitzer erst bei einem Recht; `NOT LIKE` statt `LEFT` im Verbund. 500 Tabellen: xorm Spalten 1 385 → 782 ms (Ziel unter 1 s erreicht), xorm Spalte da? 673 → 211 ms, Django Fremdschlüssel 733 → 574 ms (`docs/leistung.md` Abschnitt 13). Tests `PgKatalogLeistungTests` (fünf neue), Sabotage je Punkt rot. Rest: PR27.16f.✅
PR27.16fKatalog: lebende Zeilen von `pg_class` und `pg_constraint`ERLEDIGT 11.10.2026 (`GruppenweiseTests`, Messung `docs/leistung.md` Abschnitt 19): korrelierte Unterabfragen rechnen gruppenweise auch als `EXISTS` (über die Zählform), als Ausdruck um Aggregate (`'{' + STRING_AGG(…) + '}'`, Wert über null Zeilen einmal gerechnet) und als Einzelwert ohne Aggregat (mehrdeutige Schlüssel gehen den gewohnten Weg, 512); die Katalogsichten gruppieren die Indizes einmal statt je Tabelle; `Identitaetsart` als MAX. Bei 500 Tabellen `pg_class` 143 → 85 ms, `pg_constraint` 217 → 59 ms, xorm Spalten 998 → 381 ms, Django Fremdschlüssel 601 → 167 ms, getTables 582 → 443 ms. Rest: PR27.18f✅
PR27.18fKatalog: `pg_attribute` und `information_schema.columns` unter 100 msRest aus PR27.16f: bei 500 Tabellen kosten `pg_attribute` ~130 ms und `information_schema.columns` ~185 ms, `pgjdbc getTables` ~440 ms - keine einzelne Unterabfrage mehr, sondern die Auswertung von rund 30 Ausdrücken je Spalte (grosse CASE über die Typnummern, `PG_ACL` je Spalte). Ziel: je Sicht unter 100 ms (Ausdrücke je Typ einmal vorrechnen oder als Nachschlagetabelle) **Erledigt 14.10.2026:** pg_attribute 45 ms, information_schema.columns 89,6 → 63,3 ms, pg_class 38 ms bei 500 Tabellen (CASE über Text nachgeschlagen, varchar unter Latin1_General ohne ICU, NULL-Zeile des Aussenverbunds ohne Kopie).✅
PR27.18f1COLLATE in einer VALUES-ListeERLEDIGT 14.10.2026 (`ValuesSortierungTests`): ein ausdrückliches COLLATE an einem Wert einer VALUES-Liste gibt der ganzen Spalte seine Sortierung (ORDER BY, DISTINCT, SQL_VARIANT_PROPERTY wie am Orakel), zwei verschiedene sind 468 Zustand 9✅
PR27.16dKatalog: Gitea-Aktualisierung nachmessenNach dem Merge (samt dem Zweig mit `pg_attrdef` in `information_schema.columns`) die App-Abnahme Gitea 1.27 → 1.28 wiederholen und die Dauer der Migration festhalten. ERLEDIGT 12.10.2026: Aktualisierung in 61–81 s (vorher über 20 min), 39/39 Seiten, 0 SQL-Fehler. Funde behoben: Parse als erste Nachricht einer Verbindung hatte keinen Katalog (Parametertypen blieben Text, 22P02 bei `DELETE … WHERE is_deleted=$1`); `DROP TABLE IF EXISTS "public"."t"` mit Schema in Anführungszeichen löschte nicht; `IF … CREATE TABLE … ELSE` verschluckte das ELSE; „already exists, skipping“ kommt als NOTICE mit SQLSTATE statt als Text; `UPDATE t AS b … WHERE b.x` über Parse meldete 42P01. Das Fehlerprotokoll schreibt die Übersetzung samt Parametertypen (`Ausgefuehrt=`); die Abnahme meldet sich nach der Aktualisierung neu an (Gitea hält Sitzungen im Speicher)✅
PR27.16eKatalog: Describe einer vorbereiteten AnweisungERLEDIGT 11.10.2026 (`PgKatalogLeistungTests.Die_Beschreibung_einer_vorbereiteten_Anweisung_baut_die_Sichten_nicht_auf`, `FormauskunftOhneAusfuehrungTests.Fmtonly_baut_keine_systemsicht_auf`): unter FMTONLY (Describe `'S'`, `SET FMTONLY ON`, Vorbereitung am MariaDB-Zugang) liefert eine Systemsicht mit fester Vorlage nur ihre Spalten, aufgebaut wird sie nicht - dieselben Spalten und Typen wie bei der Ausführung; 500 Tabellen, Katalogverbund unter FMTONLY 198 ms → 0,46 ms✅
PR27.16gPostgreSQL: `mode() WITHIN GROUP`ERLEDIGT 10.10.2026 (`PgModalwertTests`, Orakel `postgres:17`): der häufigste Wert ausser NULL, bei Gleichstand der erste in der Sortierfolge (auch absteigend), nur NULL ergibt NULL; mit OVER 0A000. Im Kern als Aggregat `PG_MODE` neben den Perzentilen✅
PR27.16hPostgreSQL: `setseed` und `sha224`ERLEDIGT 10.10.2026 (`PgRechenfunktionenTests`, Orakel `postgres:17`): nach `setseed(x)` liefert `random()` dieselbe Folge wie PostgreSQL (auch je Zeile; xoroshiro128** wie in `pg_prng.c`, PostgreSQL-Lizenz), `setseed` ist `void`, ausserhalb von [-1, 1] und NaN 22023; `sha224` über dieselbe Rechnung wie `SHA2(x, 224)` bei MariaDB. Die Folge gehört der Sitzung im Kern✅
PR27.16iReste aus PR27.16hERLEDIGT 11.10.2026 bis auf die Reste in PR27.17s-u (`PgMengenfunktionenTests`, 16 Fälle, Sollwerte von `postgres:17`): `regexp_matches`, `string_to_table` (neu, auch in FROM, mit Nulltext und ohne Trenner) und `generate_subscripts` (auch rückwärts mit drittem Argument) stehen in der Auswahlliste neben anderen Einträgen (die Reihe hängt als `CROSS APPLY`/`FROM … AS rz` an, je Zeile der Quelle), ebenso `generate_series`; eine Reihe ohne Glied liefert keine Zeile (vorher zwei NULL, T-SQL-`GENERATE_SERIES(1, 0)` zählte abwärts); `mode(x)` ohne WITHIN GROUP meldet 42809 (ebenso `percentile_cont`/`percentile_disc`); `pg_typeof(max(x))` über der leeren Menge nennt den Spaltentyp (der lange Aggregatweg bekommt die Spaltentypen, der Durchlauf ebenso - in T-SQL ist `SUM(d + w)` über der leeren Menge damit decimal(38,2) wie am Orakel). Sabotage: Reihen in der Auswahlliste nicht gesammelt macht 6 Fälle rot✅
PR27.17sjsonpath am PostgreSQL-ZugangERLEDIGT 11.10.2026 (`PgJsonpfadTests`, 58 Fälle, Sollwerte von `postgres:17`): `Erweiterungstypen/Jsonpfad` im Kern liest und wertet SQL/JSON-Pfade aus (lax/strict, `$`, `@`, `$name`, `.key`, `."key"`, `.*`, `.**{m to n}`, `[i, j to last]`, `[*]`, Filter mit Vergleichen, Und, Oder, Nicht, `exists`, `like_regex … flag`, `starts with`, `is unknown`, Rechnen über numeric, Methoden `type size double ceiling floor abs keyvalue number decimal integer bigint string boolean`; die `id` von `keyvalue()` als Stelle im Binärformat von jsonb nachgerechnet); `jsonb_path_exists`, `_match`, `_query_array`, `_query_first` (je auch `_tz`), `jsonb_path_query` als Zeilen in FROM (Spalte wie der Alias) und in der Auswahlliste, `@?` und `@@` über jsonb (still), Variablen, `silent`, die Fehler mit SQLSTATE (2203A, 22039, 22033, 22038, 42601, 42704, 22012, 22036, 22023). Sabotage: Felder im Filter nicht ausgepackt macht den Fall rot✅
PR27.17yRest aus PR27.17s: jsonpath als eigener TypERLEDIGT 11.10.2026 (`PgJsonpfadTests`, jetzt 92 Fälle, Sollwerte von `postgres:17`): `jsonpath` ist ein eigener Typ (Kennung 235, OID 4072, Klasse `SqlJsonpath`) in der Normalform von `jsonpath_out` (`'$.a ? (@ > 1)'::jsonpath` ist `$."a"?(@ > 1)`, Klammern nach dem Vorrang, `- 1` als `-1`), binär mit Versionsbyte; die Datumsmethoden `.datetime([vorlage])`, `.date()`, `.time()`, `.time_tz()`, `.timestamp()`, `.timestamp_tz()` mit Ausgabe wie PostgreSQL, Vergleichen und dem Zonenfehler 0A000 ausserhalb der `_tz`-Fassungen (die mit UTC rechnen); `.decimal(p, s)`; ein Syntaxfehler im Pfad-Literal zeigt auf das Literal; `like_regex` mit POSIX-Klassen (`[[:digit:]]` …), `flag "x"` 0A000 und den Meldungen 2201B für ungültige Muster. Sabotage: Schlüssel in der Normalform ohne Anführungszeichen macht 8 Fälle rot✅
PR27.17tPostgreSQL: Typen von sum und avgERLEDIGT 11.10.2026 (`PgAggregattypenTests`, 10 Fälle, Sollwerte von `postgres:17`): auch wo der Übersetzer die Art des Arguments nicht kennt (temporäre Tabelle, VALUES, Fenster) ist `sum(smallint/int)` bigint, `sum(bigint)` numeric, `avg` über Ganzzahlen und numeric numeric mit den Stellen der numeric-Division (`1.5000000000000000`, `0.50000000000000000000`, `2147483647.00000000`), `avg(real)` double precision; im Kern über die Dialektmarke `AggregattypenNachPostgreSql`, die Division liegt als `Types/Zahlteilung` im Kern (`Pgzahlen.Teilen` prüft nur noch den Teiler 0); der Übersetzer wandelt `sum(int)` nicht mehr selbst in bigint. Sabotage: Marke aus macht 9 Fälle rot✅
PR27.17vPostgreSQL: Typ moneyERLEDIGT 11.10.2026 (`PgGeldTests`, 8 Fälle, Sollwerte von `postgres:17`): das MONEY der gemeinsamen Ablage geht am PG-Zugang als money (OID 790, Array 791): Text `$1,234.57`/`-$2.25` (auch `m::text`), binär in Cent (Ausgabe und Parameter), `pg_typeof`/`format_type`/Katalog money, Eingabe `$1,234.567` auf zwei Stellen gerundet; `money * Zahl` und `money / Zahl` bleiben money, `money / money` ist double precision (Dialektmarke `GeldNachPostgreSql`), `m::numeric` hat zwei Stellen. Sabotage: Marke aus macht 7 Fälle rot✅
PR27.17wRest aus PR27.17v: money-Grenzen und -FunktionenERLEDIGT 11.10.2026 bis auf den Wertebereich (PR27.17x) (`PgGeldTests`, 14 neue Fälle, Sollwerte von `postgres:17`): `cash_words(money)` (Gruppen bis quadrillion, „and“ nach hundred nur vor 1 bis 19, zwei Leerzeichen bei leerer Einergruppe, Einzahl, Minus; 30 Beträge gemessen); `-m` und `avg(m)` melden 42883 samt Hinweis auch über einer temporären Tabelle (die Fehlerstelle fehlt, der Kern kennt sie zur Laufzeit nicht). Sabotage: „and“ nach hundred weggelassen macht 2 Fälle rot✅
PR27.17xmoney: Wertebereich von PostgreSQLENTSCHIEDEN 11.10.2026 (Auftraggeber): die gemeinsame Ablage bleibt das MONEY von T-SQL (TDS und PostgreSQL sehen dieselben Werte); am PG-Zugang reicht money darum bis 922 337 203 685 477.5807, darüber meldet er „value out of range: overflow“ (am Orakel bis 92 233 720 368 547 758.07). Kein Umbau✅
PR27.17uPostgreSQL: regtype im BinärformatERLEDIGT 11.10.2026 (`PgMengenfunktionenTests.Pg_typeof_geht_binaer_als_Oid`): `pg_typeof(…)` geht im Binärformat als OID (vier Bytes ohne Vorzeichen, integer 23, text[] 1009, numeric 1700), Npgsql liest es als Zahl statt mit „has not consumed all of its bytes“ abzubrechen; `Pgwerte.OidVonTypname` kehrt `TypnameVon` für die eingebauten Typen und ihre Arrays um. Sabotage: regtype als Text gesendet macht den Fall rot✅
PR27.16jCOL_LENGTH, HASHBYTES MD4/MD5/SHA1, ISNUMERIC, FORMAT in der SitzungskulturERLEDIGT 10.10.2026 (`Tsql2025LueckenTests`, Orakel SQL Server 2025): `COL_LENGTH('tabelle','spalte')` wie `sys.columns.max_length` (vorher 195; Migrationsskripte prüfen damit, ob es eine Spalte gibt), `sql_variant` 8016 auch in `sys.columns` (vorher 0); `HASHBYTES` mit `MD4` (eigene Rechnung nach RFC 1320), `MD5`, `SHA`, `SHA1` (vorher bewusst weggelassen, das Orakel kennt sie), `MD2` NULL; `ISNUMERIC` wie SQL Server (`'$'`, `'+'`, `'.'`, `','` allein 1, `'1d5'` 1, `'1e'` 0); `FORMAT` ohne Kultur in der Sprache der Sitzung (en-US, nach `SET LANGUAGE German` de-DE; vorher invariant) mit den Kulturdaten von Windows (zwei Nachkommastellen bei `N`/`P`, `($1,234.50)`, U+00A0 als Trenner in fr-FR)✅
PR27.16kReste aus PR27.16jERLEDIGT 10.10.2026: `COL_LENGTH` auf Systemsichten in PR27.16w; woher die 1 von `XACT_STATE()` kommt, ist geklärt (Anweisungstransaktion, siehe PR27.16x, dort der Rest)✅
PR27.16lODBC-Katalog: sp_columns und sp_columns_100 wie SQL ServerERLEDIGT 10.10.2026 (`OdbcKatalogTests`, Orakel SQL Server 2025): ein gemeinsamer Kern `Odbcmerkmale` rechnet je Typ, was ODBC-Treiber erwarten — die alte Fassung meldet die Datumstypen ab 2008 als `nvarchar` (-9) und die max-Typen als `text`/`ntext`/`image`, `sp_columns_100` mit eigenen Codes (datetime2 11, time -154, datetimeoffset -155, xml -152, CLR-Typen -151 samt Assemblyname), `@ODBCVer = 3` float/real in Bit und die Datumscodes 91/93; dazu Präzision und Länge je Typ, `SS_DATA_TYPE` (nullbar und nicht), `int identity`, `COLUMN_DEF`, das Schema als Besitzer, Sichten, Filter `@table_owner` und `@column_name` als LIKE-Muster (auch `[abc]`), die `SS_`-Spalten von `_100`. Vorher wichen Typcodes, Präzision und Länge ab und die Filter fehlten. Dabei: eine Identitätsspalte ohne NULL-Angabe ist NOT NULL (am Orakel `is_nullable` 0)✅
PR27.16mODBC-Katalog: die übrigen ProzedurenERLEDIGT 10.10.2026 (`OdbcKatalogRestTests`, Orakel SQL Server 2025; vorher 2812): `sp_statistics`/`_100` (Tabellenzeile mit Zeilen und Seiten, je Index und Spalte NON_UNIQUE, TYPE 1 gruppiert sonst 3, COLLATION A/D, `@is_unique`), `sp_special_columns`/`_100` (Primärschlüssel oder erster eindeutiger Index, SCOPE 1, mit `@scope = 'C'` 0; `@col_type = 'V'` die rowversion), `sp_sproc_columns`/`_100` (Prozedur `name;1` mit `@RETURN_VALUE` int, Skalarfunktion `name;0` mit ihrem Typ nullbar, Tabellenfunktion `@TABLE_RETURN_VALUE`, Ausgabeparameter COLUMN_TYPE 2), `sp_datatype_info`/`_100` (die feste Typliste des Servers als Daten, `@ODBCVer = 3`, `@data_type`), `sp_table_privileges`/`sp_column_privileges` (dbo mit allen Rechten, dazu die erteilten). Alle über `Odbcmerkmale` aus PR27.16l✅
PR27.16nT-SQL: Rechte auf SpaltenERLEDIGT 10.10.2026 (`TsqlSpaltenrechteTests`, Orakel SQL Server 2025): `GRANT/REVOKE/DENY recht (spalten) ON t` (vorher als Recht „UPDATE ( B )“ ohne Wirkung abgelegt, eine Liste mit Komma zerbrach) wird ein Spaltenrecht im Kern (wie bei MariaDB, dazu Weitergabe und DENY), steht mit `minor_id` in `sys.database_permissions` und in `sp_column_privileges`; ein Tabellenrecht (GRANT oder REVOKE) ersetzt die Spalteneinträge desselben Rechts; unbekannte Spalte 4615, Spaltenliste bei DELETE 1020; wer vergibt, braucht dieselbe Befugnis. Durchsetzung vor dem Stapel: `*` und `COUNT(*)` brauchen jede Spalte, `WHERE` SELECT auf die gelesenen, `SET` UPDATE auf die gesetzten, eine verweigerte Spalte sticht das Tabellenrecht — sonst 230 mit Spalte, Objekt, Datenbank und Schema✅
PR27.16pEXECUTE AS über die StapelgrenzeERLEDIGT 10.10.2026 (`AusfuehrenAlsStapelTests`, Orakel SQL Server 2025): `EXECUTE AS USER = 'u'` gilt bis `REVERT` auch in den folgenden Stapeln der Sitzung (vorher endete der Wechsel am Stapelende, entgegen dem Orakel); ein Wechsel im Rumpf eines Moduls — Prozedur, Funktion, Trigger und dynamisches SQL (`EXEC ('…')`) — endet mit diesem, das Zurücksetzen der Verbindung (`sp_reset_connection`) hebt jeden auf. Der Stapel der Wechsel gehört der Engine der Sitzung (`ErzeugeSitzung`), kein anderer Client sieht ihn. Damit greifen auch die Spaltenrechte (PR27.16n) nach einem `EXECUTE AS` im vorigen Stapel✅
PR27.16qOrt in 547, Name von FOR XML, TRY_CONVERT nach xmlERLEDIGT 10.10.2026 (`PruefortUndXmlTests`, Orakel SQL Server 2025): die Meldung 547 einer CHECK-Bedingung nennt die Datenbank (bei temporären Tabellen tempdb), die Tabelle als `#t` bzw. mit Schema (vorher der interne Name `#t____…061` und die aktuelle Datenbank) und, wenn die Bedingung genau eine Spalte liest, die Spalte; `FOR XML` ohne TYPE heisst `XML_F52E2B61-18A1-11d1-B105-00805F49916B` (vorher ohne Namen); `TRY_CONVERT(xml, '<a>')` ist NULL (vorher 9400)✅
PR27.16rReste aus PR27.16q: FOR XML in Stücken, in INSERT … EXEC, sql_variantERLEDIGT 11.10.2026 (`ForXmlStueckeTests`, Orakel SQL Server 2025): FOR XML ohne TYPE geht auf Anweisungsebene wie FOR JSON in Stücken zu 2033 Zeichen hinaus und als ntext (am Orakel 10092 Zeichen in 5 Zeilen, `sp_describe_first_result_set` ntext; vorher eine Zeile nvarchar(max)); die Beschreibung von FOR JSON nennt `JSON_F52E2B61-…`; in der Quelle von INSERT … EXEC sind FOR XML 6819 und FOR JSON 13602 (State 5, vorher erlaubt); ein Grossobjekt (nvarchar(max), xml …) wird still nicht zu sql_variant: 206 State 2 (vorher bei SQL_VARIANT_PROPERTY angenommen, bei Zuweisung 529)✅
PR27.16sALTER TABLE DROP mit Liste, IF EXISTS und Prüfung abhängiger ObjekteERLEDIGT 10.10.2026 (`DropListeTests`, Orakel SQL Server 2025): `DROP COLUMN a, b`, `DROP CONSTRAINT IF EXISTS c, COLUMN IF EXISTS d` (vorher nur ein Teil ohne IF EXISTS); `DROP COLUMN` einer Spalte mit Vorgabe, CHECK, Fremdschlüssel oder Index meldet 5074 („Das Objekt …“ bzw. „Der Index …“) und 4922 (vorher wurde die Spalte samt Bedingung entfernt); eine Liste gilt ganz oder gar nicht, Einschränkungen gehen vor den Spalten✅
PR27.16tALTER TABLE … SWITCH (ungeteilte Tabellen)ERLEDIGT 10.10.2026 (`UmschaltenTests`, Orakel SQL Server 2025): `ALTER TABLE q SWITCH [PARTITION n] TO z [PARTITION n]` verschiebt alle Zeilen ohne Trigger in das leere Ziel, in einer Transaktion zurücknehmbar (vorher 102); geprüft wie am Orakel: gleiche Tabelle 4955, fehlendes Ziel 1088, Spaltenzahl 4943, Name 4942, Typ 4944, NULL 4985, gruppierter Index 4913, Index 4947, CHECK 4971, nicht leer 4905; PARTITION-Angaben gibt Warnung 4903, auch hinter einem Fehler✅
PR27.16uWITH VALUES, NULL in neuen Spalten, CAST ohne LängeERLEDIGT 10.10.2026 (`MitWertenTests`, Orakel SQL Server 2025): `ALTER TABLE … ADD c int DEFAULT 6 WITH VALUES, d …` liest die Liste weiter (vorher endete sie bei WITH VALUES, d fehlte); eine neue Spalte, die NULL zulässt, bleibt in vorhandenen Zeilen NULL, ausser mit WITH VALUES (vorher bekam sie die Vorgabe; PostgreSQL, MariaDB und MySQL füllen weiter); `CAST(x AS varchar)`/`CONVERT(nvarchar, x)`/`char`/`binary` ohne Länge sind 30 lang wie in SQL Server (vorher 1: `CAST('abcdef' AS varchar)` gab `a`)✅
PR27.16vIDENTITY-Angaben, ADD … IDENTITY, IDENT_SEED/IDENT_INCRERLEDIGT 10.10.2026 (`IdentitaetsangabeTests`, Orakel SQL Server 2025): `IDENTITY(-5, -2)` gilt mit Vorzeichen (vorher still 1, 1); `ALTER TABLE … ADD id int IDENTITY(100, 10)` nummeriert die vorhandenen Zeilen in Ablagereihenfolge (vorher blieb die Spalte NULL ohne Zähler), eine zweite Identitätsspalte 2744; Prüfungen wie am Orakel: Typ 2749, Start ausserhalb des Typs oder als Bruch 2752, Schritt 0 2753; `IDENT_SEED`/`IDENT_INCR` neu (vorher 195), alle drei IDENT-Funktionen mit BaseType numeric (vorher decimal)✅
PR27.16wSpalten der Systemsichten, SWITCHOFFSET aus Text, Stil 126/127 für datetimeoffsetERLEDIGT 10.10.2026 (`SystemsichtspaltenTests`, Orakel SQL Server 2025): `COL_LENGTH`, `COL_NAME` und `COLUMNPROPERTY` kennen die Spalten von `sys.*` und `INFORMATION_SCHEMA.*` (vorher NULL, COLUMNPROPERTY nur `sys.*`); `SWITCHOFFSET('… +02:00', '-05:00')` rechnet vom Versatz im Text aus (vorher als UTC); `CONVERT(varchar, datetimeoffset, 126)` ohne leeren Bruchteil, Stil 127 in UTC mit `Z` (vorher immer sieben Stellen und der eigene Versatz)✅
PR27.16xXACT_STATE() in der AnweisungstransaktionERLEDIGT 11.10.2026 (`AnweisungstransaktionTests`, 31 Fälle mit den Werten des Orakels SQL Server 2025): ausserhalb einer Benutzertransaktion meldet `XACT_STATE()` 1, wenn dieselbe Anweisung Daten liest - eine Tabellenquelle (Tabelle, #temp, Tabellenvariable, Systemsicht, `STRING_SPLIT`/`OPENJSON`, auch in Unterabfrage, EXISTS und CTE; nicht `VALUES` und eine abgeleitete Tabelle ohne Tabelle), eine der 107 Funktionen, die am Orakel 1 liefern (Messreihe über alle eingebauten Funktionen mit `sp_executesql`, Liste in `Datenzugriffe`), oder ein `@@`-Wert ausser `@@ERROR`, `@@ROWCOUNT`, `@@TRANCOUNT`, `@@FETCH_STATUS`, `@@CURSOR_ROWS`; der Parser merkt es je Anweisung am Tokenstrom (`SqlStatement.LiestDaten`), der Ausführer reicht es weiter. Sabotage: Funktionen und `@@` nicht erkannt macht 11 Fälle rot✅
PR27.16yMeldungen 2628, 220, 245 und 3621 beim SpaltenumbauERLEDIGT 10.10.2026 (`AbschneideMeldungTests`, Orakel SQL Server 2025): 2628 nennt `datenbank.schema.tabelle` (temporär `tempdb.dbo.#t_…`) und den auf die Spaltenlänge gekürzten Wert, bei Binärdaten und ALTER COLUMN '' (vorher Kurzname und der ganze Wert); 220 hat State 2 und in einer Datenänderung 3621 (vorher State 1 ohne 3621); 245 nennt den Typ ohne Länge (`varchar-Werts` statt `varchar(5)-Werts`); ALTER COLUMN endet bei 220, 245 und 8115 mit 3621✅
PR27.16zRest aus PR27.16y: UPDATE mit zu langem WertERLEDIGT 10.10.2026 (`AbschneideMeldungTests`, Orakel SQL Server 2025): mit frischem Plan meldet UPDATE (Konstante, Spalte, Variable, auch `OPTION (RECOMPILE)`) immer 2628 mit `tempdb.dbo.…` und dem gekürzten Wert - die 8152 State 31 kam aus einem zwischengespeicherten Plan; UPDATE gibt den Tabellennamen jetzt auch in T-SQL weiter (vorher 2628 mit '')✅
PR27.17aOLE-DB-Katalogprozeduren (sp_*_rowset), sysname als Aliastyp, TDS-SortierungERLEDIGT 10.10.2026 (`OledbKatalogTests`, Orakel SQL Server 2025): `sp_catalogs_rowset`, `sp_schemata_rowset`, `sp_tables_rowset`, `sp_tables_info_rowset`, `sp_columns_rowset`, `sp_procedures_rowset`, `sp_procedure_params_rowset`, `sp_primary_keys_rowset`, `sp_foreign_keys_rowset`, `sp_indexes_rowset`, `sp_views_rowset`, `sp_check_constraints_rowset`, `sp_table_constraints_rowset`, `sp_statistics_rowset` und ihre `…2`-Fassungen (vorher 2812) mit OLE-DB-Typnummern, Merkmalen, Zeichensatz, LCID, Vergleichsart und TDS-Sortierung (41 Spaltentypen gleich dem Orakel); `sysname` ist Aliastyp 256 und ohne Angabe NOT NULL (vorher nvarchar, nullbar); die TDS-Sortierung trägt bei binären Sortierungen nur BIN/BIN2, bei UTF-8 das Merkmal 0x40 und bei SQL-Sortierungen ihre Sortierkennung (vorher 0: der Client las etwa SQL_Latin1_General_CP1253 als Codepage 1252)✅
PR27.17bFassungen 90/100/64 der OLE-DB-KatalogprozedurenERLEDIGT 10.10.2026 (`OledbKatalogTests`, Orakel SQL Server 2025): `sp_columns_90_rowset`/`_100_rowset`, `sp_procedure_params_90_rowset`/`_100_rowset`, `sp_indexes_90_rowset`/`_100_rowset`, `sp_tables_info_90_rowset`, `sp_tables_info_rowset_64`, `sp_tables_info_90_rowset_64`, `sp_foreign_keys_rowset3` samt `…2`-Fassungen (vorher 2812): `_90` meldet max-Typen mit Länge 0, xml als 141, geography/geometry mit -1 und CLR-Klasse, hierarchyid als UDT 132; `_100` dazu date/datetime2/time/datetimeoffset als 133/135/145/146 (variable Skala 0x40000000) und SS_IS_SPARSE/SS_IS_COLUMN_SET; Indizes mit STATUS, Tabelleninfo mit TABLE_FLAGS, `_64` mit Lesezeichen 21/8✅
PR27.17fBeschreibung lesender SystemprozedurenERLEDIGT 10.10.2026 (`OledbKatalogTests`, `FormauskunftOhneAusfuehrungTests`, Orakel SQL Server 2025): `sys.dm_exec_describe_first_result_set`/`sp_describe_first_result_set` über `EXEC sp_tables_rowset …`, `EXEC sp_pkeys …`, `EXEC sp_who` usw. meldet die Spalten der ersten Menge (vorher keine Zeile; früher als bewusste Abweichung in S12.3e festgehalten); sp_who meldet `loginame` als nvarchar(128) und `cmd` als nchar(26) wie am Orakel (vorher nchar(128)/nchar(16))✅
PR27.17gSTDEV/VAR wie SQL Server, COMPRESS/DECOMPRESSERLEDIGT 11.10.2026 (`StreuungUndGzipTests`, Orakel SQL Server 2025): STDEV/STDEVP/VAR/VARP rechnen aus Summe und Quadratsumme (Σx² − (Σx)²/n)/(n − 1) und gleichen dem Orakel bis in die letzte Stelle, auch wo die Auslöschung 0 ergibt (vorher zweistufig über den Mittelwert, letzte Stelle abweichend); `COMPRESS`/`DECOMPRESS` neu (vorher 195): gzip mit dem Kopf `1F8B 08 00 00000000 04 00`, leere Eingabe `0x`, Zahlentyp 8116, fremde Daten 9826, Ergebnis varbinary(max)✅
PR27.17hDECOMPRESS eines abgeschnittenen StromsERLEDIGT 11.10.2026 (`StreuungUndGzipTests`, Orakel SQL Server 2025): DECOMPRESS entpackt mit eigenem Inflate nach RFC 1951 (`Types/Inflate.cs`) und gibt wie das Orakel NULL, wenn der Deflate-Strom vor dem Ende des letzten Blocks abbricht oder verdorben ist (vorher Teildaten); der Nachspann zählt nicht; gleich dem Orakel bei festen, dynamischen und gespeicherten Blöcken; jedes verfälschte Byte ergibt Daten oder NULL, nie eine unbehandelte Ausnahme✅
PR27.17iCOMPRESS byte-gleich auch bei langen EingabenERLEDIGT 11.10.2026 (`StreuungUndGzipTests`, `WerkzeugfunktionenTests`): `Types/Deflate.cs` - eigenes Deflate nach dem Vorgehen von zlib Stufe 6 (verzögerte Suche, Hashketten, Blockschnitt nach 16 383 Zeichen, Wahl gespeichert/fest/dynamisch); T-SQL `COMPRESS` (gzip) und MySQL/MariaDB `COMPRESS` (zlib mit Längenvorsatz) nehmen es. Am Orakel SQL Server 2025 sechs Proben bis 214 KB und an mysql:8.4 drei Proben byte-gleich (Länge und SHA-256)✅
PR27.18aMySQL-Port: Prüfsummen über Binärwerte, WITH RECURSIVEAm MySQL-Port hashen `SHA2`/`MD5`/`SHA1` einen Binärwert (etwa `SHA2(COMPRESS('hallo'), 256)`, `MD5(UNHEX('FF00'))`) anders als mysql:8.4 (Texteingaben stimmen); `WITH RECURSIVE` ohne gewählte Datenbank meldet 1046 und in einer abgeleiteten Tabelle 1064, das Orakel rechnet beides **Erledigt 13.10.2026:** Prüfsummen über die Bytes des Werts, WITH in der abgeleiteten Tabelle (MySQL/MariaDB), an einem eigenen mysql:8.4.11 gemessen.✅
PR27.17eÜbrige OLE-DB-KatalogprozedurenERLEDIGT 11.10.2026 (`OledbKatalogWeitereTests`, `VerbindungsserverTests.Rowsets_Ueber_Den_Verbindungsserver`, `FormauskunftOhneAusfuehrungTests`): `sp_check_constbytable_rowset`, `sp_constr_col_usage_rowset` (nur Spaltenbedingungen und Fremdschlüssel), `sp_table_privileges_rowset`/`sp_column_privileges_rowset` (Besitzer und erteilte Rechte, verweigerte fehlen; derselbe Weg wie `sp_table_privileges`), `sp_table_types_rowset`, `sp_table_type_columns_100_rowset`, `sp_table_type_primary_keys_rowset`, `sp_sparse_columns_100_rowset`, `sp_usertypes_rowset`, `sp_assemblies_rowset` (Binärinhalt NULL: fremde Datei), `sp_assembly_dependencies_rowset`, `sp_xml_schema_rowset`, `sp_linkedservers_rowset`, `sp_MSrepl_linkedservers_rowset`, `sp_MSrepl_subscription_rowset` (208), `sp_provider_types_rowset` in drei Fassungen, `sp_table_statistics_rowset` (nie eine Zeile) - je samt `…2`; die `_rmt`-Fassungen fragen das Rowset beim Verbindungsserver (7202/7411 hinter der leeren Menge, drei melden 208 wie am Orakel). Typtabellen `TT_name_kennung` und ihr Primärschlüssel stehen in `sys.objects`/`sys.key_constraints`, `OBJECT_NAME` kennt sie; die Beschreibung einer Systemprozedur trägt die NULL-Zulässigkeit des Orakels. Nebenbei: `DROP TABLE` einer Tabelle mit Fremdschlüssel auf sich selbst meldete 3726✅
PR27.18bTabellentypen: Bedingungen und Indizes im KatalogAm Orakel hat ein Tabellentyp mit `UNIQUE`/`CHECK` die Objekte `UQ__TT_…`/`CK__TT_…` (mitgeliefert), Zeilen in `sys.indexes` (PK, UQ, Heap) und `is_ms_shipped = 1` in `sys.key_constraints`; die Vorgabe heisst `DF__TT_zz_et_6477__w__…`, hier `DF__zz_et__w`. Hier fehlen UNIQUE/CHECK eines Tabellentyps ganz; auch systemvergebene CHECK-Namen enden am Orakel auf `__` und acht Hexziffern **Erledigt 13.10.2026:** UNIQUE und CHECK bleiben am Typ, eine Tabellenvariable des Typs setzt PK, UNIQUE und CHECK durch (vorher keines davon), der Katalog zeigt sie an der Typtabelle; Systemnamen offen (PR27.18b1).✅
PR27.18b1Systemnamen von Einschränkungen wie SQL ServerAm Orakel CK__/DF__ + 13 Zeichen des Tabellennamens + __spalte__ + 8 Hexziffern der Objektkennung, PK__/UQ__ + 8 Zeichen + __ + 16 Hexziffern (auch bei gewöhnlichen Tabellen); hier 8 Zeichen ohne Kennung bzw. mit Spaltennamen; die Vorgabe eines Tabellentyps heisst nach dem Typ statt der Typtabelle. Namen stehen dauerhaft im Katalog - Umstellung samt Wiederanlauf alter Abzüge. ERLEDIGT 14.10.2026 (SystemnamenTests): CK/DF/FK art__tabelle__spalte__ + 8 Hexziffern der Objektkennung, Tabelle und Spalte zusammen 14 Zeichen (Tabelle mindestens 9), ohne einzelne Spalte die Tabelle 16; PK/UQ 8 Zeichen + 16 Hexziffern; ALTER ADD und Tabellentypen (nach der Typtabelle) ebenso. Die Kennung entsteht beim Anlegen statt beim ersten Lesen - vorher wechselte sie über einen Neustart, wenn dazwischen jemand den Katalog las; zwei unbenannte CHECK über ALTER hiessen gleich. DROP TABLE nimmt die Kennungen seiner Prüfungen und Vorgaben mit (Leck je temporärer Tabelle). Nur T-SQL; ein eigener Name, der genau wie der Platzhalter des Parsers aussieht (CK__tabelle__spalte), bekommt ebenfalls den Systemnamen✅
PR27.18b2Objektkennung einer Tabelle über einen NeustartERLEDIGT 14.10.2026 (`SystemnamenTests`): die Kennung kam aus einem Zähler für den ganzen Prozess (jede temporäre Tabelle, jedes Zwischenschema zählte mit). Jetzt vergibt der Katalog jeder Datenbank sie aus einem eigenen, gesicherten Zähler - nach einem Wiederanlauf mit und ohne Kontrollpunkt dieselben Kennungen für Tabellen, PK, UQ, CHECK, DEFAULT, FK; die Zugriffsstatistik zählt je Katalog, `sys.dm_db_index_usage_stats` zeigt nur die eigene Datenbank✅
PR27.18cXML-Schemasammlungen mit mehreren Schemata`CREATE XML SCHEMA COLLECTION` mit zwei `xsd:schema` in einem Text führt am Orakel zwei Namensräume (`sys.xml_schema_namespaces`, `sp_xml_schema_rowset` je eine Zeile), `XML_SCHEMA_NAMESPACE` gibt je Namensraum das Schema in der Normalform des Servers aus (mit `xmlns:t`); hier ein Namensraum und der Text unverändert **Erledigt 14.10.2026:** mehrere Schemata in einem Text werden geteilt (je Namensraum ausgegeben und geprüft), xmlns:t in der Ausgabe; die volle Normalform offen (PR27.18c1).✅
PR27.18c1Ausgabe eines XML-Schemas in der Normalform des ServersDas Orakel schreibt ein Schema aus seinen Bestandteilen neu: Standardnamensraum wird Präfix xsd, Typen bekommen das Präfix (type="int" → "xsd:int"), xmlns:t für das Ziel. Hier bleibt der Text, nur xmlns:t kommt dazu. ERLEDIGT 14.10.2026 (XmlSchemaNormalformTests): das Schema wird aus seinem Baum neu geschrieben - jedes Element mit xsd:, an der Wurzel xmlns:xsd, xmlns:t, dann die übrigen Angaben; Verweise (type, base, ref, itemType, memberTypes, substitutionGroup, refer) als xsd: oder t:; ohne annotation, ohne minOccurs/maxOccurs=1; complexType mit eigenem Inhaltsmodell als Einschränkung von xsd:anyType. Nicht nachbaubar: die Reihenfolge der globalen Bausteine (am Orakel weder Textfolge noch nach Art oder Name, vermutlich die interne Ablage) - sie bleibt die des Textes✅
PR27.18dRowsets: Statistik und dünne Spalten aller Sichten`sp_table_statistics2_rowset` liefert am Orakel je Statistikspalte Typ 5, Stichprobe, letzte Aktualisierung, Stufen, Tupel-, Tabellenmächtigkeit und mittlere Länge samt der Meldung `DBCC execution completed…` (hier 2812); `sp_sparse_columns_100_rowset NULL` listet auch die Spalten der Systemsichten **Erledigt 14.10.2026:** sp_table_statistics2_rowset je Statistik eine Menge mit 2528, sp_sparse_columns_100_rowset mit den Systemsichten und -tabellen aus den Referenzdaten.✅
PR27.18d1Statistik des Primärschlüssels: erst bei Bedarf, Density im KopfERLEDIGT 14.10.2026 (`StatistikBeiBedarfTests`): am Orakel gemessen bleibt die Statistik eines auf leerer Tabelle angelegten Index offen (Kopf NULL, STATS_DATE NULL, Rowset leer), bis eine Suche über seine führende Spalte sie braucht (`WHERE k = 5`, `IN`, `UPDATE … WHERE k`, `UPDATE STATISTICS`; nicht Durchlauf, ORDER BY, andere Spalte) - jetzt ebenso; die Dichte im Kopf ist Σ DISTINCT_RANGE_ROWS / Σ RANGE_ROWS (vorher 0); der Aufbau zählt nicht als user_scan✅
PR27.18d2Statistik beim Anlegen eines Index auf gefüllter TabelleAm Orakel entsteht sie mit dem Index und zeigt diesen Stand, auch wenn danach Zeilen dazukommen; hier beim ersten Zeigen. Sofort berechnet kostete `Statistikbild.Berechne` bei 200 000 Zeilen rund 0,5 s je Index (Anlegen selbst 0 ms) - erst mit schnellerer Berechnung. ERLEDIGT 14.10.2026 (StatistikBeiBedarfTests): die Statistik entsteht mit dem Index und zeigt den Stand beim Anlegen (am Orakel nach 5 weiteren Zeilen weiter 10 Zeilen, Histogramm 0/1/2). Statistikbild.Berechne ordnet je Spalte nur die verschiedenen Werte (ganze Zahlen direkt nach ihrer Zahl) und sortiert die Zeilen über Ränge: 200 000 Zeilen, Index mit angehängtem Schlüssel - UPDATE STATISTICS varchar 899 -> 136 ms, int 333 -> 128 ms; CREATE INDEX 0 -> rund 145 ms. Über json, vector, xml und Geodaten bleibt es beim Berechnen auf Verlangen✅
PR27.18eBeschreibung von Systemprozeduren ohne FormAm Orakel meldet `sys.dm_exec_describe_first_result_set` für `sp_help`, `sp_helpindex`, `sp_helpdb`, `sp_helpfile`, `sp_helpuser`, `sp_helprotect`, `sp_who2`, `sp_spaceused`, `sp_enumerrorlogs`, `sp_readerrorlog`, `sp_msisContainedAGSession`, `sp_ms_replication_installed` und `sp_enum_oledb_providers` keine Spalten, sondern einen Fehler (temporäre Tabelle, erweiterte Prozedur …); hier beschreibt Kuonen ihre erste Menge **Erledigt 13.10.2026:** die 13 Prozeduren melden ihren Fehler wie das Orakel; die Fehlerzeile trägt Schwere, Zustand und Art.✅
PR27.18e1Formauskunft: Fehler eines Weges als eigene ZeilenAm Orakel zeigt sys.dm_exec_describe_first_result_set den Fehler, in dem ein Weg endet (208, 207, 2812), als eigene Zeile (column_ordinal 0) vor 11529 bzw. 11501 (column_ordinal 1); hier steht nur die Schlusszeile. ERLEDIGT 14.10.2026 (FormauskunftWegfehlerTests): die Fehler, in denen die Wege enden (208, 207, 2812), stehen als eigene Zeile mit column_ordinal 0 vor 11529 bzw. 11501 (1) und gehen nicht mehr als Meldung hinaus; sp_describe_first_result_set meldet sie weiter. Nebenbei: CROSS APPLY über eine Systemfunktion nahm die Sonderwege der Engine nicht (sys.dm_exec_describe_first_result_set(q.t, …) lieferte nichts)✅
PR27.18e2Unbekannte Spalte einer Systemsicht beim ÜbersetzenRest aus PR27.18e1: am Orakel meldet SELECT zz_gibtsnicht FROM sys.objects beim Übersetzen 207 (Formauskunft: 207 und 11501); hier beschreibt die Formauskunft eine Spalte, weil die Vorabprüfung nur Spalten aus dem Katalog kennt. Systemsichten über ihre Vorlagen prüfen - vorher je Sicht belegen, dass die Vorlage alle Spalten nennt, sonst scheitern gültige Abfragen ERLEDIGT 14.10.2026 (SystemsichtUebersetzungTests): Spalten aus SystemViewTemplates nur in T-SQL, alle Vorlagen gegen die Laufzeitspalten geprüft; 209 bei Mehrdeutigkeit wie am Orakel✅
PR27.17cErstellungs- und Änderungszeit der ObjekteERLEDIGT 10.10.2026 (`ObjektzeitenTests`, Orakel SQL Server 2025): `create_date`/`modify_date` in sys.objects, sys.tables, sys.views (und damit `DATE_CREATED` der OLE-DB-Rowsets) sind die echten Zeiten statt fest 2026-01-01; CREATE INDEX/DROP INDEX und ALTER TABLE setzen die Änderungszeit der Tabelle, INSERT nicht; eine Bedingung ohne eigenen Eintrag trägt die Erstellungszeit ihrer Tabelle; die Zeiten stehen in der Datendatei, beim Wiedereinspielen gilt die Zeit des Protokolleintrags (weicht höchstens um die Laufzeit des Stapels ab)✅
PR27.17dRest aus PR27.17cERLEDIGT 10.10.2026 (`ObjektzeitenTests`, Orakel SQL Server 2025): `EXEC tempdb..sp_columns '#s'` meldet die Spalten der temporären Tabelle der Sitzung unter ihrem inneren Namen `#s___…` (vorher keine Zeile), ein Muster wie `'#%'` bleibt Muster; `TABLE_VERSION` von `sp_tables_info_rowset` ist die Änderungszeit in Millisekunden - am Orakel eine undurchsichtige Zahl, die sich ebenso mit jeder Änderung der Tabelle ändert (ihr Wert lässt sich von aussen nicht herleiten, die Bedeutung ist dieselbe)✅
PR27.16oT-SQL: Index in CREATE TABLEERLEDIGT 10.10.2026 (`InlineIndexTests`, Orakel SQL Server 2025; vorher 2715): `b int INDEX ix [CLUSTERED]` an der Spalte und `INDEX ix [UNIQUE] [CLUSTERED/NONCLUSTERED] (spalten) [INCLUDE] [WHERE] [WITH]` als Tabellenelement; sie entstehen nach der Tabelle über denselben Rumpf wie `CREATE INDEX` (`LiesIndexrumpf`), vorher geprüft, damit bei einem Fehler auch die Tabelle nicht entsteht (doppelter Name 8168, unbekannte Spalte 1911, zwei gruppierte 8112); ein gruppierter Index macht den Primärschlüssel ohne Angabe nicht gruppiert✅
PR27.19aKS1: PostgreSQL-Port – berechnete Spalte in `pg_attribute.attgenerated`ERLEDIGT 13.10.2026 (`PgBerechneteSpalteKatalogTests`; Soll aus der Doku von PostgreSQL 17, Referenzcontainer nicht erreichbar): nach `ALTER TABLE t ADD COLUMN doppelt numeric GENERATED ALWAYS AS (betrag * 2) STORED` ist `attgenerated = 's'` und `atthasdef = t`, `pg_attrdef` führt den Ausdruck (`pg_get_expr(adbin, adrelid)`, dieselbe Schreibweise wie `information_schema.columns.generation_expression`), `is_generated` bleibt `ALWAYS`. Nachprüfung mit `pg_dump` (Docker) → PR27.20f✅
PR27.19bKS1: MySQL-Port – `information_schema` in vorbereiteter AnweisungERLEDIGT 13.10.2026 (`MySqlVorbereiteteKatalogabfrageTests`, MySQL- und MariaDB-Port): `COM_STMT_PREPARE`/`EXECUTE` über `information_schema.COLUMNS`, `information_schema.TABLES` (auch mit Backticks und Alias) und `mysql.user` mit Platzhaltern geht am MySQL-Port wie am MariaDB-Port. Ursache: nur der MySQL-Port rät die Typen der Platzhalter aus den Spalten der genannten Tabellen (`MySqlPlatzhaltertypen`) und verlangte dafür den Katalog der Datenbank — eine eingebaute hat keinen (1105 „Ungueltiger Datenbankname“); ihr Platzhalter bleibt jetzt allgemein✅
PR27.19cKS1: MariaDB-/MySQL-Port – Zeichensatz-Einleitung vor Hex-LiteralERLEDIGT 13.10.2026 (`MariaZeichensatzEinleitungTests`, beide Ports; Soll aus dem Referenzhandbuch, Referenzcontainer nicht erreichbar → PR27.19g): `_utf8mb4 X'31322E35'` (ebenso `0x…`, jeder Zeichensatz ausser `binary`) ist die Zeichenkette `'12.5'` im genannten Zeichensatz — `CHARSET` utf8mb4, `+ 1` gibt 13.5, in `DECIMAL(10,2)` 12.50 (vorher „Out of range“ bzw. 825372214). Der Lexer dekodiert die Bytes mit `MariaZeichensaetze.Kodierung`; ein verfälschtes Hexliteral bleibt beim alten Weg (Fehler an den Client, Test auf verfälschter Eingabe)✅
PR27.19dKS1: MariaDB-/MySQL-Port – `CHAR(n USING zeichensatz)`ERLEDIGT 13.10.2026 (`MariaZeichensatzEinleitungTests`): `CHAR(n, … USING zs)` liest der Parser wie `CONVERT(CHAR(…) USING zs)`; `CONVERT(bytes USING zs)` dekodiert die Bytes im Zeichensatz (`CHAR(92 USING utf8mb4)` = `\`, `CHAR(228 USING latin1)` = `ä`, `CHAR(195, 164 USING utf8mb4)` = `ä`), statt sie als Text zu lesen. Rest → PR27.19g✅
PR27.19eKS1: MariaDB-/MySQL-Port – `CONCAT` aus Text und `CHAR()`ERLEDIGT 13.10.2026 (`MariaZeichensatzEinleitungTests`): `CHAR()` ohne `USING` und `CONCAT` damit blieben binär (wie im Handbuch); beim Speichern in eine Textspalte las der Kern die Bytes aber nach T-SQL als UTF-16 („❏敎汩尠砠“). Im Dialekt mit `BinaerumwandlungIstUtf8` (MariaDB, MySQL) werden Bytes jetzt als UTF-8 zu Text (`SqlConvert.ToCharacter`) — `O'Neil \ x` mit 10 Zeichen, `Grüezi!` mit 7✅
PR27.19fKS1: TDS – Zeilenzahl bei dreiteiligem Namen doppeltERLEDIGT 12.10.2026 (`TdsGegenstueckBefundeTests.Aenderung_ueber_dreiteiligen_Namen_meldet_die_Zeilenzahl_einmal`, Sollwert aus dem Orakel-Befund des Nachbarprojekts: ein DONE mit DONE_COUNT 1): eine Anweisung mit dreiteiligem Namen (UPDATE, INSERT, DELETE, auch DDL) lief nach dem Wechsel in ihre Datenbank noch einmal durch den Rahmen und hinterliess ihr DONE doppelt (`DONE 0x11 rows=1`, dann `DONE 0x10 rows=1`, SqlClient las 2); die Umleitung führt sie jetzt ohne eigenen Rahmen aus✅
PR27.19gRest aus PR27.19c–eAm Referenzcontainer gemessen 13.10.2026 (mariadb:11.8.3, mysql:8.4.11). Erledigt: ein Wert aus `CONVERT(… USING zs)`, `CHAR(… USING zs)` und `_zs X'…'` trägt seinen Zeichensatz (die Standardsortierung am Typ) - `CHARSET()` ist `latin1`, `COLLATION()` `latin1_swedish_ci`, `HEX`/`LENGTH` zählen seine Bytes in diesem Zeichensatz (`E4`, 1); ungültige Bytes hinter einem Einleiter sind Fehler 1300 (`Invalid utf8mb4 character string: 'FF41'`), bei `CHAR … USING` NULL mit Warnung 1300, bei `CONVERT … USING` unter MariaDB `?` mit Warnung (`'\xFF'`), unter MySQL NULL. Test `MariaZeichensatzWertTests`; Rest PR27.19h✅
PR27.19hRest aus PR27.19gERLEDIGT 14.10.2026 (`MariaSpaltenzeichensatzTests`, mysql:8.4.11 gemessen): der Wert einer Textspalte trägt ihren Zeichensatz (`CHARSET(a)` latin1, `HEX(a)` E4, `COLLATION(c)` utf8mb4_bin) über die Marke `mz:` in Tabellen mit abweichender Sortierung; Funktionen geben ihn weiter (Spalte vor Literal, bei gleichem Rang utf8mb4: `CONCAT(a, 'x')` latin1, `CONCAT(a, d)` utf8mb4); `_latin1'x'` trägt latin1; Warnung 1300 von `CHAR … USING` am MySQL-Port nachgemessen (gleich). Rest → PR27.19i✅
PR27.19iRest aus PR27.19h`_binary'x'` ist am Orakel ein Binärwert (`CHARSET` binary), KuonenSQL liest gültiges UTF-8 dahinter als Text (nur ungültige Bytes werden 0x…, MY30.7). Vorsicht: Connector/J und Dumps schicken BLOBs als `_binary'…'` - das Einfügen in Text- und Binärspalten muss gleich bleiben ERLEDIGT 14.10.2026 (MariaBinaerliteralTests): _binary'…' immer als Hexliteral (CHARSET/COLLATION binary, Vergleich Byte für Byte), CHAR_LENGTH zählt Bytes, UPPER/LOWER lassen Binärwerte, gegen eine Textspalte gilt deren Sortierung (Rang Spalte vor Literal); Einfügen in Text- und Binärspalten unverändert (an mysql:8.4 und mariadb:11.8 gemessen)✅
PR27.20aZugänge zur Laufzeit: `sp_kuonensql_zugang` (Port ändern, ein- und ausschalten)ERLEDIGT 13.10.2026 (`ZugaengeTests`), Vertrag (1)–(3) wie verlangt: `EXEC sp_kuonensql_zugang [@dialekt]` liefert je Zugang (`mssql`, `mariadb`, `mysql`, `postgresql`, `redis`, `graphql`) `dialekt`, `port`, `port_laufend`, `aktiv`, `laeuft`, `tls`, `tls_pflicht`, `adresse`, `neustart_noetig`; mit `@port`/`@aktiv` dauerhaft eingestellt (`Servereinstellungen`, Feld `zugaenge`, übersteht Kontrollpunkt und Abspielen; geht den Startargumenten vor) und sofort wirksam: erst geht der neue Hörer auf, dann der alte zu, bestehende Verbindungen bleiben. Port ausserhalb 1–65535 → 15129, belegter Port → 15600 mit Port und Ursache (der alte Hörer bleibt), unbekannter Dialekt → 15123, ohne sysadmin → 15247, `mssql` abschalten → 15600, sein Port gilt nach dem Neustart (`neustart_noetig = 1`). Im Katalog wie PR27.20c. Der Host (`Program.cs`) meldet alle Hörer bei der Zugangsverwaltung des Kerns an (`Protocol/Zugaenge.cs`). Rest (4) → PR27.20e✅
PR27.20bKS1: Port in der Gesamtsicht und in `sys.dm_tcp_listener_states`ERLEDIGT 13.10.2026 (`ZugaengeTests.Gesamtsicht_und_Hoerersicht_zeigen_die_laufenden_Ports_aller_Zugaenge`): in `sys.kuonensql_einstellungen` (und `kuonensql.einstellungen`) ist `wert` von `mariadb.port`, `mysql.port`, `postgresql.port` der Port des laufenden Hörers (ohne Hörer 0 wie `@@port`), `vorgabe` bleibt 3306/5432; bei PostgreSQL ebenso `ssl`, `listen_addresses`, `data_directory` (dieselben Serverwerte wie `SHOW`). `sys.dm_tcp_listener_states` zeigt neben TSQL jeden offenen Hörer der anderen Zugänge je IPv6/IPv4 mit `type` 101–105 und `type_desc` `MARIADB`, `MYSQL`, `POSTGRESQL`, `REDIS`, `GRAPHQL` (aus der Zugangsverwaltung von PR27.20a, ändert sich mit `sp_kuonensql_zugang` sofort)✅
PR27.20cKS1: eigene Systemprozeduren im KatalogERLEDIGT 12.10.2026 (`EigeneSystemprozedurenTests`): `sp_kuonensql_einstellung`, `_vorbereiten`, `_abschliessen`, `_befoerdern` stehen in `sys.system_objects`/`sys.all_objects` (Schema sys, `type = 'P'`, `SQL_STORED_PROCEDURE`, `is_ms_shipped = 1`, Kennung unter der kleinsten von SQL Server), auch aus Benutzerdatenbanken; `OBJECT_ID('sys.…')`, `OBJECT_ID('…')`, `OBJECT_ID('master.sys.…')` und `OBJECT_NAME` kennen sie; die Parameter stehen in `sys.system_parameters`/`sys.all_parameters`. Eine neue eigene Systemprozedur kommt in `Catalog/Eigensystemprozeduren.cs` dazu✅
PR27.20dKS1: PostgreSQL-Port – `information_schema.routines`/`parameters` in PostgreSQL-SichtERLEDIGT 13.10.2026 (`PgRoutinenInformationSchemaTests`; Spalten und Bedeutung aus der Doku von PostgreSQL 17, 35.44/35.33 — 82 bzw. 32 Spalten): der Übersetzer schreibt beide Sichten auf `pg_is_routines`/`pg_is_parameters` um; die Zeilen kommen aus derselben Quelle wie `pg_proc` (`Pgnutzerzeilen.Routinen`). Nach `CREATE FUNCTION public.f(a integer, b text) RETURNS integer LANGUAGE sql AS 'SELECT a'`: `routine_schema` public, `data_type` integer, `type_udt_name` int4, `routine_body` SQL, `specific_name` `f_<oid>`; Parameter `a integer int4` und `b text text` an Stelle 1 und 2, `parameter_mode` IN, kein Eintrag für den Rückgabewert; eine Prozedur ohne Rückgabetyp, `external_language` PLPGSQL. Rest → PR27.20f✅
PR27.20eRest aus PR27.20aErledigt 13.10.2026: `CALL kuonensql.zugang(…)` am MySQL- und MariaDB-Port und `SELECT kuonensql.zugang(…)` bzw. `SELECT * FROM kuonensql.zugang(…)` am PostgreSQL-Port (Konstanten nach Stelle oder benannt `dialekt => 'redis'`) rufen `sp_kuonensql_zugang` - ebenso die übrigen eigenen Systemprozeduren (`kuonensql.einstellung`, `vorbereiten`, `abschliessen`, `befoerdern`). Beim Portwechsel des GraphQL-Endpunkts bleiben offene Verbindungen bis zu ihrem Ende (sie enden mit dem Server, nicht mit dem Hörer). Test `ZugangGegenstueckeTests`, `GqlPortwechselTests`✅
PR27.20fRest aus PR27.19a und PR27.20dAm Referenzcontainer gemessen 13.10.2026 (postgres:17.11): `pg_get_expr` des Ausdrucks einer berechneten Spalte (`(betrag * (2)::numeric)`), `information_schema.columns.generation_expression` und `pg_dump` einer Tabelle mit berechneter Spalte stimmen schon überein (pg_dump aus dem Container gegen KuonenSQL: bis auf das Token von `\restrict` und den Eigentümer gleich). Erledigt: `parameters.parameter_default` und `pg_proc.proargdefaults` tragen die Vorgabe in den Typ des Parameters umgewandelt (`5`, `'q'::text`, `'v'::character varying`), `pg_get_function_arguments`/`_result` nennen Typen ohne Länge; die Spalten von `RETURNS TABLE` sind OUT-Parameter (`parameters`, `proargnames {x,p,q}`, `proargmodes {i,t,t}`, `proallargtypes`), `proargtypes`/`pronargs` nennen nur die Eingaben. Test `PgRoutinenInformationSchemaTests`; Rest PR27.20g✅
PR27.20gRest aus PR27.20fERLEDIGT 14.10.2026 (`PgSystemroutinenTests`, postgres:17.11 gemessen): `information_schema.routines`/`parameters` nennen die 3319 Funktionen von `pg_catalog` (6643 Parameter) aus der Ausgabe des Servers (`pg_routinen.json.gz`), `pg_proc` führt alle 3319; eine PL/pgSQL-Funktion mit INOUT/OUT und `RETURNS record` wird angelegt (vorher 0A000), `pg_proc`/`parameters` mit Art und Folge wie PostgreSQL, ein falscher Rückgabetyp ist 42P13✅
PR27.21aT-SQL-Port – Ausführungsplan: `SET SHOWPLAN_XML` und `SET STATISTICS XML`Befund aus Kuonen.MultiSQLManager (12.10.2026): `SET SHOWPLAN_XML ON` und `SET STATISTICS XML ON` wurden angenommen, ohne Wirkung. Erledigt 13.10.2026 (`ExecutionEngine.Showplan`): unter `SHOWPLAN_XML` läuft der Stapel nicht, er liefert je Stapel eine Menge „Microsoft SQL Server 2005 XML Showplan“ (ShowPlanXML mit `StmtSimple`, `QueryPlan`, `RelOp` samt PhysicalOp/LogicalOp/EstimateRows/EstimateIO/EstimateCPU/EstimatedTotalSubtreeCost, `OutputList`, `Object`, `Predicate`, `SeekPredicates`); `SHOWPLAN_TEXT` und `SHOWPLAN_ALL` als Zeilen; `STATISTICS XML` führt aus und hängt je Anweisung den Plan mit `RunTimeCountersPerThread` (ActualRows, ActualRowsRead, ActualExecutions) und `QueryTimeStats` an; `SET SHOWPLAN_*` allein im Stapel (sonst 1067); `sys.dm_exec_query_plan` liefert den Plan des Texts hinter der Kennung. Die Zugriffe sind die der Engine (`PlaneZugriff`): `Table Scan`, `Clustered Index Scan/Seek`, `Index Seek` mit `Key Lookup`/`RID Lookup`, Verbund (`Hash Match`/`Nested Loops`), `Filter`, Gruppierung, `Sort`, `Top`, `… Insert/Update/Delete`; die Zeilen gezählt, die Kosten nach dem Kostenmodell von SQL Server gerechnet. Test `ShowplanTests`; Rest PR27.21e✅
PR27.21eRest aus PR27.21aoffen: am Orakel (SQL Server 2025) nachmessen - das Orakel war von diesem Rechner aus nicht erreichbar: die genaue Form des XML (Attribute und ihre Reihenfolge, `QueryHash`/`QueryPlanHash`, `MemoryGrantInfo`, `OptimizerHardwareDependentProperties`, `ScalarOperator` mit seinem Ausdrucksbaum statt nur `ScalarString`), die Kosten und Schätzungen des Orakels, `NodeId`/`Parent` in `SHOWPLAN_ALL`, die Zeilen von `SHOWPLAN_TEXT` und `STATISTICS PROFILE`; Compute Scalar für berechnete Spalten, Pläne für Systemsichten, Unterabfragen, `UNION`, Prozeduren und `SELECT … INTO` (stehen ohne `QueryPlan` da); ein Plan je Knoten mit eigener Zeit (die Engine misst nur die ganze Anweisung) ERLEDIGT 14.10.2026 (ShowplanFormTests), am Orakel mit SET SHOWPLAN_XML ON gemessen (neu: dist\orakel.ps1 -Lang gibt lange Werte ungekürzt aus): Wurzel nur mit xmlns, Version 1.599 und Build; StmtSimple, StatementSetOptions (nach der Sitzung) und RelOp in der Attributfolge des Servers mit SecurityPolicyApplied, QueryHash/QueryPlanHash (eigener FNV-Streuwert, beständig), CardinalityEstimationModelVersion nach dem Kompatibilitätsgrad, TableCardinality; Wahrheitswerte der Operatoren als 0/1; jedes Element mit End-Tag; MemoryGrantInfo, OptimizerHardwareDependentProperties (Werte des Rechners), OptimizerStatsUsage (die genutzte Statistik, beim Übersetzen aufgebaut wie am Orakel); eine Gleichheitssuche als Prefix ScanType=EQ mit dem Wert als Const (vorher StartRange GE, auch bei Eindeutig). Übrige Teile Rest PR27.21g✅
PR27.21gRest aus PR27.21eoffen: einfache Parametrisierung wie am Orakel (StatementText bleibt, die Konstante wird @1 mit CONVERT_IMPLICIT, ParameterList mit ParameterCompiledValue); ScalarOperator mit seinem Ausdrucksbaum (Compare, Identifier, Convert, Arithmetic …) statt nur ScalarString; Änderungszähler der Statistik (ModificationCount); Kosten und Schätzungen des Orakels; NodeId/Parent in SHOWPLAN_ALL, die Zeilen von SHOWPLAN_TEXT und STATISTICS PROFILE; Compute Scalar, Pläne für Systemsichten, Unterabfragen, UNION, Prozeduren und SELECT … INTO; Zeit je Knoten im tatsächlichen Plan ERLEDIGT 14.10.2026 (ShowplanParameterTests): einfache Parametrisierung (ParameterizedText, Typen der Parameter, ParameterList in der Folge des Servers) und der Ausdrucksbaum Compare/Logical/Arithmetic/Identifier/Const/Convert mit ConstExpr, StartRange/EndRange - in 55 Anweisungen gleich wie am Orakel. Übrige Teile Rest PR27.21j✅
PR27.21jRest aus PR27.21goffen: Bäume weiterer Operatoren wie am Orakel (IN als Mehrfachsuche mit je einem Prefix, Compute Scalar für Rechnungen der Auswahl und SET von UPDATE mit Expr1002, Aggregate mit ANY/Count(*), Funktionen, Verbund mit Alias im ScalarString '… as [p].[a]', = NULL zusammengefaltet); Änderungszähler der Statistik (ModificationCount) und automatisch angelegte Spaltenstatistiken (_WA_Sys_…) in OptimizerStatsUsage; Kosten und Schätzungen des Orakels (a > 15 schätzt 1 Zeile); CachedPlanSize/CompileMemory; NodeId/Parent in SHOWPLAN_ALL, die Zeilen von SHOWPLAN_TEXT und STATISTICS PROFILE; Pläne für Systemsichten, Unterabfragen, UNION, Prozeduren und SELECT … INTO; Zeit je Knoten im tatsächlichen Plan **Erledigt 21.10.2026 (Teil, Rest PR27.21k)** (`ShowplanParameterTests`, am Orakel gemessen): IN-Liste über die Suchspalte als Mehrfachsuche (je Wert ein Prefix EQ, aufsteigend, ohne Doppelte); Vergleich mit NULL unter ANSI_NULLS zum Constant Scan gefaltet (aus einem ODER fällt er weg); Zahlen mit kleinem e, kein ForceSeek am Scan; AvgRowSize nach der gemessenen Regel.✅
PR27.21kRest aus PR27.21joffen: Compute Scalar für Rechnungen der Auswahl (`a + 1` als Expr1002, der Scan liefert dann nur `a`) und für SET von UPDATE; COUNT(*) als Stream Aggregate mit `Count(*)`/`countstar` (Expr1003) unter einem Compute Scalar mit CONVERT_IMPLICIT(int); Aggregate mit ANY; Funktionen; die OutputList eines Scans nur mit den gebrauchten Spalten (heute alle bei Rechnungen und Aggregaten); Verbund wie am Orakel (Nested Loops mit Suche über den Schlüssel, OuterReferences, Alias im ScalarString `… as [p].[a]`, statt Hash Match und Filter); IN-Liste über einen Nebenindex (heute Scan statt Index Seek); Parametrisierung neben einem gefalteten NULL-Vergleich (`b = NULL AND id = 1`: am Orakel ParameterList @1); Schätzung einer leeren Suche (am Orakel 1 Zeile, KuonenSQL 0); ModificationCount und `_WA_Sys_…`; Kosten und Schätzungen (a > 15 schätzt 1 Zeile); CachedPlanSize/CompileMemory; NodeId/Parent in SHOWPLAN_ALL, SHOWPLAN_TEXT, STATISTICS PROFILE; Pläne für Systemsichten, Unterabfragen, UNION, Prozeduren und SELECT … INTO; Zeit je Knoten im tatsächlichen Plan; Falten mit Gruppierung, DISTINCT, TOP⬜
PR27.21bMySQL-/MariaDB-Port – tatsächlicher Plan: `ANALYZE FORMAT=JSON` und `EXPLAIN ANALYZE`Befund aus Kuonen.MultiSQLManager (12.10.2026): `ANALYZE FORMAT=JSON SELECT …` (MariaDB) und `EXPLAIN ANALYZE SELECT …` (MySQL) scheiterten mit 1064. Erledigt 13.10.2026 für ein `SELECT` über eine Tabelle (`MariaDbAnalyse`): die Abfrage läuft, ihre Zeilen werden verworfen; MariaDB `ANALYZE SELECT` ist die Tabelle von `EXPLAIN EXTENDED` mit `r_rows`/`r_filtered`, `ANALYZE FORMAT=JSON` das JSON mit `query_optimization`, `r_loops`, `r_total_time_ms`, je Tabelle `r_rows`, `r_table_time_ms`, `r_other_time_ms`, `r_filtered`; MySQL `EXPLAIN ANALYZE` der Baum (Zugriff, `Filter`, `Sort`, `Limit`) mit `(actual time=a..b rows=n loops=1)`. Verbunde, Unterabfragen, Gruppierung (MySQL) und Änderungen sind 1105. Test `MariaDbAnalyseTests`; Rest PR27.21d✅
PR27.21dRest aus PR27.21bAm Referenzcontainer gemessen 13.10.2026 (mariadb:11.8.3, mysql:8.4.11). Erledigt: `ANALYZE` als Tabelle stimmte schon; `ANALYZE FORMAT=JSON` mit den Feldern von 11.8 in deren Folge (`r_engine_stats.pages_accessed`, `r_total_filtered`, `r_index_rows`, `r_icp_filtered`, `r_filtered` am Ende, bei `filesort` `r_loops`, `r_total_time_ms`, `r_limit`, `r_used_priority_queue`, `r_output_rows`, `r_sort_mode`, bei `read_sorted_file` `r_rows`); `EXPLAIN ANALYZE` nennt die Zugriffe wie MySQL (`Covering index scan`, `Index range scan … over (3 < a)`, `Index lookup … (a=2)`, `Rows fetched before execution`) mit den Kosten und Schätzungen von MySQL; `EXPLAIN FORMAT=JSON` am MySQL-Port in der Form von MySQL (`cost_info`, `rows_examined_per_scan`, `rows_produced_per_join`, `filtered`, `used_columns`, `` `db`.`t`.`b` ``, `ordering_operation`). Für die gemessenen Fälle bis auf die Zeiten gleich. Test `MariaDbAnalyseTests`; Rest PR27.21f✅
PR27.21fRest aus PR27.21dERLEDIGT 14.10.2026 (`MySqlPlanVerbundTests`, mysql:8.4.11 nach ANALYZE TABLE gemessen): am MySQL-Port `EXPLAIN FORMAT=JSON` und `EXPLAIN ANALYZE` für den inneren Verbund zweier Tabellen (`ref`/`eq_ref` über einen Index, sonst Hash-Verbund; die billigere Reihenfolge wie MySQL) und für Gruppierung (über einen Index `Group aggregate`, sonst temporäre Tabelle), Kosten und Schätzungen nach den gemessenen Regeln, tatsächliche Zeilen und Durchgänge gezählt; Satzlänge statt 8 je Zahl; abgeschnitten statt gerundet. Nicht nachbildbar (Statistik von InnoDB): zwei Kosten im Baum (Nachschlagen je Durchgang, Sondierseite des Hash-Verbunds); die Zeit je Knoten bleibt die der Abfrage. Rest → PR27.21h, PR27.21i✅
PR27.21hRest aus PR27.21f (MariaDB)`ANALYZE [FORMAT=JSON]` für Verbunde, Unterabfragen, Gruppierung und `ANALYZE UPDATE/DELETE` (r_rows/r_loops je Knoten) am Port von MariaDB; `pages_accessed` geschätzt. Braucht Messungen an kq-ref-mariadb - dafür muss `KQ_REF_KENNWORT` gesetzt sein (14.10.2026 nicht gesetzt) **Erledigt 21.10.2026 (Teil, Rest PR27.21l)** (`MariaDbAnalyseTests`, an eigenem mariadb:11.8.9 gemessen): Tabellenform von `ANALYZE` für den inneren Verbund zweier Tabellen (r_rows je Durchgang, r_filtered, ohne Durchgang NULL) und `ANALYZE UPDATE/DELETE` über eine Tabelle; Schätzung eines Indexzugriffs mindestens 1 Zeile.✅
PR27.21lRest aus PR27.21h (MariaDB)offen: `ANALYZE FORMAT=JSON` für Verbunde und Änderungen (r_loops/r_rows je Knoten, `pages_accessed` geschätzt); mehr als zwei Tabellen, äussere Verbunde, Verbund mit Durchlauf/Join-Puffer an der zweiten Tabelle; Unterabfragen (gemessen: `id IN (SELECT a_id FROM b1 WHERE y > 6)` als Halbverbund `ref … 1.00 … 60.00 Using where; FirstMatch(a1)`); ANALYZE mit Gruppierung über einen Verbund; `EXPLAIN DELETE … WHERE` ohne Index (gemessen 11.8.9: `ALL`, KuonenSQL `index PRIMARY` nach J25) und `DELETE … ORDER BY id` ohne LIMIT (`ALL`, `Using filesort`)⬜
PR27.21iRest aus PR27.21f (MySQL)am MySQL-Port: mehr als zwei Tabellen, äussere Verbunde, Halbverbund (`IN (SELECT …)`: Nested loop semijoin gemessen), Bedingung auf der zweiten Tabelle, Bereich/Nachschlagen aus der eigenen Bedingung im Verbund, Sortierung/Grenze über Verbund und Gruppe; `read_cost` eines Bereichs (gemessen 0,71 bei 2 Zeilen, 0,56 bei 3, 2,01 bei 5 über einen Nebenindex) **Erledigt 21.10.2026 (Teil, Rest PR27.21m)** (`MySqlPlanVerbundTests`, an eigenem mysql:8.4 gemessen): die Verbundspalte der ersten Tabelle, die NULL sein kann, steht in deren Filter (`… and (t.a is not null)`); NULL zählt beim Nachschlagen als eigener Wert (7/6 = 1.17 je Schlüssel).✅
PR27.21mRest aus PR27.21i (MySQL)offen: mehr als zwei Tabellen, äussere Verbunde, Halbverbund (`IN (SELECT …)`: Nested loop semijoin), Bedingung auf der zweiten Tabelle (gemessen: `Filter: (a1.x < 40)` über `Single-row index lookup`, MySQL wählt dabei b1 zuerst), Bereich/Nachschlagen aus der eigenen Bedingung im Verbund, Sortierung/Grenze über Verbund und Gruppe; `EXPLAIN FORMAT=TREE` ohne ANALYZE (heute die Tabellenform); `read_cost` eines Bereichs (0,71 bei 2 Zeilen, 0,56 bei 3, 2,01 bei 5 über einen Nebenindex); die Kosten des Nachschlagens je Durchgang (0,265 bis 0,315, InnoDB)⬜
PR27.21cKS1: PostgreSQL-Port – `Describe` einer einzelnen `EXPLAIN`-AnweisungBefund aus Kuonen.MultiSQLManager (12.10.2026): `Describe` von `EXPLAIN (FORMAT JSON) SELECT 1` kam als `NoData`, `Execute` schickte doch `DataRow` („DataRow ohne RowDescription“). Erledigt 13.10.2026: `Describe` (Anweisung und Portal) meldet wie PostgreSQL 17 die eine Spalte „QUERY PLAN“, `json` bei `FORMAT JSON`, `xml` bei `FORMAT XML`, sonst `text` (`Pgerklaerung.Spalte`, ohne zu planen). Test `PgErweitertTests.Describe_einer_EXPLAIN_Anweisung_meldet_die_Spalte_QUERY_PLAN`✅
PR27.22aKS1: PostgreSQL-Port – `pg_stat_activity` für einen ProfilerBefund aus Kuonen.MultiSQLManager (Profiler mit Stichproben, 12.10.2026): `query_start`/`state_change` kamen als `text` in T-SQL-Schreibweise, `query` war leer. Erledigt 13.10.2026: `pg_stat_activity` entsteht aus den Sitzungen (`Pgsitzungssicht`); `backend_start`/`xact_start`/`query_start`/`state_change` sind `timestamp with time zone`, `query` ist der Text des Clients (einfaches und erweitertes Protokoll, an einer Zeichengrenze auf 1023 Bytes gekürzt wie bei `track_activity_query_size` = 1kB), `state` `active`/`idle`/`idle in transaction`/`idle in transaction (aborted)`, eine ruhende Sitzung wartet auf `Client`/`ClientRead`, dazu `client_addr`/`client_port` und `backend_type = 'client backend'`. `clock_timestamp()` war schon `timestamptz`; `pg_stat_statements` gibt es seit MY30.7o, wie in PostgreSQL erst nach `CREATE EXTENSION pg_stat_statements`. Test `PgStatActivityTests`✅
PR27.22bKS1: MySQL-/MariaDB-Port – Prozessliste und `performance_schema` für einen ProfilerBefund aus Kuonen.MultiSQLManager (12.10.2026): `TIME` einer ruhenden Sitzung zählte ab dem Beginn der letzten Anweisung, `INFO` zeigte die Übersetzung, am MySQL-Port fehlten `events_statements_current`/`_history` und `setup_consumers`. Erledigt 13.10.2026: die Sitzung trägt die Anweisung im Text des Clients (`Clientanweisung`, wie PR27.22a) und einen Verlauf der letzten zehn (`Anweisungsverlauf`); `PROCESSLIST` (beide Ports) zählt `TIME` im jetzigen Zustand und zeigt in `INFO` den Text des Clients; am MySQL-Port `performance_schema.events_statements_current`, `_history` (zehn je Faden), `_history_long` (leer) und `setup_consumers` (Vorgaben von MySQL 8.4) mit `THREAD_ID` = `PROCESSLIST_ID`, `EVENT_ID`, `END_EVENT_ID`, `SQL_TEXT`, `TIMER_START`/`TIMER_END`/`TIMER_WAIT` in Pikosekunden, `ROWS_SENT`/`ROWS_AFFECTED`, `ERRORS`/`MYSQL_ERRNO`/`MESSAGE_TEXT`, `CURRENT_SCHEMA`. Test `MySqlProfilerTests`; Rest PR27.22e✅
PR27.22eRest aus PR27.22bAm Referenzcontainer gemessen 13.10.2026 (mysql:8.4.11, mariadb:11.8.3). Erledigt: eine ruhende Verbindung hat in `PROCESSLIST.STATE` einen leeren Zustand (beide Orakel); im Verlauf meldet eine Anweisung mit OK `RETURNED_SQLSTATE` `00000`, eine Ergebnismenge NULL; eine Anweisung mit Tabelle belegt eine Ereignisnummer mehr (`END_EVENT_ID = EVENT_ID + 1`) und hat eine Sperrzeit; `STATEMENT_ID` ist gesetzt; `USE` heisst `statement/sql/change_db`, `CREATE/DROP DATABASE` `create_db`/`drop_db`, `SHOW x` `show_x`; ohne gewählte Datenbank ist `CURRENT_SCHEMA` NULL. Am MariaDB-Port gibt es `events_statements_current`/`_history`/`_history_long`, `setup_consumers` und `threads` mit den Spalten von 11.8, leer wie dort bei `performance_schema = OFF`. Test `MySqlProfilerTests`; Rest PR27.22f✅
PR27.22fRest aus PR27.22eERLEDIGT 14.10.2026 (`MySqlProfilerTests`): an beiden Messcontainern gemessen - ein laufendes SLEEP steht als `User sleep` in der Prozessliste (MySQL und MariaDB), `COM_INIT_DB` ist `statement/com/Init DB` ohne Text mit 00000; gemessen und als Rest PR27.22f1: Mehrfachtext je Anweisung ein Ereignis, `Waiting for table metadata lock`/`executing`, Zeitgeber, COM_STMT_EXECUTE, MariaDB mit performance_schema = ON✅
PR27.22f1performance_schema: Mehrfachtext, Sperrzustand, ZeitgeberAm Orakel gemessen: ein Text mit mehreren Anweisungen ist ein Ereignis je Anweisung mit eigenem SQL_TEXT (braucht eine Rückmeldung je Anweisung aus der Ausführung); beim Warten auf eine Tabellensperre `Waiting for table metadata lock`, eine gewöhnliche laufende Anweisung am MySQL-Port `executing`; offen: Zeitgeber ab Prozessstart, EVENT_NAME von COM_STMT_EXECUTE, Verlauf am MariaDB-Port bei performance_schema = ON **Erledigt 14.10.2026** (`MySqlProfilerTests`): Mehrfachtext je Anweisung ein Ereignis, COM_STMT_PREPARE/EXECUTE/RESET/CLOSE und COM_PING als `statement/com/…`, `updating`/`executing` in der Prozessliste, Zeitgeber ab Prozessstart wie Uptime. Rest → PR27.22f2 (MariaDB), PR27.22f3 (Metadatensperre)✅
PR27.22f2MariaDB: performance_schema = ONAn eigenem mariadb:11.8.9 mit --performance-schema=ON gemessen: Startoption `performance_schema` (Vorgabe OFF) im Kern einstellbar; `setup_consumers` mit den 15 Verbrauchern von MariaDB (YES nur global_instrumentation, thread_instrumentation, statements_digest), per UPDATE schaltbar (ENABLED ENUM YES/NO, sonst 1265); der Verlauf am MariaDB-Port bei eingeschalteten Verbrauchern, `SQL_TEXT` eines Mehrfachtexts ist der Rest ab der Anweisung, END_EVENT_ID = EVENT_ID **Erledigt 21.10.2026** (`MariaLeistungsschemaTests`): `mariadb.performance_schema` ist eine Startoption (am Zugang nur lesbar, im Kern mit `sp_kuonensql_einstellung` einstellbar, gilt ab dem nächsten Start); bei ON `setup_consumers` mit den 15 Verbrauchern und den gemessenen Vorgaben, `UPDATE … SET ENABLED` (YES/NO, sonst 1265); `events_statements_current`/`_history` aus dem Anweisungsverlauf, soweit die Verbraucher wirken und seit sie an sind, Mehrfachtext als Rest ab der Anweisung, END_EVENT_ID = EVENT_ID. Rest → PR27.22f4.✅
PR27.22f3Metadatensperre in der Prozessliste`Waiting for table metadata lock` (am Orakel: SELECT wartet auf LOCK TABLES … WRITE, ALTER TABLE wartet auf eine offene Transaktion) - braucht im Kern eine gehaltene Tabellensperre (LOCK TABLES) und DDL, das auf offene Transaktionen wartet; `statistics` für ein wartendes SELECT … FOR UPDATE über den Primärschlüssel **Erledigt 21.10.2026** (`MySqlProfilerTests`): DDL wartet im Kern schon auf offene Transaktionen (K33.20a) und jede Anweisung auf ein laufendes DDL (PR27.18j); am MySQL-Port steht ein solches Warten jetzt als `Waiting for table metadata lock` da, ein SELECT, das seine erste Tabelle über einen eindeutigen Schlüssel mit festen Werten liest (`const`), als `statistics`. Nachgemessen 21.10.2026 an mysql:8.4 und mariadb:11.8.9: über eine andere Spalte `executing`; am MariaDB-Port dieselben Zustände (`Statistics`, `Sending data`, `Updating`). Rest → PR27.22f5.✅
PR27.22f4Rest aus PR27.22f2: MariaDB performance_schema = ONoffen: bei ON `threads` (heute leer wie bei OFF; gemessen 13 Zeilen), `events_statements_history_long`, die Tabellen der Stufen, Wartezustände und Transaktionen; `LOCK_TIME` bei Anweisungen mit Tabelle und `DIGEST`/`DIGEST_TEXT` wie am Referenzcontainer (21.10.2026 gemessen: gesetzt; `SOURCE` leer und die Zählung ohne Lücken sind übernommen); `WHERE` von `UPDATE setup_consumers` über NAME =/LIKE/IN hinaus und `setup_instruments`. Messen an eigenem mariadb:11.8.9 mit `--performance-schema=ON`.⬜
PR27.22f5Rest aus PR27.22f3: LOCK TABLES mit Wirkungoffen: `LOCK TABLES … READ/WRITE` am MySQL- und MariaDB-Port ist heute ein OK ohne Sperre (D17.12). Gemessen ist: ein SELECT einer anderen Sitzung wartet auf `LOCK TABLES … WRITE` mit `Waiting for table metadata lock`. Braucht eine Sperre, die über Anweisungen und ohne Transaktion bis `UNLOCK TABLES` bzw. zum Ende der Verbindung hält (eigener Besitzer in der Sperrverwaltung, der die eigene Sitzung nicht aufhält), das implizite COMMIT von LOCK TABLES, 1100/1099 für nicht gesperrte bzw. nur lesend gesperrte Tabellen und die Wirkung auf mariadb-dump (`READ LOCAL`). Am MariaDB-Port gemessen (21.10.2026): hinter `LOCK TABLES … WRITE` warten SELECT und INSERT mit `Waiting for table metadata lock`.⬜
PR27.22cKS1: T-SQL-Port – Prädikate von EreignissitzungenBefund aus Kuonen.MultiSQLManager (XEvent-Profiler, 12.10.2026): `… ADD EVENT sqlserver.sql_batch_completed(ACTION(…) WHERE ([sqlserver].[session_id] <> (n)))` wurde angenommen, das Prädikat aber nicht ausgewertet. Erledigt 13.10.2026: die Bedingung wird beim Anlegen einmal gelesen (`Ereignispraedikat`: Operatoren `= <> != < > <= >=`, Vergleicher `package0.equal_*`/`not_equal_*`/`greater_than_*`/`less_than_*`, `sqlserver.like_i_sql_unicode_string`, AND/OR/NOT, Klammern) und je Ereignis gegen seine Felder geprüft; ein Ereignis, dessen Bedingung nicht gilt, wird nicht ausgelöst. Eine Quelle, die KuonenSQL nicht meldet, oder eine unlesbare Bedingung filtert nichts weg. `sys.server_event_session_events.predicate` trug den Text schon. Test `EreignispraedikatTests`; Rest PR27.22d✅
PR27.22dRest aus PR27.22cERLEDIGT 14.10.2026 (`FehlerereignisTests`): `sqlserver.error_reported` für jeden Fehler und jede Meldung ausser PRINT (RAISERROR 0/10/16, gefangene mit `is_intercepted`), Felder und Abbildungen wie am Orakel (category 2 SERVER, destination 0x00000002 USER); die mitgelieferten Sitzungen system_health/telemetry_xevents tragen ihre Bedingungen des Orakels (sonst zeichneten sie jeden Fehler auf); `predicate` in der Normalform des Servers (`([severity]>(10))`, Baumklammern). Rest PR27.22d1✅
PR27.22d1error_reported: 5701/5703 aus dem TDS-Weg, predicate_xml5701 eines USE im Stapel und 5701/5703 bei der Anmeldung schreibt der TDS-Weg selbst - am Orakel lösen sie error_reported aus (bei der Anmeldung ohne sql_text); `sys.server_event_session_events.predicate_xml`; ein Fehler bei einer Bedingung über ein unbekanntes Feld (KuonenSQL nimmt sie an) ERLEDIGT 14.10.2026 (FehlerereignisAnmeldungTests): USE löst error_reported 5701 (Schwere 10, Zustand 1, mit sql_text) aus, eine Anmeldung 5701 (Zustand 2) und 5703 (Zustand 1) ohne sql_text - am Orakel gemessen. predicate_xml als Baum wie am Orakel (and/or links verschachtelt, not, leaf mit comparator nach Operator und Feldtyp: _int64, _uint64, _boolean, _float64, Text _i_sql_unicode_string aus sqlserver; event oder global als Quelle; Text in CDATA, Wahrheitswert true/false). Eine Bedingung über ein unbekanntes Feld oder eine unbekannte Quelle meldet 25706 Zustand 8, bevor die Sitzung entsteht. Offen bleibt: KuonenSQL schickt bei der Anmeldung die Meldungen 5701/5703 nicht an den Client (am Orakel INFO-Token in der Anmeldeantwort)✅
PR27.22d2Anmeldeantwort mit 5701 und 5703Rest aus PR27.22d1: am Orakel enthält die Antwort auf LOGIN7 die INFO-Token 5701 (Changed database context to 'master'.) und 5703 (Changed language setting to us_english.); KuonenSQL schickt nur ENVCHANGE und LOGINACK. Am Draht messen (Reihenfolge der Token, Zustand 2 bzw. 1) und nachziehen; SqlClient und SSMS zeigen sie als Meldungen ERLEDIGT 14.10.2026 (TdsAnmeldeantwortTests): am Draht mitgeschnitten (tcpdump im Netz des Orakels, sqlcmd -N o) - ENVCHANGE Datenbank, INFO 5701 (Zustand 2, Klasse 0), ENVCHANGE Sortierung, ENVCHANGE Sprache, INFO 5703 (Zustand 1, Klasse 0), LOGINACK, ENVCHANGE Paketgrösse, DONE; KuonenSQL schickt jetzt dieselbe Folge. FEATUREEXTACK Rest PR27.22d3✅
PR27.22d3FEATUREEXTACK in der AnmeldeantwortRest aus PR27.22d2: am Orakel bestätigt die Antwort auf LOGIN7 die verlangten Erweiterungen (sqlcmd/ODBC 18: 1 SESSIONRECOVERY mit dem Zustand der Sitzung, 9 DATACLASSIFICATION Fassung 1, 10 UTF8_SUPPORT 01, 11 AZURESQLDNSCACHING 00) zwischen ENVCHANGE Paketgrösse und DONE; KuonenSQL bestätigt keine. Je Erweiterung messen, was der Client damit tut (Wiederverbinden, UTF-8-Spalten), und nur zusagen, was der Server wirklich kann **Erledigt 21.10.2026** (`TdsAnmeldeantwortTests`): am T-SQL-Zugang bestätigt KuonenSQL zwischen ENVCHANGE Paketgrösse und DONE nur, was es kann - 10 UTF8_SUPPORT `01` und 11 AZURESQLDNSCACHING `00` wie das Orakel; nicht 1 (keine Wiederherstellung der Sitzung), 9 (kein Klassifizierungstoken), 13 JSON (json bleibt nvarchar(max)), 14; der KSQL-Zugang bleibt ohne FEATUREEXTACK.✅
PR27.23aKS1: T-SQL-Port – Sitzungsoptionen `SET LOCK_TIMEOUT -1`, `SET DEADLOCK_PRIORITY`, `SET NOEXEC`, `SET PARSEONLY`Befund aus Kuonen.MultiSQLManager (Abfrageoptionen wie SSMS, 13.10.2026, am eigenen Testserver vom 12.10.2026 gemessen): (1) `SET LOCK_TIMEOUT 5000; SET LOCK_TIMEOUT -1; SELECT @@LOCK_TIMEOUT` liefert 5000 – der Wert -1 (warten ohne Grenze, die Vorgabe einer Sitzung) wird still übergangen; Soll laut Doku „SET LOCK_TIMEOUT“: -1. (2) `SET DEADLOCK_PRIORITY LOW` wird angenommen, `sys.dm_exec_sessions.deadlock_priority` der eigenen Sitzung bleibt 0; Soll -5 (LOW), 0 (NORMAL), 5 (HIGH), -10…10 als Zahl. (3) Nach `SET NOEXEC ON` liefert `SELECT 99` trotzdem 99; Soll laut Doku „SET NOEXEC“: übersetzen, nicht ausführen (keine Menge), bis `SET NOEXEC OFF`. (4) Ebenso `SET PARSEONLY ON` (wirkt beim Lesen des Stapels; nur Syntaxprüfung). Angenommen und wirksam sind bereits ROWCOUNT, TEXTSIZE, NOCOUNT, CONCAT_NULL_YIELDS_NULL, ARITHABORT, STATISTICS TIME/IO, TRANSACTION ISOLATION LEVEL, QUERY_GOVERNOR_COST_LIMIT, XACT_ABORT, IMPLICIT_TRANSACTIONS und die ANSI-Schalter. Test im MultiSQLManager: `TsqlOptionenTests.Jede_Option_wirkt_in_der_Sitzung_KuonenSql_Befund_KS1` (übersprungen; der Zwilling an SQL Server 2025 `Jede_Option_wirkt_in_der_Sitzung`). **Erledigt 13.10.2026** nach der Doku (Orakel nicht erreichbar): LOCK_TIMEOUT -1, DEADLOCK_PRIORITY in dm_exec_sessions, NOEXEC und PARSEONLY; Test `TdsSitzungsoptionenTests`. Rest in PR27.23i.✅
PR27.23bKS1: MySQL-/MariaDB-Port – Kommentarzeile vor `CREATE TABLE` kostet die SpaltenkommentareBefund aus Kuonen.MultiSQLManager (Skript erstellen im Rundweg, K6; am 13.10.2026 mit dem Stand von main 22370f5b nachgemessen): steht vor `CREATE TABLE t1 (… betrag DECIMAL(10,2) NULL COMMENT 'Betrag' …)` eine Kommentarzeile (`-- Object: …` wie im Kopf eines Skripts, ebenso `/* … */`), ist `information_schema.COLUMNS.COLUMN_COMMENT` danach leer; ohne Kommentar stimmt er. Am MariaDB- und am MySQL-Port. Früher gemessen und hier nicht neu geprüft: `DROP EVENT` hinter einem Kommentar 1064, `IS_NULLABLE` = YES für eine Sichtspalte mit dreiteiligem Namen (`db`.`t1`.`id`). Soll laut Referenzhandbuch „Comments“: ein Kommentar ändert die Anweisung nicht. Tests im MultiSQLManager: `SkripteOrakelTests.Baum_und_Rundweg_vollstaendig_KuonenSqlMaria_Befund_KS1` und `…_KuonenSqlMySql_Befund_KS1` (übersprungen). **Erledigt 13.10.2026:** Kommentare vor dem ersten Wort fallen weg (CREATE TABLE behält seine Kommentare, DROP EVENT geht), und eine Sichtspalte `db`.`t1`.`id` erbt NOT NULL; Test `MariaDbVorspannKommentarTests`.✅
PR27.23cKS1: PostgreSQL-Port – `pg_sleep` mit NachkommastellenBefund aus Kuonen.MultiSQLManager (13.10.2026, main 22370f5b): `SELECT pg_sleep(0.1)` und `SELECT pg_sleep(1)` gehen, aber `SELECT pg_sleep(0.1::double precision)` ist 42883 „function pg_sleep(double precision) does not exist“ und `SELECT pg_sleep(1.2), 'x'` 42883 „function pg_sleep(numeric) does not exist“. Soll laut PostgreSQL-Doku „Delaying Execution“: `pg_sleep(double precision)`, ein numeric-Literal wird implizit umgewandelt. Test: `PgProfilerTests.Ein_Lauf_sieht_die_Abfrage_einer_anderen_Sitzung_KuonenSql_Befund_KS1` (übersprungen; `pg_stat_activity` selbst ist seit PR27.22a richtig). **Erledigt 13.10.2026:** `pg_sleep(x)` als Funktion (void, unterbrechbar, auch in Ausdrücken und mit double precision); Test `PgSchlafenTests`.✅
PR27.23dKS1: PostgreSQL-Port – Punkt in einem Namen in AnführungszeichenBefund aus Kuonen.MultiSQLManager (seit PR27.17k, am 13.10.2026 mit main 22370f5b nachgemessen): nach `CREATE TABLE "msm_pkt.x" (c int); CREATE INDEX "msm_i.j" ON "msm_pkt.x" (c)` zeigt `pg_indexes.indexname` `msm_i` U+FDD0 `j`, `pg_get_indexdef` `CREATE INDEX msm_i\uFDD0j ON public.msm_pkt.x USING btree (c)` (Punkt als U+FDD0, Tabellenname ohne Anführungszeichen), und `DROP TABLE "msm_pkt.x"` ist 42P01 „table "x" does not exist“ (der Name in Anführungszeichen wird am Punkt getrennt). `pg_class.relname` zeigt `msm_pkt.x` richtig. Soll laut PostgreSQL-Doku „Identifiers and Key Words“: ein Name in Anführungszeichen ist ein einziger Bezeichner, `pg_get_indexdef` setzt ihn in Anführungszeichen. Test: `SkripteEinschleusungTests.Ein_Indexname_mit_Dollarzeichen_und_Punkt_schleust_nichts_ein_KuonenSql_Befund_KS1` (übersprungen). **Erledigt 13.10.2026:** Indexname mit Punkt in sys.indexes/pg_indexes, pg_get_indexdef mit Anführungszeichen, ::regclass und DROP TABLE nehmen "a.b" als einen Namen; Test `PgPunktImNamenTests`.✅
PR27.23eKS1: PostgreSQL-Port – `pg_reload_conf()` und Wirkung von `ALTER SYSTEM`Befund aus Kuonen.MultiSQLManager (Servereigenschaften „Ändern“, 13.10.2026, main 22370f5b): `SELECT pg_reload_conf()` ist 42883 „function pg_reload_conf() does not exist“. `ALTER SYSTEM SET work_mem = '5120'` wirkt dagegen sofort, auch in der eigenen Sitzung, und `ALTER SYSTEM SET max_connections = '120'` (Kontext postmaster) zeigt sofort 120. Soll laut PostgreSQL-Doku „ALTER SYSTEM“ und „Configuration Settings Functions“: `ALTER SYSTEM` schreibt nur `postgresql.auto.conf`, wirksam nach `pg_reload_conf()` (sighup/user) bzw. nach einem Neustart (postmaster, `pg_settings.pending_restart` = true); `pg_reload_conf()` liefert true. `ALTER SYSTEM RESET name`/`RESET ALL` gehen. Der Manager lässt `pg_reload_conf()` am KuonenSQL weg (`DialogeDatenbank.ServerAnwenden(ohneReload)`). Test: `EigenschaftenOrakelTests.Serverweit_aendern_wirkt_in_einer_neuen_Sitzung_und_laesst_sich_zuruecksetzen_KuonenSql`. **Erledigt 13.10.2026:** ALTER SYSTEM schreibt nur die Datei, pg_reload_conf() lädt (postmaster wartet auf den Neustart, pending_restart), beim Start gilt die Datei; Test `PgSystemeinstellungTests`. Der Manager kann pg_reload_conf() wieder senden.✅
PR27.23fKS1: MySQL-/MariaDB-Port – `SET GLOBAL wait_timeout` wird übergangenBefund aus Kuonen.MultiSQLManager (13.10.2026, main 22370f5b): `SET GLOBAL wait_timeout = 1234` läuft ohne Fehler, `@@GLOBAL.wait_timeout` bleibt 28800 (auch in einer neuen Sitzung), am MySQL- und am MariaDB-Port; ebenso `SET PERSIST`. `max_connections`, `innodb_lock_wait_timeout`, `long_query_time` (MariaDB) wirken. Soll laut Referenzhandbuch „Server System Variables“: `wait_timeout` ist global und je Sitzung dynamisch; eine neue Sitzung übernimmt den globalen Wert. Test: `ServerdialogOrakelTests.Serverweit_wait_timeout_am_MySql_Zugang_von_KuonenSql_Befund_KS1` (übersprungen). **Erledigt 13.10.2026:** wait_timeout kommt aus dem Einstellungsmodell (SET GLOBAL, SET PERSIST, neue Sitzungen); Test `MariaDbWartezeitTests`. Die Trennung einer ruhenden Verbindung steht in PR27.23i.✅
PR27.23gKS1: MySQL-Port – Typ von `@@GLOBAL.long_query_time` und Vorgabe von `max_connections`Befund aus Kuonen.MultiSQLManager (13.10.2026, main 22370f5b): `SELECT @@GLOBAL.long_query_time` meldet die Spalte als BIGINT mit dem Text „10.000000“, der Treiber kann den Wert darum nicht lesen (am MariaDB-Port kommt 10 richtig); Soll wie MySQL 8.4: DOUBLE (Spaltentyp 5) mit 10. `@@GLOBAL.max_connections` steht nach dem Start auf 32767 statt der Vorgabe 151 von MySQL 8.4 (MariaDB-Port: 151). **Erledigt 13.10.2026:** long_query_time als DOUBLE (5) am MySQL-Port; Test `MySqlVariablentypTests`. max_connections 151 nicht gebaut (Widerspruch zu J26.6, Entscheid in PR27.23i).✅
PR27.23hKS1: MariaDB-Port – unterbrochenes `SLEEP`Befund aus Kuonen.MultiSQLManager (13.10.2026, gemessen am Referenzcontainer `mariadb:11.8.3` und `mysql:8.4.11`): `SELECT SLEEP(5)` und von einer zweiten Verbindung `KILL QUERY <id>` – MariaDB 11.8 meldet Fehler 1317 (SQLSTATE 70100) „Query execution was interrupted“, MySQL 8.4 liefert 1 ohne Fehler. Der MariaDB-Port von KuonenSQL liefert 1 wie MySQL (der MySQL-Port stimmt). Test: `TransaktionOrakelTests.Abbruch_SLEEP_KuonenSqlMaria_Befund_KS1` (übersprungen; die Zwillinge an MariaDB und MySQL laufen). **Erledigt 13.10.2026:** am MariaDB-Port 1317 (an einem eigenen mariadb:11.8.3 nachgemessen), am MySQL-Port weiter 1.✅
PR27.23iRest aus PR27.23aoffen: am Orakel nachmessen - Nummer und Text des Fehlers bei DEADLOCK_PRIORITY ausserhalb -10…10 (angenommen 1983), ob `SET PARSEONLY ON; SELECT 1` im selben Stapel schon nichts liefert, und welche SET-Anweisungen unter NOEXEC noch wirken; DEADLOCK_PRIORITY wählt noch nicht das Opfer (bisher immer der Wartende) und wird nach einer Prozedur nicht zurückgesetzt. Aus PR27.23f: eine ruhende Verbindung wird nach `wait_timeout` Sekunden nicht getrennt (MariaDB/MySQL: Fehler 4031 bzw. Verbindungsende). Aus PR27.23g: **erledigt 13.10.2026** - der MySQL-Port beginnt mit `max_connections` 151 und setzt sie für seine eigenen Verbindungen durch, solange keine Grenze für alle gesetzt ist (Entscheid des Nutzers; `MySqlVerbindungsgrenzeTests`, ). Ausserdem den Text von `@@long_query_time` am MySQL-Orakel messen (10 oder 10.000000). ERLEDIGT 14.10.2026 (TdsSitzungsoptionenTests, WartezeitTrennungTests, MySqlVariablentypTests), am Orakel gemessen: DEADLOCK_PRIORITY ausserhalb -10…10 ist 2755 Zustand 1 (vorher 1983); SET PARSEONLY ON; SELECT 1 im selben Stapel liefert die Menge (wie hier); unter NOEXEC wirkt keine SET-Anweisung (wie hier); eine in einer Prozedur gesetzte Priorität gilt nach ihr nicht mehr (jetzt im gesicherten SET-Zustand). Das Deadlockopfer ist die niedrigere Priorität, bei gleicher die Transaktion mit weniger Sperren (am billigsten zurückzunehmen), sonst wie bisher der Wartende - ein anderes Opfer wird geweckt und meldet 1205. wait_timeout: am MySQL-Port 4031 (HY000) und Verbindungsende wie mysql:8.4, am MariaDB-Port Verbindungsende. @@long_query_time zeigt 10.000000 wie mysql:8.4✅
PR27.23jKS1: T-SQL – unbekannte Sprache in `CREATE LOGIN … DEFAULT_LANGUAGE`Befund aus Kuonen.MultiSQLManager (13.10.2026, Sabotage-Probe im Anmeldungsdialog): `CREATE LOGIN [x] WITH PASSWORD = N'…', DEFAULT_LANGUAGE = [<Standard>]` lehnt SQL Server 2025 mit einem Fehler ab (die Sprache gibt es in `sys.syslanguages` nicht), KuonenSQL legt die Anmeldung ohne Fehler an. Erwartet: derselbe Fehler wie am Orakel (Nummer und Text dort messen, vermutlich 15028/2756-Familie), ebenso bei `ALTER LOGIN … WITH DEFAULT_LANGUAGE` und bei `DEFAULT_DATABASE` mit einer Datenbank, die es nicht gibt. Test drüben: `SicherheitAnmeldungOrakelTests.Eine_Anmeldung_wird_ueber_den_Dialog_angelegt…` mit sabotierter Abbildung von <Standard>. **Erledigt 14.10.2026:** am Orakel 15010 (Datenbank zuerst), 15033, 15151 bei ALTER auf unbekannte Anmeldung; Sprache als Name oder Alias, gespeichert und in sys.server_principals/sql_logins/LOGINPROPERTY sichtbar (AnmeldungsvorgabenTests).✅
PR27.24aPlattform: FORMAT auf macOS wie SQL Server (Uhrzeit vor AM/PM, Tausendertrenner de-CH)Befund aus dem Volltest auf macOS (ARM64, 21.10.2026): `FORMAT(CAST('2024-05-17 13:14:15' AS datetime), 'G')` gab `5/17/2024 1:14:15` U+202F `PM` und `FORMAT(1234.5, 'N', 'de-CH')` `1'234.50` (gerader Apostroph) - die ICU von macOS; SQL Server 2025 (gemessen am Orakel, als varbinary) gibt ein Leerzeichen vor PM (ebenso vor dem Tageszeitzeichen von ko-KR, es-MX, ar-EG) und U+2019 in de-CH, it-CH, de-LI, rm-CH. **Erledigt 21.10.2026:** die Formatkultur setzt U+202F/U+00A0 in den Zeitmustern zum Leerzeichen und den geraden Apostroph als Tausendertrenner zu U+2019; `Tsql2025LueckenTests.Format_in_der_Kultur_der_Sitzung` auf macOS grün, auf Windows unverändert. Weitere Kulturdaten, in denen ICU und NLS auseinandergehen (etwa en-AU `pm` statt `PM`, fr-CH), bleiben offen in PR27.24b.✅
PR27.24bRest aus PR27.24a: Kulturdaten von FORMAT unabhängig von der ICU der Plattformoffen: am Mac weichen neben den behobenen Zeichen weitere Kulturdaten von SQL Server ab, gemessen 21.10.2026: `FORMAT(1234567.5, 'N', k)`: fr-CH `1’234’567.50` statt `1 234 567,50` (U+00A0), rm-CH `1 234 567,50` (U+202F) statt `1’234’567.50`, en-AU `pm` statt `PM`, es-MX `1:14:15 p. m.` statt `01:14:15 p. m.`, en-IN `1:14:15 PM` statt `13:14:15`, ar-EG arabische Trennzeichen (U+066C/U+066B) statt `1,234,567.50` und `1:14:15 م` statt `01:14:15 م`. Ziel: die Kulturdaten, die SQL Server zeigt, für die gängigen Kulturen als eigene Tabelle statt aus der ICU der Plattform. **Erledigt 21.10.2026** (`FormatKulturenTests`, 1809 Werte am Orakel): für 67 gängige Kulturen (darunter alle hier genannten) die Kulturdaten von SQL Server als eingebettete Tafel (Muster, Trenner, Gruppengrössen, Währungs- und Prozentmuster, Monats- und Tagesnamen auch im Genitiv), am Orakel gemessen und abgeleitet (`tools/formatkulturen`); auch unter Windows wichen vorher 416 der 1809 Werte ab. Rest → PR27.24g.✅
PR27.24gRest aus PR27.24boffen: Kulturen ausserhalb der 67 der Tafel (weiter die Daten der Plattform) und solche mit anderem Kalender (ar-SA Hidschra, fa-IR); Zeitzonen- und Ära-Angaben, native Ziffern; ein Lauf von `FormatKulturenTests` auf macOS und Linux (dort messbar, hier unter Windows grün)⬜
PR27.24cPlattform: GraphQL-Endpunkt - die Antwort 413 erreicht den Client auch auf macOS und LinuxBefund aus dem Volltest auf macOS (21.10.2026): `GqlHttpTests.Fehler_der_Anfrage_haben_die_passenden_Statuswerte` - eine Anfrage mit 2 MB Rumpf bekam statt 413 „Broken pipe“. Der Server antwortete 413 nach den Kopfzeilen und schloss die Verbindung mit ungelesenem Rumpf; das schickt ein RST, und ein Client, der noch sendet, verliert die Antwort (auf Windows schluckt der Sendepuffer den Rumpf meist ganz). **Erledigt 21.10.2026:** nach einer Fehlerantwort mitten in der Anfrage schliesst der Server erst die Sendeseite und verwirft, was noch kommt, bis der Client schliesst - mit Deckel 16 MiB / 2 s (`GqlHttp.AuslaufenAsync`); Tests `GqlHttpTests.Fehler_der_Anfrage_haben_die_passenden_Statuswerte` (auf macOS grün) und `Ein_Client_der_nach_413_immer_weiter_sendet_wird_getrennt` (Deckel; Sabotage ohne Deckel rot).✅
PR27.24dPlattform: TAN, ATAN, ASIN, ACOS, COT des MariaDB-Ports korrekt gerundet, auf jeder Plattform gleichBefund aus dem Volltest auf macOS (21.10.2026): `MariaDbFunktionenOrakelTests` - 5 von 840 Ausdrücken eine Stelle neben dem Orakel (`COT(1)`, `ACOS(0.5)`, `ASIN(0.5)`, `ATAN(0.5)`, `TAN(1)`). Ursache: `Math.Tan/Atan/Asin/Acos` rufen die Bibliothek des Systems; die von macOS lag an 2250 Zufallswerten in 271 Fällen neben dem korrekt gerundeten Wert (TAN in 32 %), MariaDB 11.8 (glibc) in 2249 von 2250 darauf. **Erledigt 21.10.2026:** `MariaDb/GenaueWinkel` rechnet in doppelter Doppelgenauigkeit (Tafel plus kurze Reihe) und rundet erst am Ende - 2250 von 2250 korrekt gerundet, 2249 gleich MariaDB; COT ist wie bei MariaDB der Kehrwert des gerundeten TAN; TAN über 2²² bleibt bei der Plattform. Tests `MariaDbFunktionenOrakelTests` (auf macOS grün), `GenaueWinkelTests`. Kosten je Wert siehe umsetzung.md. Rest in PR27.24e.✅
PR27.24eRest aus PR27.24d: übrige Gleitkommafunktionen unabhängig von der Bibliothek der Plattformoffen: am MariaDB-Port rechnen SIN, COS, ATAN2, EXP, LN/LOG, POW weiter mit `Math` (im Orakeltest auf macOS ohne Abweichung, an Zufallswerten nicht gemessen); die schnellen Wege von `GenaueWinkel` (Kosten je Wert gegenüber `Math`) verkürzen. T-SQL: SQL Server 2025 ist selbst nicht korrekt gerundet (am Orakel 4-8 % von 1200 Zufallswerten von TAN/ATAN/ASIN/ACOS eine Stelle daneben, 21.10.2026) - ob die Werte von SQL Server auf jeder Plattform nachzubilden sind, ist offen. **Erledigt 21.10.2026 (Teil, Rest PR27.24f)** (`GenaueWinkelTests`, `MariaGleitzahltextTests`; an eigenem mariadb:11.8.9 an 3900 Zufallswerten gemessen): SIN, COS, EXP, LN korrekt gerundet in doppelter Doppelgenauigkeit, LOG2 wie MariaDB als LN(x)/LN(2), LOG10 korrekt gerundet, LOG(b, x) über das genaue LN; unter Windows lagen vorher 100 von 2400 Werten neben MariaDB, jetzt nur noch Fälle, in denen glibc selbst nicht korrekt rundet. Dazu die Textform grosser DOUBLE (ab 1e15 mit Nachkommastellen fest).✅
PR27.24fRest aus PR27.24eoffen: die genauen Wege sind 15- bis 60-mal teurer als `Math` (167 bis 309 ns je Wert) - ein schneller Weg (Ergebnis der Plattform, nur nahe der Mitte zweier Nachbarn genau nachrechnen) ohne Unterschied im Ergebnis; ATAN2 und POW (unter Windows an 600 Werten gleich wie MariaDB, auf macOS nicht gemessen); die Fälle, in denen glibc selbst nicht korrekt rundet (LOG10 nahe der Mitte, vereinzelt EXP und COS), sind ohne dessen Rechenweg nicht nachzubilden; macOS-Lauf von `GenaueWinkelTests` und `MariaDbFunktionenOrakelTests` (nur dort messbar); T-SQL: SQL Server 2025 rundet selbst nicht korrekt (4-8 % bei TAN/ATAN/ASIN/ACOS) - ob seine Werte auf jeder Plattform nachzubilden sind, ist offen.⬜
PR27.25pg_notify als Funktion, NOTIFY in Triggern, Funktionen und DOBefund 22.10.2026 (Cache- und Oberflächen-Refresh über LISTEN/NOTIFY): pg_notify ging nur als eigene Anweisung mit festen Texten; mit Ausdrücken, in Triggern, Funktionen und DO-Blöcken 42883 bzw. 42601 bei NOTIFY im Rumpf. **Erledigt 22.10.2026** (Orakel postgres:17, notify2-orakel.txt, 23 Schritte): pg_notify(kanal, nachricht) ist die Kernfunktion PG_NOTIFY (beliebige Ausdrücke, je Zeile einer Abfrage, nicht strikt: NULL-Nachricht leer, NULL/leerer Kanal, Kanal über 63 Byte und Nachricht über 7999 Byte 22023); NOTIFY im PL/pgSQL-Rumpf wird PERFORM pg_notify, im Text von EXECUTE PG_NOTIFY ohne Ergebnismenge. Was eine Sitzung sendet, wartet je Sitzung bis zum Ende ihrer Transaktion (ohne Transaktionsblock das Ende der Anweisung, gescheitert verfällt es; gleiche Paare einmal; SAVEPOINT/ROLLBACK TO und BEGIN … EXCEPTION als Sicherungspunkt).✅
PR27.25aRest aus PR27.25offen: ein PostgreSQL-Trigger mit pg_notify, den eine Änderung über einen anderen Zugang (TDS, MariaDB, KSQL) auslöst, sendet sofort statt beim COMMIT jener Transaktion (die Sitzung hat keinen PostgreSQL-Anschluss, der ihr Ende meldet); Typprüfung der Argumente (am Orakel pg_notify(1, 2) 42883 function pg_notify(integer, integer) does not exist) nicht gemessen.⬜
PR27.26SqlDependency / Query Notifications (Service Broker)offen, gemessen 22.10.2026 (Orakel SQL Server 2025, tools/querynotification): .NET-Anwendungen steuern Cache und Oberflächen-Refresh mit SqlDependency; an KuonenSQL scheitert schon SqlDependency.Start (Service Broker nicht aktiviert). Nötig: (1) ENABLE_BROKER wirkt (is_broker_enabled, service_broker_guid; heute stilles OK ohne Wirkung); (2) CREATE/DROP QUEUE und SERVICE, sys.service_queues und sys.services (heute 102), CREATE/DROP PROCEDURE wie SqlDependency.Start sie anlegt; (3) WAITFOR (RECEIVE … FROM queue), TIMEOUT n (heute 102); (4) der Query-Notification-Kopf (Typ 0x0001) im ALL_HEADERS-Block wird zum Abonnement (sys.dm_qn_subscriptions, Zeitlimit) statt übersprungen; (5) eine bestätigte Änderung an einer gelesenen Zeile schickt die QueryNotification-Nachricht in die Warteschlange (Update/Insert/Delete/Truncate), ROLLBACK und fremde Zeilen nicht, TRUNCATE an alle; einmalig, danach ist das Abonnement weg; (6) ungültige Abfragen (SELECT *, Name ohne Schema) melden sofort Subscribe/Invalid/Statement. BEGIN DIALOG/SEND ON CONVERSATION allgemein (heute 102) erst danach.⬜
PR27.27DROP hält sich an die Abhängigen, auch am T-SQL-Zugang; `SET DEPENDENCY_CHECKS`Nutzerwunsch 22.10.2026 (bewusst abweichend von SQL Server): `DROP TABLE` und `DROP VIEW` sollen scheitern, solange eine Sicht, Funktion, Prozedur, ein Trigger einer anderen Tabelle oder ein Fremdschlüssel darauf zeigt, und die Meldung soll sie nennen; mitgelöscht wird nur mit `CASCADE`. Dazu `SET DEPENDENCY_CHECKS OFF` (Prüfungen samt Fremdschlüsselprüfung aus) und `ON`, das vorher meldet, was verwaist ist oder einen Fremdschlüssel verletzt. **Erledigt 22.10.2026:** Dialektschalter `DropPrueftAbhaengige` (T-SQL, KSQL), `RESTRICT`/`CASCADE` auch am T-SQL-Zugang, 3729 mit allen Abhängigen bzw. 3726 nur bei Fremdschlüsseln, `CASCADE` über mehrere Stufen; `sys.sql_expression_dependencies` führt auch Prozeduren und Trigger; `DropAbhaengigkeitenTests` (17), Sabotage rot.✅
Meilenstein 29: Schweizer Fachtypen (21/21)
PunktTitelBeschreibungStand
CH29.1AHV-Nummer (AHVN13)**Erledigt (04.10.2026):** Typ `ch_ahvn13` (Kennung 138, Klasse `SqlAhvn13`, OID 499999000) über den gemeinsamen Weg `Fachtyp` (M29): 13 Ziffern, Präfix 756, Prüfziffer EAN-13, Normalform `756.9217.0769.85`; Eingabe mit Punkten, Leerzeichen oder ohne. Methoden `Formatiert()`, `Kompakt()`, `IstGueltig()`, statisch `ch_ahvn13::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_ahvn13', text)` und `CH_FORMATIERT(a)`. Fehler je Zugang (6522 / 1292 / 22P02), Eingaben über 64 Zeichen ohne Arbeit abgewiesen. `TypFachtypenTests`, `TypFachtypenKreuzTests` (sechs Richtungen), `SqlTypesTests`.✅
CH29.2UID und MWST-Nummer**Erledigt (04.10.2026):** Typ `ch_uid` (Kennung 139): `CHE` + 8 Ziffern + Prüfziffer Modulo 11 (10 ungültig), Zusatz MWST/TVA/IVA/HR, Normalform `CHE-109.322.551 MWST`; Methoden `Zusatz()`, `Uid()`. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.3IBAN (Schweiz und Liechtenstein)**Erledigt (04.10.2026):** Typ `ch_iban` (Kennung 140): CH/LI, 21 Zeichen, MOD 97-10, Normalform in Vierergruppen; Methoden `Land()`, `Clearing()`, `Konto()`, `IstQrIban()`, `PasstZu(referenz)`. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.4QR-IBAN**Erledigt (04.10.2026):** Typ `ch_qriban` (Kennung 141): IBAN mit IID 30000–31999; `PasstZu` erlaubt mit QR-IBAN nur die QR-Referenz, mit anderer IBAN nur SCOR oder keine. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.5QR-Referenz und ESR-Referenz**Erledigt (04.10.2026):** Typ `ch_qrreferenz` (Kennung 142): 27 Ziffern, Modulo 10 rekursiv, kürzere ESR-Referenz links mit Nullen ergänzt, Normalform in Fünfergruppen von rechts. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.6Creditor Reference (SCOR)**Erledigt (04.10.2026):** Typ `ch_scor` (Kennung 143): `RF` + 2 Prüfziffern + 1–21 Zeichen, ISO 11649, Vierergruppen; Methode `Referenz()`. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.7Postkontonummer**Erledigt (04.10.2026):** Typ `ch_postkonto` (Kennung 144): `80-2-2`, `01-162-8` oder neun Ziffern, Modulo 10 rekursiv; `Iban()` gibt die IBAN der PostFinance (`CH14 0900 0000 8000 0002 2`). Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.8Postleitzahl**Erledigt (04.10.2026):** Typ `ch_plz` (Kennung 145): vier Ziffern 1000–9999, `CH-`/`FL-`/`LI-` erlaubt; `IstLiechtenstein()`. Ort und Kanton aus dem Ortschaftenverzeichnis: CH29.10b. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.9Telefonnummer**Erledigt (04.10.2026):** Typ `ch_telefon` (Kennung 146): national, `+41`, `0041`, `(0)`; Normalform `+41 79 123 45 67` bzw. `+41 800 123 456`; `Art()` (Mobil, Festnetz, Gratis, Kostenteilung, Mehrwert), `National()`, `Kompakt()` = E.164. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.10BFS-Gemeindenummer und Kantonskürzel**Erledigt (04.10.2026):** Typen `ch_gemeinde` (Kennung 147, Nummer in einem Kantonsblock der BFS-Nummerierung, `Kanton()`) und `ch_kanton` (Kennung 230, Kürzel oder BFS-Nummer 1–26, `Nummer()`, `Kantonsname()`, Ordnung nach BFS-Nummer). Name und Gültigkeit je Gemeinde: CH29.10b. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.11GTIN/EAN**Erledigt (04.10.2026):** Typ `ch_gtin` (Kennung 148): 8/12/13/14 Ziffern, GS1-Prüfziffer, eine Schreibweise je Wert (führende Nullen bis zur kürzesten Länge weg, gleich in der 14-stelligen Form); `Gtin14()`. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.12CHF-Betrag**Erledigt (04.10.2026):** Typ `ch_chf` (Kennung 149, `Betragstyp`): `decimal`, jeder Wert und jedes Ergebnis auf 5 Rappen (Hälfte von der Null weg), Normalform `1'234.55`; `+`/`-`, `*`/`/` mit Zahl, `SUM`/`MIN`/`MAX`, Ordnung nach Wert, `CAST` nach `decimal`/`money`; `Rappen()`, `Betrag()`. Gemeinsamer Weg `Fachtyp` (M29): Normalform = formatierte Schreibweise, `Formatiert()`, `Kompakt()`, `IstGueltig()`, `typ::IstGueltig(text)`, in jedem Zugang `CH_GUELTIG('ch_typ', text)` und `CH_<METHODE>(wert)`, Fehler 6522 / 1292 / 22P02, Kreuztest sechs Richtungen, Sabotage zurückgebaut.✅
CH29.10bAmtliche Verzeichnisse für `ch_gemeinde` und `ch_plz`**Erledigt (04.10.2026):** eingebettete Tabelle aus BFS, Amtliches Gemeindeverzeichnis (Stichtag 04.10.2026, 3404 Nummern seit 1960) und swisstopo, Amtliches Ortschaftenverzeichnis (Stand 30.09.2026, 3190 PLZ), 58 KB gzip, neu erzeugbar mit `tools/ch-verzeichnisse/erzeugen.py`; Quellenangabe in `THIRD-PARTY-NOTICES.md`. `ch_gemeinde` gilt nur noch, wenn die Nummer je vergeben war; neu `Name()`, `IstAktuell()`, `Aufgehoben()`, `Nachfolger()`. `ch_plz`: `Ort()`, `Gemeinde()` (NULL ohne Ortschaft). Binäre Suche ohne Belegung, NULL statt Absturz bei veraltetem gespeichertem Wert, Sabotage zurückgebaut.✅
CH29.10cAktualisierbare Verzeichnisse, `ch_plz` streng mit Post-Liste**Erledigt (04.10.2026):** das Open-Data-Portal der Post (swisspost.opendatasoft.com) ist eingestellt, die PLZ-Liste gibt es nur mit Benutzerkonto im Downloadcenter der Post — nichts heruntergeladen, nichts eingebettet. `tools/ch-verzeichnisse/erzeugen.py --aktualisieren` bzw. `dist\ch-verzeichnisse-aktualisieren.ps1` lädt BFS und swisstopo neu, erzeugt die Tabelle deterministisch (gleicher Stand, gleiche Bytes) und fasst neue/entfallene PLZ und Gemeinden zusammen; `--post <datei>` liest eine selbst bezogene Post-Datei (Spalten per Kopfzeile, sonst Satzart 01 an festen Stellen), dann prüft `ch_plz` streng (Zeile `V plz`), `Ort()`/`Gemeinde()` aus der Post-Liste, wo swisstopo fehlt; entfallene PLZ bleiben gültig (gespeicherte Werte lesbar). Zur Laufzeit ersetzt `<Datenordner>/verzeichnisse/ch_verzeichnisse.tsv.gz` die eingebettete Tabelle ohne neues Programm, geprüft (Grösse, gzip, UTF-8, Felder, Bereiche, Reihenfolge), Müll wird ohne Ausnahme verworfen. Ohne Post-Datei bleibt `ch_plz` formal. TDD, Sabotage zurückgebaut.✅
CH29.10dPost-Datei durch den Nutzer beschaffen**Erledigt (06.10.2026):** der Nutzer hat die PLZ-Liste der Post (Post_Adressdaten-20261006.zip, Stand 17.10.2026) geliefert; mit `dist\ch-verzeichnisse-aktualisieren.ps1 -Post <datei> -Datenordner <ordner> -OhneNeuLaden` eingelesen, Format (Satzart 01, Windows-1252, Semikolon) an der echten Datei bestätigt: 3440 PLZ statt 3190, 250 neu (u. a. 8000, 3000, 1211), keine entfallen, Umlaute/Akzente richtig. Nur im Datenordner (nicht eingebettet, nicht im Repo), weil die Nutzungsbedingungen der Post die Weitergabe im Programm offen lassen; Aktualisierung mit jeder Datei gleichen Aufbaus.✅
CH29.13Strassenverzeichnis der Post einlesen**Erledigt (06.10.2026):** `tools/ch-verzeichnisse/erzeugen.py --post <datei> --ziel <Datenordner>/verzeichnisse/...` (Wrapper `dist/ch-verzeichnisse-aktualisieren.ps1 -Post <datei> -Datenordner <ordner>`) schreibt neben die PLZ-Tabelle `ch_adressen.tsv.gz` (Sätze O Ortschaft, S Strasse, A andere Bezeichnung, H Gebäude; aus den Post-Sätzen 01/04/05/06, Format an der echten Datei vom 06.10.2026 bestätigt: 5 044 Ortschaften, 168 187 Strassen, 1 923 926 Gebäude, 4,4 MB gepackt). Nur im Datenordner, nie eingebettet und nie im Repo (Nutzungsbedingungen der Post). Jede neue Datei baut alles vollständig neu auf (Vorgabe Nutzer 06.10.2026), Schlüssel der Post dürfen sich ändern. Server: `Adressverzeichnis` liest beim ersten Gebrauch, prüft jede Zeile (Grössen, Verweise, Reihenfolge, Deckel), sonst ohne Verzeichnis. Python-Tests (Neuaufbau, ohne Strassen keine Datei) und `AdressenTests` (verfälschte Dateien).✅
CH29.14Typ ch_strasse**Erledigt (06.10.2026):** Typ `ch_strasse` (Kennung 233): Normalform `Hardstrasse, 8005 Zürich` aus jeder Schreibweise (kurz, lang, umgestellt, anderssprachig, eindeutiger Anfang `hardstr`), Methoden `Name()`, `Plz()`, `Ort()`, `Gemeinde()`, `Kanton()`, `IstAktuell()`; ohne Verzeichnis nur Formprüfung; Klasse `SqlStrasse`.✅
CH29.15Typ ch_adresse**Erledigt (06.10.2026):** Typ `ch_adresse` (Kennung 234): nur ein Gebäude des Verzeichnisses (sonst 6522/1292/22P02), Normalform `Hardstrasse 201a, 8005 Zürich` aus `hardstr. 201 A 8005 zurich`; Methoden `Strasse()` (ch_strasse), `Hausnummer()`, `Zusatz()`, `Plz()`, `Ort()`, `Gemeinde()`, `Kanton()`, `IstAktuell()` (0, wenn das Gebäude nach einem Neuaufbau fehlt: der gespeicherte Text bleibt lesbar); Kreuztest sechs Richtungen. Nebenbei: `TRY_CAST` in einen eigenen Typ gibt bei ungültigem Wert NULL statt 6522 (alle Fachtypen).✅
CH29.16Adresseingabe CH_ADRESSVORSCHLAG**Erledigt (06.10.2026):** eingebaute Tabellenfunktion `CH_ADRESSVORSCHLAG(plzOderOrt, strasse, hausnummer)` im Kern (T-SQL ohne Schema, MySQL/MariaDB gleich, PostgreSQL `ch_adressvorschlag`): Stufe PLZ/STRASSE/HAUSNUMMER, Spalten `stufe`, `plz`, `ort`, `strasse`, `strasse_kurz`, `hausnummer`, `zusatz`, `anzeige`, `adresse` (ch_adresse), `anzahl`, `rang` (genaue Treffer zuerst); Präfixsuche ohne Gross/Klein und Akzente über alle Schreibweisen; `OUTER APPLY` für Importprüfung; höchstens 1 000 Zeilen; ohne Verzeichnis keine Zeile.✅
CH29.17Adresseingabe: Leistung belegen**Erledigt (06.10.2026):** mit der echten Post-Datei gemessen (docs/leistung.md Abschnitt 14): je Vorschlag 0,002–0,15 ms (Ziel unter 1 ms), Laden 1,9 s beim ersten Gebrauch, Speicher 33 MB (nach Zusammenlegen gleicher Texte, vorher 49 MB; Ziel 20–30 MB knapp verfehlt). Robustheit: verfälschte Dateien (6 Arten), überlange Eingaben (5 000 Zeichen) ohne Absturz, Sabotage je Punkt.✅
CH29.18Verzeichnis als Sichten (3. Normalform)**Erledigt (06.10.2026):** schreibgeschützte Sichten im Schema `ch` aus dem Speicher des Verzeichnisses, ohne Kopie: `ch.ortschaft` (onrp, plz, zusatzziffer, ort, bfs, kanton), `ch.strasse` (strid, onrp, name, kurz), `ch.strassenname` (strid, bezeichnung), `ch.gebaeude` (strid, hausnummer, zusatz); in jedem Zugang. Vorfilter aus WHERE und inneren ON (Gleichheit, IN, Spalte = Spalte, Stufe um Stufe über gefilterte Nachbarsichten): Verbund Gebäude–Strasse über strid 0,37 ms statt 1,4 s, Ortschaft–Strasse–Gebäude über die PLZ 104 ms statt 1,5 s (docs/leistung.md Abschnitt 14). Ohne Bedingung auf einen Schlüssel baut ch.gebaeude alle 1,9 Mio. Zeilen (1,3 s). Schlüssel gelten nur bis zum nächsten Neuaufbau; im Katalog (sys.objects) nicht aufgeführt. Fünf Sabotage-Proben.✅
Meilenstein 30: MySQL-Dialekt auf dem MariaDB-Zugang (85/96)
PunktTitelBeschreibungStand
MY30.1Referenzorakel MySQL 8.4Container `kq-ref-mysql` (mysql:8.4, 127.0.0.1:13309, Kennwort nur im Container) und `dist\orakel-ref.ps1 -Dialekt mysql`; MySQL steht unter GPL: nur von aussen messen (AGENTS.md §4a).✅
MY30.2Dialektmodul MySQLEin Dialekt `MySql` als dünnes Modul über dem MariaDB-Modul (gemeinsame Nahtstellen, eigene Kennung im Protokoll): wählbar je Port oder je Anmeldung; meldet sich als `8.4.x` (Versionszeichenkette, Fähigkeits-Bits wie am Orakel gemessen, Query Attributes). Abweichungen als Eigenschaften der Dialektbeschreibung, kein zweiter Leser. **Stand:** `MySqlDialekt` (Kennung 3, seit MY30.2b direkt auf dem Kern wie `MariaDialekt`), Port `KUONENSQL_MYSQL_PORT` (aus), Gruss wie gemessen (`8.4.11-KuonenSQL`, Bit 0 + `CLIENT_QUERY_ATTRIBUTES`, Sortierung 255, Reservebytes leer, `caching_sha2_password`), Query Attributes bei COM_QUERY und COM_STMT_EXECUTE angenommen, `VERSION()`/`@@version`/`@@version_comment` je Dialekt; MySqlConnector-Abnahme (docs/umsetzung.md MY30.2).✅
MY30.2bJeder Dialekt direkt auf dem KernVorgabe des Nutzers (05.10.2026): jede zusätzliche Datenbank setzt direkt auf KuonenSQL auf. `MySqlDialekt` und `MariaDialekt` erben beide von `Dialekt` (keine Vererbung zwischen Dialekten), Gemeinsames als Eigenschaft in beiden Beschreibungen, als Regel in `MySqlFamilie` und als Leitungsbaustein `IMySqlProtokoll` (Gruss, Version, Port, Katalogform; `MySqlProtokoll.Laufend` statt `MariaDialekt.Laufend`); keine Klassenabfrage mehr, auch nicht im Modul. `DialektArchitekturTests`: Basisklasse jedes Dialekts ist `Dialekt` (Reflexion), Wächter erkennt qualifizierte Klassenabfragen, MySQL trägt jede Eigenschaft von MariaDB ausser den gemessenen Abweichungen (docs/umsetzung.md MY30.2b, docs/neuer-dialekt.md).✅
MY30.3Sortierfolgen utf8mb4_0900`utf8mb4_0900_ai_ci` (Standard von MySQL 8), `_as_cs`, `_as_ci`, `_bin` und die Sprachvarianten nach UCA 9.0.0, Vergleich und Sortierung wie am Orakel; MySQL-Dumps mit diesen Sortierfolgen lassen sich einspielen. **Stand:** die Sortierungen stehen im Kern (`Types.Ucasortierung`, `Types.Ucatabelle`, Daten aus UCA 9.0.0 und CLDR 30) und gelten für jeden Zugang (auch T-SQL `COLLATE utf8mb4_sv_0900_ai_ci`): Vergleich (NO PAD), `ORDER BY`, `GROUP BY`/`DISTINCT`, Index, `LIKE`; am MySQL-Zugang bekommen Textspalten (ausdrücklich, über die Tabelle oder ohne Angabe) und `COLLATE` sie, Literale vergleichen nach der Verbindung; `WEIGHT_STRING`, `STRCMP`, `COLLATION()`. 60 der 64 Sortierungen stimmen an 663 Proben mit dem Orakel. Reste: MY30.3b.✅
MY30.3bReste aus MY30.3(1) `ja` und `zh`, (2) lt/da-Einzelfälle, (3) `SET NAMES … COLLATE`/`SET collation_connection`, (4) `WEIGHT_STRING(… AS {CHAR|BINARY}(n))`. **Stand:** ja und zh aus einem Modell (zh: +A17D ab 1C47, -59E ab FBC0; ja: Kana nach 1FB6, sonst Vorgewicht FB86) und den am Orakel gemessenen Gewichten jedes abweichenden Codepunkts samt den Kontextpaaren (`tools/mysql/uca0900-gemessen.py`, 103 284 Einträge), vierte Stufe für `ja_0900_as_cs_ks`; alle 155 063 vergebenen Codepunkte stimmen unter zh, ja, ja_ks und der Wurzel (U+FDFA: höchstens 8 Gewichte); lt (`=` mit Primärgewicht 0) und da (`þ/h`) wie gemessen; die Sortierung der Verbindung je Sitzung; `WEIGHT_STRING` mit `AS CHAR(n)`/`AS BINARY(n)`, `utf8mb4_bin` drei Bytes, Zahlen. Alle 64 Sortierungen an den 663 Proben ohne Ausnahme (`Uca0900Tests`). Reste: MY30.3c.✅
MY30.3cGewichte der übrigen Sortierungen`WEIGHT_STRING` und der Vergleich für die Sortierungen, die keine nach UCA 9.0.0 sind: `utf8mb4_general_ci` (am Orakel 16 Bit: Grossbuchstabe ohne Akzent, ß/ſ → 0053, µ → 039C, Kyrillisch behält den Akzent, ausserhalb der BMP FFFD; mit Leerzeichen aufgefüllt), `utf8mb4_unicode_ci` (UCA 4.0.0: a 0E33, ß 0FEA 0FEA), latin1 und die übrigen Zeichensätze; bei MariaDB `uca1400` (UCA 14, Rohdaten allkeys 14.0 nötig). Dazu `WEIGHT_STRING(_binary…)` (der Leser lässt den Einleiter weg) und ein Datum (am Orakel `DATE` 99B2440000000000). Bis dahin wiegt eine solche Sortierung mit der UCA-Sortierung derselben Art. **Teil 1 erledigt 21.10.2026** (docs/umsetzung.md ): `WEIGHT_STRING` eines Zeitwerts wie am Orakel - MySQL gepackt (Datum (Jahr·13+Monat)·32+Tag, 17 Bit Uhrzeit, 24 Bit Mikrosekunden, Vorzeichen gekippt; `TIME` auch negativ und über 24 h), MariaDB die Bytes des Texts; eine vorzeichenlose Zahl ohne Kippen (MySQL), bei MariaDB jede Zahl NULL; `AS BINARY(n)` eines Zeitwerts die Bytes seines Texts; `_binary` war schon richtig. **Rest → MY30.3d.**✅
MY30.3dRest aus MY30.3c: Gewichte und Vergleich der Sortierungen, die keine nach UCA 9.0.0 sind`utf8mb4_general_ci` (16 Bit, Grossbuchstabe ohne Akzent, ß/ſ → 0053, µ → 039C, ausserhalb der BMP FFFD, PAD SPACE), `utf8mb4_unicode_ci` (UCA 4.0.0: a 0E33, ß 0FEA 0FEA), `utf8mb4_unicode_520_ci`, die Sprachsortierungen `utf8mb4_<sprache>_ci`, latin1 und die übrigen Zeichensätze; bei MariaDB `uca1400` (Rohdaten allkeys 14.0, Unicode-Lizenz). Die Gewichte lassen sich je Codepunkt am Orakel messen (`WEIGHT_STRING(CHAR(n USING utf32) COLLATE …)` über alle 65 536 der BMP) und als Tabelle ablegen. **Vor dem Bau ein Entscheid:** (1) der Name der Sortierung geht heute bei der Übersetzung verloren (`MySqlFamilie.SortierungFuer` bildet alles ausser `0900` auf `Latin1_General_*` ab), (2) `utf8mb4_general_ci` ist die Vorgabe vieler MariaDB-Spalten - der Vergleich nach der Tabelle trifft jede MariaDB-Abfrage (Verhalten und Leistung, eigene Messung). Dazu: `YEAR()` ist bei MySQL vorzeichenlos (am Orakel `WEIGHT_STRING(YEAR(…))` 00000000000007E8, hier gekippt).⬜
MY30.4y`CAST(… AS DATETIME(n))` verliert den BruchteilBefund 21.10.2026 (MY30.3c): am MySQL-Zugang ist `CAST('2024-01-02 03:04:05.123456' AS DATETIME(6))` `2024-01-02 03:04:05` (am Orakel `.123456`), `AS DATETIME(3)` ebenso ohne `.123`; `TIMESTAMP'… .123456'` behält ihn. Die Genauigkeit der Zielangabe in die Übersetzung nehmen (Rundung wie am Orakel messen).⬜
MY30.4JSON wie MySQLEigener Typ JSON (nicht LONGTEXT) mit Normalform, Operatoren `->` und `->>`, `JSON_VALUE … RETURNING`, mehrwertige Indizes `CAST(… AS … ARRAY)`, `MEMBER OF`; Ausgabe und Vergleich wie am Orakel. Umgesetzt in MY30.4a–e, Reste als MY30.4f und MY30.4g.✅
MY30.4aJSON-Kern MySQL (ohne Dialekt)`MySqlJson` über dem vorhandenen Leser (`Json.Lies`, Zahlen roh): Normalform (Schlüssel nach Länge, dann Bytes, letzter gewinnt, `", "`/`": "`), Zahlform (BIGINT/UNSIGNED, sonst DOUBLE kürzest, ausgeschrieben für Exponent −15…14), Tiefe 100 (3157), `JSON_TYPE`, `JSON_STORAGE_SIZE` (Binärform klein/gross aus Messungen), Vergleich, Sortierung (Behälter nur nach Anzahl), Gleichheitsschlüssel, `MEMBER OF`. Messreihe in `docs/umsetzung.md`, 105 Tests.✅
MY30.4bTyp JSON im MySQL-Dialekt`JSON` im Dialekt MySQL ist der eigene Typ `mysql_json` (Kennung 232, über `MySqlJson`, Schalter `Dialekt.JsonWieMySql`): Spalte, `CAST(… AS JSON)`, Zuweisung nur aus Text (sonst 3140 „not a JSON text, may need CAST“), Vergleich mit SQL-Werten (Text als JSON-Zeichenkette, Zahl als Zahl), `GROUP BY`/`DISTINCT` nach JSON-Gleichheit, `ORDER BY` mit Behältern nur nach Grösse, `CAST(j AS SIGNED`, `DECIMAL` oder `CHAR)`; Fehler 3140/3141 mit Grund und Bytestelle wie MySQL, 3146, 3157, `DEFAULT '…'` 1101, Index auf der Spalte 3152, `JSON(10)`/`CHARACTER SET` 1064; Leitung Typ 245, Zeichensatz 63, Flags `BLOB BINARY` (Text- und Binärprotokoll), `SHOW CREATE`/`information_schema` `json`; `JSON_TYPE`, `JSON_STORAGE_SIZE`, `MEMBER [OF]`. Im MariaDB-Dialekt bleibt JSON LONGTEXT.✅
MY30.4c`->`, `->>`, `JSON_VALUE … RETURNING`Pfade von MySQL (`MySqlJsonPfad`: `.name`, `."name"`, `.*`, `[n]`, `[last-n]`, `[m to n]`, `[*]`, `**`, Fehler 3143 mit Stelle), `JSON_EXTRACT` (ein oder mehrere Pfade), `JSON_UNQUOTE`, `->`/`->>` nur hinter Spaltennamen (sonst 1064), `JSON_VALUE` mit `RETURNING` (CHAR, BINARY, SIGNED, UNSIGNED, DECIMAL, DOUBLE, FLOAT, DATE, DATETIME, TIME, YEAR, JSON), `ON EMPTY`/`ON ERROR` mit NULL/ERROR/DEFAULT, Fehler 3966, 3967, 1690, 1292 wie gemessen.✅
MY30.4dMehrwertige Indizes`CAST(… AS typ ARRAY)` als Schlüsselteil in `CREATE TABLE`, `CREATE INDEX`, `ALTER TABLE ADD [UNIQUE] INDEX` (verborgene berechnete Schlüsselspalte, `DROP INDEX` räumt sie ab), Fehler 3751/3752/3903/3904/3907/3908 je Element, `UNIQUE` mit 1062 `'["b", "x"]' for key 'u.uk'` (MySQL nennt die Tabelle nun bei jeder Doppelung), 1235 ausserhalb eines Index und für DOUBLE/YEAR/JSON/CHAR ohne Länge/zwei Teile; `SHOW CREATE`, `STATISTICS.EXPRESSION`; `JSON_OVERLAPS`. Die Suche über den Index: MY30.4h.✅
MY30.4eJSON aus SQL-Werten`CAST(1.50 AS JSON)` → `1.50` (`JSON_TYPE` DECIMAL), Vergleich als `true`, Datum/Zeit als `"2024-01-02 03:04:05.000000"`, Bytes als `"base64:type15:…"` wie am Orakel.✅
MY30.4fJSON-Reste Leitung und Warnung`ORDER BY` über Felder oder Objekte meldet einmal Warnung 1235 „sorting of non-scalar JSON values“ (`GROUP BY`, `DISTINCT`, `MAX` nicht, am Orakel); ein JSON-Ausdruck (kein Spaltenwert) meldet im Spaltenkopf JSON, 4294967292, `BINARY` und die Sortierung der Verbindung, `->>`/`JSON_UNQUOTE` `LONG_BLOB` 4294967295; `CREATE TABLE … AS SELECT j->…` legt eine JSON-Spalte an. Der Text von `->` in `SHOW CREATE`: MY30.4j (berechnete Spalten) und MY30.4k (Sichten).✅
MY30.4gEigene Arten im JSON-WertDECIMAL (auch in kürzester Form), DATE, TIME, DATETIME, Bytes (BLOB, BIT) und kleine UNSIGNED als eigene Arten der Normalform (Marke `\u0001` in der Speicherform, `MySqlJson.Ausgabe` vor Leitung, `CAST … AS CHAR`, `CONCAT`/`LENGTH`): `JSON_TYPE`, `JSON_STORAGE_SIZE` (Datum 11, DECIMAL nach Binärform des Handbuchs, Konstante/Ausdruck), Ordnung (`true < DATE < TIME < DATETIME < BIT < BLOB`, nie gleich der Zeichenkette, gleich dem SQL-Datum), `DISTINCT`. Die Suche über den mehrwertigen Index: MY30.4h.✅
MY30.4hSuche über den mehrwertigen Index`MEMBER OF`, `JSON_CONTAINS` und `JSON_OVERLAPS` mit dem Ausdruck eines mehrwertigen Index lesen über ein Elementverzeichnis (Schlüssel → Zeilen, mitgetragen bei Einfügen/Ändern/Löschen/Rücknahme) statt über alle Zeilen; 20 000 Zeilen: 265 ms → 0,44 ms (`docs/leistung.md` §9).✅
MY30.4iJSON-Bauer mit eigenen Arten`JSON_ARRAY`, `JSON_OBJECT` im Dialekt MySQL als Typ JSON (`JSON_SET`/`JSON_INSERT`/`JSON_REPLACE`/`JSON_REMOVE` stehen seit MY30.7), SQL-Werte als eigene Arten (`JSON_ARRAY(DATE…)` `JSON_TYPE` des Elements DATE, `JSON_STORAGE_SIZE(JSON_ARRAY(1.5))` 14, am Orakel gemessen). **Stand:** schon mit MY30.7b erfüllt, jetzt mit Messreihe und Tests belegt (docs/umsetzung.md MY30.4i); Reste in MY30.4l–n.✅
MY30.4jBerechnete Spalten MySQL/MariaDB`typ [GENERATED ALWAYS] AS (ausdruck) [VIRTUAL/STORED/PERSISTENT]` zusammengeführt mit J26.6g (dessen Leseweg `LiesGenerierung`/`TypVereinbart` bleibt); `INSERT`/`UPDATE` mit 3105, 1136 (Werteliste zählt die berechneten Spalten, nur `DEFAULT`), 3763 (`RAND`); `SHOW CREATE TABLE` `GENERATED ALWAYS AS (json_unquote(json_extract(`j`,_utf8mb4'$.v'))) VIRTUAL`, `information_schema.COLUMNS.GENERATION_EXPRESSION`/`EXTRA`, `DESCRIBE` wie am Orakel.✅
MY30.4kSichttext wie MySQL`SHOW CREATE VIEW` und `information_schema.VIEWS.VIEW_DEFINITION` schreiben die Abfrage umgeformt (`select json_extract(`g`.`j`,'$.v') AS `a`,… from `g``), nicht den eingegebenen Text. **Stand:** `MySqlSichttext` für die gemessenen Formen (46 Sichten am Orakel, docs/umsetzung.md MY30.4k); Rückfall auf den Wortlaut bei den übrigen: MY30.4o/p.✅
MY30.4oSichttext: weitere FormenFenster- und Gruppenfunktionen, `WITH` (CTE), `CAST`/`CONVERT` mit dem Zeichensatz einer anderen Verbindung als utf8mb4 (`cast(… as char charset latin1)`; mit utf8mb4 seit MY30.4p), `GROUP_CONCAT … separator ','`, `TIMESTAMPDIFF`, `DATE_ADD … interval`, `EXTRACT`, `EXCEPT`/`INTERSECT`, `ORDER BY`/`LIMIT` an einer Mengenoperation, `USING`, Hex- und Gleitkommazahlen, Komma vor `JOIN … ON`, weitere Funktionsnamen: der Text kommt aus `MySqlSichttext` noch nicht (Rückfall auf den Wortlaut des Nutzers), am Orakel je Form zu messen. **Stand 16.10.2026:** erfüllt - 24 weitere Sichten am Orakel (t56–t79, `MySqlSichttextTests`, docs/umsetzung.md MY30.4o): Fenster samt `WINDOW`-Klausel (nur die benutzten), `GROUP_CONCAT` (mit der Eigenheit von MySQL, in jeder Sortierstelle den letzten Ausdruck zu schreiben), `STD`/`VARIANCE`/`STDDEV_SAMP`/`VAR_SAMP`, `WITH` (auch mit Spaltenliste und in einer Unterabfrage), `CAST … CHARSET`/`NCHAR`, `CONVERT … USING utf8` (`utf8mb3`), `TIMESTAMPDIFF`, `DATE_ADD`/`DATE_SUB`/`ADDDATE`/`TIMESTAMPADD`/`± INTERVAL`, `EXTRACT`, `EXCEPT`/`INTERSECT [ALL]` (INTERSECT geklammert), `ORDER BY`/`LIMIT` an einer Mengenoperation, Hexliterale, Komma vor `JOIN … ON`, `TRIM`/`INSTR`/`LOCATE`/`DATE`. Im Kern dazu: `TIMESTAMPDIFF` zählt ganze Einheiten, `STD`/`STDDEV`/`VARIANCE` und `EXCEPT ALL` bei MySQL/MariaDB, `WITH` in Sicht und `IN (…)`, `latin1` ist cp1252, eine abgeleitete Tabelle mit `WITH` schickt kein zweites Ergebnis mehr (`MySqlSichtformenKernTests`). Rest: MY30.4x.✅
MY30.4xRest aus MY30.4o: seltene Sichtformen**Teil 1 erledigt 17.10.2026:** der Leser nimmt `INTERVAL n einheit + datum` (beide Server), `CAST(… AS CHAR(n) ASCII/UNICODE)` (latin1/ucs2) und bei MySQL `RANGE INTERVAL n einheit PRECEDING/FOLLOWING` über einer Datums-/Zeitspalte (Monate nach dem Kalender; 3589/3588 bei falscher Spaltenart, MariaDB 1064) - Werte wie am Orakel. **Teil 2 erledigt:** der Sichttext dieser Formen und von rekursiver CTE (`with recursive`, Spalten ohne Alias nach dem Wortlaut jedes Glieds), `CAST(… AS BINARY(n))` (`char(n) charset binary`), `INTERSECT` am Anfang (MySQL geklammert), `RANGE` mit Intervall; bei MariaDB CTEs (`name(`a`) as (…)select`, das erste Glied mit den Spalten der Liste), Fenster (aufgelöst, eigene Abstände), `EXCEPT`/`INTERSECT` und ein `INTERSECT` weiter hinten als abgeleitete Tabelle `__n` - 22 bzw. 21 Sichten wie am Orakel.✅
MY30.4pLeser: Formen von MySQL`a IS [NOT] TRUE/FALSE`, `TRUE`/`FALSE` (auch in einer Sicht), `a MOD 2` (auch `a MOD (b)`) und `CONVERT(a, CHAR)` samt jedem Zieltyp von `CAST` (`CHAR(n)`, `DECIMAL(p,s)`, `DATE`, `DATETIME`, `TIME`, `BINARY`, `JSON`, `FLOAT`, `DOUBLE`, `SIGNED`/`UNSIGNED`) liest der Leser jetzt in MySQL und MariaDB auch ohne den Lexer des Anschlusses; `TRUE`/`FALSE` merken sich ihr Wort (`LiteralExpression.Schluesselwort`), und `MySqlSichttext` schreibt sie wie MySQL um (`true AS t`, `((0 <> a) is true)`, `(a % 2)`, `(a DIV 2)`, `cast(a as char charset utf8mb4)`, `convert(x using utf8mb4)`, `DATE'…'`). **Stand:** erfüllt (docs/umsetzung.md MY30.4p; 45 Fälle beider Dialekte, Sichten t49–t54 gegen das Orakel, Sabotage je Zweig). Der Sichttext für `NCHAR`, `BINARY(n)`, `TIME(n)` und eine Verbindung mit anderem Zeichensatz als utf8mb4 bleibt in MY30.4o.✅
MY30.4lVorzeichenlose Funktionsergebnisse`YEAR()` und `CRC32()` liefern am Orakel `UNSIGNED INTEGER` im JSON (`JSON_TYPE(JSON_EXTRACT(JSON_ARRAY(YEAR(…)),'$[0]'))`), im Spaltenkopf das Flag UNSIGNED (`YEAR` Typ 13/Länge 4, `CRC32` LONGLONG 10) und rechnen ohne Vorzeichen (`YEAR(d) - 3000` ist 1690). **Stand:** erfüllt im Dialekt MySQL (Eigenschaft `JahrUndPruefsummeOhneVorzeichen`, docs/umsetzung.md MY30.4l; 3 Tests, Sabotage); Reste (Spaltentyp in `CREATE TABLE … AS SELECT`, MariaDB-Anschluss) in MY30.4q.✅
MY30.4mDECIMAL-Spalte im JSON`JSON_STORAGE_SIZE(CAST(n AS JSON))` einer `DECIMAL(5,2)`-Spalte ist 8 (`JSON_ARRAY(n)` 15): die Spalte misst nach der deklarierten Genauigkeit (führende Neunerwörter, die der Wert nicht füllt, entfallen), ein Ausdruck (`CAST(2 AS DECIMAL(5,2))` 10) nach Neunergruppen. **Stand:** erfüllt (`SqlTypeSpec.AusSpalte` trägt die Herkunft des Werts aus der Ablage, docs/umsetzung.md MY30.4m; 5 Werte × 10 Spalten, Ableitung, Sicht, neue Tabelle, `-n`; Sabotage); Rest (`ABS`, `COALESCE`, `IFNULL`, `CASE` geben die Herkunft weiter) in MY30.4q.✅
MY30.4nZeitliterale`DATE'2024-01-02'`, `TIME'…'`, `TIMESTAMP'…'` (auch mit Leerzeichen) lesen MySQL und MariaDB streng: eine falsche Form ist 1525 `Incorrect DATE value`, `TIMESTAMP` meldet `DATETIME`; Formen, Bereich, Rundung (MySQL) und Abschneiden (MariaDB) der Sekundenbruchteile, Nulldatum nach `sql_mode`, Text in der Sicht (`DATE'2024-01-02'`). **Stand:** erfüllt (`MariaZeitliteral`, `Dialekt.Zeitliterale`; der Lexer des Anschlusses lässt das Wort stehen statt `DATE(N'…')`; docs/umsetzung.md MY30.4n; rund 100 Fälle in beiden Dialekten, Sabotage); Reste in MY30.4r.✅
MY30.4qReste: Herkunft und Typ der Funktionsergebnisse(1) `ABS(n)`, `COALESCE(n, 0)`, `IFNULL(n, 0)` und `CASE … THEN n END` geben die Herkunft eines `DECIMAL` aus einer Spalte weiter (`JSON_STORAGE_SIZE(CAST(ABS(n) AS JSON))` 8 am Orakel); (2) `CREATE TABLE … AS SELECT YEAR(d), CRC32(s)` legt am Orakel `year` und `int unsigned` an; (3) MariaDB-Anschluss: `CRC32()` ist am Orakel (11.8) LONG(10) `UNSIGNED`, `YEAR()` LONG(4) mit Vorzeichen. **Stand:** erfüllt (docs/umsetzung.md MY30.4q): (1) `SqlConvert.Coerce` gibt den Wert einer `DECIMAL`-Spalte unverändert weiter, wenn der gemeinsame Typ nur breiter wird (gleiche Nachkommastellen) — `COALESCE`, `IFNULL`, `CASE`, `IF`, `NULLIF`, `GREATEST`, `LEAST`, `ABS`; (2) `CREATE TABLE … AS SELECT YEAR(d), CRC32(s)` legt bei MySQL `year`/`int unsigned`, bei MariaDB `int(5)`/`int(10) unsigned` an; (3) MariaDB meldet `YEAR()` LONG 4 und `CRC32()` LONG 10 ohne Vorzeichen; 27 Tests, Sabotage; Reste in MY30.4s.✅
MY30.4rReste: Zeitliterale und sql_mode(1) Jahr 0 und Nullteile bei MariaDB (`DATE'0000-01-01'`, `DATE'2024-01-00'`; die Engine kennt kein Jahr 0), (2) `TIME'-00:00:00'` (am Orakel mit Minus), (3) MySQLs `TIMESTAMP'2024-01-02 -03:04:05'`, (4) die ODBC-Schreibweisen `{d '…'}`, `{t '…'}`, `{ts '…'}`, (5) der Vorgabe-`sql_mode` des MySQL-Anschlusses: am Orakel `ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION`. **Stand:** erfüllt, soweit möglich (docs/umsetzung.md MY30.4r): (5) ein frischer MySQL-Anschluss hat den Modus von MySQL 8.4 (eigene Variable `mysql.sql_mode`, Teileliste von MySQL, 1231 für unbekannte Teile bei beiden Zugängen, Nulldatum und Nullteile 1292 mit der Meldung von MySQL); (3) MySQL liest Folgen und führende Satzzeichen in der Uhrzeit; (4) die ODBC-Formen sind am Orakel nicht streng — ein gültiger Text ist das Literal, jeder andere bleibt der Text, jeder andere Schlüssel (`{fn …}`, `{oj …}`, `{d 20240102}`) fällt weg; 12 + 8 + 4 + 4 Tests, Sabotage. Nicht möglich und in MY30.4s: (1) Jahr 0 und Nullteile, (2) `-00:00:00`, dazu `ONLY_FULL_GROUP_BY` (bei MySQL steht der Modus im Text, geprüft wird noch nicht) und die Auflösung von `ANSI`/`TRADITIONAL`.✅
MY30.4sReste aus MY30.4q und MY30.4rERLEDIGT 10.10.2026 (`SqlModusTests`, `DatumAlsZahlTests`, beide Orakel): `sql_mode` steht wie beim Server — Sammelnamen aufgelöst (MySQL `ANSI`/`TRADITIONAL`, MariaDB dazu `MSSQL`, `ORACLE`, `POSTGRESQL`, `DB2`, `MAXDB`, `MYSQL323`, `MYSQL40`), jeder Teil einmal, in der Reihenfolge des Servers (alle Teile rückwärts gesetzt gemessen; allgemein für jede `SET`-Variable), `= DEFAULT` ist die globale Vorgabe; dadurch wirken `TRADITIONAL` (streng) und `ANSI` (`PIPES_AS_CONCAT`) erst wirklich. Der Doppelstrich (zwei senkrechte Striche) ist bei MariaDB und MySQL ein logisches ODER, mit `PIPES_AS_CONCAT` die Verkettung von `CONCAT` (bisher eine Addition, mit einer Zahl Fehler 1105). `DATE`/`TIME`/`DATETIME` rechnen in `+ - * / DIV`, im Vorzeichen und in `SUM`/`AVG` als Zahl (`20240102`, `102030`, `20240517134509.123`). Rest: MY30.4t✅
MY30.4tONLY_FULL_GROUP_BY wie am OrakelERLEDIGT 10.10.2026 (`VolleGruppierungTests`, beide Orakel): `ONLY_FULL_GROUP_BY` mit den Meldungen der Server — MySQL 1140/1055 nach funktionaler Abhängigkeit (Primärschlüssel, eindeutiger Schlüssel ohne NULL, Gleichheit in `ON`/`WHERE`, Ausdruck in `GROUP BY`) samt `ORDER BY`, MariaDB 1140/1055 nur nach den genannten Spalten (`ORDER BY` frei); eine Unterabfrage ohne Quelle nennt die äussere Spalte; der Name in der Meldung mit Datenbank und Alias. Eine Spalte in `HAVING`, die weder gruppiert noch ausgewählt ist, ist bei beiden auch ohne den Modus 1054 (`having clause` bzw. `HAVING`). Bisher prüfte MySQL gar nicht und MariaDB nach T-SQL 8120 (Fehler 1105, deutsch). Rest: MY30.4u✅
MY30.4u`CREATE TABLE … AS SELECT`: NOT NULL und YEARERLEDIGT 10.10.2026 (`TabelleAusAbfrageTests`, beide Orakel): `CREATE TABLE … AS SELECT` legt `NOT NULL` an, wo der Ausdruck nie NULL ist (Konstante, Spalte ohne NULL, `NOW()`/`CURDATE()`) — aus derselben Herkunft wie die Spaltenmerkmale der Leitung (MY30.7d/g) —, und eine `YEAR`-Spalte bleibt `year` bzw. `year(4)`. Bisher war jede Spalte nullbar (die abgeleitete Tabelle wurde vorher zu einer temporären Tabelle). T-SQL `SELECT … INTO` leitet am Orakel nach eigenen Regeln ab (`CAST` nullbar, `ISNULL(x, 0)` nicht) und bleibt vorerst nullbar. Rest: MY30.4v✅
MY30.4vReste aus MY30.4u(1) **→ MY30.4w** (eigener Datumstyp nötig); vorher: Jahr 0 und Nullteile in Datumswerten (`DATE'0000-01-01'`, `'2024-01-00'`, `TIMESTAMP'2024-01-00 01:02:03'`; am Orakel nehmen beide Server sie als Literal an, MariaDB auch in Spalte und `CAST`, MySQL das Jahr 0 mit Monat und Tag; die Engine kennt nur das Nulldatum als Marke in `DateTime`) und `TIME'-00:00:00'` (am Orakel `-00:00:00`); (5) **erledigt 16.10.2026** (`MySqlDezimalmassTests`: Länge und Nachkommastellen von COALESCE/IFNULL, Konstanten, ROUND/TRUNCATE, CAST AS DECIMAL und Produkten wie am Orakel); vorher: (5) die Herkunft bei einem Zweig mit anderen Nachkommastellen (`COALESCE(n, p)` misst am Orakel nach der Spalte des gewählten Werts) und `ROUND`/`TRUNCATE`/`CAST … AS DECIMAL` eines Werts ohne Vorkommastellen (`0.5`: am Orakel 6, hier 10); **(6) erledigt 16.10.2026** (`MySqlCtasTypenTests`: Ganzzahl `int`/`int(n)`, NULL `varbinary(0)`/`binary(0)`, `NOW()` bei MariaDB `timestamp`, Dezimalliterale und `ROUND`/`TRUNCATE`/`CAST … AS DECIMAL`, `COALESCE` über Dezimalspalten, die stillen Vorgaben von MySQL); vorher: Dazu aus (6): die Typen der Literale bei `CREATE TABLE … AS SELECT` (`1` ist bei MySQL `int`, bei MariaDB `int(1)`; `NULL` `varbinary(0)` bzw. `binary(0)`; `NOW()` bei MariaDB `timestamp`) und die impliziten Vorgaben von MySQL (`NOT NULL DEFAULT '0'`).✅
MY30.4wRest aus MY30.4v (1): Datumswerte mit Jahr 0 und NullteilenAm Orakel 16.10.2026 gemessen: MariaDB nimmt `DATE'0000-01-01'`, `DATE'2024-01-00'`, `DATE'2024-00-15'`, `TIMESTAMP'2024-01-00 01:02:03'`, `TIME'-00:00:00'` als Literal, in einer DATE/DATETIME-Spalte (auch im strengen Modus) und mit `CAST` an und ordnet `0000-01-01 < 2024-00-15 < 2024-01-00`; `YEAR`/`MONTH`/`DAY` geben 0, `+ INTERVAL 1 DAY` NULL. MySQL nimmt das Jahr 0 mit Monat und Tag an, Nullteile sind im strengen Modus 1525 (Literal) bzw. 1292 (Spalte), mit `sql_mode=''` gelten sie; `TIME'-00:00:00'` bei beiden. Der Kern legt Datumswerte als .NET-`DateTime` ab (nur das Nulldatum als Marke): Jahr 0, Monat 0, Tag 0 und die negative Null brauchen einen eigenen Datumswert im Kern samt Ordnung, Anzeige, Umwandlung und Datumsfunktionen.⬜
MY30.5Syntax nur MySQL`LATERAL`, `VALUES ROW(…)`, `TABLE t`, funktionale Indizes, unsichtbare Indizes und Spalten, `SET PERSIST`/`PERSIST_ONLY`, `CREATE TABLE … START TRANSACTION`, Rollen mit MySQL-Wirkung; was nur MariaDB kennt (`RETURNING`, Sequenzen, systemversionierte Tabellen) meldet im MySQL-Dialekt den Fehler von MySQL. Notiz aus MY30.2: `/*!NNNNN … */` vergleicht schon mit 80411; `/*M!…*/` (nur MariaDB) ist bei MySQL ein gewöhnlicher Kommentar. **Stand:** MY30.5a–h umgesetzt (Eigenschaft `Dialekt.MySqlSyntax`, docs/umsetzung.md MY30.5), Reste in MY30.5i.✅
MY30.5aWas nur MariaDB kenntIm MySQL-Dialekt wie am Orakel: `RETURNING`, Sequenzen (`CREATE/ALTER/DROP SEQUENCE`, `NEXT VALUE FOR`), `WITH SYSTEM VERSIONING`, `FOR SYSTEM_TIME`, `CREATE OR REPLACE TABLE`, `IGNORED` → 1064 ab dem fremden Wort; `NEXTVAL()`/`LASTVAL()` → 1305; `/*M!…*/` ist Kommentar (bei MariaDB jetzt ausführbar); 1064 nennt `MySQL server version`. Eigenschaft `Dialekt.MySqlSyntax`.✅
MY30.5bLATERAL`, LATERAL (…) AS x`, `[CROSS|INNER|LEFT] JOIN LATERAL (…) AS x [ON …]`, `FROM LATERAL (…)` wie am Orakel: `CROSS APPLY`/`OUTER APPLY`, eine `ON`-Bedingung filtert die seitliche Abfrage (bei `LEFT` bleibt NULL).✅
MY30.5cVALUES ROW und TABLE`VALUES ROW(…), ROW(…)` (Spalten `column_n`) und `TABLE t` mit `ORDER BY`/`LIMIT` als Anweisung, Unterabfrage, `IN (…)`, Mengenoperation, Quelle von `INSERT`/`CREATE TABLE`; `INSERT … VALUES ROW(…)`; Fehler 1064/1136/3942/1146 wie am Orakel.✅
MY30.5dFunktionale Indizes`INDEX name ((ausdruck))` in `CREATE TABLE`, `CREATE INDEX`, `ALTER TABLE … ADD INDEX` als verborgene berechnete Spalte wie bei MySQL; `UNIQUE` wacht über den Ausdruck, `DROP INDEX` nimmt die Spalte mit; `SHOW CREATE TABLE` wie am Orakel; 1064/3762/3758. `EXPRESSION` in `information_schema.STATISTICS`/`SHOW INDEX` gehört zu MY30.6.✅
MY30.5eUnsichtbare Indizes und Spalten`INDEX … INVISIBLE` (CREATE TABLE, CREATE INDEX, ADD INDEX), `ALTER INDEX i VISIBLE|INVISIBLE`: gepflegt und eindeutig, aber nicht im Plan; `b INT INVISIBLE`, `ALTER COLUMN … SET VISIBLE|INVISIBLE`: fehlt in `SELECT *`/`INSERT` ohne Liste; Anzeige in `SHOW CREATE TABLE`/`SHOW COLUMNS`/`information_schema.COLUMNS`; Fehler 3522/1176/4028/1054/1364 wie am Orakel. `IS_VISIBLE`/`Visible` gehört zu MY30.6.✅
MY30.5fSET PERSIST`SET PERSIST`/`PERSIST_ONLY` (auch `@@PERSIST.x`), `RESET PERSIST [IF EXISTS] [x]`, `performance_schema.persisted_variables`; `PERSIST_ONLY` gilt ab dem nächsten Start; Fehler 1193/1238/1232/3615 wie am Orakel. `variables_info` und die MySQL-Vorgaben gehören zu MY30.6.✅
MY30.5gCREATE TABLE … START TRANSACTIONDie Tabelle entsteht in einer eigenen Transaktion, bis `COMMIT`/`ROLLBACK` ist alles andere 3977, `ROLLBACK` nimmt die Tabelle zurück; CTAS 1064, `TEMPORARY` 3979 wie am Orakel.✅
MY30.5hRollen mit MySQL-WirkungMehrere Rollen zugleich (`SET ROLE a, b | ALL [EXCEPT …] | DEFAULT | NONE`), `CURRENT_ROLE()` als `` `r`@`%` ``/`NONE`, `SET DEFAULT ROLE … TO`, Listen in `CREATE`/`DROP ROLE`, `mandatory_roles` (erteilt, 3628 beim Entzug), `activate_all_roles_on_login`; Fehler 3530/3628/3162/1229/1238 wie am Orakel.✅
MY30.5iReste aus MY30.5`CREATE TABLE IF NOT EXISTS` über einer vorhandenen Tabelle mit `START TRANSACTION` beginnt am Orakel ebenfalls den Zustand (hier nicht); `… LIKE … START TRANSACTION` meldet 1064 ab `LIKE` statt ab `START`; `FORCE INDEX`/`USE INDEX` fehlen im Leser ganz (am Orakel: ein unsichtbarer Index darin ist 1176); `INVISIBLE`-Spalten am MariaDB-Port (dort ebenfalls bekannt) nicht gemessen und darum ohne Wirkung. **Stand:** alle vier umgesetzt (docs/umsetzung.md MY30.5i); Reste in MY30.5j.✅
MY30.5jReste der Indexhinweise und unsichtbaren Spalten`FORCE INDEX` ohne brauchbaren Schlüssel (der Planer scheut den Scan nicht), die Wirkung von `FOR ORDER BY`/`FOR GROUP BY` im Plan, `UPDATE t [alias] FORCE INDEX (…) SET …` (am Orakel gültig, hier Syntaxfehler), `ALTER … MODIFY` einer unsichtbaren NOT-NULL-Spalte ohne Vorgabe (MariaDB 4108), ein `ALTER TABLE` mit mehreren Änderungen lässt die erste stehen, wenn die zweite scheitert. **Stand:** erfüllt, soweit möglich (docs/umsetzung.md MY30.5j): `UPDATE` nimmt die Hinweise mit und ohne Alias (und `UPDATE t AS x SET …`, das hier gar nicht ging), 1176/1221/1064 wie am Orakel; `FORCE INDEX` schreibt dem Planer den Durchlauf in der Reihenfolge von `ORDER BY` vor (ein unbrauchbarer Index ändert nichts), `FOR ORDER BY`/`FOR GROUP BY` wirken im Plan (MariaDB: auch `FOR JOIN`); `MODIFY`/`CHANGE` schreiben die Spalte samt Vorgabe neu (hier blieb die alte); MariaDB 4108 bei `MODIFY`/`CHANGE`; 21 Tests je zwei Dialekte, Sabotage. Nicht möglich und in MY30.5k: die atomare Änderungsliste (die Engine nimmt DDL nicht zurück).✅
MY30.5kReste aus MY30.5j(1) **erledigt 16.10.2026:** ein `ALTER TABLE` mit mehreren Änderungen gilt ganz oder gar nicht, an allen Zugängen (`MySqlAlterGanzTests`); vorher: Ein `ALTER TABLE` mit mehreren Änderungen gilt am Orakel ganz oder gar nicht (`ADD g, ADD c` mit doppeltem `c`, `MODIFY c INT, MODIFY nope INT`, `ADD g, DROP COLUMN nope`, `ADD g, ADD INDEX ix (nope)`, `MODIFY a INT INVISIBLE, MODIFY b INT INVISIBLE` mit 1113: nichts ändert sich), hier bleibt die erste Änderung stehen; die Engine nimmt DDL nicht zurück (auch `BEGIN TRAN; ALTER TABLE t ADD z INT; ROLLBACK` in T-SQL lässt `z` stehen), es braucht eine Sicherung der Tabelle (Schema, Zeilen, Indizes, Randdaten) oder eine Vorprüfung aller Schritte; (2) `EXPLAIN`: `possible_keys` bei `GROUP BY` (MySQL nennt `ia,iab`, hier `NULL`), `WHERE a = 2 ORDER BY b` nimmt `iab` ohne `Using filesort` (beide Server, hier `ia` mit Sortieren), `FORCE INDEX (ia) WHERE a > 0` schätzt bei MySQL `rows` 1 (hier 5). **(2) erledigt 16.10.2026:** EXPLAIN wie am Orakel (gemessen über `u (id, a, b, ia (a), iab (a, b))`): MySQL meldet `partitions` und `filtered`; ein `ref` mit festem Wert auf `iab` liefert `ORDER BY b` ohne filesort (MySQL absteigend `Backward index scan`, MariaDB mit `Using where`), `ORDER BY id` sortiert; MySQL schätzt unter `FORCE INDEX` 1 Zeile, liest einen Bereich über alle Zeilen und einen losen Durchlauf ohne Ersparnis als Durchlauf des kleinsten deckenden Index und nennt dann die deckenden Indizes in `possible_keys`; der lose Durchlauf nennt die Länge der Gruppenspalten. Nur die Ausgabe des EXPLAIN - die Ausführung liest weiter, wie sie ordnet. `MySqlExplainRestTests` (18).✅
MY30.6Systemkatalog MySQL`information_schema` mit den Spalten von MySQL 8.4, `performance_schema` und `sys` soweit Werkzeuge sie abfragen, `mysql.user` und Rechte-Tabellen im MySQL-Aufbau, Systemvariablen und Statusvariablen von MySQL. Notiz aus MY30.2: `@@port`/`@@have_ssl` zeigen im MySQL-Dialekt den MariaDB-Hörer (Zugang MariaDb), es fehlen `@@authentication_policy` (`*,,`), `version_compile_machine`/`version_compile_zlib`; `mysql.global_priv` nennt die MariaDB-`version_id`. **Stand:** erledigt in MY30.6a (Katalogaufbau) und MY30.6b (Variablen, Status); `@@have_ssl` gibt es bei MySQL 8.4 nicht (1193, am Orakel), `@@port` zeigt den MySQL-Hörer, `mysql.global_priv` fehlt wie bei MySQL.✅
MY30.6aKatalogaufbau MySQL`MySqlKatalog` schneidet denselben Katalog in den gemessenen Aufbau von MySQL 8.4 (`mysql_katalog.tsv`): alle 78 Sichten von `information_schema` (`COLUMNS` mit `SRS_ID`, `STATISTICS` mit `IS_VISIBLE`/`EXPRESSION`, `TABLE_CONSTRAINTS.ENFORCED`, `*_EXTENSIONS`, Rollensichten), `mysql` (`user` mit 51 Spalten, `role_edges`, `default_roles`, `global_grants`; kein `global_priv`), `performance_schema` (`threads`, `processlist`, `variables_info` …) und `sys` (`version`, `sys_config` …); Werteformen als Eigenschaften des Dialekts (`int` ohne Breite, `CURRENT_TIMESTAMP(3)`, `DEFAULT_GENERATED`, `NO ACTION`, `SHOW INDEX` mit `Visible`). docs/umsetzung.md MY30.6.✅
MY30.6bVariablen und Status MySQLDie 647 Systemvariablen und 336 Statusvariablen von MySQL 8.4.11 (`mysql_systemvariablen.tsv`, `mysql_status.tsv`); Zugang `MySql` im Einstellungsmodell, gleichnamige Variablen sind dieselbe Einstellung wie bei MariaDB, eigene unter `mysql.<name>`; was MySQL nicht kennt, 1193; `@@port` am MySQL-Hörer, `@@authentication_policy`, `version_compile_machine/os/zlib`, `performance_schema` ON. docs/umsetzung.md MY30.6.✅
MY30.7Abnahme mit MySQL-Programmen`mysql`/`mysqldump` 8.4, Connector/J 9.4, MySqlConnector 2.4 und MySql.Data 9.4 (Connector/NET), mysql2 (Node), Django 5.2 mit mysqlclient, Laravel 12 und der Dump einer echten Anwendung (WordPress am Orakel installiert, mit `mysqldump` gezogen, in KuonenSQL eingespielt und Byte für Byte mit dem zweiten Dump verglichen) laufen am eigenen Testserver ohne Eintrag im Fehlerprotokoll (ausser den gewollten Fehlerproben, am Orakel belegt). **Stand 06.10.2026:** `dist\app-abnahme.ps1 -Programm mysql-cli, mysql-netz, mysql-java, mysql-node, mysql-django, mysql-laravel, mysql-dump` (Modus `Client`, `tools\app-abnahme\mysql-*`), 7 Programme, 97 Schritte, alle ✅ (docs/app-abnahme.md); behoben: `PREPARE`/`EXECUTE` auf SQL-Ebene, `SELECT … LIMIT` ohne `FROM`, Literale mit Typ und `{d '…'}`, Rohbytes in `_binary'…'`, MySQL-Sortierungen und Vorgabe `utf8mb4_0900_ai_ci`, Cursor mit `CLIENT_DEPRECATE_EOF`, Info im OK-Paket (längenkodiert), `LIKE BINARY` und der Backslash in `LIKE`, `HAVING` mit Alias, `UPDATE` mit mehreren Klauseln der Reihe nach, `JSON_SET`/`JSON_INSERT`/`JSON_REPLACE`/`JSON_REMOVE`/`JSON_CONTAINS_PATH`, ein gescheitertes `PREPARE` ließ die Verbindung in `SET FMTONLY ON`, Textlängen in Bytes, `Ssl_cipher` (docs/umsetzung.md MY30.7). MySQL Workbench: der Mitschnitt läuft seit MY30.7b, die Bedienung prüft der Nutzer (MY30.7f).✅
MY30.7bMySQL Workbench 8.4Der Mitschnitt von Workbench (Verbinden, Dashboard, Serverstatus, Users and Privileges, 29 verschiedene Anweisungen) läuft ohne Fehler: `performance_schema.events_statements_histogram_global`/`_by_digest` (450 Eimer, Zähler 0), `COALESCE(x)` mit einem Argument, `IFNULL` ohne Abschneiden des Ersatzes, `JSON_ARRAY`/`JSON_OBJECT`/`JSON_ARRAYAGG`/`JSON_OBJECTAGG` als Typ JSON (eingebettet, auch durch abgeleitete Tabellen), die Hilfsfunktionen von `sys` (`format_time`, `format_bytes`, `format_path`, `format_statement`, `quote_identifier`, `extract_*_from_file_name`, `list_add`/`list_drop`, `version_*`, `sys_get_config`, `ps_thread_id`/`ps_thread_account`/`ps_is_*`); Herleitung in `docs/umsetzung.md`.✅
MY30.7cReste aus MY30.7b**Teil 1 erledigt 16.10.2026** (docs/umsetzung.md MY30.7c): `sys.list_drop` mit mehreren Werten und Teilwörtern, `IFNULL(1)` und jede eingebaute Funktion mit falscher Argumentzahl 1582 mit dem getippten Namen, `COALESCE()` bei MariaDB 1582, `COALESCE(x)` bei MariaDB gemessen (1), die serverweiten Rechte des Verwalters (`USER_PRIVILEGES`, Workbench `globalPrivileges`) je Server wie am Orakel. **Teil 2 erledigt 16.10.2026:** `performance_schema.setup_consumers/_instruments/_threads/_actors/_objects` und `threads.INSTRUMENTED` als Zustand des Servers (1260 Messpunkte aus `mysql_messpunkte.tsv`), die Prozeduren `sys.ps_setup_*` (18), `table_exists`, `execute_prepared_stmt`, `create_synonym_db` (mit `SQL SECURITY INVOKER`), `ps_truncate_all_tables` und die Funktionen `ps_thread_stack`/`ps_thread_trx_info`; `ps_is_consumer_enabled` mit Rangfolge. Bewusst anders: `ps_setup_reset_to_default(TRUE)` zeigt am Orakel den SQL-Text der Prozedur (Quelltext von MySQL, GPL) - hier stumm. **Teil 3** in die Zeilen MY30.7c3 bis MY30.7c8 geteilt (17.10.2026); vorher: `sys.ps_thread_stack`/`ps_thread_trx_info` (Fadenverlauf, hier ohne Ereignisse), die `sys`-Prozeduren (`ps_setup_*`, `diagnostics`, `statement_performance_analyzer` …), `sys.list_drop` mit mehreren Werten oder Teilwörtern (am Orakel nicht auf Eintragsgrenzen), `IFNULL(1)` meldet hier 1105 statt 1582, `globalPrivileges` des Verwalters nennt MariaDBs `DELETE HISTORY` und kennt MySQLs `CREATE TABLESPACE`/`CREATE ROLE`/`DROP ROLE` und die dynamischen Rechte nicht, `COALESCE(x)` am Referenzcontainer MariaDB nicht gemessen (nicht gestartet).✅
MY30.7c3Teil 3 aus MY30.7c: sys-Sichten über die Anweisungsstatistik**Erledigt 17.10.2026:** `sys.statement_analysis` (war leer) und `x$statement_analysis`, `statements_with_runtimes_in_95th_percentile` und `x$…`, `x$ps_digest_avg_latency_distribution`, `x$ps_digest_95th_percentile_by_avg_us` aus der Statistik des Kerns (dieselben Werte wie `events_statements_summary_by_digest`), Aufbau und Regeln am Orakel gemessen: das Mass einer Form `ROUND(AVG_TIMER_WAIT/1e6)` µs, die Grenze das erste Mass, bis zu dem mehr als 95 % der Formen liegen (genau 0.95 reicht nicht), die Liste ab der Grenze nach dem Mittel absteigend, `statement_analysis` nach der Gesamtzeit.✅
MY30.7c4`sys.ps_statement_avg_latency_histogram`Am Orakel gemessen (docs/umsetzung.md MY30.7c Teil 3): eine Spalte LONG_BLOB 67108864, Legende, 16 Eimer mit der Grösse `TRUNCATE(Höchstwert_ms/16, 2)`, Grenzen `ROUND(i·Grösse)`, der Höchstwert selbst fällt hinaus, Zeile `Total Statements: n; Buckets: 16; Bucket Size: g ms;`. Offen sind die Regeln, die am Orakel nicht stetig sind: das Zeichen und die Länge eines Balkens (Eimer 0: `ROUND(n/3)` Zeichen, `.` bis 39, `*` bis 79, sonst `#`; die übrigen Eimer: `.`×n, ausser die Zahl ist als Text ≥ "40" - dann `#`×n/3, wenn Eimer 0 `.` trägt, sonst `*`×n/2; ein zweiter Aufruf zeigte im Eimer 0 bei 4 Formen `....`), die Breite der Beschriftung (meist längste Beschriftung + 1, in drei Messungen eins oder zwei weniger) und der Höchstwert (in vier von sieben Messungen 1-5 ms unter dem grössten Mittel). Vor dem Bau eine Messreihe, die diese Fälle klärt. **Erledigt 21.10.2026** nach einer Messreihe von rund 250 Aufrufen (docs/umsetzung.md ): die Grösse ist (grösstes − kleinstes Mittel)/16 ohne Abschneiden (das erklärt den „Höchstwert unter dem grössten Mittel“), Eimer [i·g, (i+1)·g), `Bucket Size` `ROUND(g)`, Zahlen auf die längste aufgefüllt; die Balken haben einen Stand, der in der Sitzung bleibt: die erste Zeile zeichnet nach der Zahl (40/80), Einheit 3 in einer frischen Sitzung, sonst wie die übrigen; die übrigen vergleichen die Zahl als Text mit „80“ und „40“, die Mitte ist `#`/3 nach einer ersten Zeile mit `.`, sonst `*`/2 (daher der zweite Aufruf mit `....`). Rest → MY30.7c4a.✅
MY30.7c4aRest aus MY30.7c4: Breite der Beschriftung, Spaltentyp(1) Die Breite der Beschriftung ist am Orakel meist die längste Beschriftung + 1, in rund 15 % der Aufrufe eins (einmal zwei) weniger - mit denselben Anweisungen nicht wiederholbar (z. B. Höchstwert 160,218 ms → 13, 160,196 und 160,22 ms → 14); vermutlich hängt sie an Pikosekunden, die nach dem Aufruf nicht mehr zu sehen sind. Weitere Messung mit festen Mitteln (etwa über `performance_schema`-Werte, die sich setzen lassen) oder hinnehmen. (2) Die Spalte ist am Orakel `LONG_BLOB` Länge 16777216 ohne Flags (Sortierung 8), hier `BLOB` 4294967295 mit Flag BLOB: `Systemprozeduraufruf.Menge` kennt nur Textspalten; ein Leitungsmass für Ergebnisse von Systemprozeduren nachrüsten.⬜
MY30.7c5`sys.statement_performance_analyzer`Am Orakel: `snapshot` still, `overall`/`delta` mit den Sichten (`with_runtimes_in_95th_percentile` u. a.) als Ergebnis samt Kopfzeile `Next Output`, `create_tmp`/`create_table`/`save`/`cleanup`, eine unbekannte Aktion 1265 (`Data truncated for column 'in_action'`); dazu die übrigen Sichten der Auswahl (`analysis`, `with_errors_or_warnings`, `with_full_table_scans`, `with_sorting`, `with_temp_tables`, `custom`) aufbauen und messen. **Erledigt 21.10.2026** (docs/umsetzung.md ): alle sieben Aktionen und sieben Sichten wie am Orakel - Schnappschuss je Sitzung, `overall` ohne Tabelle mit frischem, `delta` gegen eine gesicherte Tabelle (Anzahl/Summen als Unterschied), `create_table`/`create_tmp` mit den 39 Spalten von `SHOW CREATE TABLE`, `save`, `cleanup`; `snapshot` ist nicht still, sondern zeigt wie am Orakel `summary`/`Disabled 1 thread` und `Enabled 1 thread` (die Verbindung bleibt nach einem Fehler aus); Titel `Top <limit> …`, `@sys.statement_performance_analyzer.limit`/`.view` (Punkte in Namen von Benutzervariablen gehen jetzt), Fehler 1265/1644/1064 wörtlich; die inneren Schritte zählen nicht in die Statistik. Neue Sichten `sys.statements_with_errors_or_warnings` (und `x$`), `…_full_table_scans`, `…_sorting`, `…_temp_tables` (leer). Rest → MY30.7c5a.✅
MY30.7c5aRest aus MY30.7c5: Zähler, die der Kern je Form nicht misstWarnungen (`SUM_WARNINGS`, `warn_count`, `warning_pct`), Zugriffe ohne (guten) Index (`full_scan` `*`, `SUM_NO_INDEX_USED`), Sortierungen (`SUM_SORT_*`), Zwischentabellen (`SUM_CREATED_TMP_*`), gelesene Zeilen (`SUM_ROWS_EXAMINED`), Sperrzeit - am Orakel gezählt, im Kern 0; darum sind `statements_with_full_table_scans`/`_sorting`/`_temp_tables` leer. Dazu `first_seen`/`last_seen` mit Mikrosekunden (am Orakel `timestamp(6)`, hier ohne Bruchteil). Der Ausführer müsste diese Zähler je Anweisung liefern (Planer: Zugriffsart, Sortierung, Spill).⬜
MY30.7c6`sys.ps_trace_statement_digest`, `sys.ps_trace_thread`Am Orakel: Digest-Spur mit den Ergebnissen `summary`, `SUMMARY STATISTICS` (Zähler NEWDECIMAL 43), Ereignisse je Stufe, `LONGEST RUNNING STATEMENT` mit Plan; Fadenspur `Data collection starting for THREAD_ID = n` und eine .dot-Datei (am Orakel 1290 wegen `--secure-file-priv`). Messen, was der Kern ohne Stufen- und Wartereignisse ehrlich liefern kann. **Erledigt 21.10.2026** (docs/umsetzung.md ): die Digest-Spur zählt die Ausführungen der anderen Verbindungen während der Laufzeit aus dem Kern (ein Beobachter an der Anweisungsstatistik, auch für Verbindungen, die vor dem nächsten Takt wieder weg sind): Summen wie am Orakel (ohne Ausführung 0 und NULL), Stufen leer, die längste Ausführung mit Faden und Text; die Fadenspur sammelt den Anweisungsverlauf des Fadens, solange er gemessen ist, und schreibt den Graphen wie am Orakel (Kopf, je Anweisung ein Knoten mit ihren Zählern, `Stack trace written to`, `Convert to PDF/PNG`); 1064 ohne Datei, 1086 bestehend, 1290 ausserhalb der Freigabe. Das Warten hält andere Verbindungen nicht auf (wie `SLEEP`). Ehrlich leer bzw. 0: Stufen, Wartezeiten, Zugriffe ohne Index, Zwischentabellen (der Kern hat sie nicht; MY30.7c5a). Dabei gefunden: MY30.7zj.✅
MY30.7zj`SELECT … INTO @var` ausserhalb einer ProzedurBefund 21.10.2026 (MY30.7c6): am MySQL-/MariaDB-Zugang setzt `SELECT 1 INTO @v` die Variable nicht (danach `@v` NULL), `SELECT STATEMENT_DIGEST(…) INTO @d` legt sogar eine **Tabelle** `@d` an (später 1050 „Table '@d' already exists“) - der Leser kennt `INTO @var` nur im Rumpf einer Prozedur (`LiesMariaInto`), sonst nimmt die Grammatik von T-SQL `SELECT … INTO tabelle`. Am Orakel `mysql:8.4`: `@v` 1, `SELECT a INTO @w FROM t LIMIT 1` 1, der Digest 64 Zeichen. Bauen: `INTO @a[, @b]` (vor `FROM` oder am Ende) auch auf oberster Ebene als Zuweisung, mehr als eine Zeile 1172, keine Zeile 1329 (Warnung) wie im Rumpf.⬜
MY30.7c7`sys.diagnostics` und die sys-Sichten dazu`diagnostics(max_runtime, interval, auto_config)` braucht viele Sichten von `sys`, die der Kern nicht hat (`metrics`, `io_global_by_*`, `schema_*`, `memory_*`, `wait_*` …; vorhanden sind 12 der rund 100). Sichten nach dem Handbuch aufbauen, je am Orakel messen, dann die Ausgabe der Prozedur. **Teil 1 erledigt 21.10.2026** (docs/umsetzung.md ): alle 101 Sichten von `sys` (1019 Spalten, aus `information_schema.COLUMNS` des Orakels) gibt es jetzt; mit Daten des Kerns `metrics` (globale Status, Speicher, Zeit), `schema_object_overview`, `schema_auto_increment_columns`, `x$schema_flattened_keys`, `schema_redundant_indexes` (Regeln gemessen), `memory_global_total`, `session_ssl_status`, dazu die aus MY30.7c3/c5. Die übrigen sind leer, wo der Kern die Daten nicht führt. **Rest → MY30.7c7a.**✅
MY30.7c7aRest aus MY30.7c7: `sys.diagnostics` und die übrigen Sichten mit Daten(1) `sys.diagnostics(max_runtime, interval, auto_config)`: Ausgabe am Orakel messen (mehrere Abschnitte je Durchgang) und über die vorhandenen Sichten nachbauen. (2) Sichten, die heute leer sind, obwohl der Kern Daten hat: `processlist`/`x$processlist`/`session`/`x$session` (die Verbindungen; am Orakel steht die eigene darin), `host_summary*`/`user_summary*` (Anweisungen je Benutzer und Rechner - die Anweisungsstatistik müsste nach Konto zählen), `schema_table_statistics`/`schema_index_statistics` (Zugriffe je Tabelle/Index). (3) Ehrlich leer, solange der Kern es nicht misst: Datei-E/A, Wartezeiten, Stufen, InnoDB-Puffer, Speicher je Faden. (4) `information_schema.STATISTICS.SUB_PART` fehlt für Präfixindizes (`KEY k5 (z(5))`: am Orakel 5, `subpart_exists` 1 in `x$schema_flattened_keys`). (5) `session_ssl_status`: TLS-Fassung und Verfahren je Verbindung.⬜
MY30.7c8Rest aus MY30.7c3: DIGEST_TEXT wie MySQLDie Normalform des Kerns schreibt `SELECT a FROM t WHERE a < ?` und `INSERT INTO t VALUES (?), (?), (?)`; MySQL `SELECT \`a\` FROM \`t\` WHERE \`a\` < ?` und `INSERT INTO \`t\` VALUES (?) /* , ... */` (Gravis, Leerzeichen um Satzzeichen, mehrere Zeilen gefaltet) - damit auch eine andere Kennung (SHA-256 der Normalform). Am MySQL-Zugang die Form von MySQL zeigen, ohne die Zählung je Form im Kern zu verdoppeln. **Erledigt 21.10.2026** (docs/umsetzung.md ): `events_statements_summary_by_digest`, `…_histogram_by_digest` und die `sys`-Sichten zeigen die Form von MySQL (`MySqlDigest`, je Eintrag einmal gerechnet) und zählen die Formen des Kerns, die bei MySQL eine sind, für die Sicht zusammen (`Eintrag.Fuege`); der Kern zählt weiter einmal je eigener Form. `DIGEST` = `STATEMENT_DIGEST(QUERY_SAMPLE_TEXT)` wie am Orakel. Dabei gefunden: einwertige Zeilen `(?) , (?)` falten am Orakel zu `(?) /* , ... */` (auch in `STATEMENT_DIGEST_TEXT` korrigiert). Abweichung: ein Optimierer-Hinweis allein trennt die Form am Orakel, hier nicht (der Kern verwirft Kommentare).✅
MY30.7dHerkunft der Spalten in der ErgebnisbeschreibungDie Spaltendefinition einer Abfrage nennt bisher weder Tabelle noch Original noch die Merkmale der Spalte: Datenbank, Tabelle (Alias) und Originaltabelle, Originalname und die Flags `NOT_NULL`, `PRI_KEY`, `UNIQUE_KEY`, `MULTIPLE_KEY`, `AUTO_INCREMENT`, `NO_DEFAULT_VALUE`, `PART_KEY` für eine Spalte, die eine Tabellenspalte ist (am Orakel mit Connector/J, mysql2 und `mysql --column-type-info` gemessen; ein Ausdruck trägt nichts davon). Gebraucht von mysql2 `nestTables`, `isAutoIncrement`/`isNullable`/`getTableName` von Connector/J, den Ergebnisgittern von MySQL Workbench und DBeaver (Zeilen ändern) und DataTable (`AllowDBNull`, `IsKey`); MariaDB teilt die Lücke. **Stand 07.10.2026:** erledigt für MySQL und MariaDB, Textprotokoll und vorbereitete Anweisungen (`COM_STMT_PREPARE` und Ergebnis): Datenbank, Alias, Originaltabelle, Originalname und `NOT_NULL`/`PRI_KEY`/`UNIQUE_KEY`/`MULTIPLE_KEY`/`AUTO_INCREMENT`/`NO_DEFAULT_VALUE`/`PART_KEY` (Verbund mit `NOT NULL` weg auf der NULL-Seite, Alias, andere Datenbank, Sicht, abgeleitete Tabelle, CTE; die zwei Unterschiede der Server als `Dialekt.SpaltenherkunftWieMySql`), die Herkunft einmal je Abfrage im Kern (`ExecutionEngine.Spaltenherkunft.cs`), der Anschluss schreibt sie; `MariaDbSpaltenherkunftTests` (16), Abnahme `mysql-node` (`nestTables`), `mysql-java` (`isAutoIncrement`/`isNullable`/`getTableName`), `mysql-netz` (`GetSchemaTable`); Rest MY30.7g (docs/umsetzung.md MY30.7d).✅
MY30.7gReste der Spaltenherkunft`NOT_NULL` der Ausdrücke (am Orakel `1`, `a + 1`, `COUNT(*)` NOT NULL, `MAX(b)` nullbar), die Typmerkmale `TIMESTAMP`/`ON_UPDATE_NOW`/`ZEROFILL` an Tabellenspalten (und bei MariaDB `UNSIGNED` an `TIMESTAMP`), `UNION` (MySQL `orgName` = Name, MariaDB mit `PART_KEY`), `DISTINCT`/`GROUP BY` mit temporärer Tabelle, eine Sicht mit `GROUP BY`/`DISTINCT` (TEMPTABLE), die Herkunft von `SHOW`/`DESCRIBE` und `information_schema`, `SELECT 1 FROM DUAL`. **Stand 07.10.2026:** erledigt: `NOT_NULL` der Ausdrücke (Regeln je Operator und Funktion, durch Sichten und abgeleitete Tabellen), `TIMESTAMP` als Typ 7 mit den Flags `TIMESTAMP`/`ON_UPDATE_NOW` (MariaDB auch `UNSIGNED`), `UNION`/`UNION ALL`/`INTERSECT`/`EXCEPT` (Name, NOT_NULL, MariaDB `PART_KEY`), Sicht mit `GROUP BY`/`DISTINCT`/Aggregat/Begrenzung als Tabelle für sich (NOT_NULL und NO_DEFAULT_VALUE bleiben; die Annahme von 7d, NO_DEFAULT_VALUE falle weg, war falsch), `FROM DUAL`; `MariaDbSpaltenherkunftRestTests` (10). Rest MY30.7i und MY30.7j (docs/umsetzung.md MY30.7g).✅
MY30.7i`ZEROFILL`Die Spalte `INT(5) ZEROFILL` trägt am Orakel UNSIGNED und ZEROFILL (0x40), die Länge ist die Anzeigebreite (ohne Angabe die des vorzeichenlosen Typs), und der Server füllt ihre Werte im Textprotokoll mit Nullen auf (`00001`; `DECIMAL(6,2) ZEROFILL` `0001.50`, Länge 7). **Stand:** ZEROFILL macht die Spalte vorzeichenlos, die Breite steht als Marke mit `Z` an der Spalte (`Types.Ganzzahlbreite`, auch an `DECIMAL`), Flag und Länge in der Spaltendefinition, Auffüllen der Spaltenwerte im Textprotokoll (je Ergebnis einmal bestimmt), `SHOW CREATE TABLE` und `COLUMN_TYPE` mit Breite und `unsigned zerofill` bei MySQL und MariaDB (`MariaDbSpaltenbeschreibungTests`). Reste: MY30.7l.✅
MY30.7lReste aus MY30.7iDer Wert einer ZEROFILL-Spalte füllt am Orakel auch als Text auf (`CONCAT(a)`, `CAST(a AS CHAR)`, `LENGTH`, `LIKE`, `UPPER`), `MAX`/`MIN`, abgeleitete Tabelle und Sicht bleiben ZEROFILL-Spalten; `IFNULL`, `COALESCE`, `CASE`, `-a`, `DIV`, `SUM`, `UNION`, skalare Unterabfrage und `INSERT … SELECT` in eine Textspalte nehmen die Zahl (beide Server gleich). **Stand:** der Spaltenwert trägt die Marke (`MemoryTable.Ganzzahl`/`Dezimaltyp`, auch `TINYINT`/`SMALLINT`), `SqlConvert.FormatValue` füllt auf, die Zuweisung (`Coerce` in einen Texttyp), `CASE`, `UNION` und die skalare Unterabfrage nehmen sie ab, die Sicht reicht sie durch (`MariaDbSpaltenbeschreibungTests.ZEROFILL_gilt_auch_im_Text_des_Spaltenwerts`). Reste: MY30.7m.✅
MY30.7mReste aus MY30.7lERLEDIGT 10.10.2026 (`JsonFeldTests`, beide Orakel): eine ZEROFILL-Spalte in JSON — MariaDB aufgefüllt (`[00001, 0001.50]`, `{"x": 00001}`, Aggregate `{"x":0001.50}`), MySQL nicht (`[1, 1.50]`); es war vertauscht. Dabei gefunden und behoben: `JSON_ARRAY_APPEND` und `JSON_ARRAY_INSERT` fehlten an beiden Zugängen (1305), jetzt mit den Abweichungen der Server (fehlender Pfad: MySQL unverändert, MariaDB NULL; `$[last]` beim Einfügen davor bzw. dahinter; Pfad ohne Feldindex 3165 bzw. NULL); `JSON_INSERT`/`JSON_SET` mit einem vorzeichenlosen Wert (UNSIGNED, ZEROFILL) scheiterten am MySQL-Zugang mit 3141 (die Normalform wird jetzt am Baum gebildet). Rest: MY30.7t✅
MY30.7tReste aus MY30.7mDas Binärprotokoll (ein `DECIMAL` mit ZEROFILL geht als Text: aufgefüllt? mit einem Treiber messen); `DECIMAL(p,s) UNSIGNED` ohne ZEROFILL meldet das Flag UNSIGNED und eine Stelle weniger noch nicht (am Orakel messen); `CONCAT((SELECT a …))` und Ausdrücke über `MAX(a)` nicht gemessen. Dazu: ein `FLOAT(7,2) ZEROFILL` in JSON bei MariaDB `0002.25` (hier `2.25`, der Gleitkommawert trägt die Füllmarke nicht); `JSON_INSERT`/`JSON_SET` mit einem ZEROFILL-Wert gibt am MariaDB-Orakel NULL. **Stand 17.10.2026:** erfüllt (docs/umsetzung.md MY30.7t): `DECIMAL(p,s) UNSIGNED` meldet UNSIGNED und eine Stelle weniger; `CONCAT((SELECT a …))` zählt wie die Spalte (Ganzzahl mit der Breite ihres Typs); Rechnungen über `MAX`/`MIN` wie über die Spalte (MariaDB auch mit BINARY); Summe/Differenz mit einer Kommazahl (beide Server: Ganzstellen max + 1, `-d` bei MariaDB eine Stelle mehr ausser UNSIGNED); MariaDB `JSON_INSERT`/`JSON_SET`/`JSON_REPLACE` mit aufgefülltem Wert NULL; das Binärprotokoll schickt `DECIMAL ZEROFILL` aufgefüllt (am MySQL- und MariaDB-Orakel mit dem eigenen Treiber gemessen, KuonenSQL schon so). Rest: MY30.7z (`FLOAT(M,D)`/`DOUBLE(M,D)` und ZEROFILL an Gleitkomma), MY30.7za (JSON-Maß und mehrere Pfade bei MariaDB).✅
MY30.7zRest aus MY30.7t: `FLOAT(M,D)`, `DOUBLE(M,D)` und ZEROFILL an GleitkommaAm Orakel (beide Server gleich): `FLOAT(7,2)` ist FLOAT 7 mit 2 Nachkommastellen und zeigt `2.50` (hier FLOAT 12/22 mit 31 und `2.5`), `DOUBLE(8,3) ZEROFILL` DOUBLE 8/3 `UNSIGNED ZEROFILL` `0001.500`, `FLOAT ZEROFILL` FLOAT 12/31 `0000000003.5`; `COLUMN_TYPE` `float(7,2) unsigned zerofill`, `double(8,3) unsigned zerofill`, `float unsigned zerofill` (hier `float(7,2)`, `double unsigned`, `float`); `CONCAT(f)` aufgefüllt; MariaDB schreibt die aufgefüllte Zahl in JSON (`[0002.25, 0001.500, 0000000003.5]`). **Stand 17.10.2026:** erfüllt (docs/umsetzung.md MY30.7z): Gleitkommamarke am Typ (Breite, Nachkommastellen, FLOAT/DOUBLE, UNSIGNED, ZEROFILL) - Leitung, Werte (D Stellen, aufgefüllt auf M), Runden beim Speichern nach der exakten Dezimalentwicklung, Bereich 10^(M−D) − 10^−D (1264), `COLUMN_TYPE`, Genauigkeit, `SHOW CREATE TABLE`/`SHOW COLUMNS` wie an beiden Orakeln; `REAL` ist DOUBLE, `FLOAT UNSIGNED` bleibt FLOAT. Rest: MY30.7zb.✅
MY30.7zbRest aus MY30.7z: Masse von Rechnungen über `FLOAT(M,D)`Am MariaDB-Orakel: `f + 1` und `f * 2` über `FLOAT(7,2)` DOUBLE 19 mit 2 Nachkommastellen, `ROUND(f, 1)` DOUBLE 18/1, `MAX(f)` FLOAT 19/2, `SUM(f)` DOUBLE 19/2, `AVG(d)` über `DOUBLE(8,3)` DOUBLE 12/7 (`1.2350000`), `CAST(f AS CHAR)` 28 mit 0 Nachkommastellen; MySQL dazu messen (dort mit `ANY_VALUE` oder ohne `only_full_group_by`). **Stand 17.10.2026:** erfüllt (docs/umsetzung.md MY30.7zb): das Maß von `+ - * /`, Verneinung, `ABS`, `ROUND`, `MAX`/`MIN`/`SUM`/`AVG`, `IFNULL`/`COALESCE`/`CASE` über Gleitkomma je Server (MySQL Länge aus den Teilen, MariaDB 17 + D; `/` und `AVG` D + 4), `UNION` vereint die Marken (DOUBLE 8/3); ein DOUBLE-Ergebnis steht mit den Stellen seiner Spalte (`4.50`, `0.6175000`, `2.250`), auch in `CONCAT`; `BUCKET_QUANTILE` zeigt wie am Orakel `1.000000`. Rest: MY30.7zc.✅
MY30.7zcRest aus MY30.7zb**Erledigt:** `CAST(… AS CHAR)` über Gleitkomma (MySQL VAR_STRING 28/88/48/88 mit 31, MariaDB mit 0 Nachkommastellen; vorher LONGTEXT), `FLOOR`/`CEIL` (MySQL DOUBLE 23/31, MariaDB 17/0), `TRUNCATE(f, n)` wie `ROUND` (Wert mit n Stellen: `2.2`, nicht `2.20`), `GREATEST`/`LEAST` (MySQL wie `IFNULL`, MariaDB 17 + D), `IF` wie `CASE`, `CONCAT` über Gleitkomma und Mittel (MySQL 88/31, MariaDB 48…92/39) und das Mittel als Text bei MySQL kürzest (`CONCAT(AVG(d))` `1.8675`, MariaDB `1.8675000`) wie an beiden Orakeln.✅
MY30.7zfRest aus MY30.7zc**Erledigt:** die Ursache ist die Ablage: `DOUBLE(M,D)` legt den Wert bei beiden Servern auf D Stellen gerundet und aus ganzem Teil und Bruchteil zusammengesetzt ab (`1.235` als `1.2349999999999999`, `7.7777` als `7.7780000000000005`, 20 Werte); dazu schreibt der Kern einen double als Text jetzt kürzest und genau wie MySQL/MariaDB (`CONCAT(1e0/3)` `0.3333333333333333`, vorher `0.333333`; `6.666666666666666e19`, `0.00000015`).✅
MY30.7zaRest aus MY30.7t: JSON-Maß und mehrere Pfade bei MariaDBAm Orakel meldet MariaDB das Ergebnis von `JSON_OBJECT`/`JSON_ARRAY`/`JSON_INSERT`/`JSON_SET`/`JSON_REPLACE` als VAR_STRING (format=json) mit gerechneter Länge (`JSON_OBJECT('f', f)` 84, `JSON_ARRAY(f)` 52, `JSON_INSERT('{}', '$.a', a)` 68, `JSON_REPLACE('{"a":1}', …)` 108) und 39 Nachkommastellen (hier LONGTEXT 4294967295); `JSON_INSERT`/`JSON_SET`/`JSON_REPLACE` nehmen mehrere Paare aus Pfad und Wert (hier genau eines). **Stand 17.10.2026:** erfüllt (docs/umsetzung.md MY30.7za): `JSON_ARRAY`/`JSON_OBJECT`/`JSON_INSERT`/`JSON_SET`/`JSON_REPLACE` melden bei MariaDB VAR_STRING (format=json) mit der gemessenen Länge und 39 Nachkommastellen, eine JSON-Spalte darin LONG_BLOB 67108864 BINARY; mehrere Paare aus Pfad und Wert auf demselben Weg wie `JSON_ARRAY_APPEND` (anhängen, einpacken, `[last]`), eine gerade Zahl Argumente 1582. Rest: MY30.7zd.✅
MY30.7zdRest aus MY30.7za: JSON-Maß mit TEXT**Erledigt:** das Maß der JSON-Funktionen über TEXT/TINYTEXT/MEDIUMTEXT/LONGTEXT und BLOB (ein BLOB macht das Ergebnis binär), dazu `JSON_KEYS`, `JSON_EXTRACT` (auch mit mehreren Pfaden), `JSON_REMOVE`, `JSON_MERGE`/`_PRESERVE`/`_PATCH`, `JSON_ARRAY_APPEND`/`_INSERT`, `JSON_QUOTE`/`JSON_UNQUOTE` und verschachtelte Aufrufe wie am Orakel (BLOB/MEDIUM_BLOB/LONG_BLOB mit Bytes × Zeichenbreite, gedeckelt auf 67108864; `JSON_KEYS`/`QUOTE`/`UNQUOTE` ohne Format json). Nebenbei: `JSON_EXTRACT` mit mehreren Pfaden (Treffer in der Folge des Dokuments) und `JSON_REMOVE` mit mehreren Pfaden waren bei MariaDB 1582; `JSON_REMOVE` setzte ein Feldelement auf `null` statt es zu entfernen und brach bei ungültigem Dokument ab (jetzt NULL).✅
MY30.7zgRest aus MY30.7zd**Erledigt:** ein ungültiges Dokument ergibt bei MariaDB NULL mit Warnung 4037 (Textende) bzw. 4038 mit der Stelle wie am Orakel (20 Messungen), bei `JSON_VALID` als Note, in 21 Funktionen samt Argumentnummer (`json_contains` 2, `json_merge_preserve` 2, `JSON_PRETTY` als `json_detailed`); nebenbei `JSON_EXTRACT('[1,', …)` NULL statt 1, `JSON_VALID('1')` 1, `JSON_VALUE`/`JSON_QUERY` NULL ohne Warnung statt eines Fehlers.✅
MY30.7zhRest aus MY30.7zg**Erledigt:** `JSON_EXISTS` (liest nur bis zum Treffer: `'$'` über kaputtem Text 1, ein Fehler dahinter zählt nicht, davor NULL mit Warnung), `JSON_LOOSE`, `JSON_OVERLAPS`, `JSON_EQUALS` (Schlüsselfolge egal, Zahlen nach dem Wert) und `JSON_NORMALIZE` (Schlüssel sortiert, Zahlen als `1.5E0` aus allen Ziffern) wie am Orakel; `JSON_LENGTH` über einem Einzelwert liest nicht weiter (`'01'`, `'1x'` 1); der Name von `JSON_MERGE` in der Warnung ist `json_merge_preserve`.✅
MY30.7ziRest aus MY30.7zh: Zahlen wie `1.` in JSON bei MariaDB**Erledigt:** bei MariaDB (nicht MySQL) ist eine Zahl mit Punkt ohne Nachkommastellen gültig und bleibt stehen (`JSON_EXTRACT('[1.]', '$[0]')` `1.`, `JSON_COMPACT` `[1.,2.e1]`, `JSON_VALID('1.')` 1) - in allen vier Lesern des Kerns; nebenbei `JSON_TYPE` eines Einzelwerts (war NULL) und INTEGER/DOUBLE nach der Schreibweise (DOUBLE nur mit Punkt, `1e2` INTEGER).✅
MY30.7uFehlende eingebaute FunktionenERLEDIGT 10.10.2026 (`WerkzeugfunktionenTests`, beide Orakel): an beiden Zugängen fehlten (1305) `COMPRESS`, `UNCOMPRESS`, `UNCOMPRESSED_LENGTH` (4 Byte Länge, dann zlib), `RANDOM_BYTES`, `JSON_QUOTE`, `JSON_DEPTH`, `JSON_PRETTY` (MariaDB auch `JSON_DETAILED`), `FORMAT_BYTES`, `FORMAT_PICO_TIME`, am MySQL-Zugang dazu `ANY_VALUE` (auch unter `ONLY_FULL_GROUP_BY`), `UUID_TO_BIN`, `BIN_TO_UUID`, `IS_UUID`. MySQL meldet ein ungültiges Argument (3141, 1690, 1411), MariaDB gibt NULL; `JSON_PRETTY` rückt bei MySQL um zwei, bei MariaDB um vier Leerzeichen ein. Noch offen (eigene Zeile MY30.7v): `STATEMENT_DIGEST_TEXT`, `ICU_VERSION`, `VALIDATE_PASSWORD_STRENGTH`, `PS_CURRENT_THREAD_ID`, `GTID_SUBSET`/`GTID_SUBTRACT`, `JSON_SCHEMA_VALID`, `JSON_TABLE`✅
MY30.7vGTID-Mengen und Werkzeugfunktionen (MySQL)ERLEDIGT 10.10.2026 (`MySqlGtidTests`, Orakel `mysql:8.4`): `GTID_SUBSET`/`GTID_SUBTRACT` lesen GTID-Mengen samt Marken (klein geschrieben, höchstens 32 Zeichen), fassen Bereiche zusammen und schreiben sie geordnet (je UUID erst ohne Marke, dann die Marken; UUIDs durch Komma und Zeilenumbruch); `n-` und `n > m` sind leer, 0, leere Teile und Nummern ab 2^63-1 sind 1772. `ICU_VERSION` 77.1, `PS_CURRENT_THREAD_ID`/`PS_THREAD_ID` wie `performance_schema.threads` (der Faden ist die Verbindung), `VALIDATE_PASSWORD_STRENGTH` ohne Komponente 0✅
MY30.7w`JSON_TABLE` (MySQL und MariaDB)ERLEDIGT 10.10.2026 (`JsonTabelleTests`, Orakel `mysql:8.4` und `mariadb:11`): Tabellenfunktion im Kern neben OPENJSON — Zeilenpfad mit Platzhaltern, `FOR ORDINALITY`, `EXISTS PATH`, `NESTED PATH` (Geschwister nacheinander, ohne Treffer eine Zeile mit NULL), `NULL`/`ERROR`/`DEFAULT` `ON EMPTY`/`ON ERROR`; seitlich hinter Komma und `[LEFT] JOIN … ON` wie LATERAL; MySQL wandelt nur genau Passendes (3156, 3666, 3669, 3665, Vorgabe als JSON-Text, sonst 1235), MariaDB nachsichtig wie beim Einfügen (4176 bis 4179); Alias Pflicht (3667/4177). Dabei behoben: eine Sicht über eine Tabellenfunktion (auch OPENJSON bei T-SQL) nannte ihre Spalten `t.v` und meldete beim Lesen 209✅
MY30.7xReste aus MY30.7wAm MySQL-Zugang 1305 (am Orakel vorhanden): `STATEMENT_DIGEST`/`STATEMENT_DIGEST_TEXT` (3676 ohne Datenbank, Normalform der Anweisung), `JSON_SCHEMA_VALID`/`JSON_SCHEMA_VALIDATION_REPORT`. `JSON_TABLE`: MariaDB gibt eine JSON-Spalte im Rohtext samt Leerzeichen aus (`[1 , 2]`, hier dicht `[1,2]`); `BIT(n)` geht als Text statt als Bitfolge über die Leitung; die Stelle in 4042 bei MariaDB (`$x` am Orakel 2, hier 1); MySQL lehnt `AS JSON_TABLE` als Alias ab (1064); `SHOW CREATE VIEW` einer Sicht mit `JSON_TABLE` zeigt bei MySQL den Text mit Backticks statt eckiger Klammern in Textliteralen. Die Fadennummer ist beim Orakel nicht die Verbindungsnummer (hier gleich, wie in `performance_schema.threads`) **Stand 17.10.2026, Teil 1 erledigt** (docs/umsetzung.md MY30.7x): `JSON_SCHEMA_VALID`/`JSON_SCHEMA_VALIDATION_REPORT` (Draft 4, erster Verstoss in Dokumentreihenfolge, Bericht wie am Orakel, 3141/3853/1235) und `STATEMENT_DIGEST_TEXT`/`STATEMENT_DIGEST` (Normalform nach den gemessenen Regeln und Synonymen, 3676; der Hashwert ist SHA-256 der Normalform, weil MySQL ihn aus dem inneren Tokenstrom rechnet). **Teil 2 erledigt:** MariaDB gibt eine JSON-Spalte im Text der Quelle aus (`[1 , 2]`), `BIT(n)` geht als Bitfolge (Typ 16), MariaDB nennt in 4042 die Stelle des ersten falschen Zeichens ab 1 und meldet ein vorzeitiges Ende als 4041, MySQL lehnt `AS JSON_TABLE` ab (1064). **Teil 3 erledigt:** `SHOW CREATE VIEW` einer Sicht mit `JSON_TABLE` wie an beiden Orakeln (MySQL klein mit `character set`, MariaDB gross mit Anzeigebreite und jeder Tabelle mit Datenbank, auch hinter einem Komma). Rest: MY30.7ze.✅
MY30.7zeRest aus MY30.7x: Fadennummer und kleine MasseMySQL: `THREAD_ID`, `PS_CURRENT_THREAD_ID()`, `PS_THREAD_ID()`, `sys.ps_thread_id()` sind am Orakel die Verbindungsnummer plus die Hintergrundfäden (47 Fäden, Abstand 52: Verbindung 63 → Faden 115), hier gleich der Verbindung - durchgängig in `threads`, `events_statements_*`, den `sys`-Sichten und `sys.ps_thread_account`; die Spalte `THREAD_ID` aus einer Unterabfrage ist LONGLONG 20 UNSIGNED (hier NEWDECIMAL 21). MariaDB: `CONNECTION_ID()` LONG 10 NOT NULL UNSIGNED BINARY (hier LONGLONG 20); `performance_schema` ist am Orakel aus (`threads` leer). MySQL: eine `VARCHAR`-Spalte von `JSON_TABLE` hat die Sortierung 255 (hier 45). **Stand 17.10.2026:** erfüllt (docs/umsetzung.md MY30.7ze): die Fadennummer ist bei MySQL die Verbindung + 52 (fester Versatz wie am frisch gestarteten Orakel) in `threads`, `events_statements_*`, `PS_CURRENT_THREAD_ID()`, `PS_THREAD_ID()`, `sys.ps_thread_id()`; `sys.ps_thread_account` nimmt den Faden, `ps_is_thread_instrumented` und `ps_setup_*_thread` die Verbindung (wie am Orakel); die Masse von `PS_*_THREAD_ID` (LONGLONG 21 UNSIGNED), einer Unterabfrage über `THREAD_ID` (LONGLONG 20 UNSIGNED) und `CONNECTION_ID()` bei MariaDB (LONG 10). `performance_schema` bei MariaDB ist schon aus und leer; die Sortierung 45 der `JSON_TABLE`-Spalte war die des Testclients (der Kern meldet die der Verbindung).✅
MY30.7nServerangaben aus den Eigenschaften des Kerns, für jeden Zugang gleich**Vorgabe des Auftraggebers (07.10.2026):** was ein Client über den Server anzeigt (MySQL Workbench / MySQL Shell *Server Status* mit den Kacheln General, Server Features, Status, Directories and Logs, TLS; SSMS Servereigenschaften; pgAdmin Eigenschaften/Dashboard), kommt aus **einem** Modell der Servereigenschaften im Kern (`Catalog.Serverfakten` ausbauen); die Dialekte bilden es nur auf ihre Namen ab (MySQL/MariaDB Systemvariablen, Status, `information_schema.PLUGINS`; PostgreSQL `pg_settings`, `pg_stat_*`, `pg_available_extensions`; T-SQL `SERVERPROPERTY`, `sys.configurations`, `sys.dm_server_*`). Kein fester Platzhalter in `mysql_systemvariablen.tsv`/`mysql_status.tsv` mehr, wo der Kern den Wert kennt. **General:** Host (Rechnername), Server ID (Kennung der Instanz im Verbund, H20), Server UUID (Kennung der Instanz, beim ersten Start erzeugt und im Datenordner bleibend, dieselbe für `server_uuid`, `SERVERPROPERTY('…')`, `pg_control_system()` …), Bind Address und Port je Hörer, Socket (KuonenSQL hat keinen: leer wie MySQL ohne Socket), Admin Address/Admin Port (die Verwaltungsverbindung des Kerns, wie DAC), Compiled For (Betriebssystem), Configuration File (die Einstellungsquelle des Kerns: Datei/Umgebung). **Server Features** (heute alle *not installed*): je eine Fähigkeit des Kerns statt eines MySQL-Plugins - Thread Pool (Ablaufplanung des Kerns), Semisync Replication (synchrone Kopien, H20), PAM Authentication (Anmeldung über das Betriebssystem/Windows-Anmeldung), Password Validation (Kennwortrichtlinie `CHECK_POLICY`, dann `validate_password.*` aus ihr), Audit Log (Serverüberwachung `SERVER AUDIT`), Firewall/Firewall Trace (Zugriffsregeln des Kerns, falls vorhanden; sonst ehrlich *not installed*), Memcached (Schlüssel-Wert-Zugang des Kerns, z. B. der Redis-Zugang); als *installed* nur, was wirklich wirkt, mit den Variablen und Status des Originals. **Status:** Read-only/Super read-only (Schreibschutz der Instanz bzw. aller Datenbanken des Kerns, `SET GLOBAL read_only` wirkt dorthin), Offline mode (Instanz nur für Verwalter, wie Einzelbenutzer), Redo Log (Transaktionsprotokoll), Binary Log (Protokollkette, PR27.6q). **Directories and Logs:** Base/Data Directory (heute belegt), Plugins Directory (Ordner der Erweiterungen, sonst leer), Temp Directory (Ordner von `tempdb`/Auslagerung), Error Log (Fehlerprotokoll des Kerns, heute stderr), General Log (Anweisungsprotokoll des Kerns, `KUONENSQL_ANWEISUNGSPROTOKOLL`; `SET GLOBAL general_log = ON` schaltet es, `general_log_file`/`log_output` wie MySQL), Slow Query Log (die langsamen Anweisungen aus der Kernstatistik MY30.7o, `long_query_time`). **TLS:** `ssl_ca`/`ssl_cert`/`ssl_key`, `Current_tls_*`, `Ssl_server_not_before`/`_after` aus dem Zertifikat des Kerns, mit dem jeder Hörer TLS spricht. Am Orakel `kq-ref-mysql` gemessen (07.10.2026): `server_uuid` eine UUID, `socket` `/var/run/mysqld/mysqld.sock`, `plugin_dir` `/usr/lib64/mysql/plugin/`, `tmpdir` `/tmp`, `general_log_file`/`slow_query_log_file` `<datadir><host>.log`/`-slow.log`, `Ssl_server_not_before` `Oct 5 09:50:39 2026 GMT`, `information_schema.PLUGINS` ohne die neun Plugins. Nicht über den Server lösbar: *MySQL Enterprise Backup* (sucht das Programm `mysqlbackup` auf dem Rechner des Clients, dafür PR27.6o). **Teil 1 erledigt 08.10.2026:** `Serverfakten` führt Kennung der Instanz (im Datenordner bleibend), Ordner für Zwischendateien, Zertifikat und dessen Datei sowie den Schalter des Anweisungsprotokolls; MySQL zeigt daraus `server_uuid`, `tmpdir`, `slow_query_log_file`, `ssl_cert`/`ssl_key`, `Ssl_server_not_before`/`_after`, `Current_tls_cert`/`_key`; `SET GLOBAL general_log` schaltet das Anweisungsprotokoll des Kerns (Test `MySqlVariablenTests.Die_Serverangaben_kommen_aus_dem_Kern`). **Teil 2 erledigt 21.10.2026** (docs/umsetzung.md ): das Anweisungsprotokoll des Kerns ist auch PostgreSQL `log_statement = 'all'` (`ALTER SYSTEM SET log_statement` schaltet es, `SHOW`/`pg_settings` zeigen es, gleich über welchen Zugang oder die Umgebung es an ist; MySQL/MariaDB `@@general_log` ebenso), `ssl_cert_file`/`ssl_key_file` sind die Datei des Zertifikats des Kerns (Test `ServerfaktenTests.Anweisungsprotokoll_und_Zertifikat_zeigt_auch_PostgreSql_aus_dem_Kern`). Plugins Directory bleibt leer (der Kern hat keinen Ordner für Erweiterungen, wie verlangt). **Rest → MY30.7n1.**✅
MY30.7n1Rest aus MY30.7n: Server Features, Status-Kachel, Slow Query Log, T-SQL(1) **Server Features**: welche Abfrage MySQL Workbench / MySQL Shell für jede Kachel stellt (Plugins, Variablen), erst mitschneiden (Klick bei `KUONENSQL_ANWEISUNGSPROTOKOLL=1`, Handtest Marc), dann je Fähigkeit des Kerns, die wirklich wirkt, das Plugin samt Variablen und Status des Originals: Semisync aus der synchronen Kopie (`KUONENSQL_KOPIE_SYNCHRON`), PAM aus `KUONENSQL_PAM_DIENST`, Memcached aus dem Redis-Zugang, Audit Log aus `SERVER AUDIT`; Password Validation erst mit einer Kennwortrichtlinie im Kern (heute keine), Thread Pool/Firewall ehrlich *not installed*. (2) **Status-Kachel**: `read_only`/`super_read_only` als Schreibschutz der Instanz im Kern (wirkt an jedem Zugang, MySQL 1290), `offline_mode` als Zugang nur für Verwalter - beides neue Fähigkeiten des Kerns. (3) **Slow Query Log** aus der Anweisungsstatistik (`slow_query_log`, `long_query_time`, `log_output = TABLE` → `mysql.slow_log`; PostgreSQL `log_min_duration_statement`). (4) **T-SQL**: `SERVERPROPERTY`/`sys.dm_server_*` zeigen heute die feste Ansage von SQL Server 2025 (`TSql/Serverkennung`); welche davon aus `Serverfakten` kommen dürfen (Kennung der Instanz, Zertifikat, Protokoll), ist ein Entscheid des Auftraggebers. (5) PostgreSQL `pg_control_system()` mit der Kennung der Instanz.⬜
MY30.7oLeistungsberichte (*Performance Reports*)Der Bericht *Statement Histogram* in MySQL Shell für VS Code / Workbench bricht ab mit `mysqlshdk::db::mysql::Row::get_int(1): field value out of the allowed range` (Nutzerfund 07.10.2026). Vermutung: der letzte Eimer von `performance_schema.events_statements_histogram_global` endet bei 2^64 - 1 (`MySqlHistogramm`), die Spalte muss dafür am Draht als `BIGINT UNSIGNED` (Flag UNSIGNED) gehen, sonst liest der Client sie vorzeichenbehaftet; die genaue Abfrage des Berichts erst mitschneiden (MySQL-Anweisungen stehen nicht im Serverprotokoll, `dist\ssms-protokoll.ps1` sieht nur TDS: Mitschnitt am MySQL-Port oder Protokollschalter nachrüsten). Danach alle Berichte der Liste einzeln gegen das Orakel prüfen (Statement Analysis, Top I/O, Hot Spots, High Cost SQL, Wait Events, InnoDB, User/Host-Zusammenfassungen; meist `sys.*`-Sichten über `performance_schema`). **Vorgabe des Auftraggebers (07.10.2026): die Leistungsdaten kommen aus dem Kern, kein Zugang führt eigene.** Der Kern misst schon je Anweisung (Abfragespeicher/Query Store, `sys.dm_exec_query_stats`, `sys.dm_exec_requests`, Wartestatistik); er führt dazu die Verteilung der Laufzeiten (feine Eimer, aus denen sich jede Eimerteilung ableiten lässt), Zähler je Normalform (Digest = `query_hash`), Fehler/Warnungen, gelesene und gesendete Zeilen, Sperr- und Wartezeit, je Benutzer, Host, Datenbank und Zugang. Die Dialekte sind nur Sichten darauf: MySQL `performance_schema.events_statements_histogram_global`/`_by_digest` (Eimerteilung wie MySQL aus der Kernverteilung, `MySqlHistogramm` liefert dann nur noch die Grenzen), `events_statements_summary_by_digest`/`_global_by_event_name`/`_by_user_by_event_name`…, `events_statements_current`/`_history`, `events_waits_*`, `table_io_waits_summary_by_table`, `file_summary_by_*` und die `sys`-Sichten darüber; MariaDB dieselben `performance_schema`-Tabellen; PostgreSQL `pg_stat_statements` (mit `calls`, `total_exec_time`, `mean_exec_time`, `rows`, `shared_blks_*`, `queryid`), `pg_stat_activity`, `pg_stat_database`, `pg_stat_user_tables`/`_indexes`, `pg_statio_*`; T-SQL wie bisher. Eine Anweisung, die über MySQL kam, erscheint auch in `sys.dm_exec_query_stats` und `pg_stat_statements` und umgekehrt; Zurücksetzen (`TRUNCATE performance_schema.…`, `pg_stat_statements_reset()`, `DBCC SQLPERF`/Query Store leeren) wirkt auf die eine Kernstatistik. **Teil 1 erledigt 08.10.2026:** `Catalog.Laufzeitverteilung` zählt jede Anweisung jedes Zugangs (auch gescheiterte) in den 450 Eimern von MySQL; `events_statements_histogram_global` ist die Sicht darauf (Test `MySqlWorkbenchTests.Das_Histogramm_zaehlt_die_Anweisungen_aller_Zugaenge_aus_dem_Kern`, Kosten im Rauschen). **Teil 2 erledigt 08.10.2026:** `Catalog.Anweisungsstatistik` zählt je Normalform und Datenbank (Kennung SHA-256 wie MySQL, höchstens 10 000 Formen); MySQL `events_statements_summary_by_digest` und `…_histogram_by_digest` sind die Sichten darauf (88 ns je Anweisung). **Teil 3 erledigt 08.10.2026:** PostgreSQL `pg_stat_statements` 1.11 (49 Spalten wie am Orakel, nach `CREATE EXTENSION`), `pg_stat_statements_info` und `pg_stat_statements_reset()` auf derselben Statistik (Test `PgStatStatementsTests`). **Teil 4 erledigt 08.10.2026:** `TRUNCATE TABLE performance_schema.events_statements_summary_by_digest`/`…_histogram_global`/`…_histogram_by_digest` leert die Statistik des Kerns (am Orakel steht danach nur das `TRUNCATE` selbst da), `DELETE` und andere Tabellen 1142 wie am Orakel; was der MySQL-Zugang ohne den Ausführer beantwortet (`SHOW …`, Katalogbefehle), zählt jetzt wie bei MySQL in die Statistik (`ExecutionEngine.ErfasseAnweisung`; Test `MySqlWorkbenchTests.TRUNCATE_leert_die_Statistik_des_Kerns_wie_MySQL`). **Teil 5 erledigt 21.10.2026** (docs/umsetzung.md ): eine übersetzte Anweisung zählt einmal unter dem Wortlaut des Clients (MySQL/MariaDB der Rohtext, PostgreSQL der Urtext; vorher je übersetzter Teilanweisung unter dem T-SQL-Text, z. B. `SELECT [ `a` ] FROM [ `t` ]` als eigene Form); dazu MY30.7c4/c5/c6/c8 (Histogramm, Analysator, Spuren, DIGEST_TEXT). **Rest → MY30.7o1.**✅
MY30.7o1Rest aus MY30.7o: Leistungsberichte(1) Ursache von `get_int(1)` im Bericht *Statement Histogram*: erst den Klick mitschneiden (Handtest Marc, `SET GLOBAL general_log = ON`, Zeilen `[SQL]` in der Ausgabe des Servers); gemessen 21.10.2026: `events_statements_histogram_global` hat am Draht dieselben Typen und das Flag UNSIGNED wie am Orakel (nur `NOT_NULL`/`NO_DEFAULT_VALUE`/`PRI_KEY` fehlen). (2) Die übrigen Berichte der Liste (Top I/O, Hot Spots, Wait Events, InnoDB, User/Host-Zusammenfassungen) über die `sys`-Sichten - mit MY30.7c7. (3) T-SQL `sys.dm_exec_query_stats` auf dieselbe Anweisungsstatistik. (4) `pg_class`/`pg_attribute` für die Sicht der Erweiterung `pg_stat_statements`.⬜
MY30.7pMySQL Shell für VS Code: Benutzerliste**Erledigt 08.10.2026.** Die Seite *User Accounts* blieb leer, weil die Rollen- und Rechtespalten der Liste als Text statt als JSON kamen (am Orakel Typ 245). Drei Ursachen im Kern: die NULL der rechten Seite eines Aussenverbunds trug nur den Grundtyp (jetzt der volle Typ wie links), `JSON_ARRAY`/`JSON_OBJECT` galten statisch als `nvarchar(max)` (bei MySQL JSON wie die Aggregate), und die äussere Auswahl über einer abgeleiteten Tabelle zählte als Unterabfrage (Maß der JSON-Ausdrücke, Maskierung und Seitenstrom griffen dort nicht). Messreihe und Regeln in `docs/umsetzung.md` (MY30.7p); Mitschnitt der Abfrage als Fixture `mysqlshell-benutzerliste.sql`; Tests `MariaDbSpaltenbeschreibungTests`. Reste: MY30.7q.✅
MY30.7qReste aus MY30.7p(1) **Typen der Systemtabellen, erledigt 08.10.2026:** eine Systemspalte hat den Typ einer Tabellenspalte mit demselben `COLUMN_TYPE` und derselben Sortierung (derselbe Parser wie `CREATE TABLE`; `mysql_katalog.tsv` trägt dazu `COLLATION_NAME`), die Werte werden angeglichen (Text ohne Auffüllen wie `CHAR` bei MySQL): `User` STRING 128, `account_locked` STRING 4 ENUM, `password_last_changed` TIMESTAMP. **Rest erledigt 09.10.2026:** das Flag BINARY der `_bin`-Spalten (`User`, `plugin`, `authentication_string`) und die vorzeichenlosen Ganzzahlen (`password_lifetime` SHORT UNSIGNED, `max_questions` LONG UNSIGNED) wie am Orakel `kq-ref-mysql`; BINARY hängt allgemein an der Sortierung des Typs (am Orakel auch ein Ausdruck `'a' COLLATE utf8mb4_bin`, bei MySQL und MariaDB), der Katalog trägt die Marke des Aliastyps wie eine angelegte Spalte. **Rest (4) erledigt 09.10.2026:** die Systemtabellen von `mysql` melden ihre Spalten wie eine Tabelle - Datenbank `mysql`, Tabelle (Alias) und Originaltabelle, NOT_NULL, PRI_KEY/MULTIPLE_KEY/PART_KEY nach den am Orakel gemessenen Schlüsseln (`information_schema.STATISTICS`), NO_DEFAULT_VALUE für die fünf Spalten ohne Vorgabe, TIMESTAMP/ON_UPDATE_NOW bei den Zeitstempeln; BINARY auch im Verbund. Offen bleiben die Tabellen von MEB (`backup_history`, `backup_progress`: ohne echtes MEB nicht messbar). (2) **erledigt 08.10.2026:** ein `;` in einem Kommentar (`-- …`, `/* … */`) trennte im Zerleger des MySQL-/MariaDB-Zugangs die Anweisung (MySQL Shell schickt Abfragen mit Kommentarzeilen; Test `MariaDbCmsFolgeTests.Ein_Semikolon_oder_Blockwort_im_Kommentar_trennt_die_Anweisung_nicht`). (3) die Typen der Ausdrücke: MY30.7r. (4) *Statement Histogram* (MY30.7o) und *Dump to Disk → Database Schema Dump* mitschneiden (der Nutzer klickt bei `KUONENSQL_ANWEISUNGSPROTOKOLL=1`), am Orakel gegenhalten; Sicherungssperren über PR27.6n. (5) „Expired 3 of 3“ im Kopf der Seite prüfen, wenn die Liste erscheint. (6) **Dump to Disk, erledigt 08.10.2026** (nachgestellt mit `mysqlsh` 8.4 aus dem Orakel-Container gegen einen eigenen Server, `util.dumpSchemas` + `util.loadDump` Rundlauf): `SHOW PRIVILEGES` und `SHOW GRANTS` am MySQL-Zugang in der Form von MySQL 8.4 (statische und dynamische Rechte des Verwalters, kein Kennwortabdruck; vorher die MariaDB-Liste, daran brach MySQL Shell mit „Invalid privilege … BACKUP_ADMIN“ ab), `SHOW BINARY LOG STATUS`, nebeneinanderstehende Zeichenketten sind eine (`'a' 'b'`), `LOAD DATA LOCAL` mit vorangestelltem Kommentar (Tests `MySqlRechtelisteTests`, `MySqlShellLadenTests`). (7) **Konten sichern und einspielen, erledigt 08.10.2026** (`util.dumpInstance` + `loadDump` samt Konten im Rundlauf, das eingespielte Konto meldet sich mit seinem Kennwort an): im Kern `Kennwortabdruck` - eine Anmeldung aus dem Abdruck eines anderen Servers (T-SQL `PASSWORD = 0x… HASHED`: SQL Server 0x0200/0x0300, `mysql_native_password` `*…`, `caching_sha2_password` `$A$005$…`, am jeweiligen Orakel nachgerechnet; ungültig 15021); MySQL `IDENTIFIED WITH … AS '…'`/`AS 0x…`, MariaDB `IDENTIFIED BY PASSWORD '*…'` und `SET PASSWORD FOR u = '*…'` setzen ihn (**Sicherheitsfund:** bisher bekam ein so eingespieltes Konto ein leeres Kennwort); `SHOW CREATE USER` in der Form von MySQL 8.4 und MariaDB 11.8, `GRANT USAGE`, ein leeres Kennwort als Nullbyte ist 1045 „using password: NO“ (vorher „Malformed packet“). Tests `KennwortabdruckTests`, `MySqlKontosicherungTests`. **Stand 21.10.2026:** alles, was sich ohne die Oberfläche messen lässt, ist erledigt; (3) ist MY30.7r. Die übrigen Punkte brauchen einen Klick in MySQL Shell für VS Code bzw. ein echtes MEB → **MY30.7q1** (Handtest).✅
MY30.7q1Rest aus MY30.7q (Handtest Marc)(4) *Statement Histogram* mitschneiden (Klick bei `KUONENSQL_ANWEISUNGSPROTOKOLL=1` oder `SET GLOBAL general_log = ON`; die Zeilen `[SQL]` stehen in der Ausgabe des Servers) - die Ursache von `get_int(1)` gehört zu MY30.7o; (5) „Expired 3 of 3“ im Kopf der Seite *User Accounts*: am Orakel `kq-ref-mysql` (21.10.2026) haben die Konten `password_expired = N`, `password_last_changed` gesetzt, `password_lifetime` NULL, `default_password_lifetime` 0 - KuonenSQL zeigt für `sa` dasselbe; Rollen sind bei beiden `Y`/gesperrt. Ob die Seite Rollen mitzählt, zeigt nur die Oberfläche. (1) Rest: die Tabellen von MEB (`backup_history`, `backup_progress`) - ohne echtes MEB nicht messbar.⬜
MY30.7rAusdrucksmaße und Funktionstypen von MySQL**Teil 1 erledigt 09.10.2026:** zwei Fehler, die Abfragen scheitern liessen (an beiden Orakeln gemessen): `NOW(n)`/`CURTIME(n)`/`SYSDATE(n)` und die übrigen Jetzt-Funktionen mit Genauigkeit 0-6 (abgeschnitten, ohne Angabe 0, darüber 1426 im Wortlaut je Server, MariaDB rechnet `LOCALTIME` als Uhrzeit); Text und Zahl in `IF`/`COALESCE`/`IFNULL` ergeben Text (vorher 1366). **Teil 2 erledigt 09.10.2026:** die Leitungsmaße von MySQL als eigene Regeln (`MySqlAusdrucksmass`, MariaDB unverändert), 42 gemessene Ausdrücke wie am Orakel: Ganzzahlen LONGLONG (Konstante Ziffern+1, TRUE/FALSE 1, Funktionen 21/10/9), BINARY bei Zahlen, DOUBLE 23, Text 31 Nachkommastellen, `NULL` Typ NULL, Zeit-Literale NOT_NULL (TIME 10), COUNT auch ohne Tabelle. **Teil 3 erledigt 09.10.2026:** weitere 21 gemessene Ausdrücke: `CAST(… AS CHAR/BINARY)` VAR_STRING (4 je Zeichen), `CAST` einer Konstante NOT_NULL, `AS FLOAT` FLOAT, `DIV`/`MOD` die Länge der Konstante, Text aus Konstanten in `IF`/`CASE`/`CONCAT` (4 je Zeichen), JSON 31 Nachkommastellen, `DATABASE()` 256, `USER()` 1152, `VERSION()`, `MONTHNAME` 36, `HEX` 32 je Zeichen, `GROUP_CONCAT` LONG_BLOB 65536, `AVG` einer Konstante 7,4, Zeitpunkt mit NULL in `CASE`/`COALESCE` DATETIME 76. **Teil 4 erledigt 09.10.2026:** zweite Messreihe (62 Ausdrücke, dazu eine Tabelle): Systemvariablen (Schalter LONGLONG 1, Zahlen 21 ohne Vorzeichen ausser negativem Minimum oder größtem BIGINT, Text VAR_STRING 87380), Kommazahlen (`*` p1+p2, `/` p1+s2+4 mit s1+4, `ROUND`, `TRUNCATE`, `AVG`, `SUM` einer Kommaspalte), Spalten in `CONCAT`/`IF`/`CAST AS CHAR`, `DIV` einer Spalte, Hex-Literale UNSIGNED, `MAX`/`MIN` einer Spalte und Unterabfragen behalten LONG; am Orakel jetzt 88/94 und 53/62 (die übrigen gleich bis auf den Wert bei `USER()`, `VERSION()`, `@@hostname`, `@@version_comment`, `@@version`). **Teil 5 erledigt 09.10.2026:** über eine leere Tabelle (am Orakel gemessen) neue Nahtstelle `Dialekt.Funktionstyp` für die statische Ableitung: `IFNULL` gemeinsamer Typ, `DATE` Datum, `DATE_FORMAT` Text in der Länge des Formats, `DATE_ADD`/`DATE_SUB` der Zeitpunkt; dazu `DATE_ADD` auf Text STRING 116, `HEX` einer Zahl 64, `NULLIF` einer Spalte LONG, `ROUND` einer Spalte ohne NOT_NULL. **Teil 6 erledigt 09.10.2026:** `MAX`/`MIN` einer Textspalte über eine leere Tabelle in der Länge der Spalte (der schnelle Aggregatweg las nur den Grundtyp der Spalte, auch für T-SQL); ein Gleitkommaliteral so lang wie sein Text (`1e3` 3, mit Vorzeichen 23), im Text einer Sicht wie getippt (`-(1e3)`); berechnete Texte in der Sortierung der Verbindung (255 statt 45).✅
MY30.7jHerkunft aus dem Plan und von Systembefehlen`DISTINCT`/`GROUP BY`, die der Server mit einer temporären Tabelle bildet, verlieren PRI/UNIQUE/MULTIPLE/PART_KEY (`SELECT a, b FROM g GROUP BY a, b` ohne Index auf (a, b); mit Index, Primärschlüssel oder `GROUP BY a` auf Index bleiben sie): hier bleiben sie immer; die Herkunft von `SHOW`/`DESCRIBE` und `information_schema` (MySQL und MariaDB nennen verschiedene Systemsichten, Längen und Merkmale je Befehl); der Name `$$` in 1054 bei MariaDB; `MAX`/`MIN` eines Spaltenwerts behält bei MariaDB die Originaltabelle in der Sicht mit `GROUP BY`. **Stand 16.10.2026:** erfüllt (docs/umsetzung.md MY30.7j): die Zwischentabelle nach der gemessenen Regel (Gruppe enthält den Primär- oder einen eindeutigen NOT-NULL-Schlüssel oder ist in Reihenfolge der Anfang eines Index, ORDER BY nur über Spalten der Gruppe, bei MariaDB aufsteigend; Verbund bei MySQL immer) - Schlüssel weg, Zahlenausdrücke ohne BINARY (AVG behält es, bei MySQL auch BIT_*), MIN/MAX einer Spalte ohne Vorgabe mit NO_DEFAULT_VALUE; die Aggregatmaße beider Server (COUNT LONGLONG 21, SUM/AVG als Kommazahl, MAX/MIN wie die Spalte); die Spaltenbeschreibung von 29 Katalogbefehlen je Server wie am Orakel (`mysql_showspalten.tsv`; MySQL: `Language` in `PROCEDURE STATUS`, kein `Progress` in `PROCESSLIST`); in einer Sicht mit Zwischentabelle nennt MySQL für eine genannte Spalte die Sicht, für `*` die Tabelle darunter, MariaDB für MIN/MAX die Originaltabelle; `$$` schon seit MY30.7h. Rest: MY30.7y.✅
MY30.7yRest aus MY30.7j: Herkunft in SonderfällenEin Verbund mit DISTINCT bei MariaDB (am Orakel behält `DISTINCT g.b` über einen Verbund PART_KEY, verliert MULTIPLE_KEY), `GROUP BY … WITH ROLLUP` (MySQL: die Gruppenspalte ohne Schlüssel, aber mit BINARY; MariaDB behält die Schlüssel), die Katalogbefehle als vorbereitete Anweisung (COM_STMT_PREPARE) und `SHOW` mit Spalten, die der Kern nicht führt (`SHOW SLAVE HOSTS`, `SHOW EVENTS` …), je am Orakel zu messen. **Stand 17.10.2026, Teil 1 erledigt** (docs/umsetzung.md MY30.7y): `GROUP BY … WITH ROLLUP` gab es im Kern für MySQL/MariaDB gar nicht (1064) - jetzt mit den Zeilen wie am Orakel (jede Teilsumme hinter ihrer Gruppe, die Gesamtsumme zuletzt), MySQL sortiert mit `ORDER BY` danach, MariaDB lehnt es ab (1221); die Merkmale (MySQL: Gruppenspalte wie ein Ausdruck, MariaDB behält die Schlüssel) und `DISTINCT` über einen Verbund bei MariaDB (PART_KEY bleibt). **Teil 2 erledigt:** die Spaltenbeschreibung von `SHOW EVENTS`, `SHOW CREATE EVENT`, `SHOW MASTER`/`BINARY LOG STATUS`, `SHOW REPLICA`/`SLAVE STATUS` (MySQL `Replica_*`/`Source_*`, 60 Spalten; MariaDB 54), `SHOW REPLICAS`/`SLAVE HOSTS` (bei MySQL nur `REPLICAS`), `SHOW RELAYLOG EVENTS`, `SHOW BINLOG EVENTS` und `SHOW PROFILES` wie an beiden Orakeln (die letzten fünf waren im Kern unbekannt; ohne Replikation leer); ein vorbereiteter Katalogbefehl (`PREPARE`/`EXECUTE` und `COM_STMT_PREPARE`) trägt dieselbe Beschreibung wie direkt (vorher fehlten beim PREPARE die Merkmale).✅
MY30.7eReste der Spaltenbeschreibung und der Parametertypen`TINYINT(1)`/`BOOLEAN` als TINY (Länge 1, bei MySQL bool in den Treibern) statt SHORT, `TINYINT` als TINY 4, die Parametertypen im `PREPARE`-Ergebnis (MySQL meldet je Platzhalter den Typ der Zielspalte, `BIGINT UNSIGNED` mit dem Flag UNSIGNED; Connector/J bindet `BigInteger` danach), der Zeichensatz der Textspalten nach der Verbindung (mysql2 224, Connector/J 255), `TINYTEXT`/`LONGBLOB`-Längen, `select $$` mit 1064 statt 1054, `character_sets_dir`. **Stand 07.10.2026:** alles erledigt. `TINYINT`/`TINYINT(1)`/`BOOL` als TINY 4/1 (Art `W` der Typbedingung), Zeichensatz und Länge der Textspalten nach Anmeldung, `SET NAMES [COLLATE]` und `character_set_results` (`MariaZeichensaetze.Nummer`; MariaDB macht 45 zu 224), `TINYTEXT`…`LONGTEXT`/`BINARY`/`VARBINARY` mit den Längen des Orakels, die Parametertypen für Vergleich, `IN`, `BETWEEN`, Zuweisung, `INSERT`-Werte und `LIMIT` (`MySqlPlatzhaltertypen`), `$$` bei MySQL 1064, `character_sets_dir`; `MariaDbSpaltenbeschreibungTests` (18), Abnahme `mysql-node` und `mysql-java` (am Orakel gleich); Rest MY30.7h (docs/umsetzung.md MY30.7e).✅
MY30.7hReste aus MY30.7eParametertypen für Funktionen (`ABS(?)` DOUBLE, `IF(?, 1, 2)` LONGLONG, `CAST(? AS SIGNED)`), `?+0.5` (DECIMAL), `INSERT … SELECT ?`, Sichten und `FROM a, b` als Quelle; der eigene Zeichensatz einer Spalte bei `character_set_results = NULL` (`latin1` 8, `ascii` 11, `_bin` 46) und die Länge einer Spalte anderen Zeichensatzes als Parameter (`latin1` 262140); Anzeigebreiten anderer Ganzzahlen als `TINYINT(1)` (`INT(5)`, `TINYINT(2)`: am Orakel die Breite); Namen mit `$` in der Mitte (`a$b`) in `CREATE TABLE`; der Name `$$` in der Meldung 1054 bei MariaDB (hier `$`). **Stand:** erfüllt (docs/umsetzung.md MY30.7h; 85 Fälle, am Orakel gemessen): der Typ des Platzhalters richtet sich nach Funktion, Rechnung und Literal (DOUBLE, DECIMAL, LONGLONG, DATE, DATETIME(6), Text; mit einer Spalte verglichen deren Typ), `INSERT … SELECT`, Sichten und Tabellenlisten geben die Typen ihrer Spalten; Zeichensatz, Sortierung und Länge einer Textspalte (Parameter und Spalte, mit und ohne `character_set_results = NULL`, `_bin` mit BINARY); die Anzeigebreite der Ganzzahlen steht an der Spalte (`Types.Ganzzahlbreite`) und in Länge, `SHOW CREATE TABLE` und `COLUMN_TYPE` (MariaDB); Namen mit `$` an jeder Stelle, `$$` ganz in der Meldung; Sabotage; Reste in MY30.7k.✅
MY30.7kReste aus MY30.7h**(1) erledigt 09.10.2026** (am Orakel mit mysql2 gemessen): die Rechnung um einen Aufruf (`ABS(?) + 1` LONGLONG, `2.5 * FLOOR(?)` DECIMAL, auch im Vergleich mit einer Spalte), `? << 1`/`? >> 1`/`~?`/`BIT_COUNT(?)`/`? & ?` als Bitfolge VAR_STRING 65535 BINARY, die Weite einer Verschiebung LONGLONG, `CONV`/`BIN`/`OCT`/`INET_NTOA` LONGLONG, das Dokument der `JSON_*`-Funktionen und ihre Werte (`JSON_SET('{}', '$.a', ?)`) JSON, `? MEMBER OF` JSON, abgeleitete Tabellen als Quelle (`FROM (SELECT a FROM t1) d WHERE d.a = ?`); dazu `BIT_COUNT(NULL)` NULL statt 8116, Text und Kommazahl gerundet (beide Orakel). (2) und (3) stehen in MY30.7s.✅
MY30.7sReste aus MY30.7k(1) der eigene Zeichensatz eines Ausdrucks mit `character_set_results = NULL` (`CONCAT(v1, 'x')` latin1 8, Länge 11, Stellen 31, `UPPER(v2)` ascii 11) und der Parameter einer `TEXT`-Spalte dabei; (2) die Anzeigebreite in `CREATE TABLE … AS SELECT` (am Orakel nicht gemessen) und `BIGINT UNSIGNED` mit Breite in der Rechnung. **Stand 16.10.2026:** erfüllt (docs/umsetzung.md MY30.7s): das Maß von Textausdrücken (`CONCAT`, `UPPER`/`LOWER`, `TRIM`, `REVERSE`, `LEFT`/`RIGHT`, `LPAD`/`RPAD`, `SUBSTRING`, `REPEAT`, `REPLACE`) aus Zeichen und Zeichensatz der Spalten, mit `character_set_results = NULL` im eigenen Zeichensatz, BLOB-Typ über eine `TEXT`-Spalte; `CREATE TABLE … AS SELECT` übernimmt Typ, Breite und Bereichsprüfung einer Spalte (TINYINT mit Vorzeichen bleibt `tinyint`, `INSERT 200` → 1264), Breiten von `CAST … SIGNED/UNSIGNED`, Verneinung und Literal jenseits von BIGINT wie am Orakel.✅
MY30.7fMySQL Workbench durch den Nutzer geprüftDie Bedienung von MySQL Workbench 8.4 am MySQL-Port (Verbinden, Schema-Baum, Tabelle anlegen und ändern, Abfragen, Ergebnisgitter bearbeiten, Benutzer und Rechte, Dump und Import, Serverstatus) prüft der Nutzer selbst; Funde kommen als Zeilen hierher. Der Mitschnitt von Workbench läuft seit MY30.7b; die Bearbeitung der Ergebnisgitter hängt an MY30.7d (seit 07.10.2026 erledigt).⬜
Meilenstein 31: Oracle-Dialekt auf KuonenSQL (0/21)
PunktTitelBeschreibungStand
OR31.1Referenzorakel OracleContainer `kq-ref-oracle` aus `gvenzl/oracle-free:23-slim` (Oracle Database 23ai Free, Download erst nach Freigabe durch den Nutzer), nur 127.0.0.1; `dist\orakel-ref.ps1 -Dialekt oracle` (Fragen als SQL-Datei, Antwort über SQL*Plus im Container). Oracle ist nicht offen: nur von aussen messen, keine Dateien aus der Installation übernehmen. Testclient `python-oracledb` im Thin-Modus (Apache-2.0/UPL, im Container `python:3.13-slim`, Freigabe nötig).⬜
OR31.2Dialektmodul OracleDialekt `Oracle` mit eigener Kennung im Protokoll, als Modul über die gemeinsamen Nahtstellen (`docs/neuer-dialekt.md`, `DialektArchitekturTests`): eigener Hörer (Vorgabe aus, `KUONENSQL_ORACLE_PORT`, üblich 1521), Sitzungsschalter je Dialekt, Leserweiche im gemeinsamen Parser, ein Ausführer, eine Ablage. Unterschiede nur als Eigenschaften der Dialektbeschreibung.⬜
OR31.3Protokoll Oracle Net (TNS)Pakete CONNECT, ACCEPT, REFUSE, REDIRECT, RESEND, DATA, MARKER (Unterbrechung/Abbruch), Kopf mit Länge und Prüfsumme, SDU/TDU-Aushandlung, grosse Pakete (32-Bit-Länge ab 12c); Verbindungsbeschreibung `(DESCRIPTION=(ADDRESS=…)(CONNECT_DATA=(SERVICE_NAME=…)))` und Easy Connect `host:port/dienst`; Dienstnamen auf Datenbanken abbilden; Native Network Encryption ablehnen wie ein Server ohne NNE, TLS (TCPS) über den vorhandenen TLS-Weg; jede Länge aus dem Netz begrenzt (AGENTS §5), verfälschte Pakete trennen nur diese Verbindung. Herleitung nur aus dem Mitschnitt am Orakel und öffentlicher Doku.⬜
OR31.4Protokoll TTC (Two-Task Common)Aushandlung Protokollfassung (TTCPRO) und Datentyp-Darstellung (TTCDTY), Funktionsaufrufe: OALL8 (Parse, Binden, Ausführen, Holen in einem), OFETCH (weitere Zeilen, Zeilenbündel), OCOMMIT, OROLLBACK, OLOGOFF, OCLOSE/Cursor schliessen, OVERSION, Huckepack-Nachrichten (Cursor schliessen, Sitzungsdaten), Beschreibung der Spalten (Typ, Länge, Genauigkeit, Skala, Name, NULL-fähig, Zeichensatz), Bindwerte IN/OUT, Rückgabe von Zeilenzahl und ROWID, Fehlermeldung mit ORA-Nummer, Text und Stelle, Warnungen, Abbruch über MARKER, LOB-Operationen (Länge, Lesen, Schreiben, Kürzen über Locator), Serverausgabe von DBMS_OUTPUT über Aufruf.⬜
OR31.5Anmeldung O5LOGONZweistufige Anmeldung (AUTH_SESSKEY, AUTH_VFR_DATA, AUTH_PASSWORD, AUTH_SPEEDY_KEY), Prüfwerte 11g (SHA-1) und 12c (PBKDF2-SHA512) im eigenen Code aus der öffentlichen Beschreibung, Sitzungsattribute (AUTH_PROGRAM_NM, AUTH_MACHINE, AUTH_TERMINAL, AUTH_SID, AUTH_ALTER_SESSION), Fehler ORA-01017/ORA-28000/ORA-28001; Abbildung Benutzer = Schema auf das eine SQL-Server-Rechtemodell (Anmeldung + Datenbankbenutzer + Schema gleichen Namens), Proxy-Anmeldung als Rest.⬜
OR31.6Datentypen OracleNUMBER(p,s) mit bis zu 38 Stellen und negativer Skala, Leitungsform (Exponent und Basis-100-Ziffern), BINARY_FLOAT/BINARY_DOUBLE (mit Inf/NaN), FLOAT(b); VARCHAR2/NVARCHAR2/CHAR/NCHAR mit BYTE- oder CHAR-Längensemantik (NLS_LENGTH_SEMANTICS), bis 4000/32767 Bytes (MAX_STRING_SIZE); DATE (immer mit Uhrzeit, 7 Bytes), TIMESTAMP(p), TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE, INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND; RAW, LONG, LONG RAW, CLOB, NCLOB, BLOB, BFILE (nur lesen, Verzeichnisobjekte); ROWID/UROWID (stabile Zeilenkennung aus der Ablage); BOOLEAN, JSON und VECTOR (23ai, auf die vorhandenen Typen abgebildet); XMLTYPE auf xml. Abbildung jedes Typs auf die gemeinsamen SqlTypeSpec, Katalog zeigt die Oracle-Namen.⬜
OR31.7Oracle-SemantikLeere Zeichenkette ist NULL (`'' IS NULL` wahr, auch beim Einfügen und in Funktionen); Verkettung mit `&#124;&#124;` übergeht NULL; DATE-Rechnung in Tagen (`d + 1`, `d1 - d2` als Zahl, `+ 1/24`); implizite Umwandlungen nach NLS_DATE_FORMAT, NLS_TIMESTAMP_FORMAT, NLS_NUMERIC_CHARACTERS, NLS_TERRITORY/NLS_LANGUAGE (Monatsnamen); CHAR-Vergleich mit Leerzeichen aufgefüllt, VARCHAR2 ohne; Sortierung nach NLS_SORT/NLS_COMP (BINARY, BINARY_CI, LINGUISTIC); Bezeichner ohne Anführungszeichen in GROSSBUCHSTABEN, mit Anführungszeichen genau; Zeilenzahl SQL%ROWCOUNT; Rundung von NUMBER wie Oracle.⬜
OR31.8Abfragesyntax OracleDUAL, ROWNUM (auch `WHERE ROWNUM <= n` vor ORDER BY), ROWID-Pseudospalte, FETCH FIRST/NEXT … ROWS ONLY/WITH TIES, OFFSET, hierarchische Abfragen CONNECT BY/START WITH/PRIOR/LEVEL/CONNECT_BY_ROOT/CONNECT_BY_ISLEAF/SYS_CONNECT_BY_PATH/NOCYCLE/ORDER SIBLINGS BY, Aussenverbund mit `(+)`, MINUS, INTERSECT, Unterabfrage-Faktorisierung WITH (auch rekursiv mit SEARCH/CYCLE), Inline-Sichten ohne Alias, PIVOT/UNPIVOT, LATERAL und CROSS/OUTER APPLY, analytische Funktionen samt KEEP (DENSE_RANK FIRST/LAST), LISTAGG … WITHIN GROUP (ON OVERFLOW), RATIO_TO_REPORT, Folgen `seq.NEXTVAL`/`seq.CURRVAL`, Synonyme (privat und PUBLIC), Optimierer-Hinweise `/*+ … */` annehmen und übergehen, AS OF TIMESTAMP/SCN auf temporale Tabellen (sonst ORA-Fehler), SAMPLE als Rest.⬜
OR31.9Datenänderung OracleINSERT ALL/INSERT FIRST mit WHEN, INSERT … SELECT, MERGE INTO … USING … WHEN MATCHED THEN UPDATE … DELETE WHERE … WHEN NOT MATCHED THEN INSERT, UPDATE mit Unterabfrage-Mengenzuweisung `SET (a, b) = (SELECT …)`, DELETE ohne FROM, RETURNING … INTO (Bindvariablen, auch BULK COLLECT), Fehlerprotokoll `LOG ERRORS INTO` als Rest, SELECT … FOR UPDATE [OF …] [NOWAIT / WAIT n / SKIP LOCKED].⬜
OR31.10Funktionen OracleNVL, NVL2, DECODE, COALESCE, NULLIF, LNNVL, SYSDATE, SYSTIMESTAMP, CURRENT_DATE, LOCALTIMESTAMP, TO_CHAR/TO_DATE/TO_TIMESTAMP/TO_NUMBER mit allen Formatmodellen (YYYY, RR, MON, DY, HH24, FF, TZH, FM, FX, 9/0/G/D/L/S/MI/PR/EEEE), ADD_MONTHS, MONTHS_BETWEEN, LAST_DAY, NEXT_DAY, TRUNC/ROUND für Datum und Zahl, EXTRACT, NUMTODSINTERVAL/NUMTOYMINTERVAL, INSTR/INSTRB, SUBSTR mit negativem Start, LENGTH/LENGTHB, LPAD/RPAD, LTRIM/RTRIM mit Zeichenmenge, TRANSLATE, REPLACE, INITCAP, CHR/ASCII, SOUNDEX, REGEXP_LIKE/REGEXP_SUBSTR/REGEXP_INSTR/REGEXP_REPLACE/REGEXP_COUNT, GREATEST/LEAST (NULL gewinnt), USER, UID, SYS_CONTEXT('USERENV', …), SYS_GUID, ORA_HASH, STANDARD_HASH, DUMP, VSIZE, BITAND, WIDTH_BUCKET, JSON_VALUE/JSON_QUERY/JSON_TABLE/JSON_OBJECT, XMLELEMENT/XMLAGG.⬜
OR31.11DDL OracleCREATE TABLE mit Speicherklauseln (TABLESPACE, STORAGE, PCTFREE, LOGGING, COMPRESS, PARTITION BY …) annehmen und übergehen bzw. im Katalog zeigen, Einschränkungen benannt mit ENABLE/DISABLE/VALIDATE/NOVALIDATE/DEFERRABLE INITIALLY DEFERRED, IDENTITY (GENERATED ALWAYS/BY DEFAULT ON NULL), virtuelle Spalten, DEFAULT ON NULL, GLOBAL TEMPORARY TABLE (ON COMMIT DELETE/PRESERVE ROWS), PRIVATE TEMPORARY TABLE (ORA$PTT_), CREATE OR REPLACE für Sicht/Prozedur/Funktion/Paket/Trigger/Synonym/Typ, CREATE INDEX (BITMAP, Funktionsindex, REVERSE als normale Indizes), CREATE SEQUENCE mit CACHE/ORDER/CYCLE, COMMENT ON TABLE/COLUMN, ALTER TABLE ADD/MODIFY/DROP/RENAME COLUMN, RENAME, TRUNCATE, DROP … CASCADE CONSTRAINTS/PURGE, Papierkorb (FLASHBACK TABLE … TO BEFORE DROP) als Rest, Materialisierte Sichten in Grundform (vollständige Auffrischung).⬜
OR31.12PL/SQL: SpracheAnonyme Blöcke DECLARE/BEGIN/EXCEPTION/END, Variablen und Konstanten mit %TYPE/%ROWTYPE, Records, Zuweisung `:=`, IF/ELSIF, CASE (Anweisung und Ausdruck), LOOP/EXIT WHEN/CONTINUE, WHILE, FOR i IN a..b (auch REVERSE), Cursor-FOR-Schleife, explizite Cursor (OPEN/FETCH/CLOSE, %FOUND/%NOTFOUND/%ROWCOUNT/%ISOPEN, Parameter), SQL%-Attribute, SELECT INTO mit NO_DATA_FOUND/TOO_MANY_ROWS, Ausnahmen (vordefinierte, benannte, PRAGMA EXCEPTION_INIT, RAISE, RAISE_APPLICATION_ERROR -20000..-20999, SQLCODE/SQLERRM, WHEN OTHERS), EXECUTE IMMEDIATE (mit USING/INTO/RETURNING), REF CURSOR und SYS_REFCURSOR (an den Client als Ergebnis), Collections (assoziative Arrays INDEX BY, verschachtelte Tabellen, VARRAY, Methoden COUNT/FIRST/LAST/NEXT/EXTEND/DELETE/EXISTS), BULK COLLECT [LIMIT], FORALL [SAVE EXCEPTIONS], PRAGMA AUTONOMOUS_TRANSACTION, GOTO und Labels. Ausführung über den einen Ausführer (PL/SQL-Baum auf die vorhandenen Ablaufbausteine von T-SQL abbilden, kein zweiter Interpreter).⬜
OR31.13PL/SQL: gespeicherte EinheitenCREATE PROCEDURE/FUNCTION mit IN/OUT/IN OUT, NOCOPY, Vorgabewerten, benannter Übergabe `=>`, DETERMINISTIC, PIPELINED (PIPE ROW, TABLE(f(…))), RESULT_CACHE annehmen; Pakete (Spezifikation und Rumpf, öffentliche/private Teile, Paketvariablen und Initialisierungsteil je Sitzung, Überladung, ORA-04068 nach Neukompilieren); Trigger BEFORE/AFTER je Anweisung oder Zeile, `:NEW`/`:OLD`, WHEN-Bedingung, INSERTING/UPDATING/DELETING, INSTEAD OF auf Sichten, Compound Trigger, Systemtrigger (AFTER LOGON) als Rest; Objekttypen (CREATE TYPE … AS OBJECT/TABLE OF/VARRAY) in Grundform; Kompilierfehler in USER_ERRORS, Status VALID/INVALID, ALTER … COMPILE, SHOW ERRORS-Daten.⬜
OR31.14Eingebaute PaketeDBMS_OUTPUT (ENABLE, PUT_LINE, GET_LINES - Abruf durch den Client), DBMS_LOB (GETLENGTH, SUBSTR, INSTR, APPEND, WRITE, CREATETEMPORARY), DBMS_SQL (Grundzüge für dynamisches SQL), DBMS_METADATA.GET_DDL (DDL im Oracle-Format), DBMS_RANDOM, DBMS_SESSION (SET_CONTEXT, SLEEP), DBMS_LOCK.SLEEP, DBMS_UTILITY (FORMAT_ERROR_BACKTRACE, FORMAT_CALL_STACK, GET_TIME), DBMS_APPLICATION_INFO, UTL_RAW, UTL_I18N, DBMS_STATS (annehmen, Statistiken anstossen), DBMS_JOB/DBMS_SCHEDULER auf den vorhandenen Planer.⬜
OR31.15Transaktionen und Sperren OracleKein Autocommit auf dem Server, DDL schreibt die laufende Transaktion implizit fest, READ COMMITTED mit Lesekonsistenz je Anweisung (Momentaufnahme je Anweisung, Leser blockieren nie), SERIALIZABLE mit ORA-08177, SET TRANSACTION READ ONLY, SAVEPOINT/ROLLBACK TO, LOCK TABLE … IN … MODE [NOWAIT], Sperrfehler ORA-00054, Verklemmung ORA-00060 (nur die Anweisung wird zurückgenommen, nicht die Transaktion), verteilte Transaktionen als Rest.⬜
OR31.16Data Dictionary und dynamische SichtenUSER_/ALL_/DBA_-Sichten für TABLES, TAB_COLUMNS, TAB_COLS, COL_COMMENTS, TAB_COMMENTS, CONSTRAINTS, CONS_COLUMNS, INDEXES, IND_COLUMNS, IND_EXPRESSIONS, VIEWS, SEQUENCES, SYNONYMS, OBJECTS, SOURCE, PROCEDURES, ARGUMENTS, TRIGGERS, TYPES, USERS, ROLE_PRIVS, SYS_PRIVS, TAB_PRIVS, ERRORS, DEPENDENCIES, TABLESPACES, SEGMENTS (aus der Ablage), DICTIONARY/DICT_COLUMNS; V$VERSION, V$INSTANCE, V$DATABASE, V$SESSION, V$PARAMETER, V$NLS_PARAMETERS, NLS_SESSION_PARAMETERS, NLS_DATABASE_PARAMETERS, V$MYSTAT/V$STATNAME, V$LOCK, PRODUCT_COMPONENT_VERSION; jeweils genau die Spalten, die SQL Developer, DBeaver, ODP.NET, JDBC-Metadaten, Hibernate und EF Core abfragen (am Orakel gemessen). Ein Katalog, nur andere Sicht.⬜
OR31.17Benutzer, Rechte, SitzungCREATE/ALTER/DROP USER (Benutzer = Schema, IDENTIFIED BY, QUOTA, ACCOUNT LOCK, DEFAULT TABLESPACE annehmen), Systemrechte (CREATE SESSION, CREATE TABLE, CREATE ANY …, SELECT ANY TABLE, UNLIMITED TABLESPACE) und Objektrechte mit WITH GRANT/ADMIN OPTION, Rollen CONNECT/RESOURCE/DBA und eigene Rollen (SET ROLE, Standardrollen), Definer/Invoker Rights (AUTHID), alles auf das eine Rechtemodell abgebildet und in SSMS 22 sichtbar; ALTER SESSION SET CURRENT_SCHEMA/NLS_*/TIME_ZONE/ISOLATION_LEVEL, ALTER SYSTEM nur lesend bzw. ORA-01031.⬜
OR31.18Fehler wie OracleFehlernummern und Texte wie Oracle 23ai mit Platzhaltern (ORA-00942, ORA-00904, ORA-00001 mit Einschränkungsname, ORA-01400, ORA-01722, ORA-12899 mit Längen, ORA-01476, ORA-00933, ORA-00936, ORA-01403, ORA-06550 mit Zeile/Spalte und PLS-Fehlern, ORA-06512-Aufrufkette, ORA-02291/02292, ORA-01438, ORA-00957 …), Fehlerstelle im Text; Übersetzung aus den gemeinsamen Fehlern der Engine an einer Stelle (Tabelle je Dialekt), nicht verstreut.⬜
OR31.19Treiber und Werkzeuge OracleAbnahme mit python-oracledb (thin), ODP.NET Managed Driver Core (`Oracle.ManagedDataAccess.Core`, Testprojekt), JDBC thin (ojdbc11, im Container eclipse-temurin), SQL*Plus/SQLcl (Grundbefehle, DESCRIBE, SET SERVEROUTPUT ON), EF Core mit Oracle-Anbieter (Migrationen, CRUD), Hibernate OracleDialect; Handtest des Nutzers mit SQL Developer und DBeaver. Downloads je Paket nur nach Freigabe.⬜
OR31.20Migration und GleichwertigkeitDieselbe Datenbank über T-SQL, MariaDB, PostgreSQL und Oracle gleichzeitig bearbeitbar (M26): Typen, Einstellungen, Rechte und Objekte über jeden Zugang verwaltbar; Oracle-DDL aus DBMS_METADATA/SQL Developer einspielbar; ein Beispielschema (HR/SCOTT-artig, selbst gebaut) läuft vollständig.⬜
OR31.21Abnahme OraclePrüfstand-Beispiele und Handbuchkapitel Oracle (M19), Fuzzing des TNS/TTC-Lesers (PR27.7), Leck-Lauf (der Lastlauf steht als LT35.2 in M35), Absturzprüfung über den Oracle-Zugang.⬜
Meilenstein 32: Db2-Dialekt auf KuonenSQL (0/19)
PunktTitelBeschreibungStand
DB32.1Referenzorakel Db2Container `kq-ref-db2` aus `icr.io/db2_community/db2` (Db2 Community Edition, Download erst nach Freigabe durch den Nutzer; braucht `--privileged`), nur 127.0.0.1; `dist\orakel-ref.ps1 -Dialekt db2` (Fragen als SQL-Datei, Antwort über den CLP `db2` im Container). Nur von aussen messen. Testclient `ibm_db` (Apache-2.0) im Container `python:3.13-slim` (Freigabe nötig).⬜
DB32.2Dialektmodul Db2Dialekt `Db2` mit eigener Kennung im Protokoll als Modul über die gemeinsamen Nahtstellen: eigener Hörer (Vorgabe aus, `KUONENSQL_DB2_PORT`, üblich 50000), Sitzungsschalter, Leserweiche, ein Ausführer, eine Ablage; Unterschiede als Eigenschaften der Dialektbeschreibung.⬜
DB32.3Protokoll DRDA (DDM, FD:OCA)Nach der öffentlichen Spezifikation der Open Group (DRDA V5, DDM, FD:OCA) und dem Mitschnitt am Orakel: DSS-Rahmen (Länge, Kennung, Kette, Korrelation), EXCSAT/EXCSATRD (Fähigkeiten, Managerstufen), ACCSEC/ACCSECRD und SECCHK/SECCHKRM (Sicherheitsverfahren USRIDPWD, USRENCPWD und EUSRIDPWD mit Diffie-Hellman-Schlüsselaustausch im eigenen Code), ACCRDB/ACCRDBRM (Datenbankname, Codepage, Produktkennung), PRPSQLSTT/SQLDARD (Vorbereiten, Spaltenbeschreibung), DSCSQLSTT, EXCSQLIMM, EXCSQLSTT (mit Parametern SQLDTA), EXCSQLSET (Sonderregister), OPNQRY/OPNQRYRM/QRYDSC/QRYDTA (Zeilenblöcke, Blockgrösse, Vorauslesen), CNTQRY, CLSQRY, ENDQRYRM, RDBCMM/RDBRLLBCK, SQLCARD (SQLCODE, SQLSTATE, Fehlertokens), Antwortnachrichten (RDBNFNRM, SYNTAXRM, PRCCNVRM …), dynamische Pakete (NULLID.SYSSH200, SYSLH… mit Konsistenztoken), Zeichensätze CCSID 1208 (UTF-8), 1200, und EBCDIC 37/500 für Clients, die es verlangen; TLS; jede Länge aus dem Netz begrenzt, verfälschte Pakete trennen nur diese Verbindung.⬜
DB32.4Anmeldung und Sitzung Db2Benutzer und Kennwort über SECCHK auf das eine SQL-Server-Rechtemodell (Autorisierungs-ID = Anmeldung, Standardschema = Autorisierungs-ID), Fehler SQL30082N mit Grundcode, Sonderregister je Sitzung: CURRENT SCHEMA, CURRENT PATH, CURRENT ISOLATION, CURRENT LOCK TIMEOUT, CURRENT CLIENT_USERID/APPLNAME/WRKSTNNAME/ACCTNG, CURRENT DEGREE, CURRENT TIMEZONE; SET SCHEMA, SET PATH, SET CURRENT ISOLATION, SET CURRENT LOCK TIMEOUT.⬜
DB32.5Datentypen Db2SMALLINT, INTEGER, BIGINT, DECIMAL/NUMERIC bis 31 Stellen, DECFLOAT(16) und DECFLOAT(34) (IEEE-754-Dezimal, Rundungsmodus CURRENT DECFLOAT ROUNDING MODE), REAL, DOUBLE/FLOAT; CHAR, VARCHAR, LONG VARCHAR, CLOB mit FOR BIT DATA; GRAPHIC, VARGRAPHIC, DBCLOB (UTF-16); BINARY, VARBINARY, BLOB; DATE, TIME, TIMESTAMP(0..12) (auf datetime2 mit Hinweis bei mehr als 7 Stellen), BOOLEAN, XML, ROWID; benutzerdefinierte Distinct-Typen (CREATE TYPE … AS … WITH COMPARISONS, strenge Typisierung) und strukturierte Typen als Rest; Leitungsformen nach FD:OCA (gepackte Dezimalzahl, DECFLOAT, Datum als Zeichenkette).⬜
DB32.6Abfragesyntax Db2FETCH FIRST n ROWS ONLY, OFFSET … ROWS, OPTIMIZE FOR n ROWS, VALUES als Anweisung und als Tabelle (`SELECT * FROM (VALUES (1,2)) AS t(a,b)`), SYSIBM.SYSDUMMY1, Sonderregister CURRENT DATE/TIME/TIMESTAMP/USER/SERVER/SCHEMA, Isolation je Anweisung WITH UR/CS/RS/RR (USE AND KEEP … LOCKS), SELECT FROM FINAL TABLE/NEW TABLE/OLD TABLE (Datenänderung in FROM), INCLUDE-Spalten, rekursive gemeinsame Tabellenausdrücke ohne RECURSIVE, GROUPING SETS/ROLLUP/CUBE, OLAP-Funktionen samt RANGE/ROWS-Fenster, LATERAL/TABLE(), Unterabfragen in jeder Stellung, FOR READ ONLY/FOR FETCH ONLY/FOR UPDATE OF, Folgen NEXT VALUE FOR/PREVIOUS VALUE FOR (auch NEXTVAL/PREVVAL), ALIAS und Synonyme, Kommentar `--` und `/* */`, Verkettung `&#124;&#124;` und CONCAT, Zeitreisen (FOR SYSTEM_TIME AS OF, FOR BUSINESS_TIME) auf die temporalen Tabellen der Engine.⬜
DB32.7Datenänderung Db2INSERT mit mehreren Zeilen, INSERT … SELECT, MERGE (mehrere WHEN-Zweige, SIGNAL im Zweig, DELETE), UPDATE mit Zeilenzuweisung `SET (a, b) = (…)`, DELETE mit FETCH FIRST, TRUNCATE … IMMEDIATE, Identitätsspalten GENERATED ALWAYS/BY DEFAULT (IDENTITY_VAL_LOCAL), ROW CHANGE TIMESTAMP, Rückgabe der Zeilenzahl in SQLERRD.⬜
DB32.8Funktionen Db2COALESCE/VALUE, NULLIF, NVL, DECODE (Kompatibilität), CHAR/VARCHAR mit Datumsformaten (ISO, USA, EUR, JIS), VARCHAR_FORMAT/TO_CHAR, TIMESTAMP_FORMAT/TO_DATE, DATE/TIME/TIMESTAMP-Konstruktoren, Datumsrechnung mit beschrifteten Dauern (`CURRENT DATE + 3 DAYS - 2 MONTHS`), DAYS, JULIAN_DAY, MIDNIGHT_SECONDS, TIMESTAMPDIFF, DAYOFWEEK/DAYOFYEAR/WEEK_ISO, ADD_MONTHS, LAST_DAY, POSSTR, LOCATE, LOCATE_IN_STRING, SUBSTR/SUBSTRING (CODEUNITS32), STRIP, TRIM, LEFT/RIGHT, REPEAT, SPACE, DIGITS, HEX, INSERT, OVERLAY, TRANSLATE, RAND, MOD, LISTAGG, XMLAGG/XMLSERIALIZE/XMLTABLE, JSON_VALUE/JSON_QUERY/JSON_TABLE/JSON_OBJECT (11.5), REGEXP_LIKE/REGEXP_SUBSTR/REGEXP_REPLACE, GENERATE_UNIQUE, RAISE_ERROR.⬜
DB32.9DDL Db2CREATE TABLE mit IN tablespace, ORGANIZE BY ROW/COLUMN, COMPRESS, DISTRIBUTE BY, PARTITION BY (annehmen und im Katalog zeigen), Einschränkungen (NOT ENFORCED, ENABLE QUERY OPTIMIZATION), CREATE INDEX mit INCLUDE und CLUSTER, CREATE VIEW mit CHECK OPTION, CREATE ALIAS, CREATE SEQUENCE, CREATE GLOBAL TEMPORARY TABLE und DECLARE GLOBAL TEMPORARY TABLE (Schema SESSION, ON COMMIT DELETE/PRESERVE ROWS, NOT LOGGED), ALTER TABLE (ADD/ALTER/DROP COLUMN, SET DATA TYPE, ADD PERIOD, ADD VERSIONING), RENAME, COMMENT ON, LABEL ON, CREATE SCHEMA mit AUTHORIZATION, Reorganisationsbedarf (SQL0668N) nicht nachbilden, sondern dokumentiert übergehen.⬜
DB32.10SQL PLZusammengesetzte Anweisungen BEGIN [ATOMIC] … END, DECLARE von Variablen, Bedingungen (FOR SQLSTATE) und Handlern (CONTINUE/EXIT/UNDO), SIGNAL/RESIGNAL SQLSTATE mit MESSAGE_TEXT, GET DIAGNOSTICS (ROW_COUNT, MESSAGE_TEXT, DB2_RETURN_STATUS), IF/CASE/LOOP/WHILE/REPEAT/FOR (Cursorschleife)/LEAVE/ITERATE/GOTO, SET mit Zeilenzuweisung, Cursor (DECLARE … CURSOR WITH HOLD/WITH RETURN TO CALLER/CLIENT), EXECUTE IMMEDIATE, PREPARE/EXECUTE, Arrays und Zeilentypen (CREATE TYPE … AS ROW/ARRAY), assoziative Arrays, ARRAY_AGG/UNNEST. Ausführung über den einen Ausführer (Abbildung auf die vorhandenen Ablaufbausteine, kein zweiter Interpreter).⬜
DB32.11Routinen, Trigger, ModuleCREATE PROCEDURE (LANGUAGE SQL, IN/OUT/INOUT, DYNAMIC RESULT SETS n, Ergebnismengen an den Aufrufer, MODIFIES/READS SQL DATA, SPECIFIC-Namen, Überladung), CALL mit benannten Parametern und Vorgaben, Rückgabestatus; CREATE FUNCTION skalar und als Tabellenfunktion (RETURNS TABLE, inline und SQL-Rumpf, DETERMINISTIC, NO EXTERNAL ACTION); CREATE TRIGGER NO CASCADE BEFORE/AFTER/INSTEAD OF, REFERENCING NEW AS/OLD AS/NEW TABLE AS, FOR EACH ROW/STATEMENT, WHEN, Mehrfachereignisse (INSERT OR UPDATE); CREATE MODULE mit PUBLISH; CREATE OR REPLACE; Kompilierfehler mit SQLCODE und Zeile.⬜
DB32.12Transaktionen und Sperren Db2Isolationsstufen UR, CS (mit Currently Committed), RS und RR auf die Engine abgebildet (je Sitzung und je Anweisung), Sperr-Zeitlimit SQL0911N Grundcode 68 und Verklemmung Grundcode 2 (ganze Transaktion zurück), SQL0913N, LOCK TABLE … IN SHARE/EXCLUSIVE MODE, SAVEPOINT … ON ROLLBACK RETAIN CURSORS, COMMIT/ROLLBACK, Autocommit liegt beim Client, Cursor WITH HOLD über COMMIT.⬜
DB32.13Fehler wie Db2SQLCODE und SQLSTATE mit Fehlertokens im SQLCA (SQLERRMC durch 0xFF getrennt) und Meldungstexten wie Db2 11.5 (SQL0204N -204 42704, SQL0206N -206 42703, SQL0803N -803 23505 mit Indexkennung, SQL0407N -407 23502, SQL0104N -104 42601 mit Token und Erwartung, SQL0530N -530 23503, SQL0532N, SQL0911N, SQL0302N -302 22001, SQL0420N -420 22018, SQL0100W +100 02000, Warnungen +), SQLERRD (Zeilenzahl, Identität); Übersetzung aus den gemeinsamen Fehlern an einer Stelle.⬜
DB32.14Katalog Db2SYSCAT-Sichten TABLES, COLUMNS, INDEXES, INDEXCOLUSE, KEYCOLUSE, REFERENCES, TABCONST, CHECKS, COLCHECKS, VIEWS, SEQUENCES, ROUTINES, ROUTINEPARMS, TRIGGERS, SCHEMATA, DATATYPES, TABAUTH, DBAUTH, ROLES, ROLEAUTH, PACKAGES, TABLESPACES, MODULES; SYSIBM.SYSTABLES/SYSCOLUMNS/SYSINDEXES/SYSDUMMY1/SQLCAPABILITIES; SYSIBMADM-Sichten (ENV_INST_INFO, ENV_PROD_INFO, SNAPAPPL_INFO) und MON_GET_*-Tabellenfunktionen soweit Werkzeuge sie lesen; Katalogprozeduren SYSIBM.SQLTABLES, SQLCOLUMNS, SQLPRIMARYKEYS, SQLFOREIGNKEYS, SQLSTATISTICS, SQLPROCEDURES, SQLPROCEDURECOLS, SQLGETTYPEINFO, SQLTABLEPRIVILEGES, die JCC und CLI aufrufen (am Orakel gemessen). Ein Katalog, nur andere Sicht.⬜
DB32.15Rechte Db2GRANT/REVOKE von Datenbankrechten (CONNECT, CREATETAB, BINDADD, IMPLICIT_SCHEMA, DBADM, SECADM, DATAACCESS) und Objektrechten (SELECT, INSERT, UPDATE(spalten), DELETE, ALTER, INDEX, REFERENCES, EXECUTE, USAGE) samt WITH GRANT OPTION, Rollen (CREATE ROLE, GRANT ROLE … WITH ADMIN OPTION), PUBLIC, TRANSFER OWNERSHIP, alles auf das eine Rechtemodell; Zeilen- und Spaltenzugriffskontrolle (CREATE PERMISSION/MASK) auf die vorhandene Zeilensicherheit als Rest.⬜
DB32.16VerwaltungsbefehleSYSPROC.ADMIN_CMD für RUNSTATS, REORG (annehmen, Statistiken anstossen), DESCRIBE TABLE/OUTPUT, EXPORT/IMPORT/LOAD in Grundform (DEL- und IXF-Dateien im Datenordner), GET DB CFG/UPDATE DB CFG auf die Einstellungen der Engine (J26.6); Ausgabe von db2look (DDL) einspielbar; SYSPROC.ADMIN_GET_TAB_INFO u. a. soweit Werkzeuge sie rufen.⬜
DB32.17Treiber und Werkzeuge Db2Abnahme mit ibm_db (Python), IBM Data Server Driver für JDBC (db2jcc4, im Container eclipse-temurin), Net.IBM.Data.Db2 (.NET, Testprojekt), DBeaver, Hibernate DB2Dialect, EF Core mit Db2-Anbieter (CRUD, Migrationen); Handtest des Nutzers mit IBM Data Studio oder DBeaver. Downloads je Paket nur nach Freigabe.⬜
DB32.18Gleichwertigkeit und MigrationDieselbe Datenbank über T-SQL, MariaDB, PostgreSQL, Oracle und Db2 gleichzeitig (M26): Typen, Einstellungen, Rechte und Objekte über jeden Zugang verwaltbar; ein Beispielschema (SAMPLE-artig, selbst gebaut) läuft vollständig; DDL aus db2look einspielbar.⬜
DB32.19Abnahme Db2Prüfstand-Beispiele und Handbuchkapitel Db2 (M19), Fuzzing des DRDA-Lesers (PR27.7), Leck-Lauf (der Lastlauf steht als LT35.3 in M35), Absturzprüfung über den Db2-Zugang.⬜
Meilenstein 33: KSQL — der eigene Dialekt mit ehrlicher Kennung und dem ISO-Standard (67/67)
PunktTitelBeschreibungStand
K33.1KSQL als Kern, T-SQL als Dialektmodul, eigener PortNach dem Grundsatzentscheid vom 15.10.2026: der heutige Ausführer (Parser, Ausführer, Ablage, Katalog) ist der Kern = KSQL; **T-SQL wird Dialektmodul wie MariaDB** über die gemeinsamen Nahtstellen, zunächst mit genau dem heutigen Verhalten und der SQL-Server-Kennung (aus dem Kern dorthin verschoben); eigener TDS-Hörer für KSQL (`KUONENSQL_KSQL_PORT`, Vorgabe **1333**), Zugang `Zugang.Ksql` für Sitzungen, Verbindungsgrenzen und Protokoll; Unterschiede nur als Eigenschaften der Dialektbeschreibung. Dienstvorlagen (Docker, Linux, macOS, Windows) und die Installationsseite der gemeinsamen Webseite um den Port ergänzt. **Erledigt 15.10.2026:** `Dialekt.Ksql` (Kennung 4) ist der Kern, `TSql/TSqlDialekt.cs` (Kennung 0, Vorgabe jeder Sitzung ohne eigenen Anschluss) das Modul samt `TSql/Serverkennung.cs`; die sechs Namensabfragen auf T-SQL sind die Eigenschaft `Kernregeln`, die Fremddialekte setzen sie aus; Hörer `ksql` über `KUONENSQL_KSQL_PORT` (im Programm aus, in den Dienstvorlagen und im Compose 1333), `Zugang.Ksql`; `KsqlZugangTests`.✅
K33.2Ehrliche KennungAm KSQL-Port melden `@@VERSION`, `SERVERPROPERTY('ProductVersion'/'ProductMajorVersion'/'ProductLevel'/'Edition'/'EngineEdition'/'ProductBuild')`, `@@MICROSOFTVERSION`, die TDS-Vorabmeldung (Fassung) und LOGINACK (Programmname `KuonenSQL`) die echte KuonenSQL-Fassung; `sys.dm_os_host_info`, `host_platform`, Pfade und Trennzeichen das echte Wirtssystem (Linux/macOS/Windows, Architektur, .NET-Fassung); `xp_msver`, `sys.dm_os_sys_info` mit echten Werten. Eigene `EngineEdition` und Edition „KuonenSQL“. Am T-SQL-Port bleibt alles wie SQL Server 2025 (`@@KVERSION` bleibt dort). **Erledigt 15.10.2026** mit K33.1 (die Kennung ist eine Eigenschaft des Dialekts, `Dialekte/Ansage.cs`): `@@VERSION`, `@@MICROSOFTVERSION`, `SERVERPROPERTY` (Fassung, Stufe, Edition „KuonenSQL“, EngineEdition 100, EditionID „KSQL“, Pfade, Trennzeichen), `sys.dm_os_host_info`, Vorabmeldung und LOGINACK (`KuonenSQL`, SqlClient: `ServerVersion` 01.12.…) melden am KSQL-Port die Wahrheit. Rest in K33.2a.✅
K33.2aRest aus K33.2: `xp_msver` und `sys.dm_os_sys_info`Am KSQL-Port nennt `xp_msver` noch Produktname, Hersteller, Dateibeschreibung und Windows-Fassung von SQL Server (Fassung und Bau sind schon echt), `sys.dm_os_sys_info` die Werte des Nachbaus; beide aus der `Ansage` des Dialekts speisen. **Erledigt 15.10.2026:** `xp_msver` liest Produktname, Hersteller, Dateibeschreibung und -version, Programmdatei, Urheberrecht, Marke, Plattform und Windows-Fassung aus der `Ansage` (T-SQL wortgleich wie bisher); `sys.dm_os_sys_info` nennt an jedem Zugang den echten Speicher, belegten Speicher und die Startzeit des Prozesses (vorher feste 16 GB und „vor einer Stunde“).✅
K33.3Merkmalsliste ISO SQL:2023`tools/ksql/iso-merkmale.tsv` mit allen Merkmalen von Teil 2 (Foundation), 4 (PSM), 9 (MED, nur Kennung), 11 (Schemata), 14 (XML), 15 (MDA) und 16 (PGQ): Kennung, Name, Pflicht/optional, Status, Testname; Prüfung im Test, dass jedes ✅-Merkmal einen grünen Test hat. Daraus `INFORMATION_SCHEMA.SQL_FEATURES`, `SQL_IMPLEMENTATION_INFO`, `SQL_SIZING` und die Übersicht im Handbuch. **Erledigt 15.10.2026:** die Liste liegt als `src/Server/Kuonen.Data.KuonenSql/Ksql/Referenzdaten/iso-merkmale.tsv` (eingebettet; 788 Merkmale: Teil 2, 9, 11, 14 aus `information_schema.sql_features` von PostgreSQL 17 am Referenzcontainer gemessen, Teil 16 aus der öffentlichen Konformitätsliste SQL/PGQ von Oracle, Teil 4 nur P001–P003 mit unbestätigtem Namen). Jede Probe läuft am KSQL-Zugang, `KsqlMerkmalTests` verlangt Status „+“ genau dann, wenn sie läuft; Proben für alle 35 Merkmale von Core SQL, davon heute 108 Zeilen „+“. `INFORMATION_SCHEMA.SQL_FEATURES`, `SQL_PARTS`, `SQL_IMPLEMENTATION_INFO`, `SQL_SIZING` am KSQL-Zugang (Modul `Ksql/`). Die Übersicht im Handbuch gehört zu K33.15.✅
K33.4Regeln bei Widerspruch T-SQL ↔ NormWo T-SQL und Norm verschieden rechnen, gilt am KSQL-Port die Norm, T-SQL bleibt per `SET`-Schalter erreichbar - Liste mit Entscheid des Nutzers je Fall: Verkettung `||` und `+`, `"` als Bezeichner, Ganzzahldivision, NULL in Verkettungen, Vergleich mit Leerzeichen am Ende (PAD SPACE/NO PAD je Sortierung), `''` gegen NULL, Grösse von `CHAR` ohne Länge, `BOOLEAN` gegen `bit`, Ergebnistypen von Ausdrücken, Gross-/Kleinschreibung nicht zitierter Namen (Norm: Grossschreibung). **Erledigt 15.10.2026:** Liste mit Entscheid je Fall in nach der Leitlinie des Grundsatzentscheids (Norm bei Widerspruch, sonst Kern; kein SET-Schalter, die Wahl des Zugangs ist die Wahl des Verhaltens). Am KSQL-Zugang gelten jetzt `UNIQUE` mit mehreren NULL, NOT NULL durch nachträglichen Primärschlüssel, Vorgabe für vorhandene Zeilen bei `ADD COLUMN`, `CAST` ohne Länge ohne Grenze (`KsqlNormregelnTests`, je Regel beide Zugänge); dazu die Grammatik `NATURAL JOIN`/`USING`, `WITH RECURSIVE`, Zeilenwerte, `GROUPS`/`EXCLUDE`. Die Fälle `0x`-Literal und `BOOLEAN` setzt K33.5 um, `COMMIT` ohne Transaktion K33.9.✅
K33.5Core SQL: Typen und Literale`BOOLEAN` als echter Typ (TRUE/FALSE/UNKNOWN, `IS TRUE/FALSE/UNKNOWN`), `CHARACTER VARYING`, `CHARACTER LARGE OBJECT`, `BINARY LARGE OBJECT`, `DOUBLE PRECISION`, `DECFLOAT`, `TIME/TIMESTAMP WITH TIME ZONE`, `INTERVAL YEAR TO MONTH`/`DAY TO SECOND` samt Rechnung mit Zeitpunkten; Literale `DATE '…'`, `TIME '…'`, `TIMESTAMP '…'`, `INTERVAL '…' DAY`, `U&'…'` mit `UESCAPE`, `X'…'`, SQL:2023 `0x`/`0o`/`0b` und Unterstriche in Zahlen; Typen `ROW`, `ARRAY` (S091) und `MULTISET` (S271) mit Konstruktoren, `UNNEST`, `CARDINALITY`. **Erledigt 15.10.2026 bis auf K33.5a:** Literale `X'…'` (mit Leerzeichen, fortgesetzt), `U&'…'`/`U&"…"` mit `UESCAPE`, Fortsetzung über Zeilenumbrüche, `0x`/`0o`/`0b`, Unterstriche; `DATE/TIME/TIMESTAMP '…'`; `BOOLEAN` mit `TRUE`/`FALSE`/`UNKNOWN` und `IS …`; `CHARACTER VARYING`, `CHARACTER/BINARY LARGE OBJECT`, `CLOB`/`BLOB`, `DOUBLE PRECISION`, `TIME/TIMESTAMP [WITH TIME ZONE]`.✅
K33.5aRest aus K33.5: `INTERVAL`, `DECFLOAT`, `ROW`/`ARRAY`/`MULTISET``INTERVAL YEAR TO MONTH`/`DAY TO SECOND` als Typen samt Literalen (`INTERVAL '3' DAY`) und Rechnung mit Zeitpunkten (F052), `DECFLOAT(16|34)` (T076; der Kern nahm `DECFLOAT` bisher still an und rechnete falsch, K33.16), Zeilentypen `ROW(…)` mit Feldzugriff (T051, T053), `ARRAY` (S091) und `MULTISET` (S271) mit Konstruktoren, `UNNEST`, `CARDINALITY`. **Erledigt 20.10.2026** (Arrays, Zeilen, `UNNEST` und Intervall-Literale an PostgreSQL 17 gemessen, T-SQL am Orakel 2025; DECFLOAT, `(a - b) <Felder>`, DATE + INTERVAL und Monatsende nach Normtext, keine Referenz kennt sie): Intervalle mit Feldangabe, Literale in Normform (Vorzeichen für alle Felder, 22006/22015), `(a - b) DAY TO SECOND`, Zeitpunkt ± Intervall mit dem Typ des Zeitpunkts, Monatsende 22008, `EXTRACT` (Werte an PostgreSQL 17 gemessen, wo es der Norm folgt); `DECFLOAT(p)` als eigener Typ nur am KSQL-Zugang (halb-gerade auf p Stellen, Exponentenbereich decimal64/128); `ROW(…)` als Typ und Wert, `(r).feld`, `(r).* AS (x, y)`, `typ ARRAY[n]`, `ARRAY[…]`, `a[i]` (2202E), `||`, `typ MULTISET`, `MULTISET[…]`, `ELEMENT`, `CARDINALITY`, `UNNEST … WITH ORDINALITY` auch seitlich; Fehlernummern 23301–23305; T-SQL unverändert (Orakel 19./20.10.2026). F052, T076, T051, T053, S090, S091, S271 „+“. Rest K33.5c.✅
K33.5cReste aus K33.5aKonstruktoren durch Abfrage `ARRAY(SELECT …)`/`MULTISET(SELECT …)` (S095), `ARRAY_AGG` (S098), `TRIM_ARRAY`/`ARRAY_MAX_CARDINALITY` (S403, S404), Multimengen-Operatoren (S275), `(r).*` ohne Namensliste, Prüfungen 2202F und 22015, Jahr-Monat gegen Tag-Zeit, geschachtelte Sammlungen und Arrays von Zeilen (S281), `datetimeoffset`-Elemente, Funktionen über `DECFLOAT`, `INFORMATION_SCHEMA` für die neuen Typen, T-SQL-Punkte. **Erledigt 20.10.2026** (an PostgreSQL 17 und SQL Server 2025 gemessen, sonst Normtext): Arrays mit beliebigem Elementtyp (`kt:array(T)[n]`, Höchstzahl geprüft 2202F), `ARRAY(…)`/`MULTISET(…)` und `ARRAY_AGG`/`COLLECT`/`FUSION`/`INTERSECTION` über das Aggregat `__KSQL_SAMMELN`, `TRIM_ARRAY`, `ARRAY_MAX_CARDINALITY`, `MULTISET UNION/EXCEPT/INTERSECT`, `SET`, `MEMBER OF`, `SUBMULTISET OF`, `IS A SET`, `(r).*`, führende Intervall-Genauigkeit (22015), Jahr-Monat gegen Tag-Zeit 402, `ABS`/`SIGN`/`FLOOR`/`CEILING`/`ROUND`/`MOD`/`POWER`/`SQRT`/`EXP`/`LN`/`LOG`/`LOG10` über `DECFLOAT`, `COLUMNS` und `ELEMENT_TYPES`; T-SQL: `CAST` auf Nicht-Systemtyp und Aliastyp 243, Wörter hinter dem Spaltentyp und Namen in der Typklammer 102. S095, S096, S098, S275, S281, S403, S404 „+“. Rest K33.5d.✅
K33.5dReste aus K33.5cDie Länge eines Textelements einer Multimenge (`VARCHAR(3) MULTISET`) bei der Zuweisung prüfen (22001) statt zu kürzen; Jahr-Monat gegen Tag-Zeit auch prüfen, wenn eine Seite ein Intervall ohne Feldangabe aus einer Rechnung ist; T-SQL: `int(5)` als Spaltentyp 2716 (am Orakel 2025), heute angenommen; `tools/ksql/normsichten-erzeugen.py` wieder deckungsgleich mit `Normsichten.Definitionen.cs` machen. **Erledigt 20.10.2026:** Länge der Elemente von Multimengen, Arrays und Zeilenfeldern bei einer Zuweisung 2628 (22001), `CAST` kürzt; eine Intervallrechnung trägt die Felder ihrer Operanden, so bleibt die Klasse im Zwischenergebnis (402); `int(5)` u. a. am T-SQL-Zugang 2716, in `CAST` 291 (Orakel 2025); das Erzeugerskript erzeugt die Normsichten wieder byte-genau. **Bewusst nicht:** ein Intervall ganz ohne Feldangabe (PostgreSQL-Form, `sum` über Intervalle) hat keine Klasse der Norm und wird nicht als Jahr-Monat oder Tag-Zeit geraten - es vergleicht wie bei PostgreSQL.✅
K33.5eBefund aus K33.5c: verschachtelte Zahlenfunktionen exponentiellDie Typableitung von ABS, SIGN, FLOOR, CEILING, ROUND und POWER fragt seit K33.5c den Typ des ersten Arguments zweimal ab (Zweig für eigene Zahlentypen und allgemeiner Zweig) - 2^n Schritte je Verschachtelungstiefe. Der Langläufer SchachtelgrenzenTests (ABS 1013 Ebenen) lief nie zu Ende und hielt den grossen Testlauf vor der Veröffentlichung über vier Stunden auf. **Erledigt 22.10.2026:** ArgType merkt sich den Typ des ersten Arguments, der Zweig von K33.5c nutzt ihn; 200 Ebenen ABS in Millisekunden statt nie.✅
K33.6Core SQL: Abfragen**Teil 1 erledigt (15.10.2026): `FETCH FIRST|NEXT [n] [PERCENT] ROW|ROWS ONLY|WITH TIES` ohne `OFFSET`, auch in Unterabfragen, Sichten und Mengenoperationen (F433, F850–F868, T619), **Teil 2 erledigt:** `EXCEPT`/`INTERSECT` `ALL`/`DISTINCT` (E071-03, F303–F305), `CORRESPONDING [BY (…)]` (F301), erweitertes einfaches `CASE` (`WHEN < 3`, `WHEN 1, 2`, F262/F263), Spaltenliste hinter dem Tabellenalias (E051-09), `WITH` in Unterabfragen (T122, T132). **Teil 3 erledigt:** `NATURAL JOIN` über Tabellen aus demselben Stapel (F401, F405; der Ausführer liest den Rest des Stapels nach), `JOIN … USING (…) AS j` (F404), `NTH_VALUE … [FROM FIRST|LAST] [IGNORE NULLS]` (T618), `ANY_VALUE` (T626), `LISTAGG` (T625). **Teil 4 erledigt:** `FILTER (WHERE …)` an Aggregaten, auch vor `OVER` (T612); **Teil 5 erledigt:** `BETWEEN SYMMETRIC` (T461), `NULLS FIRST|LAST`, `EVERY`/`SOME`/`ANY`/`BOOL_AND`/`BOOL_OR`, `UNIQUE (…)` (F291), `GROUPING(a, b)` (T433), Gruppierungsglieder als Kreuzprodukt samt verschachteltem `ROLLUP` in `GROUPING SETS` (T432, auch T-SQL wie SQL Server) und `GROUP BY DISTINCT|ALL` (T434), funktionale Abhängigkeit vom Primärschlüssel (T301), `SET (a, b) = (…)` (T641), `LISTAGG … ON OVERFLOW`; **Teil 6 erledigt:** `LATERAL` hinter Komma, `JOIN` und `LEFT JOIN` (T491), `VALUES` und `TABLE t` als Abfrage samt `ORDER BY`/`FETCH` und als Glied einer Mengenoperation, `VALUES ROW(…)`, `WITH … AS (VALUES …)` (Spalten `column1` … wie PostgreSQL); **Teil 7 erledigt:** `TABLESAMPLE [SYSTEM] (n PERCENT|ROWS) [REPEATABLE (s)]` im Kern wie SQL Server (fehlte, auch am T-SQL-Zugang; 476 ausserhalb 0..100), am KSQL-Zugang `BERNOULLI` (T613); `LISTAGG … OVER (…)` (T625; T-SQL 4113 wie das Orakel); **Teil 8 erledigt:** `SEARCH DEPTH|BREADTH FIRST BY … SET` und `CYCLE … SET … [TO … DEFAULT …] USING` hinter `WITH RECURSIVE` (T131–T133; Pfad als geordnete Bytefolge, Reihenfolge wie PostgreSQL 17), `[KEY] k VALUE v` in `JSON_OBJECT`/`JSON_OBJECTAGG` (T811–T814); siehe .** Rest: `MATCH_RECOGNIZE` als K33.6a.✅
K33.6a`MATCH_RECOGNIZE` (R010, R020)**Erledigt (19.10.2026).** Am KSQL-Zugang Zeilenmuster nach SQL:2016 als Tabellenoperator (R010): `PARTITION BY`, `ORDER BY`, `MEASURES` mit `RUNNING`/`FINAL`, `ONE ROW|ALL ROWS PER MATCH` (`SHOW|OMIT EMPTY MATCHES`, `WITH UNMATCHED ROWS`), `AFTER MATCH SKIP PAST LAST ROW|TO NEXT ROW|TO FIRST|LAST v`, `PATTERN` mit `*`, `+`, `?`, `{n,m}` (auch zurückhaltend), Alternativen, Gruppen, `^`/`$`, `{- … -}`, `PERMUTE`, `SUBSET` und `DEFINE` mit `PREV`/`NEXT`/`FIRST`/`LAST` (verschachtelt), `CLASSIFIER`, `MATCH_NUMBER` und laufenden Aggregaten; dazu die Fensterform (R020: `MEASURES` und Muster im `WINDOW`, `INITIAL`/`SEEK`, `mass OVER w`). Orakel: Referenzcontainer `kq-ref-trino` (Trino 476, nur von aussen gemessen). Auswertung als Rückverfolgung mit eigenem Stapel, gedeckelt (Schrittgrenze, Programmgrösse) gegen bösartige Muster. Am T-SQL-Zugang bleibt es ein Syntaxfehler wie bei SQL Server 2025 (102, ohne Alias 156). Rest: K33.6b.✅
K33.6bRest aus K33.6a: R030 und Fensterform einmal je Fenster**Erledigt (20.10.2026), nach Normtext, Abgleich als K33.6c offen.** Teil 1: die Fensterform (R020) sucht die Treffer einmal je Fenster und Partition statt je Fensterfunktion (3000 Zeilen, 5 Funktionen: 48,5 → 23,0 ms). Teil 2 (R030): jedes Aggregat des Kerns in `MEASURES` und `DEFINE` der Mustererkennung, auch mit `DISTINCT`, `FILTER (WHERE …)`, `WITHIN GROUP (ORDER BY …)` und `CLASSIFIER()`/`MATCH_NUMBER()` im Argument.✅
K33.6cRest aus K33.6b: R030 am Orakel abgleichen**Erledigt (20.10.2026).** An `kq-ref-trino` gemessen: alle Aggregate, die Trino in der Mustererkennung rechnet, stimmen (`MIN(CLASSIFIER())`, `SUM(MATCH_NUMBER())`, `SUM(B.p * 2)`, `var_pop`, `stddev_samp`, `every`, `bool_or`, `COUNT(B.p + 1)`, `MAX(p) - MIN(p)`), ebenso die Fensterprobe aus K33.6b und die Fensterform mit Statistik. Abweichung behoben: `ANY_VALUE` ist im Treffer der erste Wert (Trino 8 und 5, vorher der kleinste 6 und 4). `SUM(PREV(p))` ist an beiden ein Fehler. `DISTINCT`, `FILTER` und `WITHIN GROUP` lehnt Trino in der Mustererkennung ab; KSQL rechnet sie nach der Norm (R030).✅
K33.7Core SQL: Funktionen**Erledigt (15.10.2026).** Am KSQL-Zugang nach der Norm: `POSITION`, `SUBSTRING … FROM … FOR` und `SUBSTRING … SIMILAR … ESCAPE` (T581), `OVERLAY`, `EXTRACT`, `TRIM(BOTH/LEADING/TRAILING …)`, `CHARACTER_LENGTH … USING`, `OCTET_LENGTH`, `LOCALTIME`/`LOCALTIMESTAMP`, `CURRENT_CATALOG`/`CURRENT_SCHEMA`, `LIKE_REGEX` und die `…_REGEX`-Funktionen, `[NOT] SIMILAR TO` (NonBacktracking, 2 s Frist), `LN`, `LOG(basis, zahl)` (Regel K33.4), `SINH`/`COSH`/`TANH`, `CEIL`, `WIDTH_BUCKET`, `BTRIM`, `STDDEV_POP/SAMP`, `VAR_POP/SAMP`. Kernfehler: `SUBSTRING` mit Länge 2147483647 lief über (Orakel `cdef`). 262 Merkmale „+“. Siehe .✅
K33.7aRest aus K33.7: Statistik, `NORMALIZE`, `CAST … FORMAT`**Erledigt (15.10.2026).** Am KSQL-Zugang: `COVAR_POP`, `COVAR_SAMP`, `CORR` und `REGR_SLOPE|INTERCEPT|COUNT|R2|AVGX|AVGY|SXX|SYY|SXY` als Aggregate und im Fenster (T621, über die Paare ohne NULL, zwei Durchgänge; Werte an PostgreSQL 17 gemessen), hypothetische Aggregate `RANK|DENSE_RANK|PERCENT_RANK|CUME_DIST(w) WITHIN GROUP (ORDER BY …)`, `NORMALIZE(x [, NFC|NFD|NFKC|NFKD])` und `x IS [NOT] [NFC|…] NORMALIZED` (T061-Teil), `CAST(… AS … FORMAT 'vorlage')` für Zeitwerte in beide Richtungen (T839; kein Orakel kennt es, Regeln nach der öffentlichen Beschreibung der Vorlagen). `PERCENTILE_CONT/DISC` als Aggregat und `UPPER`/`LOWER` liefen schon wie PostgreSQL. `KsqlStatistikTests` (28), `KsqlZeitvorlageTests` (23); siehe .✅
K33.8Datenänderung und DDL nach Norm`MERGE` nach Norm, `UPDATE … SET (a, b) = (…)`, Änderungstabellen (`SELECT … FROM NEW/OLD/FINAL TABLE (…)`), `GENERATED ALWAYS/BY DEFAULT AS IDENTITY`, `GENERATED ALWAYS AS (ausdruck)`, `CREATE DOMAIN`, `CREATE ASSERTION`, `CHECK`/`UNIQUE [NULLS NOT DISTINCT]`/Fremdschlüssel mit `MATCH SIMPLE/FULL/PARTIAL` und `RESTRICT`, `DEFERRABLE INITIALLY DEFERRED` samt `SET CONSTRAINTS`, `CREATE SCHEMA` mit eingeschlossenen Elementen, `GLOBAL/LOCAL TEMPORARY` mit `ON COMMIT`, Anwendungszeit (`PERIOD FOR`, `WITHOUT OVERLAPS`, `UPDATE/DELETE … FOR PORTION OF`), Systemzeit nach Norm, Sequenzen nach Norm, `CREATE TYPE` (Distinct- und strukturierte Typen), `DROP … CASCADE/RESTRICT`. **Teil 1 erledigt 16.10.2026** (F031 ganz, F032): `ALTER TABLE … ADD COLUMN`; `DROP TABLE|VIEW … RESTRICT` verweigert bei abhängiger Sicht (3729) oder Fremdschlüssel (3726), beide SQLSTATE 2BP01; `CASCADE` löscht abhängige Sichten (auch über mehrere Stufen) und die Fremdschlüssel, die Tabelle mit dem Fremdschlüssel bleibt samt Zeilen (an PostgreSQL 17 gemessen); `REVOKE … RESTRICT`. Am T-SQL-Zugang bleiben die Wörter ein Syntaxfehler (am Orakel 156). `KsqlLoeschverhaltenTests` (7). **Teil 2 erledigt 16.10.2026** (T174, T175, T178, F202, F384–F386): `GENERATED ALWAYS|BY DEFAULT AS IDENTITY [(START WITH … INCREMENT BY …)]`, ohne Spaltenliste zählt die Spalte mit; ein eigener Wert in ALWAYS ist 544 (428C9), ausser mit `OVERRIDING SYSTEM VALUE`, und rückt den Zähler nicht vor; `OVERRIDING USER VALUE`; `UPDATE` einer Identitätsspalte ist 8102 (am Orakel, nun auch am T-SQL-Zugang); `GENERATED ALWAYS AS (ausdruck)` nimmt nur `DEFAULT`; `ALTER COLUMN … RESTART [WITH n] | SET GENERATED … | SET INCREMENT BY | SET START WITH | DROP IDENTITY | DROP EXPRESSION [IF EXISTS]`; `TRUNCATE` setzt den Zähler nach Norm nur mit `RESTART IDENTITY` zurück (an PostgreSQL 17 gemessen). `KsqlIdentitaetTests` (9). **Teil 3 erledigt 16.10.2026** (F292, F492, F741): `UNIQUE NULLS NOT DISTINCT` (Spalte, Tabelle, `ADD CONSTRAINT`; ohne Angabe bleiben mehrere NULL erlaubt), `CHECK`/`REFERENCES … [NOT] ENFORCED` und `ALTER CONSTRAINT c [NOT] ENFORCED` (abgeschaltet wie NOCHECK), `MATCH FULL|SIMPLE` (gemischte NULL sind bei FULL 547); `MATCH PARTIAL` wie bei PostgreSQL abgewiesen (1235, 0A000); `INFORMATION_SCHEMA` meldet `NULLS_DISTINCT`, `ENFORCED` und `MATCH_OPTION` (FULL/NONE) nach dem Stand. Am T-SQL-Zugang ein Syntaxfehler (102, am Orakel; die Spaltenschleife verschluckte die Wörter vorher still). `KsqlEinschraenkungsartenTests` (6). **Teil 4 erledigt 16.10.2026** (F721): Fremdschlüssel `[NOT] DEFERRABLE [INITIALLY DEFERRED|IMMEDIATE]`; in einer Transaktion prüft ein aufgeschobener erst beim äussersten COMMIT (Kind- und Elternseite mit NO ACTION), ein Verstoss ist 547 und rollt zurück; `SET CONSTRAINTS {ALL|name} {DEFERRED|IMMEDIATE}` (IMMEDIATE prüft das Ausstehende; nicht aufschiebbar 7997, unbekannt 4917); `ALTER CONSTRAINT … DEFERRABLE …`; `INFORMATION_SCHEMA.TABLE_CONSTRAINTS` meldet IS_DEFERRABLE/INITIALLY_DEFERRED; `CHECK … DEFERRABLE` und `NOT DEFERRABLE INITIALLY DEFERRED` sind Fehler (an PostgreSQL 17 gemessen). `KsqlAufschiebbarTests` (4). **Teil 5 erledigt 16.10.2026** (F251, F711, E081-09): `CREATE DOMAIN d [AS] typ [DEFAULT e] [CONSTRAINT n] CHECK (… VALUE …) | NOT NULL`; die Domäne prüft jeden geschriebenen Wert (INSERT, UPDATE, MERGE) und `CAST(x AS d)` - NOT NULL 515, Bedingung 547; ihre Vorgabe gilt ohne eigene der Spalte; `ALTER DOMAIN … SET|DROP DEFAULT | SET|DROP NOT NULL | ADD [CONSTRAINT] CHECK … [NOT VALID] | DROP CONSTRAINT [IF EXISTS] | RENAME CONSTRAINT` wirkt auf die vorhandenen Spalten (SET NOT NULL und ADD CHECK prüfen die Zeilen); `DROP DOMAIN [IF EXISTS] … [CASCADE]`; `GRANT … ON DOMAIN d`. Dabei behoben: ein Typ aus `CREATE TYPE … FROM` liess sich im selben Stapel nicht als Spaltentyp nutzen (2715), und ein Recht an einem Typ (`ON TYPE::t`) stand als OBJECT_OR_COLUMN mit 0 statt als Klasse 6 TYPE in `sys.database_permissions` (am Orakel gemessen). Am T-SQL-Zugang ist `DOMAIN` 343 wie am Orakel. `KsqlDomaenenTests` (6). **Teil 6 erledigt 16.10.2026** (T171, T172, T173, F531): `CREATE TABLE q (LIKE p [INCLUDING|EXCLUDING DEFAULTS|IDENTITY|GENERATED|CONSTRAINTS|INDEXES|ALL] …, weitere Spalten)` (ohne Angabe Spalten, Typ und NOT NULL); `CREATE TABLE t [(x, …)] AS abfrage [WITH [NO] DATA]`; `CREATE [GLOBAL|LOCAL] TEMPORARY TABLE … [ON COMMIT PRESERVE ROWS|DELETE ROWS|DROP]` (DELETE ROWS leert bei jedem Festschreiben, ohne Transaktion nach jeder Anweisung); `CREATE SCHEMA s CREATE TABLE … CREATE VIEW … GRANT …` legt die Elemente im neuen Schema an - auch am T-SQL-Zugang (am Orakel gemessen, vorher landeten sie in dbo). Dabei behoben: ein temporärer Name verdeckte eine dauerhafte Tabelle auch, nachdem die temporäre fehlte (nun nur, solange es sie gibt; so auch PostgreSQL). `KsqlTabellenanlageTests` (5). **Teil 7 erledigt 20.10.2026** (F521; nach Normtext, Abgleich als K33.8b offen): `CREATE ASSERTION n CHECK (…) [[NOT] DEFERRABLE] [INITIALLY DEFERRED|IMMEDIATE]` prüft beim Anlegen und nach jeder äussersten Datenänderung einer genannten Tabelle (INSERT, UPDATE, DELETE, MERGE); falsch ist 547 (23000) und nimmt die ganze Anweisung zurück, UNKNOWN besteht; aufgeschoben prüft sie in einer Transaktion beim COMMIT bzw. bei `SET CONSTRAINTS n IMMEDIATE`; `DROP ASSERTION [IF EXISTS] n`; `DROP TABLE` einer genannten Tabelle ist 3729 (2BP01), mit `CASCADE` fällt die Assertion mit. Am T-SQL-Zugang 343 wie DOMAIN. `KsqlZusicherungenTests` (5). **Teil 8 erledigt 20.10.2026** (T495, F313, T641; nach Normtext, Abgleich als K33.8b offen): `SELECT … FROM NEW|OLD|FINAL TABLE (INSERT|UPDATE|DELETE|MERGE …) [AS] t [(spalten)]` führt die Änderung einmal ganz oder gar nicht aus und liest NEW/FINAL die geschriebenen, OLD die ersetzten Zeilen (bei MERGE nach Aktion getrennt); OLD zu INSERT und NEW zu DELETE sind 102. MERGE: `UPDATE SET (a, b) = (…)`, `SET a = DEFAULT`, `INSERT … VALUES (…, DEFAULT)`, `INSERT … OVERRIDING SYSTEM|USER VALUE`; eine mehrfach getroffene Zielzeile (8672) trägt SQLSTATE 21000. `UPDATE … SET (a) = ROW (x)` (Grad 1). DELETE in MATCHED und mehrere Bedingungen gab es schon. `KsqlAenderungstabellenTests` (4). **Teil 9 erledigt 20.10.2026** (T180, T181, T502; nach Normtext, Abgleich als K33.8b offen): `PERIOD FOR p (a, b)` (DATE/TIMESTAMP, beide NOT NULL, `a < b` als Prüfung, 547), `PRIMARY KEY|UNIQUE (…, p WITHOUT OVERLAPS)` (am Ende jeder Änderung geprüft, 2627), `UPDATE|DELETE t FOR PORTION OF p FROM x TO y …` teilt die Zeilen (die Reste vor x und nach y bleiben mit den alten Werten; SET auf eine Zeitraumspalte ist 8102), die Prädikate `OVERLAPS`, `EQUALS`, `CONTAINS` (Zeitraum oder Zeitpunkt), `PRECEDES`, `SUCCEEDS`, `IMMEDIATELY PRECEDES|SUCCEEDS` über einen Zeitraum beim Namen oder `PERIOD (x, y)`. Systemzeit auf den temporalen Tabellen von T-SQL: `WITH SYSTEM VERSIONING`, `ALTER TABLE … ADD|DROP SYSTEM VERSIONING` (DROP nimmt den Verlauf mit), `FOR SYSTEM_TIME BETWEEN SYMMETRIC|ASYMMETRIC`, kein Primärschlüssel nötig. Dabei behoben (auch am T-SQL-Zugang): das offene Ende einer Spalte `datetime2(0…6)` lief beim Runden über, INSERT scheiterte (jetzt 9999-12-31 23:59:59…). `KsqlZeitraeumeTests` (18). **Teil 10 erledigt 20.10.2026** (S011, S011-01; nach Normtext, Abgleich als K33.8b offen): `CREATE TYPE geld AS DECIMAL(10,2) [FINAL]` ist ein Distinct-Typ, streng getrennt von seinem Quelltyp: Vergleiche (auch BETWEEN, IN, in Unterabfragen) nur mit demselben Typ oder NULL, keine Rechnung, keine Zuweisung eines anderen Distinct-Typs - sonst 206 (42000); `CAST` führt hinein und zurück, eine Zuweisung aus dem Quelltyp nimmt er an; NOT FINAL ist 102. `CREATE TYPE punkt [UNDER obertyp] AS (attribut typ, …) [[NOT] INSTANTIABLE] [[NOT] FINAL]` legt strukturierte Typen an (Attribute erben, Obertyp muss NOT FINAL sein, doppeltes Attribut 2705, `DROP TYPE` eines Obertyps 3732); als Spaltentyp oder in `CAST` ist ein strukturierter Typ 1235, bis es Zeilenwerte gibt (K33.8c). Aliastypen (`FROM`) und Domänen bleiben nachsichtig. `KsqlBenutzertypenTests` (4). **Abschluss 20.10.2026: alle Teile nach Normtext, der Abgleich am Orakel ist K33.8b, die Reste K33.8c.**✅
K33.8aRest aus K33.8 Teil 4: aufschiebbares `UNIQUE`/`PRIMARY KEY``UNIQUE … DEFERRABLE INITIALLY DEFERRED` (an PostgreSQL gemessen: `UPDATE u SET a = 3 - a` über 1, 2 geht, ein Duplikat ist beim COMMIT 23505); heute mit 1235 (0A000) abgewiesen statt still verschluckt. **Erledigt (20.10.2026), abgeglichen in K33.8b:** `UNIQUE`/`PRIMARY KEY … [NOT] DEFERRABLE [INITIALLY …]` auf Spalten-, Tabellenebene und in `ADD CONSTRAINT`; nicht aufgeschoben je Anweisung (Norm; UPDATE prüft UNIQUE jetzt vorab wie den Primärschlüssel, auch am T-SQL-Zugang wie SQL Server), aufgeschoben beim COMMIT über die ganze Tabelle (2627, rollt zurück); `SET CONSTRAINTS` und `TABLE_CONSTRAINTS` für die Schlüssel. Ohne neue Messung (Docker aus), Grundlage die Messung aus K33.8 Teil 4 und das Handbuch von PostgreSQL. `KsqlAufschiebbarSchluesselTests` (21).✅
K33.8bAbgleich am Orakel für K33.8 Teile 7–10Gemessen am 20.10.2026 (SQL Server 2025, PostgreSQL 17): Ablehnung von ASSERTION (343), `FROM NEW TABLE` am T-SQL-Zugang jetzt 156, MERGE mit DEFAULT, Zeilenzuweisung, OVERRIDING und Kardinalität 21000, `PERIOD FOR` 102, offenes Ende `datetime2(0)`, `CREATE TYPE … AS` 102 - alles wie hier. Offen bleibt nur, was allein der Normtext festlegt (Nummern und Regeln für Assertions, Änderungstabellen, Anwendungszeit, Distinct-Typen); Liste in . **Abgeschlossen 20.10.2026: der Rest ist nach Normtext, kein Orakel** (keine Referenz kennt Assertions, Änderungstabellen, Anwendungszeit oder Distinct-Typen nach Norm).✅
K33.8cReste aus K33.8 Teile 7–10**Teil A erledigt 20.10.2026:** `TRUNCATE` prüft die Assertions (ganz oder gar nicht, auch ausserhalb einer Transaktion), `INFORMATION_SCHEMA.ASSERTIONS` und `PERIODS` (Anwendungs- und Systemzeit) nach ISO/IEC 9075-11, `USER_DEFINED_TYPES` (DISTINCT/STRUCTURED, IS_FINAL, Quelltyp) und `ATTRIBUTES` (geerbte zuerst) aus dem Katalog, `ALTER TABLE … ADD PERIOD FOR p (a, b)` und `DROP PERIOD FOR p` (Prüfung `t_p_check`, 3729 bei WITHOUT OVERLAPS); dabei behoben: `ALTER COLUMN … SET NOT NULL` am KSQL-Zugang prüfte vorhandene NULL nicht (jetzt 515, PostgreSQL 23502). **Teil B erledigt 20.10.2026:** F053 `(S1, T1) OVERLAPS (S2, T2)` (an PostgreSQL 17 gemessen), `FOREIGN KEY (k, PERIOD p) REFERENCES t (k, PERIOD q)` (lückenlose Deckung, 547), `FINAL TABLE` gegen einen Trigger (23321, SQLSTATE 27000); dabei behoben: eine Änderung mit Trigger liess sich vom Rahmen ihrer Anweisung (Assertion, FINAL TABLE) nicht mehr zurücknehmen. **Teil C erledigt 20.10.2026:** die strenge Prüfung der Distinct-Typen auch in MERGE, `UPDATE|DELETE … FROM`, `WITH` und in Funktionsargumenten (`SUM(geld)` 206; COUNT, MIN, MAX, COALESCE, NULLIF gehen). **Teil D erledigt 20.10.2026** (S023, S025): Werte strukturierter Typen über die Zeilentypen aus K33.5a - Spalten, `NEW typ (…)`, `NEW typ ()`, `(wert).attribut`, `CAST(… AS typ)`, streng getrennt (206). **Abgeschlossen 20.10.2026; was davon offen bleibt, steht in K33.8e.**✅
K33.8eRest aus K33.8c: strukturierte Typen über die Grundform hinaus**Erledigt 20.10.2026** (nach Normtext, kein Orakel - keine Referenz kennt es; S026, S041, S051, S081, S111, S151, S161, S251 auf „+“): Attributzugriff ohne Klammern (`p.x`, `t.p.x`), Mutatoren (`SET p.x = …`), Methoden (`METHOD …` in CREATE TYPE, `CREATE METHOD … FOR typ` mit SELF, Aufruf `p.m(…)`, geerbt und überschrieben), `CREATE|DROP ORDERING` (EQUALS ONLY, FULL BY STATE|MAP|RELATIVE), `IS [NOT] OF`, `TREAT`, getypte Tabellen (`CREATE TABLE t OF typ`, `REF IS … SYSTEM|USER GENERATED`, `WITH OPTIONS`), Untertabellen (`UNDER`, `ONLY (t)` in FROM/UPDATE/DELETE), Referenzen (`REF(typ) SCOPE t`, `r->a`, `DEREF`, selbstbezügliche Typen mit `REF USING`). **Bewusst nicht gebaut, weil nicht sinnvoll:** die *Ersetzbarkeit* (ein Wert eines Untertyps in einer Spalte des Obertyps samt dynamischer Bindung der Methoden) - sie verlangt, dass jeder Wert seinen speziellsten Typ mitträgt; die Werte sind aber die Zeilentypen aus K33.5a mit fester Feldliste, und eine zweite, selbstbeschreibende Wertform für Speicher, Vergleich, Ausgabe und die Treiber wöge den Nutzen nicht auf (kein anderes System am Prüfstand kennt sie; unter den verbreiteten Systemen nur Oracle und Db2 mit eigener Objektschicht); ohne sie ist der speziellste Typ der statische, IS OF und TREAT sind darauf genau. Ebenso nicht: `REF … DERIVED`/`REF FROM` und REF in weiteren Zusammenhängen (S043, S162), `SPECIFIC METHOD` (S027, S261), Konstruktor- und statische Methodenaufrufe (`typ::m()`), Locators (S231) - selten genutzte Teile ohne Gegenstück in den Referenzen. Die Vereinigung über Untertabellen gilt für die Hauptquelle von FROM, nicht in Verbunden.✅
K33.8dRest aus K33.8a (unter dem Ticket K33.8b committet, die Kennung war parallel vergeben): Abgleich an PostgreSQL 17 und am OrakelAn `kq-ref-postgres` (127.0.0.1:15433, eigenes Schema): `UPDATE u SET a = 3 - a` über 1 und 2 mit `UNIQUE`, `UNIQUE DEFERRABLE INITIALLY IMMEDIATE` und `… INITIALLY DEFERRED` (PostgreSQL prüft das nicht aufschiebbare je Zeile, KSQL nach Norm je Anweisung - Abweichung bestätigen), Duplikat beim COMMIT (23505, Transaktion zurückgerollt), dasselbe für `PRIMARY KEY` auf Spalten-/Tabellenebene und über `ALTER TABLE … ADD CONSTRAINT`, `SET CONSTRAINTS k DEFERRED/IMMEDIATE`, `SET CONSTRAINTS ALL …` neben einem nicht aufschiebbaren Schlüssel (Fehler 55000), `ROLLBACK TO SAVEPOINT`, `information_schema.table_constraints.is_deferrable/initially_deferred`, `UNIQUE NOT DEFERRABLE INITIALLY DEFERRED` und `NOT NULL DEFERRABLE` (Syntaxfehler). Am Orakel SQL Server 2025: `UPDATE u SET a = 3 - a` über ein `UNIQUE` und einen eindeutigen Index (je Anweisung erwartet, so jetzt am T-SQL-Zugang), `DEFERRABLE` hinter `UNIQUE`/`PRIMARY KEY` (102). **Erledigt (20.10.2026):** an PostgreSQL 17 alle Fälle wie KSQL, einzige Abweichung mit Absicht (PostgreSQL prüft ein nicht aufschiebbares UNIQUE je Zeile, KSQL nach Norm je Anweisung); am Orakel 2025 `3 - a` über UNIQUE und eindeutigen Index wie der T-SQL-Zugang, 2627/2601, `DEFERRABLE` 102.✅
K33.9Transaktionen und Sitzung nach Norm**Erledigt (16.10.2026).** Am KSQL-Zugang (Erwartungen an PostgreSQL 17): `START TRANSACTION [ISOLATION LEVEL …][, READ ONLY|READ WRITE]`, `COMMIT|ROLLBACK [WORK] [AND [NO] CHAIN]` (ohne Transaktion ein stilles OK, `AND CHAIN` dann ein Fehler), `SAVEPOINT`, `ROLLBACK [WORK] TO [SAVEPOINT]`, `RELEASE SAVEPOINT` (danach gibt es den Punkt nicht mehr), `SET TRANSACTION READ ONLY|WRITE` (laufende oder nächste Transaktion; die Isolation gilt wie in T-SQL für die Sitzung); in einer Transaktion READ ONLY ist jede Änderung 3906. `GRANT rolle TO x [WITH ADMIN OPTION]` und `REVOKE [ADMIN OPTION FOR] rolle FROM x` sind die Mitgliedschaft bzw. das Recht `ALTER` auf die Rolle (vorher legte der Kern still ein Recht namens „rolle“ an; am T-SQL-Zugang jetzt 102 wie SQL Server 2025); ein Recht `ON ROLE::r` steht in `sys.database_permissions` als `DATABASE_PRINCIPAL` (vorher Klasse 1 mit 0, KS1). `SET ROLE r|NONE` setzt `CURRENT_ROLE`. `CURRENT_USER`/`SESSION_USER`/`SYSTEM_USER`/`CURRENT_SCHEMA`/`CURRENT_CATALOG`, `SET CATALOG`, `SET TIME ZONE`, `SET SESSION AUTHORIZATION` liefen schon. `KsqlTransaktionTests` (21); T241, T261, T271, T272, T332, E152 auf „+“ (315). Rest als K33.9a.✅
K33.9aRest aus K33.9: `SET SCHEMA` (Vorgabeschema der Sitzung)`SET SCHEMA 's'` setzt das Vorgabeschema der Sitzung für unqualifizierte Namen (F761; heute ohne Wirkung, ein unbekanntes Schema wird still angenommen). Versuch 16.10.2026 zurückgenommen: der Kern normalisiert Namen mehrfach (`NormalizeTableName` aus `dbo.p` → `p`, beim zweiten Mal wäre es `s.p`), und `ParseTableName` liest auch Logins, Datenbanken, Schemas und Rollen - ein Vorgabeschema braucht eine Namensauflösung, die es kennt (unqualifiziert → Vorgabeschema, dann dbo; ausgenommen CTEs, `#`/`@`, `inserted`/`deleted`). Dieselbe Auflösung trägt das Vorgabeschema des Benutzers (`ALTER USER … WITH DEFAULT_SCHEMA`, KS1). Tests dafür lagen bereit (Anlegen, Lesen, `CURRENT_SCHEMA`, 2760, Trigger/CTE/Tabellenvariable). **Entschieden (16.10.2026, Auftraggeber):** KSQL-Sitzungen bleiben im Autocommit wie T-SQL, E151-02 (implizite Transaktionen nach der Norm) bleibt darum begründet „-“; ein Skript mit ausdrücklich begonnener Transaktion ohne COMMIT/ROLLBACK wird beim Schliessen der Verbindung zurückgerollt - an beiden Zugängen über die Leitung belegt (`TrennenRolltZurueckTests`, 6). **Erledigt 16.10.2026 (Entscheid des Auftraggebers: Vorgabeschema zuerst, dann dbo):** `SET SCHEMA 's'` (auch ohne Anführungszeichen) setzt das Vorgabeschema der Sitzung, ein unbekanntes ist 2760, `dbo` stellt zurück; `CURRENT_SCHEMA`/`SCHEMA_NAME()` nennen es. Der Parser kennzeichnet in einer Sitzung mit Vorgabeschema (auch nach `SET SCHEMA` weiter vorn im selben Stapel) unqualifizierte Namen an den Objektstellen (FROM, JOIN, INSERT/UPDATE/DELETE/MERGE, CREATE/ALTER/DROP/TRUNCATE TABLE, VIEW, INDEX ON, TRIGGER ON, REFERENCES, Sequenzen, `NEXT VALUE FOR`, Prozeduren, `EXEC`, Funktionen; nicht CTEs, `#`/`@`, `inserted`/`deleted`); erst die Normalisierung beim Ausführen löst das Kennzeichen auf - zuerst `s.name`, dann `dbo.name`, sonst `s.name` (dort legt CREATE an) -, so bleibt ein ausdrückliches `dbo.` auch nach mehrfacher Normalisierung dbo. Ohne `SET SCHEMA` liest jede Sitzung wie bisher. Abweichung von der strengen Norm und PostgreSQL 17 (gemessen: kein Rückfall auf `public`): der Rückfall auf dbo wie bei SQL Server. Dabei behoben: `sys.sequences` nannte jede Sequenz in dbo mit vollem Namen. F761 auf „+“ (316). `KsqlVorgabeschemaTests` (7). Rest als K33.9b.✅
K33.9bRest aus K33.9a: Vorgabeschema des Benutzers`CREATE USER … WITH DEFAULT_SCHEMA = s` und `ALTER USER … WITH DEFAULT_SCHEMA` (heute beim Anlegen gelesen, beim Ändern übergangen) wirken wie bei SQL Server über dieselbe Auflösung wie K33.9a: bei der Anmeldung, nach `USE` und unter `EXECUTE AS` gilt das Vorgabeschema des Benutzers der Datenbank, an allen Zugängen; `sys.database_principals.default_schema_name` zeigt es (am Orakel messen, KS1). **Erledigt 16.10.2026:** `ALTER USER … WITH DEFAULT_SCHEMA` übernimmt den Wert (ohne Prüfung, wie am Orakel); das wirksame Vorgabeschema ist `SET SCHEMA`, sonst das des Benutzers der Datenbank (auch unter `EXECUTE AS`, auch im selben Stapel), wenn es das Schema gibt; ein neues Objekt entsteht immer im Vorgabeschema, auch wenn der Name in dbo schon steht (am Orakel: `CREATE TABLE q` neben `dbo.q` legt `s.q` an) - das gilt nun auch für `SET SCHEMA`. Dabei behoben: ein Recht auf ein Schema (`GRANT SELECT ON SCHEMA::s`) kam bei keinem Objekt darin an; jetzt gilt es, und ein DENY sticht, ob am Objekt oder am Schema (am Orakel gemessen). `BenutzerVorgabeschemaTests` (3), `SchemarechteTests` (5).✅
K33.10SQL/PSM (Teil 4)Routinen nach Norm: `CREATE PROCEDURE/FUNCTION … LANGUAGE SQL`, `BEGIN [ATOMIC] … END`, `DECLARE` von Variablen, Bedingungen und Handlern (`CONTINUE/EXIT/UNDO`), `SIGNAL`/`RESIGNAL`, `GET DIAGNOSTICS`, `IF`/`CASE`/`LOOP`/`WHILE`/`REPEAT`/`FOR`/`LEAVE`/`ITERATE`, `CALL`, Trigger nach Norm (`REFERENCING OLD/NEW ROW/TABLE AS`, `FOR EACH ROW/STATEMENT`, `WHEN`) - auf die vorhandenen Ablaufbausteine von MariaDB und Db2. **Erledigt 20.10.2026 (nach Normtext, Abgleich als K33.10a offen):** KSQL liest Routinen mit dem Leser der MariaDB-Routinen (ein Weg), die Regeln der Norm schaltet `Dialekt.NormRoutinen` (`Parser/PrattParser.Psm.cs`, `Ksql/KsqlRoutinen.cs`): Rumpf = eine Anweisung, danach geht der Stapel weiter; ein Fehler ohne Handler beendet die Prozedur; Handler und `GET DIAGNOSTICS` nach dem SQLSTATE von `Normzustand` (K33.11), `SIGNAL` meldet seinen Zustand am KSQL-Zugang; `DECLARE … CONDITION`, `UNDO` in `BEGIN ATOMIC` (Sicherungspunkt), `CASE`-Anweisung (20000), `FOR … AS … DO`, `SET (a, b) = …`, `marke.x`, `f.x`, Merkmale der Norm samt `RETURNS NULL ON NULL INPUT`; Trigger mit `REFERENCING`, `FOR EACH ROW|STATEMENT`, `WHEN`, `UPDATE OF` auf den Zeilentriggern des Kerns. Schleifen bricht der Client per ATTENTION ab (Test). Kernfehler behoben: `RETURN` mit scheiterndem Ausdruck übersprang das `CATCH` (am Orakel gemessen). P001–P008 mit Namen (Mimer, postgres.cz), neu „+“: P002–P008, B128, E081, T200, T211, T212, T214–T216, T218, T321, T325, T651. Am T-SQL-Zugang unverändert. `KsqlPsmTests` (62), `KsqlPsmLeitungTests` (3). Funktionsreste in K33.10b.✅
K33.10aAbgleich am Orakel (Rest aus K33.10)Entscheid des Auftraggebers 20.10.2026: Docker blieb aus, K33.10 lief nach dem Normtext; die Fälle von aussen nachmessen. **Erledigt 20.10.2026:** PostgreSQL 17 (SQL-Rumpf, `BEGIN ATOMIC`, `RETURNS NULL ON NULL INPUT`, Übergangstabellen, `WHEN`, `UPDATE OF`, `PARAMETERS`) und MariaDB 11 (`ROW_COUNT`, 23000, `CONTINUE`-Handler, `SIGNAL` im BEFORE-Trigger) wie KSQL; nach Norm abweichend und belassen: Reihenfolge der Trigger (PostgreSQL nach Namen), `CONSTRAINT_NAME` (MariaDB leer). SQL Server 2025: zwei Abweichungen am T-SQL-Zugang behoben - `(IN x …)` ist 156 beim Schlüsselwort (vorher 102), `CALL p()` bzw. ein Aufruf als Argument von `EXEC` ist 102 (vorher 195).✅
K33.10bRest aus K33.10: was SQL/PSM noch fehlt`INSTEAD OF`-Trigger nach Norm (T213), Übergangstabellen im Zeilentrigger, Tabellenfunktionen `RETURNS TABLE … RETURN TABLE (…)` (T326), `%TYPE` (T322), Überladen (T341), `EXECUTE IMMEDIATE` in Routinen (T652), Module `CREATE MODULE` (P001); Namensauflösung nach Norm, Handler fangen 208/207 derselben Ebene, `COMMIT`/`ROLLBACK` in `BEGIN ATOMIC` abweisen, `UNDO` im Trigger als echter Sicherungspunkt, gleichnamige Variablen in inneren Blöcken. **Erledigt 20.10.2026:** alles davon (eine Spalte geht der gleichnamigen Variablen vor, Bereich ist die Anweisung; Überladungen heissen im Katalog `name__ovN`). Befund: `INFORMATION_SCHEMA.MODULES` fehlte, die P003-Probe lief nur, weil eine unbekannte Normsicht leer antwortet; `MODULES` ist jetzt da, P003 wieder „-“ (K33.10c). Neu „+“: P001, T213, T322, T326, T341, T652. `KsqlPsmRestTests` (24).✅
K33.10cRest aus K33.10b: übrige Sichten zu Modulen (P003)`INFORMATION_SCHEMA.MODULE_TABLE_USAGE`, `MODULE_COLUMN_USAGE`, `MODULE_PRIVILEGES` aus den Verweisen der Routinen eines Moduls; dazu `ROUTINES.MODULE_*`. **Erledigt 20.10.2026:** die Zugehörigkeit steht als erweiterte Eigenschaft `KSQL_MODUL` an jeder Routine des Moduls; `ROUTINES.MODULE_*` liest sie, die drei Sichten berechnet `Ksql/Normsichten.Module.cs` aus dem Katalog (Verweise wie bei Sichten und Funktionen, dazu die Ziele von Änderungen; `EXECUTE` des Besitzers). P003 „+“. Der rote Test zur letzten Stelle der Winkelfunktionen ist auf `main` genauso rot (Plattform, nicht K33.10).✅
K33.10dNachtrag zu K33.10: endlose PSM-Schleifen und der Fuzzer`KsqlSyntaxFuzzingTests` hing an einer verfälschten Routine, deren `CONTINUE`-Handler den Fehler im Schleifenrumpf schluckt (legitime Endlosschleife). Prüfen, dass ATTENTION, `CommandTimeout` und das Schliessen der Verbindung eine laufende PSM-Schleife beenden; der Fuzzer führt jeden Fall mit Frist aus und zählt eine abbrechbare Schleife nicht als Befund, ein Hängen trotz Abbruch schon. **Erledigt 20.10.2026:** alle drei Wege über TDS belegt (Funktion und Prozedur mit schluckendem Handler); der Fuzzer bricht nach 5 s ab wie ein Attention (`ExecutionEngine.AbbruchToken` öffentlich) und unterscheidet abgebrochen/hängt (`MitFrist`, eigener Test).✅
K33.11Fehler nach NormJede Meldung am KSQL-Port trägt den SQLSTATE nach Norm (Klassen 01, 02, 21, 22, 23, 25, 40, 42 …); `GET DIAGNOSTICS … RETURNED_SQLSTATE`, `MESSAGE_TEXT`, `CONSTRAINT_NAME` u. a.; Übersetzung aus den gemeinsamen Fehlern an einer Stelle. **Erledigt 15.10.2026:** Zuordnung an einer Stelle (`Execution/Normzustand.cs`, sonst `HY000`); am KSQL-Zugang trägt jede Fehlermeldung den SQLSTATE am Ende (`… (SQLSTATE 22012)`), weil das ERROR-Token von TDS kein Feld dafür hat. `GET DIAGNOSTICS` liest dieselbe Tabelle und kommt mit K33.10.✅
K33.12Information Schema (Teil 11) vollständigAlle Sichten des Information Schema nach Norm, mit den Spalten der Norm: `TABLES`, `COLUMNS`, `VIEWS`, `DOMAINS`, `DOMAIN_CONSTRAINTS`, `CHECK_CONSTRAINTS`, `TABLE_CONSTRAINTS`, `KEY_COLUMN_USAGE`, `REFERENTIAL_CONSTRAINTS`, `ROUTINES`, `PARAMETERS`, `TRIGGERS`, `TRIGGERED_UPDATE_COLUMNS`, `SEQUENCES`, `ELEMENT_TYPES`, `ATTRIBUTES`, `USER_DEFINED_TYPES`, `APPLICABLE_ROLES`, `ENABLED_ROLES`, `*_PRIVILEGES`, `SQL_FEATURES`, `SQL_IMPLEMENTATION_INFO`, `SQL_SIZING` u. a. **Erledigt 15.10.2026:** alle 64 Sichten mit den Spalten der Norm in ihrer Reihenfolge (an PostgreSQL 17 gemessen, `tools/ksql/is-spalten.tsv`); Werte aus den Sichten des Kerns samt berechneter Normspalten, neu aus dem Katalog `TRIGGERS`, `USER_DEFINED_TYPES`, `ENABLED_ROLES`, `APPLICABLE_ROLES`, `COLLATIONS`, `CHARACTER_SETS` u. a. (erzeugt von `tools/ksql/normsichten-erzeugen.py`). Sichten ohne Gegenstück im Kern (Fremddaten, Element- und Attributtypen, Rechte je Rolle) haben die Spalten und füllen sich mit den Fähigkeiten von K33.5–K33.10.✅
K33.13SQL/XML, SQL/JSON, SQL/PGQ nach NormTeil 14: `XMLELEMENT`, `XMLATTRIBUTES`, `XMLFOREST`, `XMLCONCAT`, `XMLAGG`, `XMLPARSE`, `XMLSERIALIZE`, `XMLQUERY`, `XMLEXISTS`, `XMLTABLE`, Typ `XML` nach Norm. SQL/JSON: `JSON_TABLE`, `JSON_EXISTS`/`JSON_VALUE`/`JSON_QUERY` mit `ON EMPTY`/`ON ERROR`, `JSON_SCALAR`, `JSON_SERIALIZE`, `IS JSON`, vereinfachte Pfade (SQL:2023). Teil 16: `CREATE PROPERTY GRAPH`, `GRAPH_TABLE (… MATCH …)` auf die vorhandenen Graphtabellen (C11.3). Teil 15 (MDA, mehrdimensionale Felder) nur nach Bedarf. **Teil 14 erledigt 19.10.2026:** `XMLELEMENT`/`XMLATTRIBUTES`, `XMLFOREST`, `XMLCONCAT`, `XMLAGG … ORDER BY`, `XMLPARSE`, `XMLSERIALIZE` (INDENT, Deklaration), `XMLQUERY`, `XMLEXISTS`, `XMLTABLE`, `XMLCAST`, `XMLCOMMENT`/`XMLPI`/`XMLTEXT`, `IS DOCUMENT` auf dem `xml`-Typ und Kuonen.Global.Xml (an PostgreSQL 17 gemessen), 40 Merkmale „+“, `KsqlSqlXmlTests`. **SQL/JSON erledigt 20.10.2026:** `JSON_VALUE`/`JSON_QUERY`/`JSON_EXISTS` mit `PASSING`, `RETURNING`, Hülle, `OMIT QUOTES`, `ON EMPTY`/`ON ERROR`, `JSON_TABLE` mit `NESTED` (Plan OUTER/UNION), `JSON_SCALAR`, `JSON_SERIALIZE`, `JSON(…)`, `IS JSON … WITH UNIQUE KEYS`, `FORMAT JSON`/`WITH UNIQUE KEYS`/`RETURNING` an den Konstruktoren, vereinfachter Zugriff `t.j.a.b` (T860/T861) auf der Pfadsprache von PR27.17s, 37 Merkmale „+“, `KsqlSqlJsonTests`. **Teil 16 erledigt 20.10.2026 (nach Normtext, Abgleich als K33.13a offen):** `CREATE PROPERTY GRAPH` (Knoten- und Kantentabellen, KEY, LABEL, PROPERTIES samt Ausdrücken und EXCEPT, auch über den Graphtabellen von C11.3 mit `$node_id`/`$from_id`/`$to_id`), `DROP PROPERTY GRAPH`, `GRAPH_TABLE (g MATCH … WHERE … COLUMNS (…))` mit Richtungen, Beschriftungsausdrücken und Quantoren an Kanten (Deckel 16 Schritte, 256 Zweige), 22 Merkmale „+“, `KsqlSqlPgqTests`. Teil 15 (MDA) nicht gebaut: braucht den Feldtyp aus K33.5a, siehe K33.13b. Ungemessene Annahmen: .✅
K33.13aAbgleich am Orakel für K33.13Entscheid des Auftraggebers 20.10.2026 (Docker aus): nachmessen, was K33.13 nach dem Normtext gebaut hat - an PostgreSQL 17 (`kq-ref-postgres`): Textform von `time_tz()`/`timestamp_tz()` in `JSON_VALUE`, `JSON_SERIALIZE` einer `json`-Spalte, `RETURNING` an `JSON_OBJECT`/`JSON_ARRAYAGG`, `XMLSERIALIZE … INCLUDING XMLDECLARATION`, `XMLEXISTS` über `false()`; an SQL Server 2025 (Orakel 14335): dass `CREATE PROPERTY GRAPH`, `GRAPH_TABLE`, `XMLTABLE`, `JSON_TABLE`, `IS JSON`, `JSON_VALUE … ON EMPTY` am T-SQL-Zugang dieselben Fehler wie dort geben, und `MATCH` gegen `GRAPH_TABLE` über denselben Graphtabellen (gleiche Treffer); SQL/PGQ insgesamt gegen ein System mit SQL/PGQ (Oracle 23ai oder PostgreSQL 18, wenn freigegeben): Vorgaben für Schlüssel und Kanten, ungerichtete Kanten, WALK-Zählung bei Quantoren, Fehlerklassen; `XMLQUERY`/`XMLCAST` gegen ein System mit SQL/XML-XQuery. Liste in . **Erledigt 20.10.2026:** an PostgreSQL 17 und SQL Server 2025 gemessen und behoben: `JSON_VALUE` schreibt Zeitwerte als Text ihres SQL-Typs (`2026-10-19 08:11:12+00`), `RETURNING` mit zu kurzer Länge ist 22001 statt still gekürzt (in den Abfragefunktionen an ON ERROR), am T-SQL-Zugang `CREATE|DROP PROPERTY GRAPH` 343 und `DEFAULT` in der Argumentliste 156 wie am Orakel, dazu neu `JSON_QUERY … WITH ARRAY WRAPPER` mit `[*]` (SQL Server 2025); `MATCH` und `GRAPH_TABLE` finden über denselben Graphtabellen dasselbe. SQL/PGQ als Ganzes und `XMLQUERY`/`XMLCAST` haben kein erreichbares Vergleichssystem und bleiben nach Normtext.✅
K33.13bReste aus K33.13**Erledigt 20.10.2026.** SQL/XML: Typen `XML(DOCUMENT|CONTENT(UNTYPED|ANY|XMLSCHEMA ID s))`/`XML(SEQUENCE)` mit Prüfung, `XMLVALIDATE` und `IS VALID` gegen die Schemasammlungen des Kerns als registrierte Schemata, `XMLNAMESPACES` beim Erzeugen, `OPTION … ON NULL`, Binärserialisierung mit `ENCODING`/BOM, Spaltenliste hinter dem Alias, Tabellenabbildung `TABLE_TO_XML`/`QUERY_TO_XML`/`SCHEMA_TO_XML`/`DATABASE_TO_XML` (an PostgreSQL 17 gemessen). SQL/JSON: `PLAN`/`PLAN DEFAULT`, vereinfachter Zugriff an jedem Ausdruck mit `.*`, `.'a'`, `[n]`, `[*]`, `JSON_ARRAY(abfrage)`. SQL/PGQ: `TRAIL`/`SIMPLE`/`ACYCLIC`, `ANY`/`ALL SHORTEST`, `SHORTEST k`, geklammerte Pfade mit Quantor (G035, G037), Aggregate über Gruppenvariablen (G120), `ELEMENT_ID`, `IS SOURCE|DESTINATION OF`, `SAME`/`ALL_DIFFERENT`, `v.*`, Information Schema nach Teil 16, Schutz vor `DROP TABLE … RESTRICT`. 62 Merkmale „+“. **Begründet nicht gebaut** (je Grund in ): Namensraumerklärungen ausserhalb des Erzeugens (X081–X084, X086: Kuonen.Global.Xml bindet keine Präfixe), Prüfung nach dem Dokument selbst und Schemaorte (X141–X157, X260–X286 ohne die gebauten), X047/X057, X049/X059/X131–X135, X160/X161, X211, X100–X114, X121, JSON-Vergleiche (T879–T882: der json-Typ von SQL Server 2025 ist nicht vergleichbar), T802, G940, **Teil 15 (MDA)**: nicht in der Merkmalsliste, ohne Fall, und er verlangte einen zweiten Feldtyp neben dem aus K33.5a. Nach K33.5a/K33.8 Teil 10/K33.10 je eine Probe: X011, X012, X013, X014, X015, X120.✅
K33.13cFundstellen des Syntaxfuzzers in K33.13**Erledigt 20.10.2026.** `XMLTABLE`, `JSON_TABLE`, `GRAPH_TABLE` ohne Argumentliste sind eine unbekannte Tabelle (208 wie am Orakel 2025, PostgreSQL 42P01) statt einer Ausnahme; `SUM()` und die übrigen Aggregate ohne Argument 174; XQuery-Funktionen mit zu wenigen Argumenten (`count()` u. a., XPST0017) 2236 an beiden Zugängen. Feste Fälle in `FuzzingFundeTests`, vollständige Prüfung der 38 Proben 0 Befunde in 152 408 Fällen, Fuzzer je 60 s (20261004, 4711) ohne Absturz. Offen und nicht aus K33.13: Hänger der verfälschten SQL/PSM-Probe von K33.10 (Endlosschleife unter `CONTINUE HANDLER`).✅
K33.13dNormlogik von K33.13 aus dem KSQL-Modul in den Kern**Erledigt 20.10.2026.** `DialektArchitekturTests.Der_Kern_greift_nur_mit_begruendeter_Ausnahme_in_ein_Dialekt_Modul` war nach dem Zusammenführen rot: acht Kerndateien griffen ins Modul `Ksql/` (SqlConvert, Rowsetfunktionen, PrattParser, PrattParser.SqlXml/SqlJson/Eigenschaftsgraph, ExpressionEvaluator, ExecutionEngine.Eigenschaftsgraph). `SqlXml`, `SqlJson`, `Graphmuster`, `Graphsichten` und `Normtabelle` sind Normlogik ohne Bezug zum KSQL-Modul, die Kernparser, Kernausführung und der T-SQL- bzw. MySQL-Zugang mitbenutzen - sie liegen jetzt im Kernordner `Norm/` (Namensraum `Kuonen.Data.KuonenSql.Norm`); die Schalter des Dialekts (`NormFunktionen` usw.) bleiben die Weiche. Keine neue Ausnahme in der Liste. Die letzte Stelle (`Parser/PrattParser.Psm.cs`, K33.10c) behebt K33.13e.✅
K33.13eArchitekturwächter grün: Moduleigenschaft im Kern**Erledigt 20.10.2026.** Nach K33.13d meldete `DialektArchitekturTests` nur noch `Parser/PrattParser.Psm.cs`: der Kernparser schrieb bei `CREATE MODULE` die erweiterte Eigenschaft unter dem Namen `Ksql.Normsichten.Moduleigenschaft` (K33.10c). Der Name `KSQL_MODUL` liegt jetzt als `PrattParser.Moduleigenschaft` im Kern, der ihn schreibt; `Normsichten` im Modul liest ihn von dort. Der Wächter ist grün (15/15), ohne neue Ausnahme; Sabotage (alter Verweis) → rot.✅
K33.13fGRAPH_TABLE: Tiefengrenze statt StapelüberlaufBefund 22.10.2026 (voller Lauf): Verfaelschte_Muster_werden_Fehler_und_kein_Absturz liess den Testhost mit Stack Overflow in Norm.Graphmuster+Leser enden (3000 öffnende Klammern; ebenso IS (((… und !!!… im Labelausdruck). **Erledigt 22.10.2026:** der Leser zählt die Tiefe geklammerter Pfade und Labelausdrücke (auch !); über 128 Ebenen Fehler 191 statt Absturz. Test um die beiden Label-Fälle erweitert, Sabotage (Grenze aufgehoben) stürzt wieder ab.✅
K33.14Der eigene SQL-Manager als ClientAbnahme mit dem eigenen SQL-Manager (Objektbaum, Abfragefenster, Pläne über den KSQL-Port), Microsoft.Data.SqlClient, JDBC-Treiber für SQL Server und `go-mssqldb`; kein SSMS-Zwang. Merkmale, die der Manager braucht (Katalog, Kennung, Fehler mit SQLSTATE), werden mit ihm abgestimmt. **Erledigt 16.10.2026:** die Plugin- und Treibertests des Managers laufen auch am KSQL-Port (Orakel `KuonenSqlKsql`, 88 Fälle: Erkennung, Baum, Abfragelauf, Befehle, Pläne, Live-Statistik, Treiber); JDBC (mssql-jdbc 12.8) und go-mssqldb 1.8 aus Docker (`KsqlTreiberAbnahmeTests`), SqlClient in `KsqlZugangTests`. Dabei behoben: Binärparameter wurden als `0x…` geparst, unter KSQL eine Ganzzahl (jetzt `X'…'`); die Parameter eines RPC hinterliessen gezählte DONEINPROC (go-mssqldb meldete 4 statt 1 Zeile, auch am T-SQL-Port); PRELOGIN und LOGINACK nennen am KSQL-Port die Sprachstufe 17.0.5005 (Entscheid 16.10.2026: mssql-jdbc lehnt Fassung 1 ab), ehrlich bleiben Programmname, `@@VERSION`, `ProductVersion`. Im Manager: SQLSTATE getrennt vom Text (Treiber `TdsError.SqlState`, Meldung im Studio), Sprachstufe 17 für die Schranken des Plugins, GetSchema mit den Typen von SQL Server.✅
K33.15Handbuch, Prüfstand und AbnahmeHandbuchkapitel KSQL mit der Merkmalsliste und den Regeln aus K33.4, Playground-Beispiele, Fuzzing der neuen Syntax (PR27.7), Gleichwertigkeit (M26; der Lastlauf über den KSQL-Port steht als LT35.1 in M35): dieselbe Datenbank gleichzeitig über T-SQL, KSQL, MariaDB und PostgreSQL. **Erledigt 16.10.2026:** Handbuchkapitel KSQL auf der Webseite (Zugang, Leitlinie, die Regeln aus K33.4 als Tabelle, die Merkmalsliste je Teil direkt aus `IsoMerkmale`, die Punkte von M33), acht Beispiele im Prüfstand (`Beispielkatalog`, KSQL als Sprache auf Port 1333; je Beispiel ein Test über einen KSQL-Hörer); Syntaxfuzzer `KsqlSyntaxFuzzingTests` über die Proben der unterstützten Merkmale (3000 Fälle im Testlauf, lang über `dist\fuzzing.ps1 -Protokoll Ksql`) mit 21 Funden als feste Tests (`FuzzingFundeTests`, je mit der Nummer des Orakels: VALUES ohne Zeile, ORDER BY-Nummer über int 408, Fremdschlüssel mit ungleicher Spaltenzahl 8139, LAG/LEAD 10755, FIRST_VALUE/LAST_VALUE 174, Datum/Zeit als Bedingung 4145, Rahmengrenze über int 102, Minus vor Zeitwert 8117, SUBSTRING-Länge über int 8115, Sortierfehler in WITHIN GROUP); Gleichwertigkeit: `GleichzeitigKreuzTests` mit KSQL als viertem Zugang (Sperren, Sichtbarkeit, Deadlock über die Ports). Dabei auch: `TRUE`/`FALSE` am KSQL-Zugang BOOLEAN statt 1/0, `OFFSET … FETCH … WITH TIES|PERCENT` (F867 mit Versatz, an PostgreSQL 17 gemessen).✅
K33.16Nachsichtigkeiten des Kerns: im T-SQL-Dialekt verboten, in KSQL erlaubt**Erledigt (16.10.2026).** Schalter `StrengWieSqlServer` (nur im T-SQL-Dialekt; KSQL erlaubt es weiter, die Fremddialekte haben eigene Regeln), je Fall am Orakel 2025 gemessen: `CHECK` mit Unterabfrage 1046 (beim Anlegen der Tabelle und bei `ADD CHECK`), eine unbekannte `INFORMATION_SCHEMA`-Sicht 208 (vorher leeres Ergebnis), `CREATE VIEW|PROCEDURE|FUNCTION|TRIGGER` hinter einer anderen Anweisung desselben Stapels 111 (in `EXEC ('…')` und am Stapelanfang erlaubt), `CONCAT` mit `sql_variant` 257 (`SERVERPROPERTY(…)`, `CAST(… AS sql_variant)`). `DECFLOAT` ist an beiden Zugängen schon 2715 wie am Orakel (nicht 243; am KSQL-Zugang kommt der Typ mit K33.5a). Fünf bestehende Tests legten Module mitten im Stapel an und nutzen jetzt `EXEC` oder einen eigenen Aufruf. `TsqlStrengTests` (17); siehe . Rest als K33.16a.✅
K33.16a`+` mit `sql_variant` am T-SQL-Zugang**Erledigt (16.10.2026).** Rechnen mit `sql_variant` am T-SQL-Zugang wie SQL Server 2025 (am Orakel): mit Text oder zwei Varianten 402 („Die Datentypen sql_variant und varchar sind im add-Operator inkompatibel“, in der geschriebenen Reihenfolge), mit jedem anderen Typ 257; vorher lieferte `'x' + CAST(1 AS sql_variant)` „1“. KSQL rechnet mit dem Grundtyp (`CAST(1 AS sql_variant) + 1` ist 2). Prüfung vorn in `SqlArithmetic.Binary` über das Merkmal `InVariante` (zwei Bittests, kein Mehraufwand je Zeile). `TsqlStrengTests` (9 neu).✅
K33.17Eigener KSQL-Treiber, unabhängig von Microsoft.Data.SqlClient**Nutzerwunsch 15.10.2026:** ein vollwertiger ADO.NET-Treiber für KSQL mit dem vollen Umfang, den der Server kann - SQLSTATE der Fehler (K33.11), `BOOLEAN`, Intervalle, Zeilen- und Feldtypen, Zeitzonen, `GET DIAGNOSTICS`, Routinen nach Norm, ehrliche Kennung, Information Schema -, aufgesetzt auf den eigenen TDS-Treiber `Kuonen.Data.Tds` (Manager), nicht neben ihm. Nach K33.5–K33.13, damit er deren Umfang abdeckt; Abnahme mit dem SQL-Manager (K33.14). **Erledigt 20.10.2026:** `Data/src/Kuonen.Data.Ksql` erbt von `Kuonen.Data.Tds` (`KsqlConnection`, `KsqlCommand`, `KsqlParameter`, `KsqlConnectionStringBuilder` mit den Schlüsseln von SqlClient, `KsqlException : TdsException`; `KsqlDataReader` umhüllt den TDS-Leser, `KsqlTransaction`, `KsqlProviderFactory`); das KSQL-Profil steht in `Profil/`: am KSQL-Zugang nennt ein eigener Typ im Typnamen seines UDT die Schreibweise der Norm (`int ARRAY[5]`, `ROW(a int, b varchar(5))`, `INTERVAL DAY(3) TO SECOND(2)`, `DECFLOAT(16)`; Server `Erweiterungstyp.Normname`, `Dialekt.NormtypenAufDerLeitung`), JSON trägt den Benutzertyp 244; Werte als `KsqlArray`, `KsqlMultiset`, `KsqlRow`, `KsqlInterval`, `KsqlDecfloat` (Elemente in ihrem Typ), `BOOLEAN` als `bool`, Zeitzonen als `DateTimeOffset`, XML; dieselben Typen als Parameter (Textform über `nvarchar`, Typ der Norm in der Deklaration, nur neu geschrieben aus dem Typleser) und als OUTPUT; `SqlState` neben `Number`; `ServerVersion` die Fassung von KuonenSQL, `ServerProgramName` „KuonenSQL“, ohne Port 1333; `CALL` mit OUT/INOUT, `GET DIAGNOSTICS` in Routinen; `SAVEPOINT`/`ROLLBACK TO SAVEPOINT`/`RELEASE SAVEPOINT`, `READ ONLY` (3906/25006); `GetSchema("INFORMATION_SCHEMA.<SICHT>")` mit den Spalten der Norm. Server dabei behoben: `sp_executesql` las Typen der Deklaration nur mit T-SQL-Namen (`INTEGER ARRAY`, `ROW(…)`, `INTERVAL DAY TO SECOND` wurden `nvarchar`). Manager: `SqlServerPlugin` verbindet über `KsqlConnection` (T6). Robust gegen verfälschte Antworten (Relais, 60 Zufallsfälle), Lesepfad ohne Belegen je Wert (gegen den TDS-Treiber gemessen, gegen SqlClient in `docs/leistung.md`). `Kuonen.Data.Ksql.Tests` (93), `KsqlLeitungsprofilTests` (16). Reste in K33.17a.✅
K33.17aReste aus K33.17Abgleich am Orakel (SQL Server 2025 auf 14335: `KsqlConnection` dort wie `TdsConnection`, Fehler ohne SQLSTATE; das Kennwort lag dem Worktree nicht vor), JDBC und go-mssqldb am KSQL-Port mit den neuen Typnamen des UDT nachprüfen (`KsqlTreiberAbnahmeTests`, Docker), `DbBatch` mit den Typen der Norm (`KsqlBatch`; heute liefert der `TdsBatch` Text), `GetColumnSchema` mit den Typnamen der Norm, `GET DIAGNOSTICS` ausserhalb einer Routine (heute 102), `BOOLEAN` mit eigenem Typnamen auf der Leitung (heute `bit`). Im Server gefunden: `ARRAY[ROW(…)]` hat den Elementtyp `nvarchar(max)` statt der Zeile, `JSON_OBJECT(…)` liefert `nvarchar` statt `JSON`. **Erledigt 21.10.2026:** Der Teil des Servers und der Abgleich am Orakel. (1) Orakel: `KsqlConnection` an SQL Server 2025 ist ein TDS-Treiber (`IsKsql` falsch, Fassung `17.00.5005`, Werte wie SqlClient, `1 / 0` ist 8134 ohne SQLSTATE) - `Kuonen.Data.Ksql.Tests/OrakelTests` (übersprungen ohne `KSQL_KONFORMITAET_ORAKEL_KENNWORT`). (2) `ARRAY[ROW(…), ROW(…)]`/`MULTISET[…]` mit ungleichen Feldtypen ist ein Array von Zeilen (Feld für Feld vereinigt, `Sammlungen.Vereinige`; vorher `nvarchar(max) ARRAY`), ebenso Arrays von Arrays. (3) `JSON_OBJECT`, `JSON_ARRAY`, `JSON_OBJECTAGG`, `JSON_ARRAYAGG` liefern am KSQL-Zugang ohne `RETURNING` den Typ JSON (T801; auf der Leitung Benutzertyp 244), mit `RETURNING` den genannten; T-SQL bleibt `nvarchar(max)`; der interne Schlüssel von `GRAPH_TABLE` bleibt Text. Tests in `KsqlLeitungsprofilTests`. Rest in K33.17b.✅
K33.17bK33.17 und D36.1 zusammen**Erledigt 21.10.2026:** nach dem Zusammenführen waren im Referenzlauf von D36.1 `typen.interval`, `typen.decfloat` und `typen.sammlungen` rot - der Katalog erwartete die kurzen UDT-Namen, seit K33.17 nennt der KSQL-Port die Schreibweise der Norm (`INTERVAL YEAR(2) TO MONTH`, `DECFLOAT(34)`, `ROW(f1 int, f2 varchar(1))`, `int ARRAY`). Katalog, README und Profil (§8.2: Typname, JSON-UserType 244, Typen der Norm in `@params`) nachgezogen; Referenzlauf 84 grün, 2 übersprungen.✅
K33.17cReste aus K33.17a (vom Zweig der Serverbefunde, dort als K33.17b angelegt)(1) JDBC und go-mssqldb am KSQL-Port mit den neuen Typnamen des UDT nachprüfen (`KsqlTreiberAbnahmeTests`, braucht Docker; `docker run` startete am 21.10. keine Container). (2) Treiber: `DbBatch` mit den Typen der Norm (`KsqlBatch`; heute liefert der `TdsBatch` Text), `GetColumnSchema` mit den Typnamen der Norm. (3) `GET DIAGNOSTICS` ausserhalb einer Routine (heute 102; die Übersetzung hängt am Rumpf einer Routine, `PrattParser.MariaHandler.LiesMariaDiagnostics`). (4) `BOOLEAN` mit eigenem Typnamen auf der Leitung (heute `bit`) - Entscheid über das Profil nötig. (5) Gefunden: `ROW(1)` mit einem Feld ist `int` statt einer Zeile; ein NULL-Feld (`ROW(2, NULL)`) bekommt `nvarchar(4000)` statt des Typs der übrigen Zeilen eines Arrays. **Erledigt 21.10.2026** (ohne (4)): (5) `ROW(1)` ist am KSQL-Zugang eine Zeile mit einem Feld (`ROW(f1 int)`, PostgreSQL 17: `record`); ein Feld aus einem NULL ohne Typ bleibt im Zeilentyp unbestimmt (Marke `null`) und nimmt in `ARRAY[…]`/`MULTISET[…]` den Typ der übrigen Zeilen an, allein heisst es `nvarchar(max)` wie das Element von `ARRAY[NULL]` (PostgreSQL: `text`). (3) `GET [CURRENT] DIAGNOSTICS` geht im Stapel (Variablen `@v`, `CONDITION 1` im `CATCH`) - derselbe Leser wie im Rumpf, ohne dessen Namensfilter. (2) `KsqlBatch : TdsBatch`/`KsqlBatchCommand` (Leser `KsqlDataReader`, `ExecuteScalar` mit den Typen der Norm, `KsqlException` mit SQLSTATE und Befehl), `KsqlConnection.CreateBatch` und Fabrik; `KsqlDataReader` ist `IDbColumnSchemaGenerator` mit Typname und .NET-Typ der Norm. (1) `KsqlTreiberAbnahmeTests` mit den Typen der Norm, als Bauschritt (`KUONENSQL_DOCKER_BAU=1`) gelaufen: JDBC nennt `int ARRAY`, `ROW(f1 int, f2 varchar(1))`, `INTERVAL DAY(2)` und liest die Textform; go-mssqldb liest die Werte, `ColumnTypes` bricht bei jedem UDT ausser denen von SQL Server ab (auch mit den kurzen Namen; am Orakel gemessen). Tests: `KsqlLeitungsprofilTests`, `KsqlPsmRestTests`, `Kuonen.Data.Ksql.Tests/StapelTests`. Reste in K33.17d.✅
K33.17dReste aus K33.17c(1) **Entscheid von Marc:** `BOOLEAN` mit eigenem Typnamen auf der Leitung (heute `bit`, `GetDataTypeName` „bit“). Möglichkeiten: (a) so lassen - jeder SQL-Server-Treiber liest `bool`, der Name der Norm fehlt; (b) ein UDT `BOOLEAN` wie `int ARRAY` - der Name stimmt, aber JDBC, go-mssqldb und SqlClient lesen dann Bytes statt `bool` (`getBoolean`/`Scan(&bool)` gehen nicht mehr), go-mssqldb bricht in `ColumnTypes` ab; (c) `bit` mit eigenem Benutzertyp wie JSON (244) - fremde Treiber merken nichts, `Kuonen.Data.Ksql` nennt `BOOLEAN`; braucht eine Nummer und einen Eintrag im Profil (§8.2). Vorschlag: (c). (2) go-mssqldb 1.8.0: `ColumnTypes` bricht bei jedem UDT ausser `hierarchyid`/`geometry`/`geography` mit einer Panik ab (am KSQL-Port für alle Typen der Norm, schon vor K33.17); Werte lesen geht. Am Server nicht lösbar, ohne den Namen aufzugeben - beim Treiber melden oder im eigenen Go-Treiber (M36) abdecken. (3) Beobachtet: `json` heisst am Orakel bei JDBC `varchar`, bei go `NVARCHAR`; am KSQL-Port bei beiden `nvarchar` (Benutzertyp 244) - abgleichen, ob SQL Server 2025 einem Client ohne JSON-Merkmal `varchar(max)` schickt. (4) Gefunden: `(ARRAY[ROW(…)])[2].f2` ist am KSQL-Zugang „vereinfachter Zugriff braucht JSON“; `((…)[2]).f2` geht. (5) `docker run` startet auf dem Mac keinen Container: die Abnahme lief mit `KUONENSQL_DOCKER_BAU=1`; mit `docker run` (PC) nachholen: `KUONENSQL_DOCKER_ABNAHME=1 dotnet test Kuonen.KuonenSQL.slnx --filter FullyQualifiedName~KsqlTreiberAbnahmeTests`. (6) Auf dem Mac rot, nicht verursacht und nicht auf `main` gegengeprüft: `MySqlSysSichtenTests` (zwei Perzentilfälle) und `MariaDbFunktionenOrakelTests` (Rundung in der letzten Stelle von `COT`/`ACOS`). **Erledigt 21.10.2026:** (1) Entscheid (c): am KSQL-Port trägt ein `BOOLEAN` in COLMETADATA den Benutzertyp **16**, auf der Leitung bleibt es `bit` (fremde Treiber lesen `bool`/`bit`; SQL Server 2025 schickt 0, 80, 244, 255, 256 und ab 257 die Nummer eines Aliastyps - am Orakel gemessen; 16 ist die OID von `boolean` in PostgreSQL; Profil §8.2). Im Kern ist `BOOLEAN` `bit`, darum trägt am KSQL-Port jede `bit`-Spalte die 16; der T-SQL-Port bleibt bei 0. Die eigenen Treiber nennen `BOOLEAN`: .NET `GetDataTypeName`/`GetColumnSchema`, JDBC `getColumnTypeName` mit `Types.BOOLEAN`, TypeScript `typeName`/`sql.Boolean`, Python `column_types`, Go `ColumnTypes`, ODBC `SQL_DESC_TYPE_NAME` bei `SQL_BIT`, PHP `KsqlTypeName`/`decl_type`, C++ `spalte::typ`; kanonisch (Katalog) bleibt `bit`, die Abspieler bilden `BOOLEAN` darauf ab (Katalog am KSQL-Port mit .NET, Java, TypeScript, Python, Go, ODBC, C++ und PHP: je 82 ok). C, ODBC, PHP und C++ als Bauschritte in `debian:bookworm-slim` bzw. `php:8.3-cli` gebaut und getestet (Sanitizer; `kern`, `odbc`, `cpp_tests`, PHP-Robustheit grün). Tests: `KsqlLeitungsprofilTests`, `Kuonen.Data.Ksql.Tests/TypenTests`, JDBC `PruefungenTest`, TypeScript `einheit`/`mssql`, Python `test_robustheit`, Go `treiber_test`, C `kern_tests`/`odbc_tests`, C++ `cpp_tests` . (4) `.feld` nach `a[n]` ist ein Feldzugriff wie `(r).feld` (`__KSQL_FELD`, auch mehrfach und mit folgendem `[n]`); die Laufzeit entscheidet zwischen Zeile und JSON: `(ARRAY[ROW(1, 'a'), ROW(2, 'b')])[2].f2` ist `b` (die Feldnamen `f1`, `f2` vergibt KSQL seit K33.5a; PostgreSQL 17 liefert die Zeile `(2,b)`, kennt bei einer namenlosen Zeile aber kein Feld `f2` - kein Orakel, Regel des Kerns), `r[2].t` an einer Spalte geht ohne Klammer, `JSON_QUERY(…)[0].x[1]` bleibt ein JSON-Glied. Tests: `KsqlSammeltypenResteTests`. (3) Am Orakel gemessen (21.10.2026, `Kuonen.Data.Tds` ohne FeatureExt JSONSUPPORT, Leitung mitgeschnitten): SQL Server 2025 schickt einem Client ohne JSON-Merkmal `json` als `nvarchar(max)` (Benutzertyp 0, Merkmale 0x0021, Sortierung der Datenbank 0904D00034), nicht `varchar(max)`; wer das Merkmal verlangt (SqlClient 6.1.6 nachweislich, JDBC), bekommt den Typ 0xF4 - daher `varchar` bei JDBC und `NVARCHAR` bei go-mssqldb. KuonenSQL bestätigt das Merkmal nicht; der T-SQL-Port antwortet jedem Client schon genau so (nichts anzugleichen), der KSQL-Port bleibt nach Profil §8.2 bei Benutzertyp 244. Test `KsqlLeitungsprofilTests.Json_geht_wie_bei_SQL_Server_an_einen_Client_ohne_JSON_Merkmal` (beide Zugänge, Sollfolge des Orakels; war schon grün, darum kein roter Lauf); Sabotage: Benutzertyp 244 auch am T-SQL-Port, 2 rot. Offen bleiben (2), (5), (6); den Typ 0xF4 für Clients mit Merkmal anzubieten wäre ein eigenes Ticket. **Erledigt 21.10.2026** (2), (5), (6): (5) `KUONENSQL_DOCKER_ABNAHME=1 … KsqlTreiberAbnahmeTests` mit `docker run` auf dem PC (Windows, Docker Desktop 29.5): JDBC und go-mssqldb am KSQL-Port grün (2 von 2). (6) `MySqlSysSichtenTests` und `MariaDbFunktionenOrakelTests` auf dem PC (x64) grün (4 von 4) - die Abweichungen waren nur auf dem Mac (arm64). (2) Im eigenen Go-Treiber abgedeckt: sein Abspieler ruft `rows.ColumnTypes()` für jeden Fall des Katalogs, auch für ARRAY, ROW, MULTISET, INTERVAL und BOOLEAN, ohne Panik (`TreiberPruefstandTests` go am KSQL-Port grün); die Panik von go-mssqldb 1.8.0 bei fremden UDTs bleibt dort - eine Meldung an go-mssqldb wäre Marcs Entscheid. Offen bleibt aus dieser Zeile nichts; den Typ 0xF4 für Clients mit JSON-Merkmal anzubieten wäre ein eigenes Ticket.✅
K33.18Sperrstufen: Tabelle, Seite, Hinweise**Erledigt (20.10.2026).** Absichtssperren IS/IX/SIX als echte Sperren bis zum Transaktionsende; `TABLOCK`/`TABLOCKX`, `UPDLOCK` (U, verträgt S), `XLOCK`, `HOLDLOCK`/`REPEATABLEREAD`/`READCOMMITTED` je Tabelle, `PAGLOCK`/`ROWLOCK` mit Seitensperren, Hinweise am Ziel von INSERT/UPDATE/DELETE/MERGE, Fehler 1047/650/1065/651; `ALLOW_ROW_LOCKS`/`ALLOW_PAGE_LOCKS` am gruppierten Index; Eskalation je Anweisung bei 6250 Sperren, nicht gegen fremde unverträgliche Sperren, `ALTER TABLE … SET (LOCK_ESCALATION = TABLE/AUTO/DISABLE)` samt `sys.tables`; `sys.dm_tran_locks` aus den echten Sperren; am KSQL-Zugang `LOCK [TABLE] … [IN … MODE] [NOWAIT]` mit der Matrix von PostgreSQL (55P03, 25P01). Am Orakel SQL Server 2025 und an `postgres:17` mit zwei Sitzungen gemessen, solange Docker lief; der Rest nach Doku, Abgleich als K33.18a offen. Leistung einer Sitzung unverändert ( §21). Siehe .✅
K33.18aSperrstufen: Abgleich am Orakel**Erledigt (20.10.2026).** Am Orakel SQL Server 2025 mit zwei/drei Sitzungen nachgemessen: die Annahmen aus K33.18 (Fehler 1047/1065, SNAPSHOT und READ_COMMITTED_SNAPSHOT mit `TABLOCK`/`READCOMMITTEDLOCK`, Eskalation von `UPDLOCK`/`XLOCK`-Lesen, Seitensperren über mehrere Seiten) stimmen. Behoben: `READPAST, NOLOCK` ist 650; Haufen unter SERIALIZABLE sperrt die Tabelle (S); REPEATABLE READ hält nur die Zeilen, die die Bedingung trifft; UPDATE/DELETE per Durchlauf wartet auf jede fremde X/U-Sperre (READPAST lässt sie aus); Haufen zeigt eine RID je geänderter Zeile; wartende Anfragen stehen mit `WAIT` in `sys.dm_tran_locks`; `UPDLOCK, HOLDLOCK` auf fehlende Schlüssel sperrt nur die Lücke bis zum nächsten Schlüssel. Siehe .✅
K33.18bSperrstufen: Nebenindex-Seiten, Partitionen**Erledigt (20.10.2026).** Am Orakel gemessen: `CREATE INDEX … WITH (ALLOW_ROW_LOCKS = OFF)` wirkt; eine Änderung, die einen solchen Nebenindex berührt, sperrt seine Seite X (`PAGE X`), Lesen über ihn (`INDEX(…)`, `COUNT(*)`) und jede andere Änderung daran warten; `HOLDLOCK`/`UPDLOCK` über ihn hält die Seite S/U statt der Zeilen. `LOCK_ESCALATION = AUTO` an einer Tabelle auf einem Partitionsschema (K33.18c): `HOBT IX` je Partition, Eskalation zu `HOBT X` bzw. `HOBT S` nur der Partition. Abweichung: der ganze Nebenindex gilt als eine Seite. Siehe .✅
K33.18cPartitionsfunktionen und -schemata von T-SQL**Erledigt (20.10.2026).** `CREATE/DROP PARTITION FUNCTION` (RANGE LEFT/RIGHT, Grenzen sortiert, `function_id` ab 65536) und `CREATE/DROP PARTITION SCHEME` (`ALL TO`, Liste, `data_space_id` ab 65601), `CREATE TABLE … ON schema(spalte)`, `$PARTITION.funktion(wert)`, `sys.partition_functions`, `partition_range_values`, `partition_parameters`, `partition_schemes`, `destination_data_spaces`, `sys.data_spaces`, `sys.partitions` je Partition, `sys.indexes.data_space_id`; Fehler wie am Orakel (208, 7707, 7708, 2714, 1921, 7726, 1911, 1750, 7706, 7717, 15151); dauerhaft über Kontrollpunkt und Neustart. Nicht gebaut: SPLIT/MERGE, NEXT USED, SWITCH, Partitionsauslassung, `partition_ordinal`. Siehe .✅
K33.18dTeilressourcen erst bei Bedarf (Speicher je Abfrage)Befund 22.10.2026 (voller Lauf rot): BaumdurchlaufTests über der Grenze von 78 000 Bytes je Abfrage; git bisect: K33.18b (d50d21969) hob sie von 76 760 auf 78 400 (MemoryTable legte je Instanz ein leeres ConcurrentDictionary für Teilressourcen an). **Erledigt 22.10.2026:** das Wörterbuch der Teilressourcen entsteht erst beim ersten Bedarf (LazyInitializer); 77 485 bis 77 625 Bytes je Abfrage statt 78 260 bis 79 220. Sabotage (wieder sofort angelegt) 79 203, rot.✅
K33.19Befunde aus modern-sql.com (SQL:2023 und Unterseiten)**Erledigt (15.10.2026).** Die 88 Merkmale der Seite stehen alle in der Merkmalsliste (B032-01 heisst in SQL:2023 B036). Die Beispiele der Unterseiten am KSQL-Zugang ausgeführt; drei **Kernfehler** behoben (alle Zugänge, am Orakel 2025 gemessen): verschachtelte Kommentare `/* a /* b */ c */` (SQL Server gültig, offener Kommentar 113; MariaDB/MySQL flach), `STRING_AGG(DISTINCT …)` ist 102 statt still übergangen (am KSQL-Zugang entdoppelt `LISTAGG(DISTINCT …)`, bei MariaDB `GROUP_CONCAT(DISTINCT …)` - beide übergingen es still), `VARCHAR(10 CHARACTERS)` ist 102 statt still eine Länge 1 (am KSQL-Zugang Längeneinheiten `CHARACTERS`/`OCTETS`, T062). Die Proben von T612 (jetzt mit `FILTER`, steht auf „-“) und T062 (Einheit in der Deklaration) geschärft. Siehe .✅
K33.19aRest aus K33.19: `GREATEST`/`LEAST` und Umwandlungen nach Norm**Erledigt (15.10.2026).** Am KSQL-Zugang: `GREATEST`/`LEAST` mit einem NULL-Argument ergeben NULL (T054; PostgreSQL und SQL Server übergehen es), Text in Ganzzahl und `numeric` nimmt `0x`/`0o`/`0b` und Unterstriche zwischen Ziffern (T661/T662 in der Umwandlung; Regeln an PostgreSQL 17 gemessen: `'0x_2a'` 42, `'-0x2a'` −42, `'1_000.5'` als numeric; Fehler bei `'_1'`, `'1__0'`, `'1_'`, `'0x'` und in `float`). Am T-SQL-Zugang: `SELEC 1 FROM t` am Stapelanfang ist 156 wie SQL Server 2025 (weiter hinten 102, ohne Klausel 2812 - am Orakel gemessen). `KsqlUmwandlungTests` (27); siehe .✅
K33.20Live-Abfragestatistik am Server**Nutzerwunsch 15.10.2026** (Studio T3g): `sys.dm_exec_query_statistics_xml(sitzung)` liefert den Plan der laufenden Anweisung einer Sitzung mit den bisher gezählten Zeilen je Operator (wie SQL Server mit eingeschalteter leichter Profilerstellung), `sys.dm_exec_query_profiles` (heute nur eine leere Vorlage) je Operator `row_count`/`estimate_row_count`; die Zähler kommen aus dem Ausführer, ohne den heissen Weg zu belasten (je Operator ein Zähler, kein Belegen je Zeile). Danach zeigt das Studio die Live-Abfragestatistik auch für KuonenSQL (Grund im Plugin entfällt). **Erledigt 16.10.2026:** gezählt wird je Tabellenzugriff (ein Zähler je Tabelle) und je gesendeter Zeile, nur in Sitzungen mit `SET STATISTICS XML ON` (so schaltet SSMS die Live-Statistik ein; sonst kostete die Zählung rund 5 %, docs/leistung.md §20); der Plan eines SELECT entsteht unter dem Schalter nach der Ausführung, damit die Vorzählung nicht an einer Sperre wartet. `LiveStatistikTests` (über TDS, an einer gesperrten Zeile wartend).✅
K33.20aSperren an ##-Tabellen und DROP gegen wartende LeserBefund 16.10.2026 (Manager-Test zu K33.20, gemessen am eigenen Testserver gegen SQL Server 2025): ein Leser einer globalen Temp-Tabelle (`##t`) wartet nicht an der Zeilensperre einer anderen Sitzung (`BEGIN TRAN; UPDATE ##t …`), bei gewöhnlichen Tabellen schon - SQL Server wartet in beiden Fällen; `DROP TABLE` einer Tabelle, an der ein Leser wartet, geht in KuonenSQL sofort durch (der Leser meldet danach 208), SQL Server lässt den DROP auf die Schemasperre warten. **Erledigt 16.10.2026:** `##`-Tabellen sperren wie gewöhnliche (Aufbausperre unter `tempdb`); DROP/ALTER/CREATE INDEX/TRUNCATE warten auf jede fremde Sperre der Tabelle (wie bisher nur TRUNCATE) und auf Anweisungen, die an ihr warten (geteilte Aufbausperre, Sch-S); dabei auch gefunden und behoben: DROP ging gegen eine offene fremde Schreibtransaktion durch. `AufbauGegenLeserSperrTests`.✅
K33.21`CHECKSUM` und `BINARY_CHECKSUM`**Erledigt (16.10.2026).** `CHECKSUM(…|*)` und `BINARY_CHECKSUM(…|*)` an allen Zugängen, Rechenweg aus Messreihen am Orakel 2025 (SQL_Latin1_General_CP1_CI_AS): je Wert ein 32-Bit-Wort, verknüpft durch „um 4 Bit nach links drehen, dann XOR“, innerhalb eines Texts oder Binärwerts je Zeichen bzw. Byte; NULL 0x7FFFFFFF; bigint, float und money (Zehntausendstel) hoch XOR tief; real seine Bits; date Tage seit 1900; Text ohne Leerzeichen am Ende; `CHECKSUM` über varchar mit den 256 gemessenen Sortiergewichten, `BINARY_CHECKSUM` mit dem Code. Exakt wie SQL Server für diese Typen (u. a. (1, 2) → 18, 'ab' → 2159, 9 Argumente → 591751048, 18 Bytes → 930299899). `CHECKSUM_AGG` gab es schon. `PruefsummeTests` (40). Rest als K33.21a.✅
K33.21aRest aus K33.21: Prüfsummen weiterer Typen und SortierungenGenau wie SQL Server fehlen: `CHECKSUM` über nvarchar (rechnet über den Unicode-Sortierschlüssel: N'ab' → 0x090E020E, N'a' → 114), `datetime` ('2026-01-02 03:04:05' → 3292314, nicht Tage XOR Ticks), `datetime2`/`time`/`datetimeoffset`, `decimal`/`numeric` (1.50 → 1768927692), `uniqueidentifier`, ein NULL-Text hinter einem Text (BINARY_CHECKSUM('a', NULL) → 2147482095) und die Gewichte anderer Sortierungen als SQL_Latin1_General_CP1_CI_AS. Bis dahin rechnet der Kern dort deterministisch (gleiche Werte, gleiche Summe). Nur aus weiteren Messreihen herleiten. **Erledigt 16.10.2026:** datetime (Tage XOR Dreihundertstel), smalldatetime (Tage·65536 plus Minuten), time (gefaltete 100-ns-Ticks), datetime2 und datetimeoffset (Tage XOR gefaltete Ticks, datetimeoffset in UTC), uniqueidentifier (Bytes) genau wie SQL Server; ein untypisiertes NULL als Argument ist 8116 wie am Orakel. Der NULL-Text hinter einem Text folgte schon der Regel (die Zahl oben war falsch umgerechnet). Rest als K33.21b. `PruefsummeTests` (64).✅
K33.21bRest aus K33.21a: Prüfsummen von decimal, nvarchar und weiteren Sortierungen**Erledigt für decimal/numeric (20.10.2026), am Orakel abgeglichen in K33.21c:** aus den Messreihen von K33.21a hergeleitet - die Ziffern ohne Vorzeichen, Komma und nachgestellte Nullen, auf 38 Stellen aufgefüllt (1 → 10^37), als vier 32-Bit-Wörter vom niedrigsten an verknüpft; trifft alle 17 Messwerte (1 → -1374215283, 15 = 1.50 → 1768927692, 65536 → -495039993). nvarchar und andere Sortierungen liessen sich aus den drei vorhandenen Messwerten nicht herleiten (Docker lief nicht, keine neuen Messreihen) - Rest als K33.21c. `PruefsummeTests` (88).✅
K33.21cRest aus K33.21b: Prüfsummen von nvarchar und weiteren Sortierungen`CHECKSUM` über nvarchar genau wie SQL Server: gemessen N'a' 114, N'aa' 0x020E020E, N'ab' 0x090E020E; die Primärgewichte des Sortierschlüssels als Wörter in Little Endian treffen die beiden längeren, nicht N'a' (526 statt 114) - Rest von unter 4 Byte, Akzent-/Gross-Klein-Gewichte und andere Schriften offen. Dazu die Gewichte anderer Sortierungen als SQL_Latin1_General_CP1_CI_AS (keine Messung) und der Abgleich von K33.21b am Orakel: die decimal-Werte aus `PruefsummeTests` (u. a. 1, 15, 125, 65536 als decimal(38,0), 1.50 als decimal(5,2), 0.1, 10, -1, 1e19), Werte mit 38 Stellen und grosser Skala (`CAST(0.12345678901234567890123456789012345678 AS decimal(38,38))`, `CAST(99999999999999999999999999999999999999 AS decimal(38,0))`), decimal neben einem weiteren Argument (`CHECKSUM(CAST(1.5 AS decimal(5,2)), 1)`) und `BINARY_CHECKSUM` über decimal. Die nötige Messreihe steht in docs/umsetzung.md#k3321b. Bis dahin deterministisch über den Text. **Erledigt (20.10.2026):** aus Messreihen am Orakel 2025 hergeleitet - nvarchar und varchar unter Windows-Sortierungen über die Bytes des primären Sortierschlüssels (je volles Wort Little Endian, je Restbyte, um 3 gedreht), 133 Familien samt Kontraktionen, Wiederholungsmarken und Leerzeichen am Ende; varchar unter den 77 SQL_-Sortierungen über deren Gewichte je Byte mit Erweiterungen und Kontraktionen; binär und `BINARY_CHECKSUM` über die Bytes mit Vorzeichen (bisher vorzeichenlos) bzw. UTF-16; decimal neben weiteren Argumenten wie K33.21b. Daten `pruefsummen.tsv.gz` (82 KB), Herkunft `KuonenSQL/tools/pruefsumme`. Rest als K33.21d. `PruefsummeTextTests` (93), `PruefsummeFamilienTests` (131).✅
K33.21dRest aus K33.21c: Han-Ideogramme der CJK-SortierungenDie Reihenfolge der Han-Ideogramme (U+3400–4DBF, U+4E00–9FFF, U+F900–FAFF) in den chinesischen, japanischen und koreanischen Familien (über 500 000 Einträge, rund 0,8 MB gz) und die Zeichen ausserhalb der BMP in Chinese_*_100/160 (rund 290 000) und Japanese_Bushu_Kakusu (303) - gemessen und mit demselben Verfahren herleitbar, offen ist nur die Datenmenge (Entscheid des Auftraggebers); bis dahin nimmt der Kern dort die Grundtabelle. Dazu kennt der Kern die Sortierungen der Fassung 160 (SQL Server 2025, `Chinese_Simplified_*_160_*`) noch nicht (448); ihre übrigen Gewichte liegen schon in den Daten. **Erledigt (20.10.2026, Entscheid des Auftraggebers):** Han-Reihenfolgen der 27 CJK-Familien und eigene Schlüssel ausserhalb der BMP (Chinese_*_100/160, Japanese_Bushu_Kakusu) in `pruefsummen-cjk.tsv.gz` (1,22 MB), nachgeladen beim ersten Gebrauch (Start unverändert 428/418 ms, erstes CJK-CHECKSUM 65 ms); die 32 Sortierungen der Fassung 160 im Kern (`fn_helpcollations` 5540 wie 2025). 324 frische Stichproben wie das Orakel. `PruefsummeCjkTests` (323).✅
K33.22Umbenennen und Typwechsel per Skript**Erledigt (15.10.2026, Frage Marc).** Am Orakel 2025 gemessen und an beiden Zugängen gleich: `sp_rename` für Spalte und Tabelle, `ALTER COLUMN` auf einen grösseren Typ (int → bigint, varchar(5) → varchar(50), decimal(5,2) → decimal(10,3)), von Zahl auf Text und von Text auf Zahl, wenn alle Werte passen; sonst 245, 220 oder 2628 (beim Umbau mit leerem Wert, wie SQL Server) und die Zeilen bleiben. Kernfehler: ein Typwechsel, den SQL Server schon nach den Typen abweist (int → date), ist jetzt 206 (vorher 529), auch bei leerer Tabelle. Am KSQL-Zugang dazu `ALTER TABLE … RENAME [COLUMN] a TO b` und `RENAME TO` (wie PostgreSQL) neben `ALTER COLUMN … SET DATA TYPE`. `SpaltenumbauTests`. Im Studio: siehe Manager-Roadmap T3i.✅
K33.23Neuaufbau einer Tabelle (Befunde KS1 aus dem Studio)**Erledigt (15.10.2026).** Der Neuaufbau im Designer des Studios scheiterte an KuonenSQL (KS1-Zwillinge): ein nachträglicher Primärschlüssel oder eine UNIQUE-Einschränkung über doppelte Werte wurde still angenommen (über TDS hing der Stapel) - jetzt 1505 mit dem doppelten Wert und `'dbo.t'`, gefolgt von 1750 und 3621, `ERROR_NUMBER()` 1750 (Orakel 2025; nur T-SQL und KSQL); `SET IDENTITY_INSERT dbo.t ON` mit Schema galt nicht (der Parser reihte "dbo . t" auf); `DROP TABLE` liess die Trigger stehen (ein ROLLBACK stellt sie mit der Tabelle wieder her). Ab dieser Fassung (1.21.5.0) baut das Studio die Tabelle auch bei KuonenSQL neu auf (Manager T3i). `NeuaufbauBefundeTests`.✅
K33.24Umbenennen: Befunde aus dem Studio (T3i)**Erledigt (15.10.2026).** Am MariaDB- und MySQL-Zugang gab `ALTER TABLE db.t RENAME COLUMN|INDEX … TO …` und `ALTER TABLE db.t RENAME TO …` den Namen `db.t` an `sp_rename` weiter, das `db` als Schema las („kein Element mit dem Namen 'db.t' in der aktuellen Datenbank“) - auch aus derselben Datenbank. Jetzt fällt `db.` in der gewählten Datenbank weg, eine andere übernimmt `TabelleUmbenennenStatement` (Spalte und Index neu) in deren Kontext. `sp_rename 'tabelle.spalte', 'neu'` ohne Objekttyp benennt die Spalte um wie SQL Server 2025 (der Kern meldete 15225; am Orakel gemessen, auch `[dbo].[t].[s]`). `MariaUmbenennenTests`, `SpaltenumbauTests`. Offen als KS1 (Manager): am PostgreSQL-Zugang folgt eine Sicht dem neuen Namen ihrer Tabelle nicht (PostgreSQL bindet sie an die OID).✅
K33.25Nebenbefund aus K33.8 Teil 6: Katalogfunktionen im Aggregat über Systemsichten`SELECT MAX(SCHEMA_NAME(schema_id)) FROM sys.objects …` und `MAX(OBJECT_NAME(object_id))` liefern im Kern leer (am T-SQL- und am KSQL-Zugang); ohne Aggregat, mit einem festen Argument oder über einer eigenen Tabelle stimmt es. Nicht der Plan des Stromaggregats (dort mit Katalog geprüft, ohne Wirkung) - Ursache im Weg der Systemsichten suchen, am Orakel gegenprüfen. **Erledigt (20.10.2026), am Orakel abgeglichen in K33.25a:** über einer Systemsicht hat das Aggregat keinen übersetzten Plan, und der Rückfall wertete das Argument ohne Katalog aus (`OBJECT_NAME` NULL, `SCHEMA_NAME` nur feste Schemas); jetzt mit Katalog, gleich wie ohne Aggregat. `KatalogfunktionImAggregatTests` (8).✅
K33.25aRest aus K33.25: Abgleich am OrakelAm Orakel SQL Server 2025 (eigene Objekte mit Präfix) gegen den Kern halten: `MAX(SCHEMA_NAME(schema_id))` und `MAX(OBJECT_NAME(object_id))` über `sys.objects`/`sys.tables` mit einem eigenen Schema, `MIN(OBJECT_SCHEMA_NAME(object_id))`, `COUNT(OBJECT_NAME(object_id))`, `STRING_AGG(OBJECT_NAME(object_id), ',') WITHIN GROUP (ORDER BY name)`, dasselbe mit `GROUP BY type` - Erwartung wie in `KatalogfunktionImAggregatTests`. **Erledigt (20.10.2026):** am Orakel 2025 alle Fälle wie der Kern (kr_s, kr_t, 2, kr_t,kr_u, kr_s|kr_u).✅
Meilenstein 36: KSQL-Treiber für weitere Sprachen (68/79)
PunktTitelBeschreibungStand
D36.1Protokollprofil und KonformitätsprüfungSchriftliche Beschreibung des KSQL-Profils über TDS 7.4 (was ein Treiber lesen und senden muss, was KSQL zusätzlich meldet) und ein sprachneutraler Katalog von Prüffällen (Anmeldung mit und ohne TLS, Typen hin und zurück, Parameter, Stapel, Transaktionen, Fehler mit Nummer und SQLSTATE, Abbruch, große Werte, Massenkopie, Katalog/Metadaten), als Datei, die jeder Treiber mit seinem eigenen Testlauf abspielt; Docker-Prüfstand je Sprache. **Erledigt 21.10.2026:** Profil `docs/ksql-protokollprofil.md` (aus dem Server-Code, [MS-TDS] und Messungen am eigenen Server), Katalog `tools/ksql-konformitaet/faelle.json` mit 82 Fällen in 12 Gruppen (Format und kanonische Werte im README daneben; je Fall die Erwartung am KSQL-Port und, wo es sie gibt, die an SQL Server 2025 gemessene), Abspieler `tools/Kuonen.KuonenSQL.Konformitaet` über `Kuonen.Data.Tds`, Referenzlauf `tests/Kuonen.KuonenSQL.Konformitaet.Tests` (jeder Fall gegen einen KSQL-Prüfserver im Prozess und gegen das Orakel 14335; Massenkopie über SqlBulkCopy, bis `Kuonen.Data.Tds` eine hat), Grundgerüst `docker/ksql-treiber/` mit Anleitung und einem Lauf des Abspielers im Container gegen `host.docker.internal`. Die neun Abweichungen am KSQL-Port stehen als Befunde D36.1a–D36.1i im Katalog und unten; der Referenzlauf verlangt für jeden Befund eine Zeile und wird rot, sobald einer behoben ist.✅
D36.1aBefund: `encrypt_option` ist NULL`sys.dm_exec_connections.encrypt_option` liefert an beiden TDS-Ports NULL; SQL Server 2025 nennt `TRUE` mit und `FALSE` ohne TLS (Fälle `anmeldung.tls`, `anmeldung.ohne-tls`). **Erledigt 21.10.2026:** `encrypt_option` nennt `TRUE`, wenn die Verbindung nach der Anmeldung verschlüsselt bleibt (TLS 7.x mit ENCRYPT_ON oder TDS 8.0), sonst `FALSE` (ENCRYPT_OFF: nur LOGIN7 verschlüsselt, oder ohne TLS); `Sitzung.Verschluesselt` wird beim LOGIN7 gesetzt. Fälle `anmeldung.tls`/`anmeldung.ohne-tls` ohne Befund.✅
D36.1bBefund: Anmeldefehler ohne SQLSTATEAm KSQL-Port trägt der Fehler 18456 der Anmeldung (ERROR vor dem LOGINACK) keinen SQLSTATE am Meldungsende; nach der Norm wäre es `28000` (invalid authorization specification), wie bei jeder anderen Meldung des Ports (K33.11). Fall `anmeldung.falsches-kennwort`. **Erledigt 21.10.2026:** Der Server schickte den Zusatz `(SQLSTATE 28000)` schon (neuer Test `KsqlSqlstateTests.Auch_der_Fehler_der_Anmeldung_traegt_den_SQLSTATE`, T-SQL-Port ohne); der Treiber `Kuonen.Data.Tds` trennte ihn nur nach dem LOGINACK „KuonenSQL“ ab. Jetzt genügt vor dem LOGINACK der Unterbau 3 im PRELOGIN (`TdsAntwort`, Test `AntwortTests.Der_Anmeldefehler_von_KuonenSQL_liefert_den_SQLSTATE_schon_vor_dem_LOGINACK`); Profil 8.1 sagt es den anderen Treibern. Fall `anmeldung.falsches-kennwort` ohne Befund.✅
D36.1cBefund: Grenzwert von `real` als Literal`CAST(3.4028235E+38 AS real)` (und negativ) meldet an beiden Ports 232; SQL Server rundet das achtstellige Literal auf den grössten Wert von `real` (3.4028235E+38). Fall `typen.real-grenze`. **Erledigt 21.10.2026:** Die Umwandlung in `real` rundet zum nächsten Wert und meldet 232 erst, wenn das Ergebnis unendlich wäre (`SqlConvert`, `float.IsInfinity((float)zahl)` statt `> float.MaxValue`): `3.4028235E+38` und `3.40282356E+38` werden FLT_MAX, `3.40282357E+38` bleibt 232 - wie am Orakel, auch aus Text. Tests `FloatStilTests.Der_Grenzwert_von_real_rundet_auf_FLT_MAX`/`Was_zu_unendlich_rundet_ist_232`, Fall `typen.real-grenze` ohne Befund (beide Ports).✅
D36.1dBefund: Wahrheitswerte über Spalten als `int`Am KSQL-Port haben `w IS NOT TRUE`, `NOT w`, `w AND TRUE` über einer `BOOLEAN`-Spalte den Typ `int` (Werte 0/1) statt `BOOLEAN` (auf der Leitung `bit`); über Konstanten und Unterabfragen stimmt es. Fall `typen.boolean-ausdruck`. **Erledigt 21.10.2026:** Die Werte waren schon `bit`; falsch war der Kopf einer strömenden Menge (P15.8b: eine Spalte vorn, ORDER BY über den Primärschlüssel), den `ExpressionEvaluator.InferType` vor der ersten Zeile bestimmt - dort waren Prädikate (`NOT`, Vergleiche, `AND`/`OR`, `IS`, `LIKE`, `IN`, `EXISTS`) immer `int`. Am KSQL-Zugang (`Dialekt.Wahrheitswerte`) sind sie jetzt `bit` (`Praedikattyp`), sonst bleibt `int` (MariaDB). Tests `KsqlLeitungsprofilTests.Ein_Wahrheitswert_ueber_einer_Spalte_geht_als_bit` (über TDS), `KsqlTypenTests.Ein_Wahrheitswert_ueber_einer_Spalte_ist_BOOLEAN`; Fall `typen.boolean-ausdruck` ohne Befund.✅
D36.1eBefund: `sp_prepare` im Stapel`EXEC sp_prepare @h OUTPUT, …; EXEC sp_execute @h, …` im Stapel meldet an beiden Ports 2812; über RPC (Kennungen 11/12/15, Fall `parameter.vorbereitet-rpc`) geht es. SQL Server kennt die Prozeduren auch im Stapel. Fall `parameter.vorbereitet`. **Erledigt 21.10.2026:** `sp_prepare`, `sp_prepexec`, `sp_execute`, `sp_unprepare` sind Systemprozeduren des Kerns (`ExecutionEngine.Vorbereitet.cs`), ausgeführt über `sp_executesql` (ein Weg für Werte, Mengen, OUTPUT); `sp_prepare` liefert nur die Spalten (FMTONLY), der Griff geht in die OUTPUT-Variable. Die Griffe liegen in der `Vorbereitungsablage` der Verbindung, die auch der RPC-Weg (`VorbereiteteAnweisungen`) nimmt - wie bei SQL Server gilt ein Griff aus dem RPC im Stapel. Unbekannter Griff 8179, fehlender 214 (Texte wie am Orakel). Nebenbei: die Rückkehr aus `sp_executesql` setzte `_schalter.NurFormat` auf den Stand beim Eintritt. Tests in `VorbereiteteAnweisungenTests`, Fall `parameter.vorbereitet` ohne Befund (beide Ports).✅
D36.1fBefund: `OPTION (MAXRECURSION n)` wirkt nichtÜber TDS gilt an beiden Ports immer die Grenze 100: `OPTION (MAXRECURSION 1000)` endet mit 530, `OPTION (MAXRECURSION 10)` lässt 50 Stufen zu. Fall `stapel.maxrecursion`. **Erledigt 21.10.2026:** Der Leser von SELECT übersprang `OPTION (…)` schon, bevor die Abfrage mit CTE ihn lesen konnte - die Grenze blieb immer 100 (auch über den Kern, nicht nur über TDS). `PrattParser.LiesAbfragehinweise` merkt sich `MAXRECURSION n` in jedem Hinweisblock (auch neben `RECOMPILE`; über 32767 Fehler 310 Klasse 15), `WITH … ` nimmt ihn. Dazu die Zählung wie am Orakel: erschöpft ist die Grenze erst, wenn eine Stufe über ihr noch Zeilen liefert (`MAXRECURSION 10` lässt 10 Stufen zu, vorher 9). Tests `CteAndUnionTests.OPTION_MAXRECURSION_…`/`MAXRECURSION_ueber_32767_ist_310`, Fall `stapel.maxrecursion` ohne Befund (beide Ports).✅
D36.1gBefund: `THROW` mit eigener Nummer ohne `45000``THROW 50001, …` trägt am KSQL-Port `SQLSTATE HY000`; `Normzustand` kennt für `45000` nur die Nummer 50000. Jede Nummer ab 50000 ist eine Ausnahme des Benutzers. Fall `fehler.throw`. **Erledigt 21.10.2026:** `Normzustand.Von` nennt für jede Nummer ab 50000, die die Tabelle nicht kennt, `45000` (unhandled user-defined exception): `THROW 50001`, Meldungen aus `sp_addmessage`. Eigene Fehler des Kerns liegen alle unter 50000 (geprüft). Test `KsqlSqlstateTests.Die_Zuordnung_folgt_der_Norm` (50001, 2147483647; „unbekannt“ jetzt 49999), Fall `fehler.throw` ohne Befund.✅
D36.1hBefund: `sp_describe_first_result_set`An beiden Ports sind für berechnete Spalten `is_updateable`/`is_computed_column` vertauscht (SQL Server: `false`/`true`), `system_type_name` ist `nvarchar(256)` statt `nvarchar(128)`, `order_by_is_descending` `smallint` statt `bit`. Fall `katalog.metadaten-ohne-zeilen`. **Erledigt 21.10.2026:** `is_updateable`/`is_computed_column` waren fest 1/0; jetzt aus der Herkunft der Spalte (`SpaltenherkunftVon`, MY30.7d): eine gewöhnliche Tabellenspalte 1/0, ein Ausdruck und eine berechnete Spalte 0/1 (wie am Orakel, auch über `*`; zusammengeführte Wege nur, wenn beide aktualisierbar; OUTPUT-Spalten aus ihrer Zieltabelle). `system_type_name` ist `nvarchar(128)`, `order_by_is_descending` `bit` (auch der Wert NULL als bit). Test `FormauskunftOhneAusfuehrungTests.Aktualisierbar_ist_nur_eine_gewoehnliche_Tabellenspalte`, Fall `katalog.metadaten-ohne-zeilen` ohne Befund (beide Ports).✅
D36.1iBefund: `sp_pkeys` ohne Namen der Einschränkung`PK_NAME` ist an beiden Ports `PK__tabelle`, auch wenn der Primärschlüssel benannt ist (`CONSTRAINT kk_pk PRIMARY KEY`); JDBC `getPrimaryKeys` und ODBC `SQLPrimaryKeys` lesen ihn daraus. Fall `katalog.sp-pkeys`. **Erledigt 21.10.2026:** `sp_pkeys` und `sp_fkeys` nennen in `PK_NAME` den Namen der Einschränkung wie `sys.key_constraints` (`SystemCatalogProvider.PrimaerschluesselName(katalog, tabelle)`: der Name des Primärschlüsselindex, unbenannt `PK__tabelle__hex`). Test `OledbKatalogTests.Sp_pkeys_und_sp_fkeys_nennen_den_echten_Namen_des_Primaerschluessels`, Fall `katalog.sp-pkeys` ohne Befund (beide Ports).✅
D36.1jRest aus D36.1(1) ~~Die Antwort des Servers auf eine Massenkopie mit NULL in einer `NOT NULL`-Spalte ist nicht gemessen~~ – gemessen mit D36.5 (21.10.2026): beide Ziele melden 515 Klasse 16 (KSQL mit `23000`), nichts eingefügt, die Verbindung bleibt; der Fall `massenkopie.not-null` kann damit `{"nummer": 515, "klasse": 16, "sqlstate": "23000"}` verlangen, sobald der Referenzlauf ohne SqlBulkCopy kopiert. (2) Die Massenkopie des Referenzlaufs geht über SqlBulkCopy, bis `Kuonen.Data.Tds` (K33.17) eine eigene hat; der Abspieler im Container überspringt die drei Fälle. (3) MARS, Sitzungswiederherstellung (FeatureExt 0x01), Always Encrypted, Tabellenwertparameter und die UTF-8-Erweiterung stehen im Profil, aber noch nicht im Katalog. (4) Für K33.17: `Kuonen.Data.Tds` schickt einen Zeitparameter mit Skala 0 als Skala 7; der Katalog nimmt darum Skala 3. (5) `DockerPruefstandTests` ist geschrieben, lief aber noch nicht durch: Docker Desktop startete am 20./21.10. keinen Container (auch `docker run … echo` blieb in „Created“); nachholen mit `KUONENSQL_DOCKER_ABNAHME=1`. **Erledigt 21.10.2026:** (4) Am Orakel mit SqlClient 6.1.6 gemessen: auch SqlClient schickt `time`, `datetime2` und `datetimeoffset` mit ausdrücklich gesetzter Skala 0 als Skala 7 (`SQL_VARIANT_PROPERTY(…, 'Scale')` = 7; mit 3 ist es 3) - `Kuonen.Data.Tds` verhält sich gleich, kein Fehler; der Katalog bleibt bei Skala 3. (5) `DockerPruefstandTests` lief mit `KUONENSQL_DOCKER_ABNAHME=1` durch (`mcr.microsoft.com/dotnet/runtime:10.0`, grün). (1) bis (3) in D36.1k.✅
D36.1kRest aus D36.1j(1) Der Fall `massenkopie.not-null` kann `{"nummer": 515, "klasse": 16, "sqlstate": "23000"}` verlangen, sobald der Referenzlauf ohne SqlBulkCopy kopiert. (2) Die Massenkopie des Referenzlaufs geht über SqlBulkCopy, bis `Kuonen.Data.Tds` (K33.17) eine eigene hat; der Abspieler im Container überspringt die drei Fälle. (3) MARS (seit D36.9 am Server), Sitzungswiederherstellung (FeatureExt 0x01), Always Encrypted, Tabellenwertparameter und die UTF-8-Erweiterung stehen im Profil, aber noch nicht im Katalog.⬜
D36.2Java (JDBC 4.3)Eigener JDBC-Treiber mit der Schnittstelle von mssql-jdbc (Klassen `SQLServerDriver`/`SQLServerDataSource`-gleiche Namen unter eigenem Paket, URL `jdbc:ksql://…` und die Schlüssel von `jdbc:sqlserver://`, `DatabaseMetaData` wie mssql-jdbc, Massenkopie, Typen wie `microsoft.sql.DateTimeOffset`); ohne Fremdabhängigkeiten, Java 17+; Verteilung über Maven. **Erledigt 21.10.2026:** `Data/src/Kuonen.Data.Ksql.Java` (Paket `org.kuonen.ksql.jdbc`, Maven `org.kuonen.ksql:kuonen-ksql-jdbc:2.0.0`, rund 11 500 Zeilen eigener Code, TDS 7.4 samt TLS im PRELOGIN selbst): Treiber, DataSource, Statement/Prepared/Callable (RPC nach Namen, OUTPUT, Rückgabewert), `DatabaseMetaData` über `sp_tables`/`sp_columns`/`sp_pkeys`/…, `SQLServerBulkCopy` mit `ISQLServerBulkData`, `org.kuonen.ksql.sql.DateTimeOffset` und `Types` mit den Werten von `microsoft.sql.Types`; KSQL dazu SQLSTATE in `getSQLState()`, BOOLEAN, INTERVAL, DECFLOAT, ROW/ARRAY/MULTISET als `Struct`/`Array` mit den Typnamen der Norm. Abspieler `Data/tools/Kuonen.Data.Ksql.Java.Konformitaet`, Prüfstand `docker/ksql-treiber/java`, `JavaTreiberTests`. Katalog am eigenen Server: 73 ok, 0 abweichend, 9 Befunde (D36.1a, c–i), 0 übersprungen; dasselbe als Bauschritt im Abbild des Prüfstands; `mvn install` in `maven:3-eclipse-temurin-17` (Maven 3.10) grün. Dabei aufgeklärt: D36.1b war eine Lücke in `Kuonen.Data.Tds`. Unter TLS geht jedes Paket in genau einem Record (höchstens 16 384 Bytes; SQL Server schliesst sonst die Verbindung), geprüft mit einem TLS-Spielserver. Reste in D36.2a.✅
D36.2aRest aus D36.2**Teilweise erledigt 21.10.2026:** (1) Der Katalog läuft mit dem Java-Treiber auch am Orakel 14335 (Kennwort zur Laufzeit aus dessen Container): 72 ok, 0 abweichend, 10 übersprungen (nur KSQL) - erst nach drei Korrekturen, die der KSQL-Port nicht zeigte: der TLS-Handschlag ging als eine PRELOGIN-Nachricht je Record (SQL Server schloss jede Verbindung), der Name eines Sicherungspunkts mit seiner Länge in Zeichen statt Bytes (3979), die Massenkopie mit festen Merkmalen statt denen der Zielspalte (4816). (2) Docker: der Prüfstand `java/` baut mit JDK 17 (16 Prüfungen) und spielt den Katalog als Bauschritt an beiden Zielen ab; `docker run` steht weiter aus. (3) TDS 8 (`encrypt=strict`): TLS 1.2/1.3 vor dem ersten Paket mit ALPN `tds/8.0`, PRELOGIN im Tunnel; geprüft mit einem TLS-Spielserver und am KSQL-Port, am Orakel schliesst SQL Server den TLS-Aufbau (wie beim C-Kern; der Container hat kein Zertifikat für TDS 8). Dazu: unter TLS 1.3 höchstens 16 367 Bytes je Paket, und `encrypt=true` fällt nicht mehr still auf die verschlüsselte Anmeldung allein zurück. **Offen:** die Abbildung Nummer → SQLSTATE an SQL Server und die Spaltenreihenfolge der Zusatzspalten von `DatabaseMetaData` sind nicht gegen mssql-jdbc gemessen; TDS 8 am Orakel; MARS, rollbare und änderbare Ergebnismengen, SQLXML als Objekt (Clob/NClob/Blob seit D36.8f), Always Encrypted, Tabellenwertparameter, integrierte Anmeldung und Entra ID, XA; Verteilung über Maven Central. **Erledigt 21.10.2026** (mit Docker, Freigabe Marc): (2) der Prüfstand `java/` lief als Test (`TreiberPruefstandTests`) am Prüfserver und am Orakel grün. SQLSTATE und DatabaseMetaData gegen mssql-jdbc 12.8.1 gemessen (Programm `abnahme.Vergleich` im Abnahmeabbild, beide Treiber am selben Orakel, 37 Fehler und 25 Methoden): mssql-jdbc bildet den SQLSTATE aus „S“ und dem Zustand der Meldung (2812 Zustand 62 → `S0062`, RAISERROR Zustand 27 → `S0027`), ausser 208 → `S0002`, 2714 → `S0001` und 2627/2601/515/547 → `23000`; der eigene Treiber tut das jetzt an SQL Server ebenso (am KSQL-Port bleibt der SQLSTATE der Norm). DatabaseMetaData liefert dieselben Spalten in derselben Reihenfolge mit denselben Typen (getTables nur die fünf Spalten von sp_tables, getColumns mit SS_IS_SPARSE … SS_XML_SCHEMACOLLECTION_NAME statt SS_DATA_TYPE, getProcedures ohne SPECIFIC_NAME, getProcedureColumns mit den SS_-Spalten, getFunctions über sp_stored_procedures, Fremdschlüsselregeln als int, leere Mengen mit char/int, getClientInfoProperties leer); danach sind beide Ausgaben gleich. `PruefungenTest` (18, vorher 1 rot); Hibernate, Flyway und Spring am KSQL-Port weiter grün. Rest in D36.2e.✅
D36.2eRest aus D36.2aTDS 8 (`encrypt=strict`) am Orakel nicht geprüft (der Container hat kein Zertifikat für TDS 8); nicht gebaut: MARS, rollbare und änderbare Ergebnismengen, SQLXML als Objekt, Always Encrypted, Tabellenwertparameter, integrierte Anmeldung und Entra ID, XA; Verteilung über Maven Central (Entscheid Marc, Code bleibt geschlossen). Der SQLSTATE von 1205 (`40001`) und der Anmeldefehler ist nicht gegen mssql-jdbc gemessen.⬜
D36.2bBefund: TM-Anfrage und Massenkopie ohne PrüfungDer KSQL-Port nimmt eine Transaction Manager Request an, deren Name die Länge in Zeichen statt in Bytes trägt (er liest den Namen dann kürzer und den Rest nicht), wo SQL Server mit 3979 abweist; ebenso prüft er die Merkmale (Flags) in der COLMETADATA einer Massenkopie nicht gegen die Zielspalte, wo SQL Server mit 4816 abweist. So blieben drei Fehler des Java-Treibers am eigenen Server unsichtbar (gefunden in D36.2a). Der Katalog prüft beides nicht, weil ein Treiber nach Profil die richtige Form sendet. **Erledigt 21.10.2026:** am Orakel mit rohen Anfragen gemessen (Python-Treiber, 16 Formen der TM-Anfrage, 13 Merkmalssätze der Massenkopie) und nachgebaut: `TdsTransactionRequest` liest die Anforderung ganz, bevor sie wirkt (zu kurz 4002, zu lang 3979, ungerader Name 3976, leerer Sicherungspunkt 3977), der Name zählt in Bytes; `TdsMassenkopie` prüft fNullable gegen die Zielspalte (4816 mit der Spaltennummer, nichts eingefügt); eine offene Transaktion wird dabei zurückgenommen (ENVCHANGE 10) wie am Orakel. Dieselbe Messung am KSQL-Port ergibt jetzt in 30 von 31 Messpunkten dieselbe Nummer und denselben `@@TRANCOUNT`. Tests `TransaktionsanforderungTests` (17; ohne die Prüfungen 14 rot). Kein Katalogfall: ein Treiber sendet nach Profil die richtige Form, die falsche lässt sich nicht sprachneutral verlangen. Rest in D36.2c.✅
D36.2cRest aus D36.2bEine Transaction Manager Request unbekannter Art (etwa 12) beantwortet SQL Server mit 8502 Klasse 20 („Unknown token … from MS DTC“) und schliesst die Verbindung; der KSQL-Port antwortet mit einem leeren DONE. Ebenso nicht nachgebaut: TM_PROMOTE_XACT (6) und die Anforderungen verteilter Transaktionen (1 bis 4). **Erledigt 21.10.2026:** am Orakel gemessen (Arten 0 bis 4, 6, 12, 99, mit und ohne Transaktion) und nachgebaut: eine unbekannte Art (2, 4, 12, 99 …) beantwortet der KSQL-Port mit 8502 Klasse 20 („Unknown token '0x…' received from … (MS DTC) .“), nimmt eine offene Transaktion zurück (ENVCHANGE 10), meldet 596 Klasse 21 und DONE 0x0102 und schliesst die Verbindung; TM_GET_DTC_ADDRESS (0) und TM_PROPAGATE_XACT (1) ohne Angaben sind zu kurz (4002, mit Rücknahme), TM_PROMOTE_XACT ohne lokale Transaktion 3965. Dieselbe Messung am KSQL-Port gleicht dem Orakel (ohne den SQLSTATE-Zusatz des Ports), auch darin, welche Verbindung danach zu ist. Tests in `TransaktionsanforderungTests` (7 neue, ohne den Zweig 3 rot), Protokoll-Fuzzing 10 grün. Rest in D36.2d.✅
D36.2dRest aus D36.2c: verteilte TransaktionenNicht ohne MS DTC nachzubauen: Art 3 beantwortet SQL Server mit einer Menge „TM Address“ (die Adresse seines DTC), TM_PROMOTE_XACT in einer Transaktion mit ENVCHANGE 15 und einem DTC-Token, TM_PROPAGATE_XACT mit einem DTC-Puffer bindet die Sitzung an eine verteilte Transaktion; der KSQL-Port antwortet darauf mit einem leeren DONE. Erst mit einem eigenen Koordinator (oder der Entscheidung, sie abzuweisen) sinnvoll.⬜
D36.3TypeScript/JavaScript (Node.js, auch Deno/Bun)Eigener Treiber mit der Schnittstelle von tedious (Connection, Request, Ereignisse) und darüber der von `mssql` (Pool, `query`, Tagged Templates, Typen `sql.Int` …), in TypeScript geschrieben, Typdeklarationen dabei; ohne Fremdabhängigkeiten; Verteilung über npm. **Erledigt 21.10.2026:** npm-Paket `@kuonen/ksql` in `Data/src/Kuonen.Data.Ksql.TypeScript` (neben `Kuonen.Data.Ksql`, ohne `.csproj`; der Struktur-Wächter prüft nur Projektdateien), Node.js 18+, ESM, Typdeklarationen aus dem Bau (`npm run bauen`, `npm pack`), TypeScript nur als Entwicklungsabhängigkeit. Unten `Connection`/`Request`/`TYPES`/`BulkLoad` wie tedious (execSql über sp_executesql, execSqlBatch, callProcedure, prepare/execute/unprepare, Transaktionen über den Transaction Manager Request, cancel und Frist über ATTENTION, Massenkopie), darüber `ConnectionPool`, `sql.connect`, `query` als Tagged Template, `input`/`output`, `execute`, `Transaction`, `PreparedStatement`, `Table`/`bulk`, Strom-Modus und die Verbindungszeichenfolge der Microsoft-Treiber wie mssql. KSQL: `sqlState` an jedem Fehler, `BOOLEAN` als boolean, INTERVAL/DECFLOAT/ROW/ARRAY/MULTISET als Text, `valueFormat: 'text'` für genaue Werte, `recordsAffected`. Prüfkatalog über den Abspieler `Data/tests/Kuonen.Data.Ksql.TypeScript.Tests/konformitaet/abspieler.ts`: KSQL-Port 69 ok, 10 Befunde D36.1a–i, 3 Abweichungen (D36.3a); SQL Server 2025 72 ok, 10 übersprungen (nur KSQL); nichts übersprungen, was der Treiber nicht kann (auch die Massenkopie läuft). Robustheit: 31 Fälle mit einem falschen Server (Paketköpfe, PRELOGIN, Token, Längen, PLP, Schweigen) und 500 zufällig verfälschte Antworten. Gemessen: SQL Server verlangt unter TLS einen TLS-Satz je Paket; der TextDecoder von Node liest windows-1252 als ISO-8859-1; NULL in einer `NOT NULL`-Spalte per Massenkopie ergibt an beiden Zielen 515 und keine Zeile (Rest (1) aus D36.1j). Prüfstand `docker/ksql-treiber/node/`. Reste: D36.3c.✅
D36.3aBefund: Katalog nennt die kurzen UDT-NamenDer KSQL-Port nennt die Typnamen der KSQL-Typen seit K33.17 in der Schreibweise der Norm (`INTERVAL DAY(2)`, `DECFLOAT(34)`, `ROW(f1 int, f2 varchar(1))`, `int ARRAY`, `int MULTISET`); der Katalog auf `main` erwartet noch `interval`, `decfloat`, `row`, `array`, `multiset` (Fälle `typen.interval`, `typen.decfloat`, `typen.sammlungen`). Nachgezogen in K33.17b auf dem Zweig `claude/d36-2`; mit dessen Katalog sind die drei Fälle grün. Nach dem Zusammenführen die Einträge aus `Bekannt` in `konformitaet.test.ts` entfernen. **Erledigt 21.10.2026:** mit K33.17b auf `main` sind die drei Fälle grün; die Einträge aus `Bekannt` sind weg. Dabei zeigte sich derselbe Fehler wie D36.1b im TypeScript-Treiber: er trennte den SQLSTATE erst nach dem LOGINACK ab, seit D36.2 verlangt der Katalog bei `anmeldung.falsches-kennwort` aber `28000`. Jetzt zählt vor dem LOGINACK der Unterbau aus dem PRELOGIN (`connection.ts`, Test in `robustheit.test.ts` samt Gegenprobe mit Unterbau 0; ohne die Korrektur rot). Katalog am eigenen Server alle Fälle ok oder Befund, nichts übersprungen; am Orakel grün; Einheits-, Robustheits- und mssql-Tests 55 grün.✅
D36.3bBefund: varchar-Parameter in der Codepage seiner SortierungEin varchar-Parameter `'€ ‰'` (RPC) kam an beiden TDS-Ports als `'? ?'` an; SQL Server 2025 liefert `'€ ‰'`. `TdsRpcWerte` las varchar/char/text mit `Encoding.Latin1` statt mit der Codepage aus der Sortierung des Parameters. **Erledigt 21.10.2026:** `TdsRpcWerte` liest `varchar`/`char`/`text` jetzt in der Codepage der Sortierung aus dem TYPE_INFO des Parameters (`TdsAntwortleser.Kodierung`, jetzt `internal`: Gebietskennung → Codepage, alte SQL-Sortierkennungen 437/850, Bit 26 UTF-8). Tests `VorbereiteteAnweisungenTests.Ein_varchar_Parameter_wird_nach_der_Codepage_seiner_Sortierung_gelesen` (1252, 1253, UTF-8) und über SqlClient `KsqlLeitungsprofilTests.Ein_varchar_Parameter_kommt_in_seiner_Codepage_an`.✅
D36.3cRest aus D36.3(1) Der Prüfstand im Container lief nicht: das Abbild `node:22-bookworm-slim` liegt nicht lokal und wurde ohne Freigabe nicht geladen; nachholen mit `KUONENSQL_DOCKER_ABNAHME=1 node --test "Data/tests/Kuonen.Data.Ksql.TypeScript.Tests/konformitaet.test.ts"`. (2) Geprüft nur mit Node 25; Node 18/20/22, Deno und Bun nicht (lokal nicht installiert). (3) Nicht gebaut: `Encrypt=Strict` (TDS 8.0), Windows- und Entra-Anmeldung, benannte Instanzen (SQL Browser), MARS, Tabellenwertparameter, Parameter vom Typ sql_variant und UDT, Always Encrypted. (4) Nur ESM, kein CommonJS-Bau für `require` unter Node 22.12. (5) mssql: `parseJSON`, `Table.fromRecordset`, Ereignisse des Pools fehlen. **Erledigt 21.10.2026:** (4) CommonJS-Bau neben ESM (`tsconfig.cjs.json` → `dist/cjs/`, `exports` mit `import`/`require` samt Typen; Test über ein fremdes Projekt mit `require('@kuonen/ksql')`). (5) mssql: `parseJSON` und das Zusammensetzen der Stücke aus `FOR JSON`/`FOR XML` (am Orakel 300 Zeilen in mehreren Stücken), `Table.fromRecordset`/`recordset.toTable()`, Ereignisse des Pools (`connect`, `close`, `error`; unter `pool.pool` `createRequest` … `poolDestroySuccess` mit den Zählern wie der Pool unter mssql). Aus (3): benannte Instanzen über den SQL Browser ([MC-SQLR], UDP 1434, Deckel für Länge, Datagramme und Frist; Test gegen einen nachgebauten Browser und den KSQL-Testserver), `encrypt: 'strict'`/`Encrypt=Strict` (TDS 8.0, ALPN `tds/8.0`, TLS 1.2/1.3; am KSQL-Port grün, `encrypt_option` TRUE), Tabellenwertparameter (`sql.Table`, `sql.TVP`, `TYPES.TVP`; in Abfragen und an Prozeduren, am KSQL-Port und an SQL Server 2025 grün). Befund: SQL Server 2025 im Container schliesst nach dem ClientHello (TDS 8.0 dort ohne Zertifikat nicht eingerichtet, wie beim C-Kern und in D36.2a; Test hält es fest). Befund D36.3e. Der veraltete Test zu D36.3b erwartet jetzt `'€ ‰'`. `node --test` 73 grün samt Katalog an beiden Zielen. Rest D36.3d.✅
D36.3dRest aus D36.3c(1) Der Prüfstand im Container lief nicht: das Abbild `node:22-bookworm-slim` liegt nicht lokal und wurde ohne Freigabe nicht geladen; nachholen mit `KUONENSQL_DOCKER_ABNAHME=1 node --test "Data/tests/Kuonen.Data.Ksql.TypeScript.Tests/konformitaet.test.ts"`. (2) Geprüft nur mit Node 25; Node 18/20/22, Deno und Bun nicht (lokal nicht installiert). (3) Nicht gebaut: Windows- und Entra-Anmeldung, MARS, Parameter vom Typ sql_variant und UDT, Always Encrypted, die Umleitung (ENVCHANGE 20). (4) TDS 8.0 an SQL Server nicht geprüft (Orakel ohne Zertifikat für TDS 8.0); der SQL Browser nur gegen den nachgebauten Browser, nicht gegen einen echten SQL-Server-Browser. **Teilweise erledigt 21.10.2026:** aus (3) die Umleitung: nennt der Server nach der Anmeldung einen anderen (ENVCHANGE 20), meldet sich die Verbindung dort neu an (höchstens 5 Mal, dann `EREDIRECT`; die Frist der Anmeldung gilt über alle Schritte; verformte Umleitungen sind `EPROTOCOL`); Tests gegen falsche Server in `erweiterungen.test.ts`, `node --test` 75 grün. Rest D36.3f.✅
D36.3fRest aus D36.3d(1) Der Prüfstand im Container lief nicht: das Abbild `node:22-bookworm-slim` liegt nicht lokal und wurde ohne Freigabe nicht geladen; nachholen mit `KUONENSQL_DOCKER_ABNAHME=1 node --test "Data/tests/Kuonen.Data.Ksql.TypeScript.Tests/konformitaet.test.ts"`. (2) Geprüft nur mit Node 25; Node 18/20/22, Deno und Bun nicht (lokal nicht installiert). (3) Nicht gebaut: Windows- und Entra-Anmeldung, MARS, Parameter vom Typ sql_variant und UDT, Always Encrypted. (4) TDS 8.0 an SQL Server nicht geprüft (Orakel ohne Zertifikat für TDS 8.0); der SQL Browser nur gegen einen nachgebauten, die Umleitung nur gegen einen falschen Server (der KSQL-Port leitet nur bei Lesezugriff auf eine Kopie um). **Erledigt 21.10.2026:** (1) Mit `KUONENSQL_DOCKER_ABNAHME=1` lief `konformitaet.test.ts` ganz durch: KSQL-Port, Orakel und der Prüfstand im Abbild `kuonen-ksql-treiber-node` (aus `node:22-bookworm-slim`, 0 abweichend) - 3 von 3. (2) Node 24.12 lokal: alle 76 Tests, 75 grün, 1 übersprungen (TDS 8 am Orakel); Node 22.23 im Container: die 48 Tests ohne Server grün (die mit Server brauchen dotnet, das Abbild hat keins). Rest in D36.3g.✅
D36.3gRest aus D36.3f(1) Node 18/20, Deno und Bun nicht geprüft; Node 22 nur ohne Server. (2) Nicht gebaut: Windows- und Entra-Anmeldung, MARS, Parameter vom Typ sql_variant und UDT, Always Encrypted. (3) TDS 8.0 an SQL Server nicht geprüft (Orakel ohne Zertifikat für TDS 8.0); der SQL Browser nur gegen einen nachgebauten, die Umleitung nur gegen einen falschen Server.⬜
D36.3eBefund: FOR JSON über UNION ALL`SELECT 1 AS a, N'x' AS b UNION ALL SELECT 2, N'y' FOR JSON PATH` scheitert am KSQL-Port mit 13605 („Spaltenausdruecke … ohne Namen oder Alias“); SQL Server 2025 nimmt die Namen des ersten Zweigs und liefert `[{"a":1,"b":"x"},{"a":2,"b":"y"}]`. Gefunden mit D36.3c. **Erledigt 21.10.2026:** FOR JSON hinter dem letzten Zweig gilt für die ganze Mengenoperation (der Parser hebt es vom letzten Zweig auf die Abfrage), die Schlüssel sind die Namen des ersten Zweigs; ohne Namen im ersten Zweig bleibt es 13605, auch wenn ein späterer Zweig einen trägt (am Orakel gemessen: UNION ALL, UNION mit ORDER BY, WITHOUT_ARRAY_WRAPPER, `[x.y]`, EXCEPT). Servertest `JsonTests.ForJson_Ueber_Union_Nimmt_Die_Namen_Des_Ersten_Zweigs`, `erweiterungen.test.ts` erwartet jetzt das JSON.✅
D36.4PHPEigene Erweiterung mit den Funktionen von `sqlsrv` und ein PDO-Treiber wie `pdo_sqlsrv` (DSN `ksql:Server=…`), PHP 8.2+, in C über den Protokollkern aus D36.5; Verteilung über PECL bzw. vorgebaute Pakete für Linux, Windows und macOS. **Erledigt 21.10.2026:** Erweiterung `ksql` in `Data/src/Kuonen.Data.Ksql.Php` (phpize/`config.m4`, ein Modul mit den `sqlsrv_*`-Funktionen und dem PDO-Treiber `ksql:` auf dem C-Kern; Parameter, Ausgaben und `{? = call}`, mehrere Ergebnisse, Fehler mit SQLSTATE, Transaktionen über die Transaction-Manager-Anfrage; eigene Erweiterungen: Massenkopie `ksql_bulk_copy`/`PDO::ksqlBulkCopy`, Abbruchfrist `KsqlCancelAfterMs`, Klasse der Meldungen, kanonischer Spaltentyp), Kern erweitert (SQL-Text, SQLSTATE-Abbildung, Serverleser, Abbruchfrist), Abspieler `tests/konformitaet.php` über beide Schnittstellen, Robustheit `tests/robustheit.sh` unter Adress- und UB-Sanitizer gegen die Attrappe des Kerns, Prüfstand `docker/ksql-treiber/php` mit `PhpPruefstandTests`. Katalog über sqlsrv und PDO: SQL Server 2025 72 ok, 0 abweichend (10 nur KSQL); KSQL-Port 73 ok, 9 Befunde, 0 abweichend. Gebaut und geprüft auf Linux mit PHP 8.3; Rest D36.4a.✅
D36.4aRest aus D36.4(1) Verteilung: kein PECL-Paket (`package.xml`) und kein Bau für Windows (`config.w32`, SChannel) oder macOS (Secure Transport); geprüft nur Linux arm64 mit PHP 8.3 (8.2 und 8.4 nicht gebaut). (2) Nicht in der Erweiterung: rollbare Cursor und `sqlsrv_num_rows`, Ströme (`SQLSRV_PHPTYPE_STREAM` liefert den ganzen Wert), Verbindungspool, `CharacterSet` (immer UTF-8), MARS, Windows- und Entra-Anmeldung, Always Encrypted. (3) Die Werte von `SQLSRV_SQLTYPE_*`/`SQLSRV_PHPTYPE_*` sind eigene Kodierungen (Programme nutzen die Namen; wer die Zahlen speichert, merkt den Unterschied zu sqlsrv). (4) `PDO::PARAM_STR` geht als `nvarchar` und der Server wandelt in den deklarierten Typ (wie pdo_sqlsrv); Typen ausserhalb dessen, was SQL Server so wandelt, brauchen `CAST` im Text. **Erledigt 21.10.2026** aus (2): Tabellenwertparameter (sqlsrv mit `SQLSRV_SQLTYPE_TABLE`, PDO mit `PDO::PARAM_LOB`, je `[Typname => Zeilen]`; die Spalten des Typs fragt die Erweiterung wie der Microsoft-Treiber beim Server), rollbare Cursor (`Scrollable` static, keyset, dynamic, buffered: auf dem Client gepuffert; `sqlsrv_num_rows`, `SQLSRV_SCROLL_*`; PDO `PDO::CURSOR_SCROLL` mit `PDO::FETCH_ORI_*` und `rowCount`), echte Ströme (`SQLSRV_PHPTYPE_STREAM` liest LOB-Spalten Stück für Stück, der Kern liest Zeilen dafür stückweise: `ksql_setze_stueckweise`, `ksql_strom_lesen`). Geprüft mit `tests/merkmale.php` an KSQL-Port und SQL Server 2025 (je 49/0). Rest D36.4b.✅
D36.4bRest aus D36.4a(1) Verteilung: kein PECL-Paket (`package.xml`) und kein Bau für Windows (`config.w32`, SChannel) oder macOS (Secure Transport); geprüft nur Linux arm64 mit PHP 8.3 (8.2 und 8.4 nicht gebaut). (2) Nicht in der Erweiterung: Verbindungspool, `CharacterSet` (immer UTF-8), MARS, Windows- und Entra-Anmeldung, Always Encrypted; Ströme über PDO (`bindColumn` mit `PDO::PARAM_LOB` liefert den ganzen Wert); Ströme als Parameter (`SendStreamParamsAtExec`) lesen den Strom vor dem Senden ganz. (3) Rollbare Cursor sind immer statisch (keyset und dynamic sehen Änderungen anderer nicht) und halten die Menge im Speicher; ein Strom gilt nur bis zum nächsten Aufruf auf die Anweisung. (4) Die Werte von `SQLSRV_SQLTYPE_*`/`SQLSRV_PHPTYPE_*` sind eigene Kodierungen (Programme nutzen die Namen; wer die Zahlen speichert, merkt den Unterschied zu sqlsrv). (5) `PDO::PARAM_STR` geht als `nvarchar` und der Server wandelt in den deklarierten Typ (wie pdo_sqlsrv); Typen ausserhalb dessen, was SQL Server so wandelt, brauchen `CAST` im Text. **Erledigt 21.10.2026** aus (2): Ströme über PDO (`bindColumn` mit `PDO::PARAM_LOB` liefert einen Strom vom Server; PDO holt alle gebundenen Spalten vor dem Lesen, darum strömt nur die letzte gebundene LOB-Spalte, frühere kommen in den Speicher), Ströme als Parameter ohne vorheriges Lesen (sqlsrv mit `SQLSRV_PHPTYPE_STREAM`, PDO mit `PARAM_LOB` und einer Ressource; gesendet als PLP unbekannter Länge Stück für Stück, UTF-8 als nvarchar(max)), Verbindungspool (`ConnectionPooling`, Vorgabe an, auch in der PDO-DSN: je Prozess bis 32 ruhende Verbindungen, 300 s; beim Herausnehmen RESETCONNECTION mit einer kleinen Anfrage, eine offene Transaktion wird beim Zurückgeben zurückgenommen). Geprüft mit `tests/merkmale.php` an KSQL-Port und SQL Server 2025 (je 59/0). Rest D36.4c.✅
D36.4cRest aus D36.4b(1) Verteilung: kein PECL-Paket (`package.xml`) und kein Bau für Windows (`config.w32`, SChannel) oder macOS (Secure Transport); geprüft nur Linux arm64 mit PHP 8.3 (8.2 und 8.4 nicht gebaut). (2) Nicht in der Erweiterung: `CharacterSet` (immer UTF-8), MARS, Windows- und Entra-Anmeldung, Always Encrypted. (3) Rollbare Cursor sind immer statisch und halten die Menge im Speicher; ein Strom gilt nur, solange die Anweisung auf seiner Spalte steht; bei PDO strömt nur die letzte gebundene LOB-Spalte. (4) Der Pool gilt je Prozess und kennt keine Einstellung der Grösse oder Ruhezeit (`php.ini`). (5) Die Werte von `SQLSRV_SQLTYPE_*`/`SQLSRV_PHPTYPE_*` sind eigene Kodierungen (Programme nutzen die Namen; wer die Zahlen speichert, merkt den Unterschied zu sqlsrv). (6) `PDO::PARAM_STR` geht als `nvarchar` und der Server wandelt in den deklarierten Typ (wie pdo_sqlsrv); Typen ausserhalb dessen, was SQL Server so wandelt, brauchen `CAST` im Text. **Erledigt 21.10.2026:** (1) PECL-Paket: `paket.sh` baut `ksql-<Fassung>.tgz` mit `package.xml` (von `pecl package-validate` geprüft) und dem Kern unter `kern/`; `config.m4` legt die Unterordner für den Bau ausserhalb der Quellen an (`PHP_ADD_BUILD_DIR`, sonst scheiterte `pecl install`). Der Prüfstand `php/pecl.Dockerfile` installiert das Paket in einem frischen PHP 8.2, 8.3 und 8.4 (Linux x64) und lädt es. Der Prüfstand `php/Dockerfile` nimmt die PHP-Fassung als Bauargument; `PhpPruefstandTests` spielt den Katalog jetzt mit PHP 8.2, 8.3 und 8.4 am KSQL-Port und mit 8.3 am Orakel ab, jeweils samt `merkmale.php` (Tabellenwerte, Cursor, Ströme, Pool; 0 Fehlschläge) - alles grün. `merkmale.php` nahm am KSQL-Port `0x01` (dort eine Ganzzahl) und umging `SUBSTRING` über `varbinary(max)` (behoben in D36.5d). Rest in D36.4d.✅
D36.4dRest aus D36.4c(1) Verteilung: kein Bau für Windows (`config.w32`, SChannel) oder macOS (Secure Transport); das PECL-Paket nur für den eigenen Gebrauch (Code bleibt geschlossen). (2) Nicht in der Erweiterung: `CharacterSet` (immer UTF-8), MARS, Windows- und Entra-Anmeldung, Always Encrypted. (3) Rollbare Cursor sind immer statisch und halten die Menge im Speicher; ein Strom gilt nur, solange die Anweisung auf seiner Spalte steht; bei PDO strömt nur die letzte gebundene LOB-Spalte. (4) Der Pool gilt je Prozess und kennt keine Einstellung der Grösse oder Ruhezeit (`php.ini`). (5) Die Werte von `SQLSRV_SQLTYPE_*`/`SQLSRV_PHPTYPE_*` sind eigene Kodierungen (Programme nutzen die Namen; wer die Zahlen speichert, merkt den Unterschied zu sqlsrv). (6) `PDO::PARAM_STR` geht als `nvarchar` und der Server wandelt in den deklarierten Typ (wie pdo_sqlsrv); Typen ausserhalb dessen, was SQL Server so wandelt, brauchen `CAST` im Text.⬜
D36.5C (ODBC-Treiber)Eigener ODBC-Treiber (ODBC 3.8) wie „ODBC Driver 18 for SQL Server“: dieselben Verbindungsschlüssel, `SQLGetInfo`, Typabbildung, Massenkopie-Erweiterungen; zugleich der Protokollkern in C, auf dem PHP und C++ aufsetzen; Windows, Linux, macOS (x64 und ARM64). **Erledigt 21.10.2026:** Protokollkern `Data/src/Kuonen.Data.Ksql.C` (C11, CMake; TDS 7.4 mit KSQL-Profil, TLS im PRELOGIN und TDS 8 strict über OpenSSL, Secure Transport oder SChannel hinter einer Speicher-Schnittstelle; jede Länge vom Server geprüft), ODBC-Treiber `Data/src/Kuonen.Data.Ksql.Odbc` (Schlüssel, `SQLGetInfo`, Typen samt `SQL_SS_*`, Diagnose mit `SQL_DIAG_SS_*`, `{call}`, Escapes, Abbruch und Frist über ATTENTION, Handbetrieb über die Transaction-Manager-Anfrage, Kataloge, `bcp_*`; eigene Erweiterung `SQL_CA_KSQL_SERVERTYP` für money/datetime-Parameter), Tests `Data/tests/Kuonen.Data.Ksql.C.Tests` unter `-fsanitize=address,undefined` (Attrappe, 9000 verdorbene Antworten über Kern und ODBC), Abspieler `Data/tools/Kuonen.Data.Ksql.Odbc.Abspieler` über unixODBC, Prüfstand `docker/ksql-treiber/c` mit `OdbcPruefstandTests`. Katalog: SQL Server 2025 72 ok, 0 abweichend (10 nur KSQL); KSQL-Port 69 ok, 9 Befunde, 4 Abweichungen auf Server- bzw. Katalogseite (D36.5a). Linux arm64 und amd64 gelaufen, macOS nur der Kern, Windows übersetzt (mingw-w64).✅
D36.5aRest aus D36.5(1) Katalog: seit K33.17 nennt der KSQL-Port die KSQL-Typen in COLMETADATA mit ihrer Normschreibweise (`INTERVAL DAY(2)`, `DECFLOAT(34)`, `ROW(f1 int, f2 varchar(1))`, `int ARRAY`); `typen.interval`, `typen.decfloat`, `typen.sammlungen` erwarten `interval`, `decfloat`, `row`/`array`/`multiset` – der .NET-Referenzlauf weicht gleich ab; Katalog oder kanonische Form (README) nachziehen. (2) D36.1b: der Server hängt an 18456 inzwischen `(SQLSTATE 28000)` an (der eigene Treiber liest es, Klasse 14); `Kuonen.Data.Tds` trennt den Zusatz bei Anmeldefehlern nicht ab und meldet „keiner“ – Befund aus dem Katalog nehmen, sobald K33.17 den Zusatz auch bei der Anmeldung liest. (3) Nicht gelaufen: Windows (nur mit mingw-w64 übersetzt und gelinkt; MSVC, SChannel und der Windows-Treibermanager ungeprüft), macOS mit dem Treiber (nur der Kern mit Secure Transport und OpenSSL gegen den KSQL-Port; ODBC-Köpfe fehlen auf dem Mac). (4) `docker run` startete am 21.10. keinen Container (bleibt in „Created“): die Läufe gingen über `docker build` (KSQL-Port auf dem Wirt) und `docker exec` im Orakel-Container (Treiber für amd64, das Kennwort blieb in dessen Umgebung); `OdbcPruefstandTests` ist geschrieben, lief aber noch nicht. (5) Nicht im Treiber: scrollbare Cursor, Parameterfelder, asynchrone Ausführung, `SQLSetPos`/`SQLBulkOperations`, `SQLBrowseConnect`, eigene Deskriptoren und `SQLCopyDesc`, MARS, Always Encrypted, Tabellenwertparameter, `bcp` mit Dateien, Windows-/Entra-Anmeldung, benannte Instanzen ohne Port, andere Codepages als 1252 und UTF-8 bei `char`/`varchar`. (6) Der Savepoint geht über `SAVE TRANSACTION` im Stapel (ODBC hat keine Funktion dafür); `SQLDescribeParam` fragt den Server nicht. **Erledigt 21.10.2026:** (1) und (2) hatten schon K33.17b (Typnamen im Katalog) und D36.2/D36.7b (D36.1b) erledigt; der Katalog läuft am KSQL-Port mit 73 ok, 0 abweichend. (4) Die Prüfstände laufen als Bauschritt (`Baulauf`: ein Dockerfile auf dem Abbild ruft `lauf.sh`, das Kennwort geht als Build-Geheimnis aus der Umgebung des docker-Prozesses); `OdbcPruefstandTests` und `PhpPruefstandTests` grün gegen KSQL-Port und SQL Server 2025. (5) Benannte Instanzen ohne Port über den SQL Browser ([MC-SQLR], UDP 1434) im Kern, damit in ODBC und PHP; Parameterfelder (`SQL_ATTR_PARAMSET_SIZE` > 1, spalten- und zeilenweise, `SQL_ATTR_PARAM_STATUS_PTR`, `…_PROCESSED_PTR`, `…_OPERATION_PTR`, Zeilenzahl je Satz über `SQLMoreResults`). (6) `SQLDescribeParam` fragt wie der Microsoft-Treiber `sp_describe_undeclared_parameters`. Rest D36.5b.✅
D36.5bRest aus D36.5a(1) Nicht gelaufen: Windows (nur mit mingw-w64 übersetzt und gelinkt; MSVC, SChannel und der Windows-Treibermanager ungeprüft), macOS mit dem Treiber (nur der Kern mit Secure Transport und OpenSSL; ODBC-Köpfe fehlen auf dem Mac); der SQL Browser nur gegen einen nachgebauten Browser im Test des Kerns, nicht gegen eine echte benannte Instanz. (2) Nicht im Treiber: scrollbare Cursor, asynchrone Ausführung, `SQLSetPos`/`SQLBulkOperations`, `SQLBrowseConnect`, eigene Deskriptoren und `SQLCopyDesc`, MARS, Always Encrypted, Tabellenwertparameter, `bcp` mit Dateien, Windows-/Entra-Anmeldung, andere Codepages als 1252 und UTF-8 bei `char`/`varchar`, Daten bei der Ausführung in Parameterfeldern, `SQL_ATTR_PARAM_BIND_OFFSET_PTR`. (3) Parameterfelder gehen Satz für Satz als eigene Anfrage (der Microsoft-Treiber bündelt sie in einen Stapel); Mengen der Sätze entfallen (`SQL_PAS_NO_SELECT`). (4) Befund für den Server: KuonenSQL beantwortet `sp_describe_undeclared_parameters` immer leer, `SQLDescribeParam` bleibt dort beim Text (`SQL_WVARCHAR`, 4000); bei `{call}` fragt der Treiber nicht. (5) Der Savepoint geht über `SAVE TRANSACTION` im Stapel (ODBC hat keine Funktion dafür). **Erledigt 21.10.2026** aus (2): Tabellenwertparameter im Kern (`KSQL_T_TVP`, `ksql_tvp`) und im ODBC-Treiber wie ODBC Driver 18 (`SQL_SS_TABLE`, `SQL_SOPT_SS_PARAM_FOCUS`, `SQL_CA_SS_TYPE_NAME`/`SCHEMA_NAME`, `SQL_DEFAULT_PARAM` für den leeren); der Server las den leeren TVP als Vorgabe nicht (8016) und tut es jetzt (`TdsRpcParser`, Test). Scrollbare Cursor: statisch, auf dem Client gepuffert (`ksql_menge_lesen`), `SQLFetchScroll` mit NEXT, PRIOR, FIRST, LAST, ABSOLUTE, RELATIVE samt 01S06, Zeilensätze, `SQLMoreResults` dahinter; Keyset und dynamisch werden statisch (01S02). Geprüft mit `odbc_tests` am KSQL-Port und an SQL Server 2025 (je 83/0). Rest D36.5c.✅
D36.5cRest aus D36.5b(1) Nicht gelaufen: Windows (nur mit mingw-w64 übersetzt und gelinkt; MSVC, SChannel und der Windows-Treibermanager ungeprüft), macOS mit dem Treiber (nur der Kern mit Secure Transport und OpenSSL; ODBC-Köpfe fehlen auf dem Mac); der SQL Browser nur gegen einen nachgebauten Browser im Test des Kerns, nicht gegen eine echte benannte Instanz. (2) Nicht im Treiber: asynchrone Ausführung, `SQLSetPos`/`SQLBulkOperations`, Lesezeichen, `SQLBrowseConnect`, eigene Deskriptoren und `SQLCopyDesc`, MARS, Always Encrypted, `bcp` mit Dateien, Windows-/Entra-Anmeldung, andere Codepages als 1252 und UTF-8 bei `char`/`varchar`, Daten bei der Ausführung in Parameterfeldern und in Tabellenwertparametern (Zeilen als Strom), Tabellenwertparameter in Parameterfeldern, `SQL_ATTR_PARAM_BIND_OFFSET_PTR`, `SQL_SOPT_SS_NAME_SCOPE` für Tabellentypen, `SQLGetData` stückweise vom Server (der Strommodus des Kerns aus D36.4a wird im Treiber nicht genutzt). (3) Der statische Cursor hält die Menge im Speicher (der Microsoft-Treiber nimmt einen Cursor des Servers) und sieht keine Änderungen. (4) Parameterfelder gehen Satz für Satz als eigene Anfrage (der Microsoft-Treiber bündelt sie in einen Stapel); Mengen der Sätze entfallen (`SQL_PAS_NO_SELECT`). (5) Befunde für den Server: KuonenSQL beantwortet `sp_describe_undeclared_parameters` immer leer, `SQLDescribeParam` bleibt dort beim Text (`SQL_WVARCHAR`, 4000); `UNION ALL` aus `varbinary(max)` und dem Literal `0x01` meldet 8115 (SQL Server nimmt es an). (6) Der Savepoint geht über `SAVE TRANSACTION` im Stapel. **Erledigt 21.10.2026** aus (2): Lesezeichen (`SQL_ATTR_USE_BOOKMARKS`, Spalte 0 mit `SQL_C_BOOKMARK`/`SQL_C_VARBOOKMARK`, `SQL_FETCH_BOOKMARK` mit `SQL_ATTR_FETCH_BOOKMARK_PTR`), `SQLSetPos` mit `SQL_POSITION` und `SQL_REFRESH`, `SQLBulkOperations` mit `SQL_FETCH_BY_BOOKMARK`, am statischen Cursor; wie die statischen Cursor von SQL Server nur lesend (`SQL_UPDATE`, `SQL_DELETE`, `SQL_ADD` und die Änderungen nach Lesezeichen: HY092). Geprüft mit `odbc_tests` am KSQL-Port und an SQL Server 2025 (je 93/0). Rest D36.5d.✅
D36.5dRest aus D36.5c(1) Nicht gelaufen: Windows (nur mit mingw-w64 übersetzt und gelinkt; MSVC, SChannel und der Windows-Treibermanager ungeprüft), macOS mit dem Treiber (nur der Kern mit Secure Transport und OpenSSL; ODBC-Köpfe fehlen auf dem Mac); der SQL Browser nur gegen einen nachgebauten Browser im Test des Kerns. (2) Nicht im Treiber: ändernde Cursor (Keyset und dynamisch des Servers, `SQLSetPos` mit `SQL_UPDATE`/`SQL_DELETE`, `SQLBulkOperations` mit `SQL_ADD` und den Änderungen nach Lesezeichen), asynchrone Ausführung, `SQLBrowseConnect`, eigene Deskriptoren und `SQLCopyDesc`, MARS, Always Encrypted, `bcp` mit Dateien, Windows-/Entra-Anmeldung, andere Codepages als 1252 und UTF-8 bei `char`/`varchar`, Daten bei der Ausführung in Parameterfeldern und Tabellenwertparametern, Tabellenwertparameter in Parameterfeldern, `SQL_ATTR_PARAM_BIND_OFFSET_PTR`, `SQL_SOPT_SS_NAME_SCOPE` für Tabellentypen, `SQLGetData` stückweise vom Server (der Strommodus des Kerns wird im Treiber nicht genutzt). (3) Der statische Cursor hält die Menge im Speicher. (4) Parameterfelder gehen Satz für Satz als eigene Anfrage; Mengen der Sätze entfallen (`SQL_PAS_NO_SELECT`). (5) Befunde für den Server: `sp_describe_undeclared_parameters` antwortet leer; `UNION ALL` aus `varbinary(max)` und `0x01` meldet 8115; `SUBSTRING` über einen `varbinary(max)`-Parameter bricht den TDS-Strom (die Spalte wird zu kurz angekündigt, auch über sqlcmd); SQL Server nimmt alle drei an. (6) Der Savepoint geht über `SAVE TRANSACTION` im Stapel. **Erledigt 21.10.2026:** (5) am Orakel gemessen und nachgebaut: `sp_describe_undeclared_parameters` leitet die Typen nicht erklärter Parameter ab (einfache Ableitung bei =, <>, IN, Zuweisung, INSERT VALUES, CAST; allgemeine bei <, >, BETWEEN, LIKE und Rechnungen: decimal(38,19), varchar(8000), nvarchar(4000), datetime2(7) …; 11508, 11503, 11506) mit den 24 Spalten des Orakels; `SUBSTRING` über `varbinary(max)` bleibt `varbinary(max)` (vorher kündigte die Spalte 1 Byte an und der Wert brach den Strom); `UNION ALL` aus `varbinary(max)` und `0x01` geht am T-SQL-Port schon - am KSQL-Port ist `0x01` nach Profil eine Ganzzahl (T661), mit `X'01'` geht es dort ebenso, der Befund lag am Prüfprogramm. Tests `UndeklarierteParameterTests` (19, vorher 18 rot), `BinaerParameterTests` (6, vorher 3 rot). (1) bis (4) und (6) unverändert in D36.5e.✅
D36.5eRest aus D36.5d(1) Nicht gelaufen: Windows (nur mit mingw-w64 übersetzt und gelinkt; MSVC, SChannel und der Windows-Treibermanager ungeprüft), macOS mit dem Treiber (nur der Kern mit Secure Transport und OpenSSL; ODBC-Köpfe fehlen auf dem Mac); der SQL Browser nur gegen einen nachgebauten Browser im Test des Kerns. (2) Nicht im Treiber: ändernde Cursor (Keyset und dynamisch des Servers, `SQLSetPos` mit `SQL_UPDATE`/`SQL_DELETE`, `SQLBulkOperations` mit `SQL_ADD` und den Änderungen nach Lesezeichen), asynchrone Ausführung, `SQLBrowseConnect`, eigene Deskriptoren und `SQLCopyDesc`, MARS, Always Encrypted, `bcp` mit Dateien, Windows-/Entra-Anmeldung, andere Codepages als 1252 und UTF-8 bei `char`/`varchar`, Daten bei der Ausführung in Parameterfeldern und Tabellenwertparametern, Tabellenwertparameter in Parameterfeldern, `SQL_ATTR_PARAM_BIND_OFFSET_PTR`, `SQL_SOPT_SS_NAME_SCOPE` für Tabellentypen, `SQLGetData` stückweise vom Server (der Strommodus des Kerns wird im Treiber nicht genutzt). (3) Der statische Cursor hält die Menge im Speicher. (4) Parameterfelder gehen Satz für Satz als eigene Anfrage; Mengen der Sätze entfallen (`SQL_PAS_NO_SELECT`). (5) (erledigt in D36.5d) (6) Der Savepoint geht über `SAVE TRANSACTION` im Stapel.⬜
D36.6C++Moderne C++20-Schnittstelle über dem C-Kern (RAII, Ausnahmen mit Nummer und SQLSTATE, typsichere Parameter); daneben bleibt ODBC der Weg für bestehende C/C++-Programme; Verteilung über vcpkg und Conan. **Erledigt 21.10.2026:** `Data/src/Kuonen.Data.Ksql.Cpp` (C++20, CMake, Bibliothek `ksqlcpp` über `ksqlkern`; eigener Code): `ksql::verbindung` (Verbindungstext mit den Schlüsseln von ODBC Driver 18), `antwort` als Bereich über die Zeilen samt Mengen, Zeilenzahl, Rückgabe, Ausgaben und Meldungen, `transaktion` über die Transaction-Manager-Anfrage, `massenkopie`, Abbruch aus einem zweiten Faden und Befehlsfrist; RAII überall (der Zerstörer liest zu Ende, nimmt zurück, schliesst); `ksql::fehler` mit Nummer, Klasse, Zustand und SQLSTATE, darunter `leitungsfehler`, `abbruch`, `fristablauf`, `wertfehler`; typsichere Parameter (der SQL-Typ folgt aus dem C++-Typ, `ksql::als` nennt ihn ausdrücklich, auch für die Typen der Norm) und `get<T>` mit `std::optional` und `std::chrono`. Tests `Data/tests/Kuonen.Data.Ksql.Cpp.Tests` gegen die Attrappe des Kerns unter Adress- und UB-Sanitizer (78 Prüfungen, 3000 verdorbene Antworten); Abspieler `Data/tools/Kuonen.Data.Ksql.Cpp.Abspieler`, Prüfstand `docker/ksql-treiber/cpp`. Katalog am eigenen Server 73 ok, 0 abweichend, 9 Befunde, 0 übersprungen, am Orakel 72 ok, 0 abweichend, 10 übersprungen (nur KSQL) - im Container über OpenSSL und auf dem Mac über Secure Transport. Verteilung vorbereitet, nicht veröffentlicht: vcpkg-Overlay-Port und Conan-Rezept, beide mit `docker build` geprüft (`vcpkg install` mit Verbraucher, `conan create` mit `test_package`). Reste in D36.6a.✅
D36.6aRest aus D36.6**Teilweise erledigt 21.10.2026:** `ksql::anweisung` (vorbereitet: erster Lauf `sp_prepexec`, dann `sp_execute` mit der behaltenen Kennung, `sp_unprepare` im Zerstörer oder mit der nächsten Anfrage; nach Namen aufgerufen, denn über die Kennung nimmt SQL Server bei `sp_execute` keine benannten Parameter an, 4002), die KSQL-Typen als eigene C++-Typen `intervall`, `sammlung` (ARRAY, MULTISET) und `zeilenwert` (ROW) zum Lesen und als Parameter (Textformen wie der Java-Treiber, Tiefe und Länge gedeckelt), `ksql::pool` für mehrere Fäden. Tests ohne Server 115, mit Server am KSQL-Port 136 und am Orakel 128 Prüfungen, je ohne Fehlschlag, auf dem Mac und im Container (dort als Bauschritt); Katalog im Container am KSQL-Port 82 ok, 0 abweichend, 0 Befunde, am Orakel 72 ok, 10 übersprungen. Gegenprobe: ohne das Abfangen der Kennung vier Fehlschläge. **Offen:** (1) Windows (MSVC, SChannel); macOS nur von Hand übersetzt (lokal fehlt CMake). (2) Kein .NET-Test wie `JavaTreiberTests`. (3) MARS, Tabellenwertparameter, Always Encrypted, integrierte Anmeldung und Entra ID, eine asynchrone Schnittstelle (Koroutinen); der Pool setzt den Sitzungszustand nicht zurück (kein RESETCONNECTION). (4) Verteilung nur vorbereitet; die Fassung in `vcpkg.json` ist von Hand nachzuführen. **Erledigt 21.10.2026:** (2) `TreiberPruefstandTests` (Bauschritt, `KUONENSQL_DOCKER_ABNAHME=1`) baut den Prüfstand `cpp/` (g++ 12 in `debian:bookworm-slim`, Tests mit Sanitizern, `find_package`-Probe, Abspieler) und spielt den Katalog am KSQL-Port und am Orakel ab - 2 von 2 grün. Der erste Lauf auf x64 fand einen Fehler, den clang auf dem Mac verdeckte: `sammlung::von` rief `elementtyp_aus(…, texte)` und `std::move(texte)` in derselben Argumentliste; g++ wertet von rechts aus, die Texte waren schon weg und eine Sammlung aus `dezimal` wurde `decimal(1,0)` statt `decimal(4,2)` (Prüfung „von dezimal“ rot). Jetzt wird der Typ vorher berechnet. Rest in D36.6g.✅
D36.6bBefund: keine OUTPUT-Parameter bei vorbereiteten AnweisungenAm KSQL-Port geben `sp_prepexec` und `sp_execute` nur `@handle` zurück, die OUTPUT-Parameter der Anweisung nicht (`SET @o = @a * 2` mit `@o int OUTPUT`); SQL Server 2025 liefert `@o` in beiden. `VorbereiteteAnweisungen` setzt die Werte als Text in die Anweisung ein und kennt keine Ausgaben. Die C++-Tests prüfen die Lücke so, dass sie rot werden, sobald sie behoben ist. **Erledigt 21.10.2026:** am Orakel mit rohen Aufrufen gemessen (Tokenfolge von `sp_prepexec`, `sp_execute` mit und ohne Namen, ohne OUTPUT-Merkmal, mit Startwert, `sp_prepare`, `sp_unprepare`) und nachgebaut: `sp_prepexec` und `sp_execute` gehen jetzt wie `sp_executesql` über einen Plan (`TdsRpcParser.BaueVorbereitet`: die Werte als typisierte Variablen der vorbereiteten Parameterliste), so kommen DONEINPROC je Anweisung, RETURNSTATUS 0, der Griff (Stelle 0), die Ausgaben mit ihrer Stelle im Aufruf und DONEPROC 0x0000/0x00E0 wie am Orakel; `sp_prepare` und `sp_unprepare` in derselben Ordnung. Dieselbe Messung am KSQL-Port ist Token für Token gleich, bis auf das DONEINPROC, das das Orakel bei `sp_prepare` vor RETURNSTATUS schickt. Tests `VorbereiteteAusgabenTests` (Plan, SqlClient mit `Prepare()` über drei Läufe; auf dem alten Weg rot), die C++-Tests verlangen die Ausgabe jetzt an beiden Zielen (KSQL-Port 136, Orakel 128 ohne Fehlschlag), Konformität samt Orakel 166 grün.✅
D36.6cRest aus D36.6bVor dem RETURNSTATUS von `sp_prepare` schickt SQL Server ein DONEINPROC (0x0011, 0x00C1, 0 Zeilen; gemessen mit `SET @o = @a + 1`), der KSQL-Port nicht; ob es bei einer Anweisung mit Ergebnismenge Spalten trägt, ist nicht gemessen. Tabellenwert-Parameter in vorbereiteten Anweisungen gehen noch über das Einsetzen (ohne OUTPUT). **Erledigt 21.10.2026:** am Orakel mit SqlClient (RPC `sp_prepare` nach Namen, `Prepare()`) und dem Mitschnitt gemessen: `sp_prepare` einer einzelnen Anweisung schickt erst ihre Spalten (bei einer Ergebnismenge COLMETADATA, bei ORDER BY samt ORDER), dann DONEINPROC 0x0011 mit dem Befehlscode der Anweisung (SELECT, SET, Zuweisung 0x00C1, SELECT INTO 0x00C2, INSERT 0x00C3, DELETE 0x00C4, UPDATE 0x00C5) und 0 Zeilen, RETURNSTATUS 0, den Griff, DONEPROC 0x0000/0x00E0; bei mehr als einer Anweisung (ein DECLARE ohne Wert zählt nicht, IF, EXEC und ein DECLARE mit Wert schon, BEGIN … END nach seinem Inhalt) weder Spalten noch DONEINPROC, sondern RETURNSTATUS 8182; scheitert die Anweisung, ihr Fehler, 8180, RETURNSTATUS 8180, der Griff NULL, DONEPROC 0x0002. Beide Zugänge (T-SQL und KSQL) antworten jetzt so (`TdsTokenWriter.CreateVorbereitungsAntwort`, `ExecutionEngine.VorbereitungsBefehl`; die Parameter als Variablen wie im Plan von `sp_execute`, so geht auch `SELECT @o = @a`). Tabellenwert-Parameter gehen bei `sp_prepexec` und `sp_execute` über den Plan aus D36.6b (`BaueVorbereitet`: die Tabellenvariable unter dem Namen der Parameterliste vor den Deklarationen, ein fehlender Tabellenwert leer), mit den skalaren OUTPUT-Parametern daneben; SqlClient mit `Prepare()` (Tabellenwert, Wert, Ausgabe; zwei Läufe) ist an beiden Zugängen Token für Token wie am Orakel (Griff mit leerem Namen an Stelle 0, `@o` an Stelle 5, dann über `sp_execute` an Stelle 3). OUTPUT für den Tabellenwert selbst lässt SQL Server nicht zu: SqlClient weist eine andere Richtung als Input ab, `READONLY OUTPUT` in der Parameterliste ist 102 mit 8180. Tests `VorbereiteteTabellenwerteTests` (23 Fälle, beide Zugänge; vorher 19 von 19 rot), gefiltert (vorbereitete Anweisungen, RPC, Tabellenwert, Prepare, Protokolle) 147 grün, Konformität samt Orakel 166 grün. Sabotage: ohne das DONEINPROC 12 rot, mit dem alten Ausweichen der Tabellenwerte 2 rot. Reste in D36.6d.✅
D36.6dRest aus D36.6c(1) `sp_prepare` mit `WITH … SELECT`: SQL Server schickt die Spalten (Merkmale 0x0001), KuonenSQL keine (FMTONLY überspringt die Anweisung mit CTE). (2) `EXEC sp_prepare` im Stapel (D36.1e) ist nicht nach D36.6c gemessen; mit einem Tabellentyp in der Parameterliste meldet es 208 auf `@t` statt wie SQL Server 352 (ohne READONLY) bzw. 102 (`READONLY OUTPUT`) samt 8180. (3) Über RPC nimmt KuonenSQL einen Tabellentyp ohne READONLY und `READONLY OUTPUT` in der Parameterliste von `sp_prepexec` an. (4) `sp_prepexec`/`sp_execute` nach Namen mit benannten Werten: SQL Server 349 (kein Parameter dieses Namens), KuonenSQL führt aus. (5) Der alte Einsetzweg in `VorbereiteteAnweisungen.Behandle` für `sp_prepexec`/`sp_execute` dient nur noch den Tests in `VorbereiteteAnweisungenTests`. **Erledigt 21.10.2026:** am Orakel mit SqlClient 6.1.6 (RPC nach Namen und Stapel, mitgeschnitten) gemessen. (1) `WITH … SELECT` beschreibt seine Spalten bei `sp_prepare` (RPC und Stapel) und unter `SET FMTONLY ON` (Merkmale wie am Orakel, z. B. 0x0001 für `SELECT @a`, 0x0000 für Konstanten, 0x0008 für eine NOT-NULL-Spalte); unter FMTONLY gilt auch ein DECLARE (mit Wert samt DONE, auch eine Tabellenvariable), wie am Orakel. (2) `EXEC sp_prepare` im Stapel antwortet wie über RPC: eine Abfrage mit Spalten, jede andere einzelne Anweisung mit DONEINPROC ihres Befehlscodes und 0 Zeilen, ohne sie auszuführen (vorher lief `WITH … INSERT` wirklich), mehrere mit RETURNSTATUS 8182; ein Fehler (208, 352 ohne READONLY, 102 bei `READONLY OUTPUT`) ist er selbst, dann 8180, RETURNSTATUS 8180, DONEPROC 0x0003, der Griff bleibt NULL, der Stapel läuft weiter; ein Tabellentyp mit READONLY beschreibt seine Spalten. (3) Über RPC weisen `sp_prepare` und `sp_prepexec` dieselben Parameterlisten mit 352 bzw. 102, 8180, RETURNSTATUS 8180 und dem Griff NULL ab. (4) 349 kommt nicht: SQL Server nimmt benannte Werte an (auch in anderer Reihenfolge); fehlt einem `sp_prepexec`/`sp_execute` ein Wert (ein fremder Name zählt nicht), ist es 8178 „The parameterized query … expects the parameter …“ mit RETURNSTATUS 1 (bei sp_prepexec der Griff NULL), mehr Werte als Parameter 8144 (Zustand 2, ohne RETURNSTATUS; im Stapel DONE mit curCmd 0x00FD). Beide Zugänge antworten jetzt so (`ExecutionEngine.FehlerInDerParameterliste`, `TdsRpcParser.FehlendeWerte`, `TdsTokenWriter.CreateVorbereitungsfehler`, `KuonenSqlException.Aufrufstatus`). (5) `Behandle` nimmt nur noch `sp_prepare` und `sp_unprepare`; `Einsetzen` und `Parameternamen` sind entfernt, die Tests gehen über `Plan` bzw. einen Testserver. Tests `VorbereiteteAnweisungenResteTests` (72 Fälle, beide Zugänge; vorher 65 von 70 rot), gefiltert (vorbereitete Anweisungen, Protokolle, Formauskunft, Massenkopie, Sitzungsschalter, Modulrahmen, RPC, Tabellenwert) 488 grün, rot nur `ErgebnisstromTests.Ein_Aufruf_Mit_Ausgabeparameter_Stroemt` (auf `main` unter macOS ebenso), Konformität samt Orakel 169 grün. Sabotage: ohne `WITH … SELECT` unter FMTONLY 12 rot, ohne die Prüfung der Werte 23 rot. Reste in D36.6e.✅
D36.6eRest aus D36.6d(1) `sp_executesql` prüft die Werte nicht: SQL Server meldet 8178 (RETURNSTATUS 1) bzw. 8144 auch dort, KuonenSQL setzt NULL bzw. meldet 137; ebenso fehlen dort 352 und 102 für die Parameterliste. (2) Am Orakel hängt das Ergebnis eines fehlenden Werts am Plan-Cache: ist die Anweisung noch nicht übersetzt, kommt 8178 mit 8180 und RETURNSTATUS 8180 statt RETURNSTATUS 1 (einmal beobachtet) - KuonenSQL antwortet immer wie mit Plan. (3) Der RETURNSTATUS von `EXEC sp_prepare` im Stapel ist am Orakel der Status des vorigen Aufrufs auf der Verbindung (z. B. 8178 nach einem gescheiterten sp_prepexec); KuonenSQL schickt 0. (4) Ein Übersetzungsfehler einer Datenänderung (`INSERT` in eine fehlende Tabelle) bei `sp_prepare` ist nicht gemessen; KuonenSQL beschreibt sie ohne Prüfung. **Erledigt 21.10.2026** (am Orakel gemessen, SqlClient 6.1.6 mit Mitschnitt): (1) `sp_executesql` prüft über RPC und im Stapel die Parameterliste (352, 102) und die Werte (8178, 8144), dazu Werte ohne Liste (214 Zustand 3) und mit leerer Liste (8146, RETURNSTATUS 1); der Aufruf endet, der Stapel läuft weiter, `EXEC @r = …` trägt den Status. (2) Gemessen ist die Regel: der Status hängt daran, ob dieselbe Anweisung mit derselben Parameterliste schon übersetzt ist (aus `sp_executesql`, `sp_prepexec` oder `sp_prepare`, über Verbindungen hinweg) - dann 8178 mit RETURNSTATUS 1 und 8144 als Fehler der EXEC-Anweisung (curCmd 0x00FD, ohne Status, bricht den Stapel ab), sonst bei `sp_executesql` RETURNSTATUS gleich Fehlernummer, bei `sp_prepexec` 8180 samt Meldung 8180. KuonenSQL führt dafür je Datenbank einen gedeckelten `Planbestand`. (3) Der RETURNSTATUS von `sp_prepare` (RPC und Stapel) ist `@@ERROR` vor dem Aufruf, und `@@ERROR` gilt über das Ende eines Stapels hinaus (auch das war bei KuonenSQL anders); ein DECLARE ohne Wert und `sp_prepare` lassen es stehen, `sp_unprepare` und SET setzen es auf 0. (4) INSERT, UPDATE und DELETE an einer fehlenden Tabelle: 208, 8180, RETURNSTATUS 8180, Griff NULL. Tests `AusfuehrungsWerteTests` (37, vorher 35 rot), angepasst `VorbereiteteAnweisungenResteTests` (frische Anweisungen jetzt mit 8180). Rest in D36.6f.✅
D36.6fRest aus D36.6e(1) Am Orakel meldet `sp_prepare` für `INSERT sys.objects …` 259 („Ad hoc updates to system catalogs are not allowed.“) mit 8180; KuonenSQL kennt 259 nicht. (2) Nicht gemessen: ob `sp_reset_connection` `@@ERROR` zurücksetzt (KuonenSQL behält es), ob der Plan-Cache des Orakels auch nach den SET-Optionen unterscheidet (der `Planbestand` nur nach Datenbank, Liste und Text) und was `sp_prepare` bei MERGE, `UPDATE … FROM` und Zielen in einer anderen Datenbank prüft (KuonenSQL prüft dort nichts vor). (3) Ob ein DECLARE mit Wert oder ein DECLARE einer Tabellenvariablen `@@ERROR` stehen lässt, ist nicht gemessen (KuonenSQL setzt es auf 0).⬜
D36.6gRest aus D36.6a(1) Windows (MSVC, SChannel); macOS nur von Hand übersetzt (lokal fehlt CMake). (2) MARS, Tabellenwertparameter, Always Encrypted, integrierte Anmeldung und Entra ID, eine asynchrone Schnittstelle (Koroutinen); der Pool setzt den Sitzungszustand nicht zurück (kein RESETCONNECTION). (3) Verteilung nur vorbereitet; die Fassung in `vcpkg.json` ist von Hand nachzuführen.⬜
D36.7Python und GoPython mit der Schnittstelle von `mssql-python` bzw. DB-API 2.0 (PEP 249), Go mit der von `go-mssqldb` (`database/sql`-Treiber `ksql`); je Eigenbau ohne Fremdabhängigkeiten; Verteilung über PyPI und als Go-Modul. **Erledigt 21.10.2026:** Python `Data/src/Kuonen.Data.Ksql.Python` (Paket `ksql`, Paketname `kuonen-ksql`, `pyproject.toml`; nur Standardbibliothek, TLS über `ssl.MemoryBIO` im PRELOGIN; `connect()` mit den Schlüsseln von ODBC Driver 18, `paramstyle` qmark, `Param` mit SQL-Typ und Richtung, `callproc` mit Rückgabestatus, `bulkcopy`, `cancel()` per ATTENTION, Befehlsfrist, Ausnahmen nach PEP 249 mit `sqlstate`/`server_sqlstate`, KSQL-Typen als `Interval`/`Row`/`Array`/`Multiset`/`Decimal`, siebte Stelle von `time(7)` in `nanosecond`). Go `Data/src/Kuonen.Data.Ksql.Go` (Modul `kuonen.org/ksql`, nur Standardbibliothek mit `crypto/tls`; DSN als ADO-Form und URL mit den Schlüsseln von go-mssqldb, `@p1`/`sql.Named`, `sql.Out`, `ReturnStatus`, `VarChar`/`NVarCharMax`/`DateTimeOffset`/`UniqueIdentifier`/`TypedValue`, `CopyIn`, Abbruch und Frist über den Kontext mit ATTENTION, `BeginTx` über die Transaction-Manager-Anfrage, `ksql.Error` mit den Feldern von `mssql.Error` und SQLSTATE). Je ein Abspieler (`konformitaet.py`, `cmd/konformitaet`), Robustheitstests gegen einen falschen Server (abgeschnitten, verfälscht, riesige Längen, Schweigen), Prüfstände `docker/ksql-treiber/python|go`. Katalog je Treiber, gelaufen in `python:3.12-slim` bzw. `golang:1.23`: SQL Server 2025 72 ok, 0 abweichend (10 nur KSQL); KSQL 69 ok, 9 Befund, 4 Abweichungen, alle Befunde des Servers bzw. Katalogs (D36.7a, D36.7b). Befunde D36.7a–d, Rest D36.7e.✅
D36.7aBefund: KSQL-Typen heissen im UDT-Kopf nach der NormSeit K33.17 trägt TYPE_INFO den Typnamen in der Schreibweise der Norm (`INTERVAL DAY(2)`, `DECFLOAT(34)`, `int ARRAY`, `ROW(f1 int, f2 varchar(1))`); Katalog und Profil (8.2) nennen den Typnamen `interval`, `decfloat`, `row`, `array`, `multiset`. Die Fälle `typen.interval`, `typen.decfloat`, `typen.sammlungen` weichen darum ab, auch im .NET-Abspieler. Zu entscheiden: Katalog und Profil auf die Normschreibweise umstellen (dann den Typ am Klassennamen `Kuonen.Global.SqlTypes.SqlInterval` erkennen, wie die Treiber aus D36.7 es tun) oder den kurzen Namen senden. Der Referenztest erlaubt Befunde nur als `D36.1x`; darum steht kein `befund` im Katalog. **Erledigt 21.10.2026:** entschieden mit K33.17b: Katalog und Profil nennen die Normschreibweise. Python und Go brauchten keine Änderung (sie erkennen den Typ am Klassennamen und trennen den SQLSTATE schon vor dem LOGINACK). Katalog am eigenen Server je 73 ok, 0 abweichend, 9 Befunde, 0 übersprungen; am Orakel je 72 ok, 0 abweichend, 10 übersprungen (nur KSQL); Go im `docker build` des Prüfstands (go vet, go test) und der Katalog als Bauschritt; Python `tests/` 12 grün.✅
D36.7bBefund D36.1b ist am Server behobenDer Fehler 18456 der Anmeldung endet am KSQL-Port heute mit `(SQLSTATE 28000)`; die Treiber aus D36.7 melden darum „Befund ist behoben“. Der .NET-Abspieler sieht ihn nicht, weil `Kuonen.Data.Tds` den Zusatz bei einem Fehler vor dem LOGINACK nicht abtrennt. Zu tun: `Kuonen.Data.Tds` nachziehen, dann `befund` aus `anmeldung.falsches-kennwort` und die Zeile D36.1b streichen. **Erledigt 21.10.2026** mit D36.2 und D36.1b: `Kuonen.Data.Tds` trennt den SQLSTATE schon vor dem LOGINACK ab, die Abspieler melden den Befund nicht mehr.✅
D36.7cBefund: implizite Transaktion ohne ENVCHANGE 8Unter `SET IMPLICIT_TRANSACTIONS ON` (so setzen ODBC-Treiber `autocommit=False` um) beginnt am KSQL-Port eine Transaktion (`@@TRANCOUNT` 1), ohne dass der Server ENVCHANGE 8 mit dem Deskriptor schickt; SQL Server 2025 schickt ihn. Ein Treiber, der wie die Microsoft-Treiber mit der Transaction-Manager-Anfrage bestätigt, bestätigt dann nichts, und die Änderungen gehen beim Schliessen verloren. Der Python-Treiber weicht auf `IF @@TRANCOUNT > 0 COMMIT` aus. **Erledigt 21.10.2026:** Der implizite Beginn unter `SET IMPLICIT_TRANSACTIONS ON` meldet jetzt wie `BEGIN TRAN` ENVCHANGE 8 mit neuem Deskriptor (`ExecuteStatement`: `Rahme(Rahmenart.TransBeginn)`), an beiden TDS-Ports; ein `BEGIN TRAN` unter der Option meldet ihn nur einmal (@@TRANCOUNT 2), `COMMIT` schickt ENVCHANGE 9 wie bisher. Test `TdsRahmenTests.Eine_implizite_Transaktion_meldet_ihren_Beginn_per_ENVCHANGE` (Mitschnitt über SqlClient).✅
D36.7dBefund: Massenkopie meldet die Zeilen als SELECTDie Antwort des KSQL-Ports auf BULK LOAD ist ein DONE mit `DONE_COUNT`, aber `curCmd` 0xC1 (SELECT; `TdsMassenkopie.Behandle` ruft `CreateNonQueryResponse` mit der Vorgabe); SQL Server 2025 nennt die Zeilen so, dass sie zählen (gemessen: 2 von 2). Ein Treiber, der wie SqlClient Zählungen nach SELECT weglässt, sieht keine Zeilenzahl; die Treiber aus D36.7 nehmen dann die gesendeten Zeilen. **Erledigt 21.10.2026:** Am Orakel mitgeschnitten (SqlBulkCopy mit zwei Zeilen durch einen Abgriff): das letzte Token ist `FD 1000 F000 02…` - DONE mit DONE_COUNT, curCmd 0x00F0, 2 Zeilen. `TdsMassenkopie.Behandle` antwortet jetzt ebenso (vorher curCmd 0x00C1). Test `MassenkopieTests.Die_Massenkopie_meldet_ihre_Zeilen_mit_curCmd_F0_und_DONE_COUNT`.✅
D36.7eRest aus D36.7(1) `docker run` startete am 21.10. keinen Container (blieb in „Created“, auch bei anderen Sitzungen); die Katalogläufe in den Abbildern liefen darum als Bauschritt (`docker build` mit `--secret` und `--add-host`); `pruefstand.ps1` und ein Test wie `DockerPruefstandTests` für Python und Go sind noch nicht durchgelaufen. (2) Go: Zeichentypen mit einer Codepage ausser 1252 und UTF-8 kommen als Byte (keine Tabellen der Codepages in der Standardbibliothek); Python liest sie über `codecs`. (3) Beide: keine benannten Instanzen, keine Windows- oder Entra-Anmeldung, keine Umleitung (ENVCHANGE 20), keine Tabellenwertparameter, kein Always Encrypted. (4) Verteilung: das Python-Paket trägt „Private :: Do Not Upload“, das Go-Modul `kuonen.org/ksql` braucht einen Ort, der den Importpfad ausliefert - beides erst nach Entscheid von Marc (Code bleibt geschlossen). (5) `tools/roadmap.py` braucht Python 3.10+, lokal gibt es 3.9: Status von Hand gesetzt, `nachziehen` nicht gelaufen. **Erledigt 21.10.2026:** (2) Go liest und schreibt `char`/`varchar`/`text` in den Codepages 437, 850, 874, 932, 936, 949, 950, 1250, 1251, 1253 bis 1258 (`codepage.go`, Tabellen `codepages.bin.gz`, 135 KB): jedes Byte und jedes Paar Leitbyte/Folgebyte am Orakel gemessen (`KuonenSQL/tools/ksql-codepages/erzeugen.py`) und mit den Unicode-Zuordnungstabellen abgeglichen (Herkunft und Lizenz in `THIRD-PARTY-NOTICES.md`; wo Unicode ein Zeichen nennt, stimmt die Messung, ausser cp950 C6A1–C8FE). Aus (3), in beiden Treibern: benannte Instanzen über den SQL Browser ([MC-SQLR], UDP 1434, Deckel für Länge, Datagramme und Frist; Go auch `sqlserver://host/instanz`), die Umleitung (ENVCHANGE 20, höchstens 5 Mal, verformte Umleitungen sind Fehler), Tabellenwertparameter (`ksql.TableValue` bzw. `ksql.TVP`, Go auch mit `Value` als Slice von Structs wie go-mssqldb; in Abfragen und an Prozeduren am KSQL-Port und an SQL Server 2025 grün). Gelaufen als Bauschritte in `python:3.12-slim` und `golang:1.23`: Einheiten- und Robustheitstests (Python 22, Go alle grün), Livetests (TVP, Codepages, TDS 8.0 am KSQL-Port; am Orakel TDS 8.0 übersprungen, siehe D36.3c) und der Katalog je Treiber: KSQL-Port 82 ok, 0 abweichend, 0 Befund, 0 übersprungen; SQL Server 2025 72 ok, 0 abweichend, 10 übersprungen (nur KSQL). Befund D36.7g. Rest D36.7f.✅
D36.7fRest aus D36.7e(1) `docker run` startete am 21.10. keinen Container; `pruefstand.ps1` und ein Test wie `DockerPruefstandTests` für Python und Go sind noch nicht durchgelaufen (die Läufe gingen als Bauschritte). (2) Beide: keine Windows- oder Entra-Anmeldung, kein Always Encrypted; Go: `TVP` nicht als `sql.Out`; der SQL Browser nur gegen einen nachgebauten, nicht gegen einen echten SQL-Server-Browser; die Umleitung nur gegen einen falschen Server (der KSQL-Port leitet nur bei Lesezugriff auf eine Kopie um). (3) Python liest Codepages über `codecs`: undefinierte Byte werden zum Ersatzzeichen bzw. zum Byte gleicher Nummer, SQL Server nennt dafür teils Zeichen der privaten Zone (Messung in D36.7e). (4) Verteilung: das Python-Paket trägt „Private :: Do Not Upload“, das Go-Modul `kuonen.org/ksql` braucht einen Ort, der den Importpfad ausliefert - beides erst nach Entscheid von Marc (Code bleibt geschlossen). (5) `tools/roadmap.py` braucht Python 3.10+, lokal gibt es 3.9: Status von Hand gesetzt, `nachziehen` nicht gelaufen. **Erledigt 21.10.2026:** (1) `PythonGoPruefstandTests` (Bauschritt wie `OdbcPruefstandTests`, `KUONENSQL_DOCKER_ABNAHME=1`): Python (`python:3.12-slim`, 23 Tests im Bau) und Go (`golang:1.23`, gofmt, vet, Tests im Bau) bestehen den Katalog am KSQL-Port (0 abweichend, 0 übersprungen) und an SQL Server 2025 (nur die Fälle „nur ksql“ übersprungen) - 4 von 4 grün. Dafür liegen die Go-Quellen mit LF im Arbeitsbaum (`.gitattributes` des Treibers; unter Windows scheiterte `gofmt -l` an CRLF) und beide Abbilder haben ein `Dockerfile.dockerignore`. (3) Am Orakel gemessen (je eine Datenbank in der Sortierung, alle Byte 0x80-0xFF von 12 Einzelbyte-Codepages): abweichend von den Codecs nur 874 (DB-DE, FC-FF → U+F8C1-F8C8), 1253 (AA, D2, FF → U+F8F9-F8FB), 1255 (CA → U+05BA, D9-DF, FB, FC, FF → U+F88D-F896) und 1257 (A1, A5 → U+F8FC, F8FD); der Python-Treiber liest sie so und schreibt sie zurück zu ihrem Byte (je Codepage ein Fehlerbehandler `ksql-roh-<cp>`, Test `test_undefinierte_byte_der_codepages_wie_sql_server`, vorher rot; Sabotage ohne die Tabelle rot). (5) `tools/roadmap.py` läuft mit Python 3.14: `pruefen` bestätigt die von Hand gesetzten Stände der Meilensteine (nur Datum und Fassung der Kopfzeilen wären nachzuziehen). Python lokal (3.14) 24 Tests, 3 übersprungen. Rest in D36.7h.✅
D36.7gBefund: Tabellenwert ohne Typnamen an einer ProzedurEin Tabellenwertparameter ohne Typnamen (TVP_TYPENAME leer) beim RPC-Aufruf einer Prozedur `@t d367e_liste READONLY`: SQL Server 2025 nimmt den Tabellentyp des Prozedurparameters, der KSQL-Port meldet 2715 („Der Datentyp ; wurde nicht gefunden“; `TdsRpcParser.LiesTabellenwert` baut den Typnamen aus Schema und Name). Mit Namen geht es an beiden. Gefunden mit D36.7e. **Erledigt 21.10.2026:** ohne Typnamen deklariert der RPC-Leser eine Tabellenvariable mit den Spalten des Tabellenwerts (Typen aus der TYPE_INFO), beim Binden wandelt die Prozedur die Zeilen in den Tabellentyp ihres Parameters wie SQL Server (am Orakel mit SqlClient und dem TypeScript-Treiber gemessen: bigint und nvarchar(max) gehen, zu lang 2628, NULL in NOT NULL 515, falsche Spaltenzahl 500). Servertest `BenutzerobjekteTests.Tabellenwert_Ohne_Typnamen_An_Einer_Prozedur_Ueber_Tds` (derselbe Test am Orakel grün); die Treibertests erwarten jetzt das Ergebnis: `test_live.py` (Python) und `live_test.go` (Go) als Bauschritt in `python:3.12-slim`/`golang:1.23` gegen einen eigenen KSQL-Testserver grün, `erweiterungen.test.ts` mit `node --test`. Nicht gemessen: Spalten mit Standardwert (fDefault) in einem Tabellenwert ohne Typnamen.✅
D36.7hRest aus D36.7f(1) Beide: keine Windows- oder Entra-Anmeldung, kein Always Encrypted; Go: `TVP` nicht als `sql.Out`; der SQL Browser nur gegen einen nachgebauten, nicht gegen einen echten SQL-Server-Browser; die Umleitung nur gegen einen falschen Server (der KSQL-Port leitet nur bei Lesezugriff auf eine Kopie um). (2) Mehrbyte-Codepages (932, 936, 949, 950): wie in D36.7e gemessen; die Byte ohne Zeichen in den Einzelbyte-Codepages 437, 850 und 1250-1258 ausser den gemessenen sind Zeichen gleicher Nummer. (3) **Entscheid von Marc:** Verteilung - das Python-Paket trägt „Private :: Do Not Upload“, das Go-Modul `kuonen.org/ksql` braucht einen Ort, der den Importpfad ausliefert (Code bleibt geschlossen).⬜
D36.8Abnahme und weitere SprachenJeder Treiber besteht die Konformitätsprüfung gegen KSQL und SQL Server 2025, dazu je Sprache verbreitete Programme bzw. Rahmenwerke (Hibernate/Spring, Prisma/TypeORM, Laravel/Doctrine, SQLAlchemy/Django, GORM); Handbuchseite je Treiber im Prüfstand. Weitere Sprachen (Rust, Ruby, Swift, Kotlin/Native, Delphi) als eigene Zeilen, sobald gewünscht. **Erledigt 21.10.2026:** Handbuchseiten. Rahmenwerke in `docker/ksql-treiber/abnahme/<sprache>` (Programm mit Migration, Einfügen, Lesen mit Bedingung, Sortierung und Seiten, Transaktion mit Rollback, Löschen, Aufräumen; Pakete mit fester Fassung nur im Docker-Bau, der Lauf als Bauschritt über `abnahme.sh`; je Lauf dieselben Ergebniszeilen am KSQL-Port und am Orakel): **Java** Hibernate ORM 6.6.4 (`SQLServerDialect`, JPA mit optimistischer Sperre) und Flyway 10.20.1: am Orakel mit mssql-jdbc und mit `jdbc:ksql://` grün (Flyway V1/V2, Schema von Hibernate geprüft), am KSQL-Port Hibernate grün und zeilengleich, Flyway nicht (D36.8a). **TypeScript** TypeORM 0.3.20 (Treiber `mssql`, Repository, QueryBuilder, Migrationen hin und zurück): am Orakel mit mssql 10.0.4 und mit `@kuonen/ksql` als `mssql` grün und zeilengleich; am KSQL-Port alles grün bis auf das Zurücknehmen von V2 (D36.8b). Dabei im Treiber behoben: `Transaction.begin(callback)` ohne Isolationsstufe rief nie zurück (TypeORM hing beim ersten BEGIN); Test in `mssql.test.ts` (vorher rot durch Zeitablauf, nachher 14/14). **Python** SQLAlchemy 2.0.36 (ORM mit `version_id_col`) und Alembic 1.14.0 (hin und zurück) über den Dialekt `mssql+ksql` (`ksql_dialekt.py`, erbt von `mssql`, nimmt am KSQL-Port die Kompatibilitätsstufe als Fassung): am KSQL-Port, am Orakel mit `mssql+ksql` und mit `mssql+pymssql` grün und zeilengleich. Dabei Befund D36.8c (KSQL nimmt GROUP BY mit verschiedenen Parametern an). **Go** GORM 1.25.12 mit `gorm.io/driver/sqlserver` 1.5.4 über ein `*sql.DB` des Treibers `ksql` (Migrator: AutoMigrate in zwei Stufen, Spalte, Index und Tabelle entfernen; Transaktionen mit Commit, Rollback und Fehler): am KSQL-Port, am Orakel mit `ksql` und mit go-mssqldb 1.7.2 grün und zeilengleich. **PHP** Doctrine DBAL 4.2.1 (Plattform SQL Server, Treiber `Abnahme\KsqlDriver` über PDO `ksql:`; QueryBuilder, Transaktionen mit Sicherungspunkt, Fehlerumsetzung) und Doctrine Migrations 3.8.2: am Orakel mit `ksql:` grün (einen Microsoft-Treiber gibt es ohne Microsofts ODBC-Treiber nicht); am KSQL-Port alle DBAL-Schritte zeilengleich, die Migrationen nicht (D36.8d) und `lastInsertId` nicht (D36.8e). Befunde D36.8a–e, Rest D36.8f.✅
D36.8aFlyway-Erweiterung für den KSQL-PortBefund: Flyway 10 (`flyway-sqlserver`) kennt weder die URL `jdbc:ksql:` noch den Produktnamen „KuonenSQL“ („No database found to handle jdbc:ksql://…“) und scheitert danach an `SERVERPROPERTY('EngineEdition')` = 100 („Unknown SQL Server engine edition: 100“). Entscheid Marc 21.10.2026: Flyway-Erweiterung liefern, die Kennung bleibt ehrlich (EngineEdition 100, Produktname „KuonenSQL“, K33.2). **Erledigt 21.10.2026:** eigenes Modul `Data/src/Kuonen.Data.Ksql.Java.Flyway` (JAR `org.kuonen.ksql:kuonen-ksql-flyway`, Paket `org.kuonen.ksql.flyway`; Flyway nur `provided`, der Treiber bleibt ohne Abhängigkeiten). Über die Plugin-Schnittstelle (`META-INF/services/org.flywaydb.core.extensibility.Plugin`) meldet `KuonenSqlDatabaseType` (erbt von `SQLServerDatabaseType`) die URL `jdbc:ksql:` und den Produktnamen „KuonenSQL“ an; `KuonenSqlDatabase` nimmt als Fassung die Kompatibilitätsstufe (170 → 17.0, der KSQL-Port nennt 2.x) und verlangt wie SQL Server mindestens 10.0; `KuonenSqlConnection` schreibt nur die Abfrage `SELECT SERVERPROPERTY('engineedition')` so um, dass 100 als 3 (vollwertiger SQL Server) gilt. Kein Flyway-Code übernommen (Apache-2.0, nur die öffentliche API). Am Orakel meldet der Treiber „Microsoft SQL Server“, dort bleibt es beim Datenbanktyp von Flyway. Abnahme `abnahme.sh java …` (die Erweiterung im Bau übersetzt, `abnahme.KuonenSqlFlyway` entfernt): am KSQL-Port (eigener Testserver) vorher rot (`Unknown SQL Server engine edition: 100`), nachher grün (Flyway V1/V2, erneut 0, Schema von Hibernate geprüft); am Orakel mit `jdbc:ksql://` und mit mssql-jdbc unverändert grün; alle drei Läufe zeilengleich. Sabotage: Umschreiben der Abfrage ausgeschaltet → wieder rot.✅
D36.8bBefund: `sicht.*` über eine Systemsicht ohne Alias in einem JOIN bleibt leer`SELECT COLUMNS.*, s.name AS sn FROM INFORMATION_SCHEMA.COLUMNS LEFT JOIN sys.schemas s ON 1=0 WHERE TABLE_NAME = …` liefert am KSQL-Port nur `sn`, SQL Server 2025 alle Spalten der Sicht und `sn`; ebenso `tables.*` mit `FROM sys.tables LEFT JOIN …`. Ohne JOIN, mit Alias (`c.*`) oder bei einer Benutzertabelle (`d368_x.*`) stimmt es. TypeORM liest so die Spalten einer Tabelle (`SELECT "COLUMNS".*, "cc"."is_persisted", "cc"."definition" FROM "master"."INFORMATION_SCHEMA"."COLUMNS" LEFT JOIN "sys"."computed_columns" "cc" …`), sieht keine und meldet beim Zurücknehmen einer Migration `Column "aktiv" was not found`. Gemessen am KSQL-Port (eigener Testserver) und am Orakel mit `@kuonen/ksql`; der T-SQL-Port ist nicht gemessen. **Erledigt 21.10.2026:** am T-SQL-Port (eigener Testserver) ebenso gemessen. Ursache: eine Quelle ohne Alias steht im Verbund unter ihrem ganzen Namen (`INFORMATION_SCHEMA.COLUMNS`, `sys.tables`), der Stern suchte nur den Alias. Jetzt trifft `vorsatz.*` ohne passenden Alias die Quelle, deren mehrteiliger Name auf `.vorsatz` endet (`SternquelleImVerbund`); an beiden Ports dieselben Spalten und Zeilen wie am Orakel, auch in der Form von TypeORM. Test `AbnahmeBefundeTests` (T-SQL und KSQL), Herleitung `docs/umsetzung.md` (D36.8b).✅
D36.8cBefund: GROUP BY mit anderen Parametern als die Auswahlliste wird angenommen`EXEC sp_executesql N'SELECT menge + @a AS k FROM t GROUP BY menge + @b', N'@a int, @b int', 1, 1` und ebenso `CASE WHEN menge > @a THEN @b ELSE @c END … GROUP BY CASE WHEN menge > @d THEN @e ELSE @f END`: SQL Server 2025 meldet 8120 (die Ausdrücke sind verschieden, weil die Parameter verschieden sind), der KSQL-Port rechnet und liefert Gruppen. SQLAlchemy erzeugt genau diese Form, wenn ein Ausdruck mit gebundenen Werten in SELECT und GROUP BY steht; die Abnahme setzt dort darum Literale. Gemessen am KSQL-Port (eigener Testserver) und am Orakel; der T-SQL-Port ist nicht gemessen. **Erledigt 21.10.2026:** am T-SQL-Port ebenso gemessen; auch `menge + 1` gegen `menge + 2`, vertauschte Operanden, anderer Zieltyp und `SELECT menge … GROUP BY menge + 1` gingen durch (geprüft wurde nur bei GROUP BY aus blossen Spalten). Jetzt deckt ein Ausdruck in GROUP BY nur genau denselben Ausdruck (`GleicherAusdruck`, jetzt auch mit CASE); was ausserhalb bleibt, muss gruppiert sein: 8120 in der Auswahl, 8121 in HAVING, 8127 in ORDER BY, an beiden Ports wie am Orakel. KSQL nach der Norm: GROUP BY kennt dort nur Spaltenverweise, ein Ausdruck ist eine Erweiterung, von der nur derselbe Ausdruck funktional abhängt; zwei verschiedene dynamische Parameter sind unabhängige Werte, darum dieselbe Antwort (PostgreSQL 17 ebenso). Test `AbnahmeBefundeTests`, Herleitung `docs/umsetzung.md` (D36.8c). SQLSTATE am KSQL-Port: D36.8f (6).✅
D36.8dBefund: verschachtelter JOIN ohne Klammern ist ein Syntaxfehler`FROM sys.tables t INNER JOIN sys.columns c INNER JOIN sys.types y ON y.user_type_id = c.user_type_id ON c.object_id = t.object_id` (das zweite ON gehört zum ersten JOIN): SQL Server 2025 nimmt es an, der KSQL-Port meldet 102 „'On' erwartet, aber 'INNER' …“; mit Klammern um den inneren JOIN geht es. Doctrine DBAL 4 liest so die Fremdschlüssel (Abfrage über `sys.foreign_keys` und `sys.foreign_key_columns`); darum scheitern am KSQL-Port der Schema-Abgleich und alle Migrationen von Doctrine Migrations. Gemessen am KSQL-Port (eigener Testserver) und am Orakel; der T-SQL-Port ist nicht gemessen. **Erledigt 21.10.2026:** am T-SQL-Port ebenso gemessen. Folgt nach der rechten Quelle eines JOIN statt ON ein weiterer Verbund, liest der Parser die Kette bis zum nächsten freien ON als Verbundgruppe wie mit Klammern (`LiesQuellen(erste: …)`); an beiden Ports zeilengleich mit dem Orakel für INNER, LEFT aussen und innen, RIGHT, CROSS innen, dreifach verschachtelt und die Fremdschlüsselabfrage von Doctrine; ohne zweites ON weiter 102. Nach der Norm ist die rechte Seite einer `<qualified join>` eine `<table reference>`, also gilt es am KSQL-Port ebenso. Test `AbnahmeBefundeTests`, Herleitung `docs/umsetzung.md` (D36.8d). Abnahmelauf danach am KSQL-Port (eigener Testserver mit D36.8b–e): Doctrine DBAL und Migrations (PHP) mit `lastInsertId` 1–5, TypeORM (Node) mit Zurücknehmen von V2 und SQLAlchemy/Alembic (Python) je `abnahme: ok`.✅
D36.8eBefund: `@@IDENTITY` und `SCOPE_IDENTITY()` gelten nicht über den Stapel hinausAuf derselben Verbindung: `INSERT` (als Stapel oder per RPC `sp_executesql`), danach ein eigener Stapel `SELECT @@IDENTITY, SCOPE_IDENTITY()`: SQL Server 2025 liefert 1/NULL nach dem RPC und 2/2 nach dem Stapel, der KSQL-Port beide Male NULL/NULL; im selben Stapel stimmen beide. Treiber, die `lastInsertId` so nachfragen (PDO `ksql:` wie pdo_sqlsrv mit `SELECT CAST(@@IDENTITY AS nvarchar(40))`), bekommen am KSQL-Port nichts; Doctrine DBAL meldet `NoIdentityValue`. Hibernate, TypeORM, SQLAlchemy und GORM fragen im selben Stapel (OUTPUT bzw. SCOPE_IDENTITY) und sind nicht betroffen. Gemessen mit `@kuonen/ksql` (Pool mit einer Verbindung) am KSQL-Port und am Orakel. **Erledigt 21.10.2026:** genau gemessen (SqlClient, ein Befehl ohne Parameter ist ein Stapel, mit Parameter ein RPC-Aufruf): `@@IDENTITY` gilt für die Sitzung über jeden Stapel hinaus; `SCOPE_IDENTITY()` ist der Wert des eigenen Bereichs - alle Stapel der Verbindung teilen sich den äussersten (2/2 bleibt im nächsten Stapel), jeder RPC-Aufruf, jedes `sp_executesql`, jede Prozedur und jeder Trigger beginnt leer und lässt den des Aufrufers stehen (nach RPC-INSERT 1/NULL, per RPC gefragt 2/NULL, nach `EXEC sp_executesql` 3/2, nach Trigger 50/1). Rücknahme, USE und DROP ändern nichts. Jetzt an T-SQL- und KSQL-Port in allen Schritten wie am Orakel (`_bereichsIdentitaet`, gesichert in `BetreteModul` und beim RPC-Aufruf). Test `AbnahmeIdentitaetTests` über TDS, Herleitung `docs/umsetzung.md` (D36.8e).✅
D36.8fWeitere Rahmenwerke (Rest aus D36.8)Auftrag: Spring Data JPA, Laravel/Eloquent und Django abnehmen, prüfen, ob Prisma einen Treiberadapter über `mssql` hat (dann abnehmen), und Binärwerte, LOB, Tabellenwertparameter und eine gespeicherte Prozedur je Sprache über ein Rahmenwerk, wo es sie anbietet. Aus D36.8 übernommen: (2) kein Microsoft-Treiber für PHP und Python, (4) Läufe von Hand, (6) SQLSTATE von 8120/8121/8127 am KSQL-Port - alle drei weiter in D36.8m; (5) erledigt mit D36.8b–e. **Erledigt 21.10.2026:** **Java** Spring Boot 3.4.1 mit Spring Data JPA (Hibernate 6.6.4, HikariCP) und Spring JDBC (`abnahme.sh java <ziel> [treiber] spring`, Objekte `d368f_java_`): Repository mit abgeleiteten Abfragen, Seiten, JPQL, `@Modifying`, nativem SQL, `@Procedure` mit OUTPUT, `SimpleJdbcCall`, `EXEC` mit Ergebnismenge; `@Transactional` mit Rücknahme und `REQUIRES_NEW`, `NESTED` über Sicherungspunkte; optimistische Sperre; `@Lob byte[]`/`@Lob String` und Ströme über `JdbcTemplate`. Am Orakel mit mssql-jdbc 12.8.1 und mit `jdbc:ksql://`, am KSQL-Port (eigener Testserver) je `abnahme: ok`; KSQL-Port und Orakel mit dem eigenen Treiber zeilengleich bis auf `SimpleJdbcCall` über Metadaten (D36.8g); mssql-jdbc lehnt `releaseSavepoint` ab, darum scheitert `NESTED` unter Spring nur dort (der eigene Treiber gibt den Sicherungspunkt frei). Dabei im Treiber behoben: `getClob`/`getNClob`/`getBlob` (Ergebnis und OUTPUT) und `createClob`/`createNClob`/`createBlob` warfen `SQLFeatureNotSupportedException`, Hibernate liest `@Lob` so (vorher sechs Schritte rot, nachher grün; Prüfung „Clob, NClob und Blob“ in `PruefungenTest`, vorher rot, 18/18). Befund D36.8h: fehlender Prozedurparameter ohne Vorgabe. Tabellenwertparameter: der JDBC-Treiber hat keine (mssql-jdbc nur über `SQLServerDataTable`), Spring bietet keine eigenen. **PHP** Laravel 11 (`illuminate/database` 11.36.1 mit Capsule; Eloquent, Query Builder, Schema, Migrator) über einen eigenen Laravel-Treiber `ksql` (Verbinder mit DSN `ksql:`, Verbindung auf `SqlServerConnection` mit der SQL-Server-Grammatik; `abnahme.sh php <ziel> eigen laravel`, Objekte `d368f_php_`): Migrator hin und zurück, Schema lesen, Eloquent, Transaktionen mit Sicherungspunkt, `UniqueConstraintViolationException`, `varbinary(max)`/`binary(16)` als Strom, `nvarchar(max)`, Prozedur mit Ergebnismenge, Tabellenwertparameter (gebunden als Feld mit `PDO::PARAM_LOB`). Am Orakel `abnahme: ok`; am KSQL-Port alles zeilengleich bis auf zwei Befunde: D36.8i (`SELECT @sql += …`, so entfernt Laravel beim Zurücknehmen die Vorgabe-Bedingung einer Spalte) und D36.8j (Binärvergleich mit Nullen am Ende). Der Treiber meldet sich als `sqlsrv` (sonst nimmt Laravel rohes `BEGIN TRAN` ohne Sicherungspunkte) und erkennt doppelte Schlüssel an 2601/2627 (Laravel prüft den englischen Text, KuonenSQL meldet in der Sprache der Sitzung). **Python** Django 5.1.4 mit dem eigenen Backend `ksql_django` auf `mssql-django` 1.6 (BSD, nur als Paket und ohne pyodbc; `abnahme.sh python <ziel> eigen django`, Objekte `d368f_python_` samt Buchführung der Migrationen): Migrationen hin und zurück, Introspektion, ORM mit Filtern, Aggregaten, Gruppen, `select_for_update`, `atomic` mit Sicherungspunkt, `on_commit`, `IntegrityError`, `BinaryField`/`TextField`, `raw` über eine Prozedur, `callproc` mit OUTPUT, Tabellenwertparameter. Am Orakel und am KSQL-Port je `abnahme: ok` und zeilengleich. Das Backend ersetzt Verbinden, Autocommit (mssql-django schaltet es für FreeTDS nur bei offener Transaktion ein), Parameter `None` (als `NULL` im Text, weil ksql None als nvarchar schickt und SQL Server das nicht in varbinary wandelt), `cursor.columns` (`sp_columns_100`) und die Fassung (SQL Server 2025 fehlt in mssql-django 1.6). **TypeScript** Prisma ORM 7.7.0 mit dem Treiberadapter `@prisma/adapter-mssql` 7.7.0 (er lädt `mssql` und öffnet `new sql.ConnectionPool(config)`; `node_modules/mssql` zeigt wie bei TypeORM auf `@kuonen/ksql`; `abnahme.sh node <ziel> [treiber] prisma`, Objekte `d368f_node_`; Pakete ohne Installationsskripte, `prisma generate` ohne Netz, die Tabelle legt das Programm an, weil `prisma migrate` die Schema-Maschine von binaries.prisma.sh bräuchte): create, createMany, findMany, aggregate, groupBy, Bytes, `nvarchar(max)`, update/updateMany/upsert, `$transaction` als Stapel und interaktiv, P2002, `$queryRaw` über eine Prozedur. Am Orakel mit mssql 12.2.0 und mit `@kuonen/ksql` je `abnahme: ok` und zeilengleich; am KSQL-Port nicht (D36.8k, D36.8l). **Go** die GORM-Abnahme (jetzt Präfix `d368f_go_`) zusätzlich mit `varbinary(16)`, `varbinary(max)`, `nvarchar(max)`, Prozedur mit Ergebnismenge und OUTPUT über `Raw` und Tabellenwertparameter (`ksql.TVP` bzw. `mssql.TVP` als Slice von Structs): am Orakel mit go-mssqldb 1.7.2 und mit `ksql`, am KSQL-Port je `abnahme: ok`; zeilengleich bis auf den Typnamen `boolean` am KSQL-Port (K33.17d). Dabei im Go-Treiber behoben: ein nil-`[]byte` ging als leerer Wert statt NULL (GORM legte leere Binärfelder als `0x` an), ein Zeigerfeld in einem TVP-Struct (`*string`) war „kann der Treiber nicht senden“ (go-mssqldb nimmt beides); Test `TestNullAusLeeremByteSliceUndZeigernImTVP` vorher rot, nachher grün. `sql.Out` als Stellungsparameter füllt nur der eigene Treiber, go-mssqldb lässt das Ziel stehen. Läufe am KSQL-Port gegen einen eigenen Testserver (Stand `main`, KSQL-Port 16333), am Orakel nur Objekte `d368f_`, alle wieder entfernt. Befunde D36.8g–l, Rest D36.8m.✅
D36.8gBefund: Spring JDBC kennt den Produktnamen „KuonenSQL“ nicht`SimpleJdbcCall` liest die Parameter einer Prozedur über `DatabaseMetaData.getProcedureColumns`, aber nur für Produkte aus einer festen Liste in `CallMetaDataProviderFactory` („Microsoft SQL Server“ ja, „KuonenSQL“ nein). Am KSQL-Zugang meldet Spring „KuonenSQL is not one of the databases fully supported for procedure calls … Metadata processing disabled“ und ruft `{call d368f_java_zaehle()}` ohne Parameter; das OUTPUT bleibt `null` (am Orakel mit beiden Treibern 3). Mit `withoutProcedureColumnMetaDataAccess()` und erklärten Parametern geht es überall. Ebenso fehlt „KuonenSQL“ in `sql-error-codes.xml` (Fehlerumsetzung dann nur über den SQLSTATE). Eine Schnittstelle zum Erweitern hat Spring dafür nicht (anders als Flyway, D36.8a). Gemessen mit Spring Boot 3.4.1 (Spring 6.2.1) am KSQL-Port (eigener Testserver). Zu entscheiden: hinnehmen und dokumentieren, eine `sql-error-codes.xml` mit „KuonenSQL“ beilegen, oder einen Schalter des Treibers für den Produktnamen (K33.2 verlangt die ehrliche Kennung). **Erledigt 21.10.2026 (Entscheid Marc: Vorlage beilegen und dokumentieren, Produktname bleibt):** neue Vorlage `Data/src/Kuonen.Data.Ksql.Java/spring/sql-error-codes.xml` mit der Bean `KuonenSQL` und den Nummern, die Spring 6.2.1 für „Microsoft SQL Server“ führt (KuonenSQL meldet dieselben); die Anwendung legt sie als `sql-error-codes.xml` in ihren Klassenpfad. Bewusst nicht im JAR: Spring liest nur die erste Datei dieses Namens, eine im Treiber verdeckte die der Anwendung. Die Java-Seite des Handbuchs nennt die Vorlage und `SimpleJdbcCall` mit `withoutProcedureColumnMetaDataAccess()` und erklärten Parametern (die Metadaten liest Spring nur für Produkte seiner festen Liste). Herkunft in `THIRD-PARTY-NOTICES.md` (Apache-2.0). Test `SpringFehlercodesTests` (Produktname wie im Treiber, Nummern wie Spring 6.2.1, Seite). Die Spring-Abnahme ist nicht wiederholt (D36.8m).✅
D36.8hBefund: fehlender Prozedurparameter ohne Vorgabe wird nicht gemeldet`CREATE PROCEDURE d368f_p @ab decimal(10,2), @anzahl int OUTPUT AS SET @anzahl = 5; SELECT @ab AS ab`, dann `EXEC d368f_p` und `EXEC d368f_p 1.5`: SQL Server 2025 meldet 201 („Procedure or function 'd368f_p' expects parameter '@ab', which was not supplied.“ bzw. `'@anzahl'`), der T-SQL- und der KSQL-Port (eigener Testserver) führen die Prozedur mit NULL aus; ebenso `{call d368f_p()}`. Aufgefallen bei Spring `SimpleJdbcCall` am KSQL-Port (D36.8g), das ohne Parameter aufruft: statt eines Fehlers kommt still `null`. Nach der Norm (SQL/PSM) ist ein fehlendes Argument ohne Vorgabe ebenfalls ein Fehler. **Erledigt 21.10.2026:** ein Parameter ohne Vorgabe, der beim Aufruf mit `EXEC` fehlt oder `DEFAULT` bekommt, ist jetzt Fehler 201 mit dem Text von SQL Server (auch mit OUTPUT; nur die Anweisung bricht ab, die Transaktion bleibt offen, am Orakel gemessen). Dabei gefunden und mitbehoben: `DEFAULT` als Argument war Fehler 207 (`EXEC p 1, DEFAULT` ging gar nicht); jetzt bleibt die Vorgabe des Parameters wie am Orakel. `CALL` folgt weiter den Regeln seines Dialekts. Test `AbnahmeBefundeTests.Fehlender_Prozedurparameter_ohne_Vorgabe`; Sabotage je Teil rot. Benutzerfunktionen mit zu wenigen Argumenten (am Orakel 313) sind nicht geprüft.✅
D36.8iBefund: zusammengesetzte Zuweisung `SELECT @v += …` ist ein Syntaxfehler`DECLARE @sql nvarchar(max) = N''; SELECT @sql += name + N';' FROM sys.columns WHERE object_id = OBJECT_ID(N'd368f_t') ORDER BY column_id; SELECT @sql` liefert bei SQL Server 2025 `id;k;v;n;`, ebenso `DECLARE @i int = 1; SELECT @i += 2, @i *= 3; SELECT @i` → 9; der T-SQL- und der KSQL-Port (eigener Testserver) melden 102 „Unerwartetes Token '='“. Laravel (`SqlServerGrammar::compileDropDefaultConstraint`) entfernt so die Vorgabe-Bedingungen vor `dropColumn`; darum scheitert am KSQL-Port das Zurücknehmen einer Migration mit einer Spalte mit Vorgabe. **Erledigt 21.10.2026:** `SELECT @v += …` und die übrigen zusammengesetzten Zuweisungen (`-=`, `*=`, `/=`, `%=`, `&=`, `&#124;=`, `^=`) wie bei `SET`. Dabei gefunden und mitbehoben: ohne FROM rechneten mehrere Zuweisungen einer Auswahlliste alle mit dem alten Wert (`SELECT @i = @i + 2, @i = @i * 3` ergab 3, am Orakel 9; `SELECT @a = 5, @b = @a` ergab für `@b` den alten Wert, am Orakel 5) - jetzt von links nach rechts wie mit FROM. Test `AbnahmeBefundeTests.Zusammengesetzte_Zuweisung_in_SELECT`; Sabotage je Teil rot. Gefunden: Fehler 141 (Zuweisung und Ausgabe gemischt) fehlt ganz, Zeile D36.8o.✅
D36.8jBefund: Binärvergleich beachtet Nullen am EndeSQL Server 2025: `0x0102 = 0x010200` ist wahr, `0x0102 < 0x010200` falsch; eine Spalte `binary(16)` mit `0x05FF00800A` (aufgefüllt mit Nullen) trifft `WHERE k = 0x05FF00800A`, eine `varbinary(16)` mit `0x05FF00800A` trifft `WHERE v = 0x05FF00800A0000`. Der T-SQL- und der KSQL-Port (eigener Testserver) liefern ungleich bzw. kleiner und keine Zeile. Laravel sucht so über einen Binärwert (`where('kennung', $strom)`), am KSQL-Port ohne Treffer. Am KSQL-Zugang zu entscheiden: nach der Norm sind Binärketten verschiedener Länge wohl ungleich (zu prüfen); am T-SQL-Zugang gilt SQL Server. **Erledigt 21.10.2026 (Entscheid Marc: T-SQL wie SQL Server, KSQL nach der Norm):** am Orakel gemessen füllt SQL Server Binärwerte beim Vergleichen überall mit Nullen auf - `=`, `<`, IN, BETWEEN, NULLIF, `binary(n)` gegen einen kürzeren Wert, DISTINCT, COUNT(DISTINCT), GROUP BY, PARTITION BY, UNION, INTERSECT, JOIN, ORDER BY, der Primärschlüssel (`0x01` und `0x0100` sind 2627) und CHECKSUM; führende Nullen zählen. Der T-SQL-Zugang tut das jetzt ebenso (Dialektschalter `BinaerwerteAufgefuellt`). Der KSQL-Zugang bleibt nach der Norm (ISO/IEC 9075-2, 8.2: bei gleichem Anfang ist die kürzere Kette kleiner, wie PostgreSQL bytea), MariaDB/MySQL unverändert. Grenze: dieselbe Datenbank über mehrere Zugänge - ein eindeutiger Index, der am T-SQL-Zugang `0x01` und `0x0100` als Doppel abweist, nimmt beide über die anderen Zugänge an. Tests `AbnahmeBefundeTests.Binaerwerte_vergleichen_aufgefuellt_wie_SQL_Server` und `Binaerwerte_vergleichen_am_KSQL_Zugang_nach_der_Norm`; Sabotage (Schalter aus, GROUP BY ohne Zweig) rot.✅
D36.8kBefund: Prisma kennt den Spaltentyp `sql.Boolean` des TypeScript-Treibers nichtAm KSQL-Port ist eine `bit`-Spalte BOOLEAN (Benutzertyp 16, K33.17d); die mssql-Schicht von `@kuonen/ksql` nennt dann `recordset.columns[…].type === sql.Boolean`, einen Typ, den das Paket mssql nicht hat. `@prisma/adapter-mssql` ordnet die Spaltentypen mit `switch (col.type)` über die Typen von mssql zu und wirft für jeden anderen `UnsupportedNativeDataType`; Prisma meldet P2010 „Failed to deserialize column of type 'N/A'“, also scheitert jede Abfrage, die eine `bit`-Spalte liest (auch `create`, das die Zeile zurückliest). Am Orakel (T-SQL-Zugang) nicht betroffen. Gegenprobe im Bau: mit `Bit` statt `Boolean` in `typDerSpalte` laufen `create`, `findMany`, `aggregate`, `groupBy`, `update`, `upsert` und `$transaction` am KSQL-Port. Zu entscheiden (K33.17d betrifft die Leitung, hier geht es um die mssql-Schicht): `sql.Bit` melden wie mssql und BOOLEAN an anderer Stelle kennzeichnen (Test `mssql.test.ts` erwartet heute `sql.Boolean`), oder so lassen und dokumentieren. **Erledigt 21.10.2026 (Entscheid Marc: `sql.Bit` wie mssql):** die mssql-Schicht von `@kuonen/ksql` meldet für jede `bit`-Spalte, auch BOOLEAN (Benutzertyp 16), `recordset.columns[…].type === sql.Bit`; BOOLEAN nennt weiter `typeName`, `sql.Boolean` bleibt als Parametertyp. Test `mssql.test.ts` („KSQL-Typen …“, vorher rot); README angepasst. Die Prisma-Läufe am KSQL-Port sind nicht wiederholt (D36.8m).✅
D36.8lBefund: mehrteilige Spaltennamen in der Spaltenliste von INSERT`INSERT INTO [dbo].[d368f_t] ([dbo].[d368f_t].[name], [dbo].[d368f_t].[menge]) VALUES (N'a', 1), (N'b', 2)`, ebenso `INSERT INTO d368f_t (d368f_t.name, menge) …` und `INSERT INTO dbo.d368f_t (master.dbo.d368f_t.name) …`: SQL Server 2025 fügt ein, der T-SQL- und der KSQL-Port (eigener Testserver) melden 102 „'CloseParen' erwartet, aber '.'“. Prisma (`createMany`) schreibt die Spalten so. **Erledigt 21.10.2026:** am Orakel gemessen nimmt SQL Server 2025 jeden Vorsatz an und wertet nur den letzten Teil aus (`x.name`, `master.dbo.t.name`, `srv.db.dbo.t.name` gehen; der Vorsatz wird nicht geprüft). Der T-SQL- und der KSQL-Zugang lesen die Spaltenliste von `INSERT` jetzt ebenso (Schalter `EinfuegespaltenMitVorsatz`; PostgreSQL und MariaDB/MySQL unverändert). Test `AbnahmeBefundeTests.Mehrteilige_Spaltennamen_in_der_Einfuegeliste`; Sabotage: Schalter aus, beide Fälle rot. Gefunden: Fehler 264 (Spalte doppelt in der Liste) fehlt ganz, Zeile D36.8o.✅
D36.8mRest aus D36.8f(1) Kein Microsoft-Treiber als Vergleich für PHP (`pdo_sqlsrv`) und Python (pyodbc bzw. ODBC für Django); dort ist die Wahrheit der eigene Treiber am Orakel (für SQLAlchemy auch pymssql). (2) Die Läufe sind von Hand über `abnahme.sh` gestartet, kein Test wie `DockerPruefstandTests`; `go.sum`, `composer.lock` und die npm-Abhängigkeiten zweiter Stufe sind nicht festgeschrieben (entstehen im Bau). (3) Am KSQL-Port tragen 8120, 8121 und 8127 SQLSTATE HY000 statt eines Zustands der Klasse 42 (PostgreSQL meldet 42803). (4) Tabellenwertparameter: der JDBC-Treiber hat keine Schnittstelle dafür (mssql-jdbc: `SQLServerDataTable`), TypeORM und Prisma bieten keine; Ausgabeparameter bieten Laravel und Prisma nicht. (5) Nach D36.8g–l die betroffenen Läufe am KSQL-Port wiederholen (Spring `SimpleJdbcCall`, Laravel zurück und Binärvergleich, Prisma ganz); Prisma `createMany` zeigte in der Gegenprobe nach D36.8k erst D36.8l, dahinter ist nichts gemessen. (6) Einmal beobachtet und nicht wiederholt: `SimpleJdbcCall` über Metadaten lieferte am Orakel mit `jdbc:ksql://` `null` statt 3 (danach in vier Läufen 3). (7) Die D36.8-Abnahmen für Java (Hibernate/Flyway), TypeScript (TypeORM), Python (SQLAlchemy) und PHP (Doctrine) legen weiter Objekte `d368_<sprache>_` an und sind mit den Treiberänderungen aus D36.8f (Clob/Blob in Java) nicht erneut gelaufen. **Erledigt 21.10.2026** (mit Docker, Freigabe Marc; eigener KSQL-Testserver aus diesem Bau): (3) erledigt mit D36.8n. (5) Spring am KSQL-Port: `SimpleJdbcCall` über die Metadaten bricht seit D36.8h mit 201 ab (Spring ruft wegen des Produktnamens ohne Parameter, Entscheid D36.8g) - das Programm führt die Zeile jetzt als `~`, sonst gleich wie am Orakel; Laravel am KSQL-Port fand den Befund D36.8p (`OBJECT_NAME` einer Vorgabe), danach grün, einziger Unterschied der Binärvergleich nach der Norm (D36.8j); Prisma ganz (auch `createMany`, Bit-Spalten) am KSQL-Port und am Orakel gleich. (6) In diesem Lauf lieferte `SimpleJdbcCall` am Orakel mit `jdbc:ksql://` wieder 3. (7) wiederholt: Hibernate (KSQL-Port grün), TypeORM, SQLAlchemy, Django, Go/GORM an beiden Zielen gleich; Doctrine am KSQL-Port grün. Am Orakel scheitern Flyway („non-empty schema without history“) und die Migrationen von Doctrine („Unknown database type json“) mit beiden Treibern (auch mssql-jdbc) an 27 fremden Tabellen ohne Präfix in `master` (`T_…t` mit json-Spalten, `pr_*`, `tdsprobe*` u. a., angelegt 24.-26.09.) - eine Frage der Umgebung, nicht der Treiber. Rest in D36.8q.✅
D36.8nRest aus D36.8f (6): SQLSTATE der Klasse 42Am KSQL-Port tragen 8120, 8121 und 8127 SQLSTATE HY000 statt eines Zustands der Klasse 42. **Erledigt 21.10.2026:** `Normzustand` ordnet sie 42000 zu (syntax error or access rule violation; die Norm kennt in Klasse 42 keine Unterklasse, die Zuordnung der Klasse 42 bleibt wie bei 208/207 bei 42000, die Unterklassen von PostgreSQL führt dessen Modul). Dabei die übrigen häufigen Fehler ohne Zuordnung geprüft und je am Referenzcontainer PostgreSQL 17 belegt (von aussen, psql mit VERBOSITY verbose): 8118, 147, 130 (PG 42803), 4145 (42804), 145 (42P10), 116, 109, 110, 120, 121, 205 (42601), 1013 (42712), 4121, 402, 8117 (42883), 3701 (42P01), 137, 1087 (42703), 4108 (42P20) → 42000; 8179 (unbekannte vorbereitete Anweisung) → 26000 invalid SQL statement name (Norm, PG ebenso). Tests `KsqlSqlstateTests` (23 neue Zuordnungen, drei Abfragen am KSQL-Zugang; vorher 26 rot), gefiltert (Ksql, Sicherheitsrest, AbnahmeBefunde, Diagnose) 1226 grün, `Kuonen.Data.Ksql.Tests` 97 grün, Konformität samt Orakel 169 grün. Sabotage: 8120, 8121, 8127 aus der Zeile genommen, 6 rot.✅
D36.8oBefund: Fehler 264 und 141 fehlenGefunden bei D36.8l und D36.8i. (1) SQL Server 2025 meldet 264, wenn eine Spalte in der Spaltenliste von `INSERT` oder in `UPDATE … SET` zweimal vorkommt (`INSERT INTO t (dbo.t.name, name) …`, am Orakel gemessen); KuonenSQL nimmt es an. (2) `SELECT @i += 2, 5 AS x` (Zuweisung und Ausgabe in einer Auswahlliste) ist am Orakel 141; KuonenSQL meldet nichts. Beide Nummern kommen im Code nicht vor. **Erledigt 21.10.2026:** am T-SQL- und am KSQL-Zugang beim Übersetzen wie am Orakel (vom Stapel läuft nichts): eine Zielspalte zweimal in der Spaltenliste von `INSERT`, in `UPDATE … SET` (jede Form, auch mit Vorsatz, anderer Schreibweise oder `+=`) oder in `MERGE … UPDATE SET` ist 264; eine Auswahlliste, die Variablen zuweist und zugleich ausgibt, ist 141 (beide Reihenfolgen, mit FROM und `*`). Dabei gefunden und mitbehoben: `UPDATE … SET spalte += …` (ebenso `-=`, `*=` …) war ein Syntaxfehler (102). Dialektschalter `ZuweisungsregelnWieSqlServer` (MariaDB/MySQL und PostgreSQL unverändert). Tests `AbnahmeBefundeTests.Doppelte_Zielspalte_ist_264` und `Zuweisung_und_Ausgabe_gemischt_ist_141`; Sabotage je Teil rot.✅
D36.8pBefund: `OBJECT_NAME` kennt keine EinschränkungenBei der Wiederholung der Laravel-Abnahme am KSQL-Port (D36.8m (5)) scheiterte „migration zurück“ mit 5074: Laravel entfernt eine Spalte mit Vorgabe über `SELECT @sql += 'ALTER TABLE … DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) …; EXEC(@sql)`, und `OBJECT_NAME` lieferte für die Kennung einer Vorgabe NULL (an beiden Zugängen). **Erledigt 21.10.2026:** am Orakel gemessen nennen `OBJECT_NAME` und `OBJECT_SCHEMA_NAME` auch Vorgaben, Prüfungen, Fremd- und Primärschlüssel; KuonenSQL sucht eine unbekannte Kennung jetzt unter den Einschränkungen (`SystemCatalogProvider.Einschraenkung`, nur wenn sie kein anderes Objekt ist). Test `EinschraenkungsnamenTests` (beide Zugänge, samt dem Muster von Laravel; vorher rot), Sabotage rot; Laravel am KSQL-Port danach grün.✅
D36.8qRest aus D36.8m(1) Kein Microsoft-Treiber als Vergleich für PHP (`pdo_sqlsrv`) und Python (pyodbc bzw. ODBC für Django); dort ist die Wahrheit der eigene Treiber am Orakel. (2) Die Abnahmen laufen von Hand über `abnahme.sh`, kein Test wie `DockerPruefstandTests`; `go.sum`, `composer.lock` und die npm-Abhängigkeiten zweiter Stufe sind nicht festgeschrieben. (3) Tabellenwertparameter: der JDBC-Treiber hat keine Schnittstelle dafür (mssql-jdbc: `SQLServerDataTable`), TypeORM und Prisma bieten keine; Ausgabeparameter bieten Laravel und Prisma nicht. (4) ~~Fremde Tabellen in `master` des Orakels~~ **Erledigt 22.10.2026:** auf Entscheid von Marc die 27 Tabellen und 8 Prozeduren der Hand-Proben vom 24.-26.09. entfernt (in `master` liegt kein fremdes Objekt mehr); am Orakel danach `abnahme.sh java sqlserver eigen`, `java sqlserver microsoft` und `php sqlserver` je `abnahme: ok` (Flyway V1/V2, Hibernate, Doctrine-Migrationen hin und zurück, keine Tabellen übrig); die Zeilen von eigenem Treiber und mssql-jdbc gleich.⬜
D36.9MARS (SMP) an den TDS-PortsAm T-SQL- und am KSQL-Port bestätigt der Server im PRELOGIN die Option MARS (Wert 1 wie SQL Server 2025), wenn der Client sie verlangt, und spricht nach dem LOGINACK das Session Multiplex Protocol ([MS-SMP]): SYN, ACK, FIN, DATA, Fenster je Sitzung wie am Orakel gemessen (DATA ab 1, Fenster = gelesene Pakete + 4, nie mehr DATA als das Fenster des Clients). Je Sitzung eine eigene TDS-Schleife mit eigener Wache (ATTENTION je Sitzung); Anmeldung, Engine und Transaktion gehören der Verbindung, die Sitzungen führen nacheinander aus, und eine Antwort wartet im Ausgang ihrer Sitzung statt in der Ausführung - so läuft ein zweiter Befehl neben einem offenen, nicht lesenden Leser. BEGIN bzw. COMMIT über TM, solange eine andere Sitzung noch Antwort aussteht, ergibt 3988 bzw. 3981 (Text und Klasse wie am Orakel). Robustheit: SMID, Flags, Länge, unbekannte/doppelte Sitzung, Folgenummer, Fenster rückwärts, DATA über das angesagte Fenster, mehr als 256 Sitzungen oder mehr als 256 MiB zurückgehaltene Antwort trennen genau diese Verbindung. Tests: `MarsSmpTests` (20, SqlClient mit `MultipleActiveResultSets=True` an beiden Ports, mit und ohne TLS, 13 verfälschte Köpfe) und `Kuonen.Data.Tds` `MarsOrakelTests` (34, die 13 früheren KS1-Zwillinge laufen jetzt gegen KuonenSQL). Ohne MARS unverändert.✅
D36.9aRest aus D36.9(1) Gegendruck: hält ein Leser seine Antwort an, puffert der Server sie bis 256 MiB und trennt dann die Verbindung; SQL Server hält stattdessen die Ausführung an. (2) Regeln des Orakels unter MARS noch offen: 3997 (BEGIN TRAN in einem Stapel, während eine andere Anfrage läuft: am Orakel zurückgenommen), 3989 (Anfrage mit dem Deskriptor einer nach 3981 zurückgenommenen Transaktion). (3) `KILL QUERY` erreicht nur die Wache der Leitung, nicht die einer SMP-Sitzung; ein offener Livestrom (S12.12c) hält die Ausführung der anderen Sitzungen an, bis er abgebrochen wird. (4) Paketgrössen über 16384 nimmt der Server unter MARS an (SQL Server 2025 trennt dann). (5) Am Orakel schickt SQL Server bei ATTENTION vor der Antwort ein ACK und beim abgebrochenen WAITFOR ein DONE mit DONE_ERROR vor DONE_ATTN; das FIN des Servers auf das FIN des Clients ist nur aus der Spezifikation (SqlClient schliesst die Leitung, bevor es ankommt). (6) `Kuonen.Data.Ksql` hat keinen MARS-Schalter. **Erledigt 22.10.2026:** (4) am Orakel gemessen (SqlClient 6.1.6, 18 Paketgrössen von 512 bis 32767, ohne TLS, mit TLS, mit MARS): SQL Server trennt eine MARS-Verbindung mit grossen Paketen nicht (auch ein 60 000-Zeichen-Parameter und 70 000 Zeichen zurück gehen) - die Annahme aus D36.9 war falsch. Gemessen wurde dafür die Aushandlung: höchstens 192 Byte unter einem Vielfachen von 8192 oder genau eines → das Vielfache weniger 192 (8192 → 8000, 16384 → 16192, 32767 → 32576), sonst auf 8 abgerundet (16385 → 16384), mit TLS über die ganze Verbindung höchstens 16384; `sys.dm_exec_connections.net_packet_size` zählt mit TLS 170, unter MARS 16 Byte dazu. KuonenSQL bestätigte jede verlangte Grösse und nannte `net_packet_size` nicht; beides jetzt wie am Orakel (`TdsLogin7.AusgehandeltePaketgroesse`, `Sitzungsdaten.Netzpaketgroesse`). Test `PaketgroesseTests` (15 an beiden Zugängen, vorher alle rot; Sabotage ohne die 192-Regel 8 rot); `TdsLangeWerteTests` erwartet bei 16384 jetzt 16192. (6) `Kuonen.Data.Ksql` hat den MARS-Schalter schon - geerbt vom TDS-Treiber (`MultipleActiveResultSets` wie bei SqlClient); Test `VerbindungTests.MARS_gilt_auch_am_KSQL_Zugang` mit zwei offenen Lesern (ARRAY, INTERVAL; war grün, Sabotage MARS aus rot). Rest in D36.9b.✅
D36.9bRest aus D36.9a(1) Gegendruck: hält ein Leser seine Antwort an, puffert der Server sie bis 256 MiB und trennt dann die Verbindung; SQL Server hält stattdessen die Ausführung an. (2) Regeln des Orakels unter MARS noch offen: 3997 (BEGIN TRAN in einem Stapel, während eine andere Anfrage läuft: am Orakel zurückgenommen), 3989 (Anfrage mit dem Deskriptor einer nach 3981 zurückgenommenen Transaktion). (3) `KILL QUERY` erreicht nur die Wache der Leitung, nicht die einer SMP-Sitzung; ein offener Livestrom (S12.12c) hält die Ausführung der anderen Sitzungen an, bis er abgebrochen wird. (4) Am Orakel schickt SQL Server bei ATTENTION vor der Antwort ein ACK und beim abgebrochenen WAITFOR ein DONE mit DONE_ERROR vor DONE_ATTN; das FIN des Servers auf das FIN des Clients ist nur aus der Spezifikation (SqlClient schliesst die Leitung, bevor es ankommt). (5) Nicht gemessen: die Paketgrösse bei TDS 8 (`Encrypt=Strict`) und mit einem Client, der eine Grösse unter 512 verlangt. **Erledigt 23.10.2026** (gemessen 21.10. am Orakel mit SqlClient 6.1.6 und `MultipleActiveResultSets=True`, Weiterleiter mit Mitschnitt; umgesetzt im Worktree d36-9a, nach main übertragen): (1) Gegendruck: stehen 4 Pakete im Ausgang einer SMP-Sitzung, wartet ihre Ausführung auf den Leser (sys.dm_exec_requests: suspended, ASYNC_NETWORK_IO, wie am Orakel); will eine andere Sitzung ausführen, gibt sie den Weg frei, indem sie den Rest ihres Stapels in den Ausgang legt (keine Verklemmung). (2) 3997: unter MARS wird eine Transaktion, die ein Stapel oder RPC begonnen hat und die an seinem Ende noch läuft, zurückgenommen (auch ohne anderen Leser, auch nach SET IMPLICIT_TRANSACTIONS); nach 3981 ist jede neue Anfrage 3989, bis die andere endet. (3) KILL QUERY und KILL treffen die Wache der ausführenden SMP-Sitzung; ein offener Livestrom gibt die Ausführung frei und gilt weiter als laufende Anfrage. (4) ATTENTION: ACK sofort, dann DONE mit DONE_ERROR und DONE_ATTN als eigene Nachrichten; das FIN des Servers (SeqNum = letztes DATA, Fenster = empfangene DATA + 4) am eingeschleusten FIN bestätigt. Dazu die Paketgrösse feiner: die Abtastung von 512 bis 32768 (mit und ohne MARS gleich) zeigt die Rundung auf 4 statt 8 (4095 → 4092, 16191 → 16188) und 32768 → 32764; die Messwerte aus D36.9a passen auch zu dieser Regel, TLS und `net_packet_size` bleiben. Unter MARS trägt ein DATA bis zur ausgehandelten Grösse, ein Byte mehr trennt. Tests: `MarsSmpRestTests` (30, mit der Paketgrössen-Theorie), `MariaDbKillTdsTests` (+1), `KsqlMarsTests` (3); Sabotage 3997 rot, die Fassung von main bei 4095/16191/32768 rot. Ohne MARS nicht langsamer (`docs/leistung.md` §28). (5) TDS 8 und Grössen unter 512 bleiben offen. Rest in D36.9c.✅
D36.9cRest aus D36.9b(1) Kein Verschränken mitten in einer Anweisung: will eine andere Sitzung ausführen, während ein Leser angehalten ist, legt die angehaltene Sitzung den Rest ihres Stapels in den Ausgang (höchstens 256 MiB, dann Trennung) und führt auch Anweisungen hinter dem SELECT aus; am Orakel bleibt die Anfrage auf ASYNC_NETWORK_IO stehen, und ein INSERT dahinter läuft erst, wenn der Leser weiterliest (bräuchte einen Ausführungskontext je Anfrage statt des Stapelzustands der Engine). (2) Unter MARS meldet SQL Server weder Beginn noch Rücknahme einer Stapel-Transaktion; KuonenSQL meldet den Beginn (ENVCHANGE 8) und schickt vor dem DONE nach 3997 ENVCHANGE 10. (3) RPC mit `BEGIN TRAN`: am Orakel 266 vor 3997, KuonenSQL nur 3997 (266 fehlt dem Port auch ohne MARS). (4) DONE_ERROR beim Abbruch trägt curCmd 0xC1 nur bei angefangener gestromter Menge (am Orakel auch für ein fertiges SELECT vor einem WAITFOR, dessen DONE SQL Server zurückhält); ATTENTION zwischen zwei Anfragen und beim gesammelten Senden (PR27.15o) unter MARS nicht gemessen. (5) `Kuonen.Data.Tds` (`TdsSmp.MaxPaketgroesse` = 16384, MultiSQLManager/docs/umsetzung.md) beruht auf der Fehldeutung aus D36.9: SQL Server 2025 nimmt unter MARS jede ausgehandelte Grösse. (6) Nicht gemessen: die Paketgrösse bei TDS 8 (`Encrypt=Strict`) und mit einem Client, der eine Grösse unter 512 verlangt. (7) Abgleich am Orakel: die feinere Paketgrössen-Regel (Rundung auf 4, 32768 → 32764) zusammen mit TLS in einem Lauf von `PaketgroesseTests` bestätigen (die beiden Messungen liefen getrennt). **Erledigt 23.10.2026** (auf dem Mac, ohne Orakel; nach den Messungen aus D36.9b): (2) Unter MARS verschweigt ein Stapel, der ohne offene Transaktion beginnt, seine Transaktionswechsel (`RahmenOhneTransaktionswechsel`: kein ENVCHANGE 8, 9, 10; nach 3997 kein ENVCHANGE 10); die Transaktion der Schnittstelle meldet sich wie bisher. (3) 266 (Klasse 16, Zustand 2) jetzt auch nach `EXEC('…')`, nach `EXEC sp_executesql` und am Ende eines RPC mit `sp_executesql` (`PruefeTrancountNachExecute`); unter MARS folgt 3997. (4) ATTENTION nach einem fertigen SELECT: was vor dem Abbruch fertig war, geht noch hinaus, das DONE der letzten Abfrage fällt weg und ihr curCmd 0xC1 steht im DONE_ERROR. (5) `Kuonen.Data.Tds`: `TdsSmp.MaxPaketgroesse` entfällt, LOGIN7 verlangt unter MARS die volle Grösse (am KSQL-Zugang 32767 → 32576, `net_packet_size` 32592). Tests: `MarsSmpRestTests` (+3), `ProzedurTrancountFehler266Tests` (+3), `KsqlMarsTests` (+1), `MarsTests` im TDS-Treiber (angepasst); Sabotage je Punkt rot. Offen bleiben (1), (6) und (7) sowie die Abgleiche am Orakel: Rest in D36.9d.✅
D36.9dRest aus D36.9c(1) Kein Verschränken mitten in einer Anweisung: will eine andere Sitzung ausführen, während ein Leser angehalten ist, legt die angehaltene Sitzung den Rest ihres Stapels in den Ausgang (höchstens 256 MiB, dann Trennung) und führt auch Anweisungen hinter dem SELECT aus; am Orakel bleibt die Anfrage auf ASYNC_NETWORK_IO stehen (bräuchte einen Ausführungskontext je Anfrage statt des Stapelzustands der Engine). (2) Abgleich am Orakel: ENVCHANGE 9/10 unter MARS bei COMMIT/ROLLBACK im selben Stapel (D36.9c lässt sie weg, gemessen ist nur Beginn und 3997); 266 nach `EXEC sp_executesql N'BEGIN TRAN'` als Stapel und ob der Stapel danach abbricht (gemessen nur `EXEC('BEGIN TRAN')` und der RPC); `Procedure` im 266 des RPC; die Zeilen vor einem Abbruch bei mehreren Anweisungen. (3) Der Text von 266 ist deutsch (wie bisher bei Prozeduren), am Orakel englisch. (4) ATTENTION zwischen zwei Anfragen und beim gesammelten Senden (PR27.15o) unter MARS nicht gemessen. (5) Paketgrösse bei TDS 8 (`Encrypt=Strict`) und unter 512 nicht gemessen; die Regel aus D36.9b (Rundung auf 4, 32768 → 32764) mit TLS in einem Lauf von `PaketgroesseTests` am Orakel bestätigen. **Erledigt 23.10.2026 (ohne (1)):** am Orakel gemessen (`KuonenSQL/tools/mars/d369d-messung.cs`). ENVCHANGE 8/9/10 bei COMMIT, ROLLBACK, SAVE und IMPLICIT_TRANSACTIONS im MARS-Stapel: keine (wie D36.9c). 266 beendet den Stapel nicht (auch nach Prozeduren, mit XACT_ABORT ON; die frühere Annahme kam aus `ExecuteScalar`), OUTPUT und Rückgabestatus sind gesetzt, Text englisch, Zeile 0, im TRY unbestätigbar (XACT_STATE -1, 3998). Beim Abbruch trägt das DONE_ERROR den curCmd der letzten Anweisung (INSERT 0xC3, PRINT 0xF7 …); auch ohne MARS DONE_ERROR und danach DONE_ATTN (0xFD) als eigene Nachricht; ATTENTION ohne Anfrage: kein ACK, DONE_ATTN 0xFD; Abbruch eines ausgelagerten Lesers ohne Fehler 50000. Paketgrösse mit TLS und unter 512 wie am Orakel. Nebenbefund: SELECT … INTO ohne FROM beginnt unter IMPLICIT_TRANSACTIONS eine Transaktion. Rest: D36.9e.✅
D36.9eRest aus D36.9d(1) Kein Verschränken mitten in einer Anweisung: will eine andere Sitzung ausführen, während ein Leser angehalten ist, legt die angehaltene Sitzung den Rest ihres Stapels in den Ausgang (höchstens 256 MiB, dann Trennung) und führt auch Anweisungen hinter dem SELECT aus; am Orakel bleibt die Anfrage auf ASYNC_NETWORK_IO stehen. Braucht einen Ausführungskontext je Anfrage statt des Stapelzustands der Engine (rund 175 Instanzfelder in 127 Teildateien) - eigener Umbau. (2) TDS 8 (`Encrypt=Strict`) am Orakel nicht messbar: der Orakel-Container trennt beim TLS-Handschlag; erst mit einem Zertifikat für TDS 8 am Container. (3) Abbruch mitten im Senden einer gesammelten Antwort (PR27.15o): das DONE_ERROR trägt fest curCmd 0xC1 (die Menge, die gerade hinausgeht); bei einem Schnitt zwischen zwei Anweisungen nicht gemessen. (4) SESSIONSTATE (0xE4) nach dem Anlegen einer temporären Tabelle sendet KuonenSQL nicht (am Orakel im Mitschnitt von D36.9d gesehen). (5) Text von 3998 ist deutsch, am Orakel englisch. **Erledigt 23.10.2026 (Punkt 1):** am Orakel gemessen (`KuonenSQL/tools/mars/d369e-messung.cs`, M1–M10) und umgesetzt nach `docs/superpowers/plans/2026-10-23-d36-9e-anfragekontext.md`. Der Zustand einer Anfrage liegt im `Anfragekontext` (rund 100 Felder der Engine); wartet eine Anfrage unter MARS beim Senden aus dem Spool oder beim Zwischenstand an einer Anweisungsgrenze auf den Client und will eine andere Sitzung ausführen, parkt sie (Kontext und Stapelteil der Sitzung abgelegt), die andere läuft dazwischen, danach geht sie weiter - Reihenfolge B vor A2 wie am Orakel, auch geschachtelt (C, B2, A2). SET, USE, @@ERROR, @@IDENTITY, @@FETCH_STATUS gelten je Anfrage, wer endet, schreibt zurück; sys.dm_exec_requests zeigt geparkte und laufende Anfrage (request_id ab 2). Im Protokoll gilt das Parken als Wartepunkt (PR27.14b). Entscheidungen: in einer Transaktion wird nicht geparkt (E1), Schreiben in die gelesene Tabelle wartet nicht (E2), unter MARS strömen Mengen über den Spool (E3, leistung.md §30). Rest: D36.9f.✅
D36.9fRest aus D36.9e(1) TDS 8 (`Encrypt=Strict`) am Orakel nicht messbar: der Orakel-Container trennt beim TLS-Handschlag; erst mit einem Zertifikat für TDS 8 am Container. (2) Abbruch mitten im Senden einer gesammelten Antwort (PR27.15o) und nach einem grossen Zwischenstand (D36.9e): das DONE_ERROR trägt curCmd 0xC1; bei einem Schnitt zwischen zwei Anweisungen nicht gemessen. (3) SESSIONSTATE (0xE4) nach dem Anlegen einer temporären Tabelle sendet KuonenSQL nicht. (4) Text von 3998 ist deutsch, am Orakel englisch. (5) Bewusste Unterschiede zum Orakel unter MARS (Entscheidungen E1, E2 in D36.9e): in einer Stapel-Transaktion wird nicht verschränkt (am Orakel wartet die Anfrage daneben bis zum Zeitlimit), und eine Anfrage daneben, die die gerade gelesene Tabelle ändert, wartet nicht. (6) Der Spool unter MARS kostet beim Strömen grosser Mengen rund 65 % (leistung.md §30); prüfen, ob eine Menge direkt strömen und nur der Rest in den Ausgang gehen soll. (7) Das Protokollregister (PR27.18g2) ist je Engine geführt: endet B, während A geparkt ist, entfernt B den Eintrag, obwohl der Stapel von A noch offen ist (Protokollsicherung in dieser Zeit prüfen). (8) Nebenbefund: `SET DATEFORMAT dmy` wirkt bei `CONVERT(datetime, '01/02/2020')` nicht (am Orakel 2, KuonenSQL 1), auch ohne MARS.⬜
Meilenstein 37 (optional): InfluxDB-Zugang — Line Protocol, InfluxQL und SQL, Fassungen 1 bis 3 (58/60)
PunktTitelBeschreibungStand
IX37.1Referenzorakel InfluxDB 1, 2 und 3Drei Container (`influxdb:1.11`, `influxdb:2.7`, `influxdb:3-core`) nur auf 127.0.0.1, gestartet und entfernt über `dist\orakel-ref.ps1 -Dialekt influx1`, `influx2`, `influx3` (Download erst bei Bedarf, nach den Messungen wieder weg). Messwerkzeug `tools/influx` (Python, nur Standardbibliothek: HTTP-Aufrufe, Antwort roh als Fixture); Beispielpunkte mit allen Feldtypen, Sonderzeichen in Tags und Messungsnamen und allen Genauigkeiten. **Erledigt 23.10.2026:** `orakel-ref.ps1 -Dialekt influx1&#124;influx2&#124;influx3` (Wegwerf-Container mit Zufallszugang, `-Behalten`/`-Entfernen`), Messwerkzeug `tools/influx/influx-orakel.py`; erste Messreihe `Influx/Fixtures/grundlagen-v1..v3` (40/48/50 Anfragen) mit HERKUNFT.md.✅
IX37.2Zugangsmodul InfluxDBZugang `Influx` als Modul über die gemeinsamen Nahtstellen (`docs/neuer-dialekt.md`, `DialektArchitekturTests`): eigener HTTP-Hörer (Vorgabe aus; `KUONENSQL_INFLUX_PORT`, üblich 8086 für 1.x/2.x, `KUONENSQL_INFLUX3_PORT`, üblich 8181 für 3.x), TLS wie die übrigen Zugänge, `Content-Encoding: gzip`, Kopfzeilen `X-Influxdb-Version`/`X-Influxdb-Build` je Fassung; `/ping` (204, auch `verbose`), `/health`, `/metrics` (Prometheus-Text, Grundwerte). Anmeldung je Fassung: 1.x `u`/`p` in der Adresse und Basic, 2.x/3.x `Authorization: Token …` bzw. `Bearer`; Benutzer und Token in der gemeinsamen Benutzerverwaltung (M26). **Erledigt 23.10.2026:** Modul `Influx/` (Einstellungen, Dienst, HTTP-Weg) mit Hörern `influx` (1.x/2.x, `KUONENSQL_INFLUX_FASSUNG`) und `influx3` in der Zugangsverwaltung (`sp_kuonensql_zugang`, Lizenzmerkmal `sql:protocol.influx`); `/ping`, `/health`, `/ready`, `/metrics`, `/debug/vars`, CORS, gzip (1.x) und die Anmeldung je Fassung wie gemessen (`zugang-v1..v3`). HTTP-Leser und Anmeldeprüfung samt Sperre aus GraphQL nach `Protocol/` verschoben (ein Weg für alle HTTP-Zugänge). Eigene Token → IX37.10/IX37.12, Rest → IX37.2a.✅
IX37.2aRest aus IX37.2`GET /api/v2` (Wegeliste wie 2.7), `/debug/pprof` (2.x) und die Prometheus-Werte unter den Namen des Orakels, soweit KuonenSQL sie hat (`/metrics` liefert bisher eigene Grundwerte `kuonensql_influx_*`); Bearer-JWT mit gemeinsamem Geheimnis wie 1.x (`shared-secret`); die Oberfläche von 2.x unter `/` gibt es nicht (404 statt HTML, gewollt). Erledigt 2026-10-24, gemessen an 1.11, 2.7 und 3-core. - `GET /api/v2` (auch `/api/v2/`, ohne Anmeldung) liefert die Wegeliste von 2.7 wörtlich (Fixture wege-v2). - `/metrics` hat die Familien unter den Namen des Orakels, soweit KuonenSQL Werte hat: - 1.x/2.x: go_goroutines, go_info, go_memstats_*_bytes und go_threads, gerechnet aus der Laufzeit von .NET, dazu process_*. - 2.x zusätzlich: influxdb_*_total aus dem Katalog, influxdb_info, influxdb_uptime_seconds{id} und http_api_requests_total{handler,method,path,response_code,status,user_agent}. - 3.x: http_requests_total{method,method_path,path,status} und process_start_time_seconds mit den Angaben von 3.x. - Bearer-JWT mit gemeinsamem Geheimnis an 1.x (`KUONENSQL_INFLUX_SHARED_SECRET`): HS256/384/512 mit den Meldungen und der Reihenfolge des Orakels. Ohne Geheimnis bleibt es bei `bearer auth disabled`. Bewusst nicht gebaut: `/debug/pprof`, das sind Laufzeitprofile von Go. Auch die Oberfläche von 2.x unter `/` bleibt 404. Tests InfluxZugangTests (Metriken je Fassung, Wegeliste, JWT), Lauf Influx, Flux, Beispielkatalog 171/171. Sabotage ok (Unterschrift nicht geprüft → rot).✅
IX37.2bBefund: TLS am Influx-Port nur auf VerlangenBei der Probe mit Grafana (23.10.2026): der Server erzeugt für TDS immer ein selbstsigniertes Zertifikat, der Influx-Zugang übernahm es und sprach HTTPS — Grafana, Telegraf und die Clients sprechen ohne Angabe `http://` (InfluxDB: `https-enabled = false`), die Verbindung brach ab. TLS nur mit `KUONENSQL_INFLUX_TLS=an`. **Erledigt 23.10.2026:** `InfluxEinstellungen.AusUmgebung` nimmt das Zertifikat nur mit `KUONENSQL_INFLUX_TLS=an&#124;1&#124;true`. Probe mit `grafana/grafana:11.6.0` gegen einen Server aus dem Zweig (Fassung 1): „Save & test“ OK, Zeitreihe je Host, alle Abfragen des Abfrage-Editors beantwortet. Test `TLS_nur_auf_Verlangen_wie_InfluxDB`.✅
IX37.3Datenmodell und AbbildungMessung, Tags, Felder und Zeit auf Tabellen wie oben; Serienschlüssel (Messung und sortierte Tags) als Index; ein Feld ohne Wert in einem Punkt bleibt NULL; Typkonflikt eines Felds mit der Meldung des Orakels (1.x/2.x `field type conflict`, 3.x eigene) statt stiller Umwandlung; gleiche Serie und Zeit führt die Felder zusammen (am Orakel messen, ob ersetzt oder ergänzt); neue Spalten ohne Sperre der ganzen Tabelle; Katalogeintrag, damit die Tabellen in `sys.tables`, `information_schema` und im Studio als Zeitreihen erkennbar sind; Lesen und Schreiben derselben Zeilen über T-SQL, KSQL und PostgreSQL (M26). **Erledigt 23.10.2026:** `Influx/InfluxAblage.cs`: jede Datenbank von KuonenSQL ist eine Influx-Datenbank, Richtlinie = Schema (`autogen`), Messung = Tabelle mit `_serie` (Serienschlüssel) und `time` (bigint ns) als Primärschlüssel, berechneter `_zeit datetime2(7)`, Tags `nvarchar(max) BIN2`, Felder typisiert (`u` → `decimal(20,0)`); Katalog `influx.richtlinien/messungen/felder` für Gross-/Kleinschreibung und lange Namen; Zusammenführen gleicher Serie und Zeit per `MERGE` in einer Transaktion je Anfrage; Typkonflikte und `time` je Fassung wie gemessen; Lesen/Ändern über T-SQL, KSQL, PostgreSQL, `JOIN` mit Stammdaten. Engine-Befunde dabei → IX37.3a, IX37.3b; Reste → IX37.3c.✅
IX37.3aBefund: DATEADD mit Nanosekunden und in berechneten SpaltenAm Orakel (SQL Server 2025, 23.10.2026): `DATEADD(NANOSECOND, n, …)` rundet auf 100 ns, die Hälfte weg von der Null (KuonenSQL schnitt ab); Einheiten, die nicht zum Typ passen (date ohne Uhrzeitteile, time ohne Datumsteile, datetime/smalldatetime/Text ohne Mikro- und Nanosekunden), sind 9810 `The datepart … is not supported by date function dateadd for data type …` (KuonenSQL rechnete); in einer berechneten Spalte galt der Datumsteil von `DATEADD`/`DATEDIFF` … als Spaltenname (207). Gefunden beim Bau der Spalte `_zeit` (IX37.3). **Erledigt 23.10.2026:** Rundung `NanosekundenAlsTicks` (auch für Weitzeit), Prüfung Einheit/Typ mit 9810 und dem Wortlaut des Orakels, `Modulverweise` übergeht den Datumsteil der Funktionen mit Datumsteil (`Ausdrucksplan.MitDatumsteil`). `DateaddEinheitenTests` (23).✅
IX37.3bBefund: ALTER TABLE ADD mit COLLATEAm Orakel (SQL Server 2025, 23.10.2026): `ALTER TABLE t ADD s nvarchar(max) COLLATE Latin1_General_BIN2` legt die Spalte mit dieser Sortierung an (`sys.columns.collation_name`, Vergleich und `ORDER BY` binär); KuonenSQL verwarf die Angabe still (bei `CREATE TABLE` galt sie schon). Gefunden beim Bau der Tag-Spalten (IX37.3). **Erledigt 23.10.2026:** der Spaltenleser von `ALTER TABLE … ADD` liest `COLLATE` (ausser im Dialekt MySQL, der es schon über `CHARACTER SET`/`COLLATE` kennt) und gibt sie der Textspalte. `AlterAddSortierungTests`.✅
IX37.3cRest aus IX37.3Schreiben derselben Datenbank läuft hinter einem Riegel je Datenbank, und der Katalog wird je Anfrage gelesen — für die Schreibrate (M34) Katalog im Speicher mit Abgleich und feinere Sperre (neue Spalten ohne die ganze Datenbank zu halten); ±Unendlich (3.x nimmt `1e400` an) wird bisher NULL statt des Werts; ein Tag und ein Feld gleichen Namens in 1.x/2.x liegen in zwei Spalten (Abfrage wie das Orakel mit IX37.6); Markierung der Tabellen als Zeitreihen im Studio (mit IX37.17). Erledigt 2026-10-24, gemessen an 1.11 (gleichname-v1) und 3-core (unendlich-v3). Tag und Feld gleichen Namens laufen wie das Orakel und sind jetzt belegt (InfluxqlV1Tests.Tag_und_Feld_gleichen_Namens_…, alle 12 Fälle): - `x` ist das Feld, `x::tag` gibt nichts; - `*` zeigt `x` und `x_1`; - `WHERE x = 'a'` gibt nichts, `GROUP BY x` gruppiert nach dem Tag. Das Schreiben von ±Unendlich in 3.x ist belegt: `1e400` geht, `inf`/`NaN` als Wort nicht (InfluxApiV3Tests.Unendlich_schreiben_…). Reste als eigene Zeilen: - IX37.3d: ±Unendlich in float im Kern; der Wert landet bisher als NULL. - IX37.3e: Schreibrate mit Katalog im Speicher und feinerer Sperre, mit M34. - Die Markierung als Zeitreihe im Studio steht bei IX37.17c.✅
IX37.3dKern: ±Unendlich und NaN in float wie PostgreSQLRest aus IX37.3c (gemessen unendlich-v3). 3.x schreibt `1e400` und `-1e400` als ±Unendlich. Gelesen zeigt JSON `null`, CSV `inf`/`-inf`; sum, max und min ergeben `null`. In KuonenSQL wird der Wert NULL. `float` im Kern kennt kein ±Unendlich, wie bei SQL Server; PostgreSQL kennt `Infinity`, MySQL nicht. Das Protokoll hält Stapel als Text, darum reicht kein Kniff im Influx-Teil. Zu entscheiden (Marc): soll `float` im Kern ±Unendlich und NaN halten können, wie PostgreSQL? Dann gehören dazu: - das Lesen als Text (`'Infinity'`) über PostgreSQL und KSQL; - die Ausgabe über TDS (SQL Server lehnt den Wert ab) und MariaDB; - Vergleich und Sortierung wie PostgreSQL. Erledigt 2026-10-24 (Entscheid Marc: ja, wie PostgreSQL), gemessen an postgres:17. `float` hält ±Unendlich und NaN. PostgreSQL: - liest `Infinity`, `inf` und `NaN` mit Vorzeichen und in jeder Schreibung; - gibt `Infinity`, `-Infinity` und `NaN` aus; - `'1e400'` meldet „is out of range for type double precision“. Für alle Dialekte gilt: - NaN ist gleich NaN und grösser als alles, die Reihenfolge ist −∞ < Zahlen < +∞ < NaN; - ∞ − ∞ ergibt NaN, ein Überlauf aus endlichen Werten bleibt ein Fehler; - sum, avg und max mit NaN ergeben NaN; `Infinity::int` meldet „integer out of range“. Über die Zugänge: - TDS liefert die IEEE-Zahl (SqlClient liest ∞ und NaN), als Text `Infinity`; - MariaDB zeigt `inf`, `-inf` und `nan` wie C; - T-SQL und KSQL lesen Text mit Sonderwert nicht (8114 wie SQL Server). InfluxDB 3 schreibt `1e400` als Wert: nach dem MERGE ein Stapel im Dialekt von PostgreSQL in derselben Transaktion; das Protokoll spielt ihn in diesem Dialekt ab. Tests: PgUnendlichTests (alle Fälle des Orakels), FloatSonderwerteKreuzTests (TDS, MariaDB, PostgreSQL, KSQL), InfluxApiV3Tests.Unendlich_schreiben_… (ohne U6, U7 wie W20). Lauf Pg, Maria, Float, Zahl, Influx, Ksql, Vergleich 6319/6319. Sabotage ok (NaN-Ordnung aus → 2 rot; Influx-Nachtrag aus → rot).✅
IX37.3eSchreibrate: Katalog im Speicher, feinere Sperre (mit M34)Rest aus IX37.3c, mit M34 (Leistung erst nach dem Bau). Das Schreiben derselben Datenbank läuft hinter einem Riegel je Datenbank, und der Katalog (Messungen, Spalten) wird je Anfrage gelesen. Für die Schreibrate gehört der Katalog in den Speicher, mit Abgleich bei Änderungen über die SQL-Zugänge, und eine feinere Sperre (neue Spalten ohne die ganze Datenbank zu halten). Messen gegen Telegraf und die Schreibrate von 1.x/2.x am Orakel. **Erledigt 25.10.2026:** Die Bremse war das MERGE (es las je Rumpf die ganze Messung); es sucht jetzt bei einem ON aus Gleichheiten über das Schlüsselverzeichnis einer Zahlenspalte (time). Dazu der Katalog im Speicher mit Abgleich über den Änderungsstand der Katalogtabellen (auch bei Änderungen über SQL) und eine Lese-/Schreibsperre je Datenbank mit Sperre je Messung beim Einfügen. Gemessen (10 Punkte je Rumpf): ein Schreiber 431 → 3148 Punkte/s, gleichbleibend über die Grösse; vier Schreiber 1377 → 3682 Punkte/s. Vergleich mit Telegraf/1.x/2.x am Orakel im Dauerlauf (IX37.17c).✅
IX37.4Line-Protocol-LeserVollständige Syntax: Messung, Tag-Satz, Feld-Satz, Zeitstempel; Maskierung von Komma, Leerzeichen, Gleichheitszeichen, Anführungszeichen und Rückstrich je Stelle; Feldtypen `i`, `u`, Gleitkomma (auch `1e3`), `"Text"`, Wahrheitswerte in allen Schreibweisen; Genauigkeit `ns`/`us`/`ms`/`s` (2.x/3.x auch die langen Namen), fehlender Zeitstempel = Serverzeit; Kommentarzeilen, leere Zeilen, CRLF. Einfügen stapelweise (eine Transaktion je Anfrage), Teilfehler wie das Orakel (1.x 400 `partial write` mit Zeilennummer, 3.x `accept_partial`). Fuzzing des Lesers wie PR27.7. **Erledigt 23.10.2026 (Leser):** `Influx/LineProtokoll.cs` liest je Fassung wie gemessen (`lp-v1..v3`: 48 Fehler- und 20 Grenzfälle, `lp2-v1..v3`: Fluchtzeichen in Namen, mehrere Fehler je Anfrage, Kommentare, CR, Tabulatoren): 1.x/2.x mit den Regeln und Meldungen des Go-Lesers, 3.x mit denen des eigenen Lesers samt seiner Zeilennummern; Genauigkeit als Nanosekunden je Einheit, 3.x automatisch. Fuzzing 3 × 20 000 Rümpfe. Einfügen je Anfrage und Teilfehler mit dem Schreibweg → IX37.5.✅
IX37.51.x: Schreib-API`POST /write?db=&rp=&precision=&consistency=` mit Line Protocol im Rumpf, 204 bei Erfolg, Fehler als JSON `{"error": …}` mit dem Statuscode des Orakels (400, 401, 404 `database not found`, 413, 500); unbekannte Aufbewahrungsrichtlinie; Grösse des Rumpfs begrenzt (`max-body-size`). **Erledigt 23.10.2026:** `Influx/InfluxSchreiben.cs`: `POST /write` mit `db`, `rp`, `precision` (`n`/`ns`, `u`, `ms`, `s`, `m`, `h`), `consistency` (angenommen; ein Knoten), gzip mit Grenze auch entpackt (Schutz vor gepackter Bombe), 204/400/404/405/413/500 und `X-Influxdb-Error` wie gemessen; Teilschreiben wie 1.x (Lesefehler verwerfen ihre Zeilen, `partial write: … dropped=N`, Konflikt im Rumpf `field type conflict`), eine Transaktion je Anfrage. Alle 24 Schreibfälle von `grundlagen-v1` und die 88 Fälle von `lp-v1`/`lp2-v1` über HTTP gleich wie 1.11. Rechte `WRITE` → IX37.8.✅
IX37.61.x: InfluxQL-Abfragen`GET`/`POST /query?db=&q=&epoch=&chunked=&chunk_size=&pretty=&params=` mit mehreren Anweisungen je Aufruf, Antwort `{"results":[{"statement_id",…,"series":[{"name","tags","columns","values"}]}]}` exakt wie das Orakel (Reihenfolge, Zeit als RFC3339 oder `epoch`, Ganzzahlen ohne Nachkommastellen), CSV mit `Accept: application/csv`. Sprache: `SELECT` mit Feldern, Tags, `*`, `::field`/`::tag`, Arithmetik; `FROM` mit mehreren Messungen, regulären Ausdrücken und Unterabfrage; `WHERE` mit Zeitbereichen (`now()`, Dauern wie `1h`, `-7d`, RFC3339-Texte), Tag-Vergleichen, `=~`/`!~`, `AND`/`OR`; `GROUP BY` Tags, `*` und `time(intervall, versatz)`; `fill(null)`, `fill(none)`, `fill(previous)`, `fill(linear)`, `fill(<Zahl>)`; `ORDER BY time DESC`; `LIMIT`, `OFFSET`, `SLIMIT`, `SOFFSET`; `tz('Europe/Zurich')`; gebundene Werte über `params`. **Erledigt 23.10.2026:** InfluxQL von 1.x im Eigenbau: Zerleger und Parser nach dem Verhalten des Go-Parsers (Meldungen mit Zeile/Zeichen, gebundene Werte), Ausführer (Quellen samt Regex, `db.rp.m`, `db..m`, Unterabfragen; Zeitbereich aus `WHERE` an die Engine; Tag-/Feldbedingungen, Serien, `GROUP BY` Tags/`*`/`time(i,o)`, `fill` alle fünf, `ORDER BY time DESC`, `LIMIT`/`OFFSET`/`SLIMIT`/`SOFFSET`, `tz()`), Ausgabe wie 1.x (JSON mit Go-Zahlen und RFC3339Nano, `epoch`, `pretty`, `chunked` mit `partial`, CSV), `/query` per GET/POST-Formular. Alle 91 verglichenen Blöcke von `influxql-v1` gleich wie 1.11 (auch die Reihenfolge gleicher Zeiten aus dem Heap von Go). Verwaltungsanweisungen → IX37.8, weitere Funktionen → IX37.7, `/query` an 2.x/3.x → IX37.10/IX37.12.✅
IX37.71.x: InfluxQL-FunktionenAggregate (`COUNT`, `DISTINCT`, `INTEGRAL`, `MEAN`, `MEDIAN`, `MODE`, `SPREAD`, `STDDEV`, `SUM`), Selektoren mit der Zeit des gewählten Punkts (`BOTTOM`, `FIRST`, `LAST`, `MAX`, `MIN`, `PERCENTILE`, `SAMPLE`, `TOP`), Umformungen (`ABS`, Winkelfunktionen, `CEIL`, `FLOOR`, `ROUND`, `EXP`, `LN`, `LOG`, `POW`, `SQRT`, `CUMULATIVE_SUM`, `DERIVATIVE`, `NON_NEGATIVE_DERIVATIVE`, `DIFFERENCE`, `NON_NEGATIVE_DIFFERENCE`, `ELAPSED`, `MOVING_AVERAGE`), technische Analyse (`CHANDE_MOMENTUM_OSCILLATOR`, `EXPONENTIAL_MOVING_AVERAGE`, `DOUBLE_EXPONENTIAL_MOVING_AVERAGE`, `KAUFMANS_EFFICIENCY_RATIO`, `KAUFMANS_ADAPTIVE_MOVING_AVERAGE`, `TRIPLE_EXPONENTIAL_MOVING_AVERAGE`, `TRIPLE_EXPONENTIAL_DERIVATIVE`, `RELATIVE_STRENGTH_INDEX`), Vorhersage `HOLT_WINTERS`/`HOLT_WINTERS_WITH_FIT`; Ergebnisse bis aufs Bit wie das Orakel (Gleitkomma wie PR27.24d). **Erledigt 23.10.2026:** alle Funktionen wie 1.11 nach Quelltext nachgebaut (Mischreihenfolge, dreistufige Reduktion, Zwei-Punkte-Grenze bei first/last, pdqsort, Go-Mathematik bitgleich, Prüfung wie compile.go); funktionen-v1 160/160, funktionen2-v1 104/104, selektoren-v1 96/96. Rest IX37.7a.✅
IX37.7a1.x: Funktionen über mehrere ShardsIX37.7 rechnet wie ein einziger Shard (Teilergebnisse je Serie → Shard → Abfrage). Liegen Daten in mehreren Shard-Gruppen (z. B. über mehrere Wochen bei autogen), mischt 1.x je Shard und dann über die Shards: am Orakel mit Daten über drei Wochen messen (mean/sum in Gleitkomma, first/last mit Grenze 2 je Shard, percentile-Reihenfolge) und nachbauen. Erledigt 2026-10-23 (gemessen shards2-v1, Daten über drei Wochen-Shards, Werte mit Auslöschung): count/sum/mean/min/max/first/last rechnen je Serie und Shard vor; die Shards laufen im Heap von Go zusammen (gleiche Fenster in der Reihenfolge 1, n, n-1 …), sodass Gleitkommasummen genau wie 1.x ausfallen (sum 4 statt 3.9). Rohpunkte mischt jeder Shard für sich, die Shards hängen nach ihrer Zeit aneinander (gleiche Zeiten nach Serie). 30/31 Blöcke gleich, auch integral, percentile, mode, top/bottom, GROUP BY time(1w). Rest: Unterabfragen (F28) → IX37.7b.✅
IX37.7b1.x: Unterabfragen über mehrere ShardsRest aus IX37.7a (shards2-v1 F28): `SELECT sum(x) FROM (SELECT v AS x FROM m)` über drei Shards ergibt am Orakel 2.5, KuonenSQL 3.0999999999999996. Nachgerechnet: die Unterabfrage gibt ihre Punkte ungeordnet weiter — die Shards im MergeIterator (Heap: 1, n, n-1 …), darin Serie um Serie (a ganz, dann b), erst dann summiert die äussere Abfrage. KuonenSQL ordnet die Punkte einer Unterabfrage nach Zeit (AlsMessung, IqlGruppe). Nachbauen samt der Frage, wann 1.x die innere Abfrage geordnet liest (äussere Rohabfrage, GROUP BY time). Erledigt 2026-10-24, gemessen an influxdb:1.11 (Fixture shards3-v1, U1–U15). Liest eine Aggregatabfrage eine innere Rohabfrage, kommen deren Punkte ungeordnet: die Shards in der Folge des Heaps von Go (1, n, n-1 … 2), darin Serie um Serie, jede nach Zeit. Das gilt auch mit GROUP BY time über alle Shards, mit Rechnung, Bedingung, innerem GROUP BY s und verschachtelt. F28 und U1 ergeben 2.5, U2 0.25. Eine äussere Rohabfrage und ein inneres Aggregat bleiben nach Zeit geordnet (U4, U5, U15; U12 ergibt 3.0999999999999996). Jede Unterabfrage liest die äussere Abfrage als einen Strom, ohne Teilergebnisse je Serie. first/last über eine Unterabfrage reduzieren ohne den Sonderweg der Engine (Grenze 1): früheste bzw. späteste Zeit, bei Gleichstand der grössere Wert (U6 ergibt 0.3 und 0.7). Tests: InfluxqlFunktionenV1Tests mit shards2-v1 ganz (F28 jetzt dabei) und shards3-v1 ganz; Lauf Influx, Flux, Beispielkatalog 168/168. Sabotage ok (ungeordnetes Lesen aus → 2 rot; Strom aus → 2 rot).✅
IX37.81.x: Verwaltung und Schemaerkundung`SHOW DATABASES`, `SHOW MEASUREMENTS` (auch `WITH MEASUREMENT =~`), `SHOW TAG KEYS`, `SHOW TAG VALUES WITH KEY`, `SHOW FIELD KEYS`, `SHOW SERIES`, `SHOW RETENTION POLICIES`, `SHOW USERS`, `SHOW GRANTS`, `SHOW CONTINUOUS QUERIES`, `SHOW QUERIES`, `SHOW STATS`/`SHOW DIAGNOSTICS` (Grundwerte), Kardinalitäten (`SHOW SERIES CARDINALITY` u. a., auch `EXACT`); `CREATE`/`DROP DATABASE`, `CREATE`/`ALTER`/`DROP RETENTION POLICY` (Dauer, `REPLICATION` und `SHARD DURATION` angenommen, `DEFAULT`), `DROP MEASUREMENT`, `DROP SERIES`, `DELETE FROM … WHERE time …`, `CREATE`/`DROP USER`, `SET PASSWORD`, `GRANT`/`REVOKE` (`READ`, `WRITE`, `ALL`) auf die gemeinsamen Rechte (M26), `KILL QUERY`. **Erledigt 23.10.2026:** alle genannten Anweisungen gelesen (eigener Parserteil mit den Meldungen von 1.x) und ausgeführt (`InfluxqlVerwaltung`): SHOW DATABASES/MEASUREMENTS (WITH MEASUREMENT, WHERE, LIMIT/OFFSET, ON)/TAG KEYS/TAG VALUES (WITH KEY =, !=, =~, !~, IN)/FIELD KEYS/SERIES/RETENTION POLICIES/USERS/GRANTS/CONTINUOUS QUERIES/QUERIES/STATS/DIAGNOSTICS/SHARDS/SUBSCRIPTIONS, alle Kardinalitäten; CREATE/DROP DATABASE (auch WITH … NAME), CREATE/ALTER/DROP RETENTION POLICY (Mindestdauer, Vorgaben der Shard-Dauer), DROP MEASUREMENT/SERIES, DELETE (Felder in WHERE abgewiesen), CREATE/DROP USER, SET PASSWORD, GRANT/REVOKE auf Anmeldungen, `sysadmin` und Datenbankrollen, KILL QUERY, DROP SHARD, EXPLAIN; Warnung bei ändernden Anweisungen per GET. 103 von 107 Blöcken von `verwaltung-v1` gleich wie 1.11 (die übrigen wechseln je Lauf oder weichen gewollt ab). Engine-Befunde → IX37.8a, IX37.8b; Rest → IX37.8c.✅
IX37.8aBefund: USER_ID eines unbekannten NamensAm Orakel (SQL Server 2025, 23.10.2026): `USER_ID(N'gibtsnicht')` ist NULL; KuonenSQL gab 1 (wie `dbo`) — `IF USER_ID(N'x') IS NULL CREATE USER …` legte darum nie an. Gefunden bei `GRANT … ON db TO user` (IX37.8). **Erledigt 23.10.2026:** `EvaluateUserId` gibt für einen unbekannten Namen NULL. `UserIdTests`.✅
IX37.8bBefund: Rollensicht und Typ von USER_IDGefunden bei IX37.8: (1) `sys.database_role_members` zeigt nach `ALTER ROLE db_datareader ADD MEMBER u` die Mitgliedschaft nicht (nur `db_owner/dbo`), `IS_ROLEMEMBER` kennt sie; am Orakel mit eigener Anmeldung messen und angleichen. (2) `USER_ID(…)` ist am Orakel `smallint`, in KuonenSQL `int` (Ergebnistyp in Auswertung und Typableitung). Erledigt 2026-10-23 (am Orakel SQL Server 2025 mit Benutzern ohne Anmeldung): Die festen Datenbankrollen wirken jetzt in der Rechtefrage (`PermissionManager.Erlaubt`). db_datareader liest und db_datawriter schreibt jedes Objekt, wie ein GRANT der Datenbankebene, das ein DENY am Objekt sticht. db_denydatareader und db_denydatawriter verbieten, auch gegen ein GRANT. `sys.database_role_members` zeigt die Mitgliedschaften in festen Rollen (deren Kennung kommt aus `DatenbankprinzipalKennung`). `USER_ID` ist smallint. Test `FesteDatenbankrollenTests` (9). Befund dabei: `UPDATE … WHERE` verlangt am Orakel auch SELECT → IX37.8e.✅
IX37.8baBefund: DATABASE_PRINCIPAL_ID wurde smallintGefunden beim Langläufer DdlOracleTests (Release 2.8): seit IX37.8b liefert USER_ID smallint, DATABASE_PRINCIPAL_ID reichte das für die festen Namen (dbo, public …) durch; am Orakel bleibt es int. Erledigt 2026-10-25: Datenbankprinzipal wandelt das Ergebnis von USER_ID für die festen Namen nach int. Prüfung: DdlOracleTests (Langläufer, 1111 Stapel gegen SQL Server 2022) vorher 1 Abweichung (b, c smallint), jetzt grün.✅
IX37.8cRest aus IX37.8Ein Benutzer mit nur `WRITE` braucht Lesezugriff auf den Katalog `influx.*` (Schreibweg liest ihn) — Recht auf das Schema mitgeben oder den Katalog unter dem Zugang lesen; `SHOW SHARDS`/`SHARD GROUPS` (KuonenSQL hat keine Shards: bisher je Datenbank leer), `SHOW STATS`/`SHOW DIAGNOSTICS` mit den Modulen und Werten von 1.x, soweit KuonenSQL sie hat; `SHOW QUERIES` und `KILL QUERY` brechen eine laufende Abfrage bisher nur über das Abbruchzeichen ab (in die Auswertung durchreichen); Zeitbedingungen in `SHOW TAG VALUES`/`SHOW SERIES`. Erledigt 2026-10-23: eigene Rechteschicht `InfluxRechte` wie bei MariaDB/PostgreSQL (Vorgabe Marc: die Rechte von KuonenSQL, über jeden Zugang gleich). READ ist `GRANT SELECT` auf der Datenbank, WRITE ist `GRANT INSERT, UPDATE, DELETE`, ALL beides; SHOW GRANTS liest sie zurück. Vor der Ausführung prüft die Schicht jede Anweisung wie 1.x (READ, WRITE oder Verwalter; 403 mit dem Wortlaut von 1.x), den Schreibweg vor allem anderen. SHOW DATABASES zeigt nur lesbare Datenbanken. Schreiben und Löschen laufen nach der Prüfung wie ein Modul mit `EXECUTE AS OWNER`, sodass ein Schreiber ohne SELECT schreiben kann. Zeitbedingungen in SHOW TAG KEYS/TAG VALUES/SERIES wählen ganze Shard-Gruppen; SHOW MEASUREMENTS und SHOW SERIES CARDINALITY lehnen Zeit ab wie 1.x. Fixture rechte-v1, 57/57 gleich. SHOW SHARDS → IX37.9a; KILL QUERY → IX37.15a; SHOW STATS/DIAGNOSTICS → IX37.8d.✅
IX37.8d1.x: SHOW STATS und DIAGNOSTICSRest aus IX37.8c: `SHOW STATS` und `SHOW DIAGNOSTICS` mit den Modulen und Werten von 1.x (runtime, queryExecutor, write, database, shard, tsm1_* …), soweit KuonenSQL sie hat; heute nur httpd, build und system. Erledigt 2026-10-24, gemessen an 1.11 (Fixture stats-v1). `SHOW STATS` hat die Module von 1.11 mit ihren Tags und Spalten: runtime, queryExecutor, database{database}, localStore, write, subscriber, cq und httpd{bind}. Die Werte sind die von KuonenSQL: - runtime aus .NET (Go-Eigenes 0); - Abfragen, Schreiben, CQs und HTTP gezählt (InfluxZaehler); - numMeasurements und numSeries aus dem Katalog, gleich wie am Orakel. `SHOW DIAGNOSTICS` hat build, config-*, network, runtime und system mit den eingestellten Werten. FOR 'modul' wählt ein Modul, ein unbekanntes gibt nichts. Nicht da: shard und tsm1_* (keine TSM-Engine, die Shards sind Partitionen) sowie indexes. Test InfluxVerwaltungV1Tests.Laufwerte_mit_den_Modulen_von_1x, Lauf Influx, Flux, Beispielkatalog 174/174. Sabotage ok (Schreibzähler aus → rot).✅
IX37.8eBefund: UPDATE/DELETE mit WHERE verlangt SELECTBefund aus IX37.8b (am Orakel): `UPDATE dbo.t SET a = 7 WHERE a = 6` meldet einem Benutzer mit INSERT/UPDATE, aber ohne SELECT, 229 auf SELECT (die Spalten in WHERE und rechts von SET werden gelesen). `UPDATE dbo.t SET a = 8` ohne Lesen geht. KuonenSQL prüft beim UPDATE/DELETE nur das Recht der Anweisung. Gilt ebenso für DELETE … WHERE und MERGE; über alle Zugänge (T-SQL, MariaDB, PostgreSQL) am jeweiligen Orakel messen. Erledigt 2026-10-24, am Orakel gemessen (SQL Server 2025 kq37rt, PostgreSQL 17 und MariaDB 11.8 in den Referenzcontainern). Ein UPDATE oder DELETE, das Spalten seines Ziels liest (WHERE, rechts von SET), verlangt SELECT: - ohne SELECT 229 (PostgreSQL über denselben Weg `permission denied for table`); - mit Spaltenrechten 230 für die erste fehlende Spalte; - bei MariaDB 1143 mit der ersten gelesenen Spalte, auch ganz ohne Spaltenrecht. Ohne gelesene Spalte geht es ohne SELECT. OUTPUT war schon abgedeckt. MERGE prüfte bisher gar keine Rechte. Jetzt prüft es das Recht jeder Aktion (INSERT/UPDATE/DELETE) und SELECT auf das Ziel. Tests LeserechtDmlTests (T-SQL über die Engine, MariaDB über den Draht). Lauf Recht, Berechtig, Merge, Grant, Sicherheit, Spalten, Prinzipal 989/989. Sabotage ok (Leseprüfung aus → 2 rot; SELECT im MERGE weg → rot).✅
IX37.91.x: Aufbewahrung und Continuous QueriesHintergrunddienst, der Punkte älter als die Dauer der Aufbewahrungsrichtlinie löscht (Takt wie gemessen, nie im Schreibpfad); `CREATE CONTINUOUS QUERY … BEGIN SELECT … INTO … GROUP BY time(…) END` mit `RESAMPLE EVERY … FOR …`, Ausführung im Takt, Nachholen nach Neustart; `SELECT … INTO` (auch `:MEASUREMENT` und Rückschreiben der Tags). Beim Wiederanlauf und bei `RESTORE LOG` nichts doppelt (wie PR27.6d). **Erledigt 23.10.2026:** SELECT INTO wie 1.11 (Tags/Felder, :MEASUREMENT, written), CQs gespeichert und gezeigt wie 1.x (normalisiert), Takt 1 s nach continuous_querier (erstes Fenster nur gemerkt; nach Neustart kein Nachholen — wie 1.x), Aufbewahrung alle 30 min nach Shard-Gruppen, alte Punkte beim Schreiben verworfen; cq-v1 61/61, Zeittests nach Messung. Rest IX37.9a.✅
IX37.9a1.x: Shard-Gruppen sichtbar`SHOW SHARDS` und `SHOW SHARD GROUPS` zeigen wie 1.x die Shard-Gruppen je Datenbank und Richtlinie (aus den vorhandenen Zeiten abgeleitet: Beginn/Ende/Ablauf, fortlaufende ids), `DROP SHARD id` löscht die Punkte der Gruppe. Am Orakel messen (cqzeit R5 zeigt das Format, auch `_internal`). Erledigt 2026-10-23: Gruppen im Katalog `influx.shardgruppen` (id, rp, beginn, ende). Sie entstehen beim Schreiben (1.x/2.x) für jeden Punkt ausserhalb einer bestehenden Gruppe der Richtlinie, abgerundet wie Go ab dessen Nullzeit (Wochen beginnen am Montag), mit fortlaufender Kennung über alle Datenbanken. `SHOW SHARDS`/`SHOW SHARD GROUPS` zeigen sie wie 1.x (expiry_time leer bzw. Ende plus Dauer). `DROP SHARD` löscht die Punkte der Gruppe, `shard not found` wenn es sie nicht gibt. Aufbewahrung und `DROP RETENTION POLICY` nehmen die Gruppen mit. Fixture shards-v1, 21/21 gleich.✅
IX37.9bKern: Dateigruppen und DateienKern (Entscheid Marc 23.10.2026: Shards als Partitionen auf benannten Dateigruppen): `ALTER DATABASE … ADD FILEGROUP | ADD FILE … TO FILEGROUP | REMOVE FILE | REMOVE FILEGROUP | MODIFY FILEGROUP … DEFAULT | NAME =`, Dateigruppen ohne Datei, `CREATE TABLE … ON dateigruppe` (1921), Einfügen in eine Dateigruppe ohne Datei (622), nicht leer (5042), Meldungen 5021/5044, Partitionsschema mit unbekannter Dateigruppe (208); `sys.filegroups`, `sys.data_spaces`, `sys.database_files`, `sys.indexes.data_space_id`. Am Orakel SQL Server 2025 gemessen (kq37fg). Erledigt 2026-10-23 (Katalog und Befehle; die eigene Datei je Dateigruppe folgt mit IX37.9c): Dateigruppen sind eine eigene Liste der Datenbank (Kennung = data_space_id, PRIMARY 1) samt Standard-Dateigruppe; ALTER DATABASE ADD FILEGROUP, ADD [LOG] FILE … TO FILEGROUP (ohne: PRIMARY), REMOVE FILE, REMOVE FILEGROUP, MODIFY FILEGROUP DEFAULT | NAME mit den Fehlern und Meldungen des Orakels (5035, 1921, 1828, 622, 5042, 5014, 5009, 5020, 5045, 5012, 208; 5044, 5046, 5021). CREATE TABLE … ON dateigruppe; eine Tabelle ohne ON kommt in die Standard-Dateigruppe; sys.filegroups, sys.data_spaces, sys.database_files, sys.indexes.data_space_id und sys.destination_data_spaces zeigen die Kennungen. Test DateigruppenTests (18).✅
IX37.9cKern: Ablage je DateigruppeKern: eigene Ablage je Dateigruppe. Der Kontrollpunkt schreibt die Zeilen jeder Dateigruppe in ihre eigene Datei (neben `<db>.mdf`, Pfad aus `ADD FILE`), eine partitionierte Tabelle je Partition in die Datei der Dateigruppe ihrer Partition; das Laden setzt sie wieder zusammen. Kontrolldatei, Wiederanlauf, BACKUP/RESTORE und Kopien (H20) ziehen mit. Erledigt 2026-10-23: Jede Dateigruppe mit Datei hat ihre eigene Ablage `<db>.<gruppe>.ndf` neben `<db>.mdf` (Kopf und Kontrollpunktnummer wie die Datendatei). Der Kontrollpunkt schreibt die Zeilen einer Tabelle in ihrer Dateigruppe und einer partitionierten je Zeile in die Datei der Gruppe ihrer Partition; die primäre Datei behält Katalog, PRIMARY-Zeilen und Grossobjekte. Die Kontrolldatei nennt je Datenbank die Gruppendateien (Umbenennen nach einem Absturz, verwaiste Dateien weg); das Laden hängt ihre Zeilen an, fehlt eine, startet die Datenbank nicht (5120). Kopien übertragen auch `.ndf`; RESTORE übernimmt Dateien, Dateigruppen und die Standard-Dateigruppe. Tests DateigruppenAblageTests (3).✅
IX37.9dKern: Partitionen ändern, leeren, umhängenKern: `ALTER PARTITION FUNCTION … SPLIT RANGE | MERGE RANGE` (7710, 7715), `ALTER PARTITION SCHEME … NEXT USED`, `TRUNCATE TABLE … WITH (PARTITIONS (n, a TO b))` (7722, 7729), `ALTER TABLE … SWITCH [PARTITION n] TO … [PARTITION m]` (4904, 4938, 4939, 4943, 4950, 4982, 1088). Am Orakel gemessen (kq37pt, kq37sw). Erledigt 2026-10-23: ALTER PARTITION SCHEME … NEXT USED [g] (die nächste Gruppe hinter den Partitionen), ALTER PARTITION FUNCTION … SPLIT RANGE (die neue Seite — bei RIGHT rechts, bei LEFT links — in die nächste Gruppe; 7710, 7721) und MERGE RANGE (die Gruppe der wegfallenden Seite frei, NEXT USED bleibt; 7715), TRUNCATE TABLE … WITH (PARTITIONS (n, a TO b)) (7722, 7729), ALTER TABLE … SWITCH [PARTITION n] TO … [PARTITION m] [WITH (…)] mit den Prüfungen des Orakels (1088, 4955, 4950, 4943, 4938, 4939, 4904, 4905, 4982 über die CHECK-Einschränkung der Quelle). TRUNCATE und SWITCH über eine neue Ablage (ROLLBACK nimmt beides zurück), der Wiederanlauf spielt alles aus dem Protokoll ab. Nachtrag IX37.9b: die Dateigruppe einer Tabelle steht jetzt im Katalogabzug. Tests PartitionsaenderungTests (5). Nachtrag 2026-10-24 (Volltest): Der SWITCH aus IX37.9d hatte den älteren aus PR27.16t verdrängt; damit fehlten 4942, 4944, 4985, 4913, 4947, 4971 und die Warnung 4903. Jetzt gibt es einen Weg mit der Reihenfolge des Orakels: 4943, 4938/4939, 4913, 4904/4905, 4942/4944/4985, 4947, 4971. Eine CHECK-Einschränkung des Ziels nur auf der Partitionsspalte gilt bei einer Partition als Quelle als eingehalten, wenn deren Grenzen sie einhalten; sonst kommt 4972 (`t <= 199` gegen [100, 200) ebenfalls 4972, weil das Orakel die Grenzen vergleicht). Tests: UmschaltenTests +3, PartitionsaenderungTests +3 Fälle. Sabotage ok (4972 aus → rot).✅
IX37.9eInflux: Shards als Partitionen auf DateigruppenInflux 1.x/2.x: Shards als Partitionen auf benannten Dateigruppen — je Richtlinie eine Partitionsfunktion auf `time` (Grenzen an den Anfängen der Shard-Gruppen) und ein Schema, je Shard-Gruppe eine Dateigruppe mit Datei (`<rp>_<Beginn>`); jede Messung der Richtlinie liegt auf dem Schema. Neue Gruppe: Dateigruppe anlegen, NEXT USED, SPLIT RANGE; `DROP SHARD` und die Aufbewahrung leeren die Partition (`TRUNCATE … WITH (PARTITIONS)`) und geben Dateigruppe und Grenze frei. Bestehende Ablagen werden beim ersten Schreiben umgestellt. Erledigt 2026-10-24: Bei 1.x und 2.x liegt jede Messung auf dem Partitionsschema `influx_<rp>` (Funktion RANGE RIGHT auf `time`, bigint). Jede Shard-Gruppe bekommt beim ersten Punkt ihre Dateigruppe `influx_<rp>_JJJJMMTT` (unter einem Tag `_JJJJMMTT_HHmm`) mit Datei `.ndf`, dazu NEXT USED und SPLIT RANGE am Gruppenbeginn. Messungen von vor IX37.9e kommen ohne Umschreiben auf das Schema. `DROP SHARD`, die Aufbewahrung und `DROP RETENTION POLICY` leeren mit TRUNCATE … WITH (PARTITIONS), danach MERGE RANGE und REMOVE FILE/FILEGROUP. DROP RETENTION POLICY entfernt zusätzlich Schema und Funktion. V3 bleibt ohne Partitionen. Kernbefunde nebenbei: - ALTER TABLE ADD, sp_rename (Tabelle, Spalte) und der Schemawechsel verloren Partitionsschema und Dateigruppe (am Orakel bleiben sie, auch beim Umbenennen der Partitionsspalte). Neu übernimmt `TableSchema.MitAblage` sie. - `sys.partition_range_values.value` war kein echtes sql_variant (CAST schlug fehl). - `Partitionsfunktion.Nummer` sucht binär. Tests: InfluxShardpartitionenTests, PartitionsaenderungTests.Spalte_dazu_und_Umbenennen_…; Lauf Influx, Flux, Partition, Dateigruppe, Rename, AlterTable, Katalog 522/522. Sabotage ok (Partition ausgelassen → rot; MitAblage beim ALTER weg → 2 rot).✅
IX37.9fKern: Tablespaces von MySQL, MariaDB und PostgreSQL auf DateigruppenRest aus IX37.9b (Frage Marc, 24.10.2026): Die Dateigruppen gibt es am T-SQL- und am KSQL-Zugang (DateigruppenTests.Am_KSQL_Zugang_…). Das Gegenstück der anderen Zugänge fehlt noch: Tablespaces von MySQL 8.4/MariaDB (`CREATE TABLESPACE … ADD DATAFILE`, `CREATE TABLE … TABLESPACE x`, information_schema.FILES) und von PostgreSQL (`CREATE TABLESPACE … LOCATION`, `… TABLESPACE x`, pg_tablespace). Sie sollen auf Dateigruppen abgebildet werden, am jeweiligen Orakel gemessen. Bis dahin kommt eine Tabelle von dort in die Standard-Dateigruppe.⬜
IX37.102.x: HTTP-API ohne Flux`POST /api/v2/write?org=&bucket=&precision=` (auch `orgID`, `bucketID`), Fehler als `{"code","message"}`; Einrichtung `/api/v2/setup`; Organisationen, Buckets (mit Aufbewahrungsregel wie IX37.9), Token und Berechtigungen (`/api/v2/authorizations`), Benutzer; `/api/v2/delete` mit Zeitbereich und Prädikat; die 1.x-Kompatibilität `/query` und `/write` über DBRP-Zuordnung (`/api/v2/dbrps`) mit dem InfluxQL aus IX37.6 bis IX37.8; explizites Schema je Bucket (`/api/v2/buckets/…/schema/measurements`) annehmen oder ablehnen wie gemessen. Werkzeug `influx` 2.x für `write`, `query` (über v1), `bucket`, `auth`. **Erledigt 23.10.2026:** Bucket = Datenbank (Entscheid Marc), Katalog master.influx2; Einrichtung, Orgs, Buckets, Benutzer, Token mit Rechten, DBRP, /api/v2/write, /api/v2/delete, 1.x-Wege über DBRP wie 2.7; explizites Schema gibt es in OSS nicht (404, gemessen); api2-v2 98/98, Zugangstest über alle SQL-Zugänge. Rest IX37.10a (Werkzeug influx).✅
IX37.10a2.x: Abnahme mit dem Werkzeug influxDas Werkzeug `influx` 2.x (aus dem Abbild `influxdb:2.7`) gegen einen laufenden KuonenSQL mit `KUONENSQL_INFLUX_FASSUNG=2`: `influx write`, `influx query` nur über v1 (`influx v1 shell`), `influx bucket list/create/delete`, `influx auth list/create`, `influx v1 dbrp list/create`; Abweichungen als Befunde beheben. Gehört mit IX37.16 zusammen. Erledigt mit IX37.16: influx 2.7 gegen KuonenSQL (bucket create/list, write, query, auth create/list, v1 dbrp list) läuft; Befund `/api/v2/me` für sa behoben.✅
IX37.112.x: Flux (Teilmenge)`POST /api/v2/query` mit Flux, Antwort als annotiertes CSV (`#datatype`, `#group`, `#default`, eine Tabelle je Gruppe) exakt wie das Orakel. Die Teilmenge, die Grafana und die Telegraf-Dashboards brauchen: `from`, `range`, `filter`, `keep`/`drop`, `group`, `aggregateWindow` (mit `fn`, `createEmpty`), `mean`/`sum`/`min`/`max`/`last`/`first`/`count`, `map`, `pivot`, `sort`, `limit`, `yield`, `v.timeRangeStart`/`v.windowPeriod`. Weiteres Flux nur auf Wunsch (InfluxData führt Flux in 3.x nicht weiter); nicht unterstützte Funktionen mit der Meldung des Orakels für unbekannte Bezeichner. Umgesetzt (Welle F): `POST /api/v2/query` mit Flux (vnd.flux oder JSON mit dialect, extern wie Grafana, now), eigener Leser (Stellen wie Flux, `undefined identifier` vor dem Lauf) und Ausführer über Tabellenströme: from/range (Bucket = Datenbank, Tabellen je Serie und Feld, filter auf _measurement eingeengt), filter, keep/drop/rename, group (by/except), aggregateWindow (every, period, offset, fn, createEmpty, timeSrc), mean/sum/count, min/max/first/last, map (mit/ohne with), pivot, sort, limit, yield, fill, difference, timeShift, toFloat/toInt/toString/toBool, int/float/string/bool/uint/time, now, option v. Annotiertes CSV mit CRLF, #datatype/#group/#default, Zahlen wie Go 'f' -1. Gemessen flux-v2 (51): 49 gleich, Reste IX37.11a. Abnahme: Flux liest, was T-SQL, KSQL, PG, MariaDB und MySQL ändern (InfluxZugaengeTests).✅
IX37.11a2.x: Flux über die Teilmenge hinausOffen aus IX37.11: Meldungen des Flux-Lesers bei Syntaxfehlern wörtlich (flux-v2 F25), Reihenfolge mehrerer `yield` (2.x gibt fertige zuerst aus, F23), Paket `influxdata/influxdb/schema` (measurements, tagKeys, tagValues, fieldKeys — der Abfrage-Baukasten von Grafana), `buckets()`, weitere Funktionen nur auf Wunsch (window, quantile/median, stddev, derivative, cumulativeSum, distinct/unique, join/union, to()). Erledigt 2026-10-24: gemessen in drei neuen Folgen am Orakel influxdb:2.7 — flux2-v2 (74 Fälle), die Prüfreihe flux3-v2 (41, erst nach dem Bau gemessen) und flux4-v2 (27, mit Annotationen). **Leser.** Der Flux-Leser ist nach libflux 0.194 nachgebaut (Scanner, Parser, check). Er sammelt alle Fehler und meldet sie wörtlich wie 2.x (`compilation failed: error @1:23-1:36: expected RPAREN, got EOF` …, F25, E1–E17). Die Bedeutungsfehler kommen ebenso gesammelt: unbekannter Bezeichner, unbekanntes Paket, ungültiger Operator. **Mehrere `yield`.** Sie kommen in der Folge von 2.x: geordnet nach dem Text der Operationskennung (`yield10` vor `yield4`). Ein `to()` ohne `yield` heisst `to5`. **Neu:** - das Paket `influxdata/influxdb/schema` (measurements, tagKeys, tagValues, fieldKeys, measurement…, fieldsAsCols). measurements liest ohne Zeit, die übrigen auf Ebene der Shard-Gruppen. - `buckets()`, gruppiert nach organizationID, `array.from` und Texte mit `${…}`; - window (auch `every: inf`), duplicate, median/quantile (t-digest, exact_mean, exact_selector), stddev, spread; - derivative, cumulativeSum, integral, movingAverage, elapsed, distinct, unique, join, union und to(). **Folge der Spalten.** Aggregate, die 2.x nicht in der Ablage rechnet, setzen `_value` ans Ende. `group()` aus der Ablage behält `_start`/`_stop` im Schlüssel. `map` behält die Spaltenfolge nur, wenn 2.x vektorisiert. `pivot` behält Schlüssel- und Zeilenspalten in der Folge des Auftretens. Leere Tabellen haben einen eigenen Kopf. **Nicht nachgebaut.** Bei Tabellen mit weniger als n Zeilen hängt 2.x das Mittel von `movingAverage` nur manchmal als weitere Tabelle an (flux3-v2 V25, flux4-v2 A1x/A3; A2: nie). **Tests.** InfluxFluxTests mit drei neuen Folgen, flux-v2 ohne Ausnahmen. Lauf Influx/Flux/Flight/Beispielkatalog 189/189. Sabotage ok (drei Proben rot).✅
IX37.123.x: HTTP-API`POST /api/v3/write_lp?db=&precision=&accept_partial=&no_sync=`, dazu die Schreibwege von 1.x und 2.x am 3.x-Port wie beim Orakel; `GET`/`POST /api/v3/query_sql` und `/api/v3/query_influxql` mit `format=json`, `jsonl`, `csv`, `pretty`, `parquet` (eigener Parquet-Schreiber, nur was das Orakel liefert); Verwaltung `/api/v3/configure/database`, `…/table`, Token (Verwalter- und Ressourcentoken), `…/distinct_cache`, `…/last_cache`; `/health`, `/ping`. Das SQL von 3.x (Apache DataFusion) auf den KSQL-Kern abbilden: Bezeichner wie PostgreSQL, `date_bin`, `date_bin_gapfill`, `locf`, `interpolate`, `selector_first`/`selector_last`/`selector_min`/`selector_max`, `now()`, Systemtabellen `system.queries`, `information_schema`. Werkzeug `influxdb3` für `write`, `query`, `create database`, `create token`. Umgesetzt (Welle E): `write_lp` (precision auto/s/ms/us/ns, accept_partial, Datenbank entsteht beim Schreiben, Fehlerliste mit line_number/original_line), `/write` und `/api/v2/write` am 3.x-Port mit den Fehlerformen von 3.x, `query_sql` und `query_influxql` per GET/POST (JSON-Rumpf, params) in json/jsonl/csv/pretty, `/query` wie 1.x mit den Meldungen von 3.x, `configure/database&#124;table` (GET/POST/DELETE). SQL von DataFusion über den PostgreSQL-Übersetzer mit Suchpfad `iox` (je Messung eine Sicht `iox.<messung>`, auch für T-SQL/KSQL/PG/MariaDB/MySQL lesbar) und einer Vorstufe für date_bin, date_bin_gapfill/locf/interpolate, to_timestamp*, GROUP BY Alias, CAST wie DataFusion, information_schema, SHOW TABLES/COLUMNS, system.*; Fehler wie DataFusion. Zahlen und Zeiten wie 3.x (zahlen-v3). Gemessen sql-v3 (143), zahlen-v3 (9); sql-v3 119/143, zahlen/lp/lp2/influxql/grundlagen-v3 gleich bis auf die benannten Reste (IX37.12a–e). Abnahme: über write_lp Geschriebenes ist über alle fünf SQL-Zugänge les- und änderbar (InfluxZugaengeTests).✅
IX37.12a3.x: Zeit unter 100 ns in SQLDie Sichten `iox.<messung>` zeigen `time` als `datetime2(7)` (100 ns); 3.x rechnet in Nanosekunden (sql-v3 S29–S31, grundlagen-v3 S2/S3: `.123456789` wird `.123456800`). Gespeichert ist die Zeit genau (`time bigint`); fehlt ein Zeittyp mit ns in der Engine oder ein Weg, `time` als bigint durch die Abfrage zu tragen und erst in der Ausgabe als Zeit zu schreiben. Auch `to_timestamp(n)` über `timestamp` (µs). Erledigt 2026-10-24: SQL von 3.x zeigt die Zeit auf die Nanosekunde, wie gemessen in sql-v3 S11, S29–S31 und grundlagen-v3 S2/S3 (`.123456789`, vorher `.123456800`). Ein Zeitpunkt in der Engine trägt jetzt den Rest unter 100 ns mit (`SqlValue.Nanorest`). `DATEADD(NANOSECOND, …)` auf datetime2 legt ihn neben die Ticks; diese sind gerundet wie SQL Server. Vergleich, Text und TDS kennen den Rest nicht, für T-SQL ändert sich also nichts. Die Sichten `iox.<messung>` rechnen `time` aus `[time]`, nicht aus `_zeit`, das auf 100 ns gerundet abliegt. Der Rest geht so durch Auswahl, Sortierung und Filter. Die Ausgabe von 3.x (JSON, CSV, pretty, Flight) rechnet ihn ein. Tests: InfluxApiV3Tests mit sql-v3 und grundlagen-v3 ohne die bisherigen Ausnahmen S11, S29–S31, S2, S3. Lauf Influx, Flux, Beispielkatalog, Date, Datum, Zeit, Flight 1295/1295. Sabotage ok (Rest nicht eingerechnet → 2 rot).✅
IX37.12b3.x: Ausgabe als Parquet`format=parquet` an `query_sql`/`query_influxql` antwortet heute 405 (KuonenSQL). Eigener Parquet-Schreiber (Thrift compact, PLAIN, eine Zeilengruppe) und ein Prüfleser im Test, der zuerst eine Parquet-Datei des Orakels liest. Erledigt 2026-10-24: `format=parquet` an `query_sql` und `query_influxql` (POST und GET) schreibt Parquet, gemessen in parquet-v3 (P1–P7, je mit dem JSON-Zwilling J1–J7). Aufbau wie 3.x: eine Zeilengruppe, Wurzel `arrow_schema`, nie-NULL-Spalten REQUIRED, Text BYTE_ARRAY/STRING, Zeit INT64/TIMESTAMP(NANOS, nicht UTC), UInt64 mit INTEGER(64, unsigned), `ARROW:schema` in den Metadaten; ein leeres Ergebnis ist ein leerer Rumpf. Anders als 3.x (ZSTD, Wörterbuch) unkomprimiert und PLAIN; pyarrow liest die Dateien mit denselben Typen und Werten. Nebenbei: Ausdrücke nur aus Literalen und `now()` sind nie NULL (P4), wie in DataFusion. Messwerkzeug und Testfolge halten Parquet als `base64:` fest. Tests: InfluxParquetTests (Prüfleser liest die Fusszeile des Orakels; Fusszeilen und Kopfzeilen wie das Orakel, Werte wie dessen JSON; 200 zufällige Ergebnisse bis 20 Spalten und 300 Zeilen). Lauf Influx/Flight/Flux/Beispielkatalog/Sortierung 426/426. Sabotage ok (Definitionsstufen, STRING-Typ → rot).✅
IX37.12c3.x: SQL-Funktionen und Namen von DataFusionNoch nicht wie DataFusion (sql-v3, zahlen-v3): `selector_first&#124;last&#124;min&#124;max` (Struct mit value/time), `arrow_typeof`, `EXPLAIN` (405), `CAST('NaN'&#124;'inf' AS DOUBLE)`, Spaltennamen ohne Alias (`Int64(1)`, `count(*)`), „Did you mean“ in Meldungen, Text in UNION/ORDER BY binär sortiert (S19). Erledigt 2026-10-24: SQL von 3.x benennt, typisiert und wählt wie DataFusion, gemessen in sql-v3, zahlen-v3 und der neuen Folge funktionen-v3 (28 Fälle). Spalten ohne Alias heissen wie in DataFusion (`Int64(1)`, `count(*)`, `sum(cpu.usage)`, `cpu.usage * Int64(2)`, `(- cpu.n)`, `CAST(cpu.n AS Float64)`); zwei gleiche Namen weist 3.x ab, KuonenSQL auch. `arrow_typeof` nennt den Typ von Arrow (Tag `Dictionary(Int32, Utf8)`, `Null`), auch im WHERE. `selector_first|last|min|max(wert, zeit[, …])` gibt die Struktur `{value, time, other_1…}`, `['value']` ein Feld. `EXPLAIN SELECT <Literale>` zeigt den Plan von DataFusion. Eine unbekannte Funktion schlägt die nächste der 303 Funktionen von 3.x vor (`Did you mean 'sum'?`). `CAST('NaN'|'inf'|'-inf' AS DOUBLE)`, `CAST(1735689600 AS TIMESTAMP)` (Sekunden) und `avg` in Float64 rechnen wie 3.x. Text vergleicht und sortiert binär (`Bern` vor `a`). Dazu ein Befund im Kern (T-SQL wie SQL Server 2025): eine abgeleitete Tabelle verlor die Sortierung aus `x COLLATE s` und aus UNION; `ORDER BY` und `MIN` darüber folgen ihr jetzt. Tests: InfluxApiV3Tests (funktionen-v3 neu; sql-v3, zahlen-v3 ohne die bisherigen Ausnahmen bis auf E7), AbgeleiteteSortierungTests (T-SQL, KSQL). Lauf Influx/Flight/Beispielkatalog/Sortierung 496/496. Sabotage ok (3 Proben rot). Rest: IX37.12g (Pläne über Tabellen, `information_schema.routines`, Name eines Intervalls), IX37.12h (PostgreSQL-Übersetzer).✅
IX37.12g3.x: Pläne über Tabellen, routines, Name eines IntervallsRest aus IX37.12c (gemessen funktionen-v3). `EXPLAIN` über Tabellen sowie `EXPLAIN ANALYZE` und `EXPLAIN VERBOSE` (X1–X3) antworten noch 405. Der physische Plan von 3.x hängt an Partitionen und Optimierer (`RepartitionExec: partitioning=RoundRobinBatch(32)`). Ebenso offen: `information_schema.routines` mit den 453 Einträgen von 3.x (F5, F6; heute die Liste von PostgreSQL) und der Name eines Intervalls ohne Alias (N4: `date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0, nanoseconds: 3600000000000 }"),cpu.time)`). Erledigt 2026-10-24 (ohne die Pläne über Tabellen, Rest IX37.12i): gemessen in routinen-v3 und plaene-v3. `information_schema.routines` (453), `information_schema.parameters` (3112) und `SHOW FUNCTIONS [LIKE …]` (914) zeigen die Funktionen von 3.x samt Beschreibungen, Typen (UInt64, UInt8, Boolean) und Listen; die Daten liegen als Referenzdaten bei (`tools/influx/datafusion-funktionen.py`). Intervalle: Name ohne Alias (`IntervalMonthDayNano(…)`, auch in `date_bin`), Wert als Text wie Arrow (`1 hours 30 mins`, `5.000000000 secs`, leer bei null), `arrow_typeof` `Interval(MonthDayNano)`; `TIMESTAMP '…'` heisst wie Text. `pretty` verteilt mehrzeilige Zellen auf Zeilen; `EXPLAIN FORMAT INDENT`. SQL von 3.x läuft jetzt ganz im Dialekt von PostgreSQL (NULL bei ASC zuletzt wie DataFusion); `sum` über Ganzzahlen bleibt Int64. Tests: InfluxApiV3Tests (routinen-v3, plaene-v3 neu; funktionen-v3 jetzt mit N4, F5, F6; SHOW FUNCTIONS LIKE als Menge, weil 3.x je Lauf anders ordnet). Lauf Influx/Flight/Beispielkatalog/PostgreSQL 1417/1417. Sabotage ok (Sekunden ohne neun Stellen → rot, ohne Dialekt → rot).✅
IX37.12hPostgreSQL-Übersetzer: avg in KlammernBefund aus IX37.12c. Der Übersetzer des PostgreSQL-Zugangs zerlegt ein `avg` in Klammern falsch: `SELECT (avg(1))` wird zu `pg_numeric_div ( sum ( cast ( 1 as decimal(38, 0) ) ) ) , count_big ( 1 ) ) )` und scheitert mit 102. Ohne Klammer stimmt es. SQL von 3.x umgeht es, indem es dort keine Klammer setzt. Zu messen am Orakel PostgreSQL, dann im Übersetzer zu beheben. Erledigt 2026-10-24: am Orakel postgres:17 gemessen — `(avg(1))` ist `1.00000000000000000000` (numeric), `((avg(x)))` und `(avg(x) + 1)` rechnen wie ohne Klammer, `(avg(x) OVER ())` ebenso. Der Knoten eines Aufrufs in Klammern reicht über die Klammern; der Übersetzer nahm die schliessende als Teil des Fensters. Jetzt bleiben die Klammern aussen stehen, und das Fenster ist nur `OVER …`. Tests: PgAggregattypenTests (4 neue Fälle). Lauf PostgreSQL 1307/1307. Sabotage ok (Klammer hinten weg → 2 rot).✅
IX37.12i3.x: EXPLAIN über Tabellen (logischer Plan)Rest aus IX37.12g (gemessen plaene-v3 P1–P11, routinen-v3 X4–X6, funktionen-v3 X1). `EXPLAIN` über Tabellen antwortet 405. Der logische Plan von DataFusion ist fest und nachbaubar. Er zeigt `Projection`, `Filter`, `Sort` (`ASC NULLS LAST`, `fetch=`), `Limit`, `Aggregate: groupBy=[[…]], aggr=[[…]]` und `TableScan: cpu projection=[…], partial_filters=[…]`. Dazu die Regeln des Optimierers: Projektion weglassen, Filter und `fetch` in den Scan, Literale angepasst (`Dictionary(Int32, Utf8("a"))`, `Float64(1)`, `TimestampNanosecond(…, None)`), `count(*)` als `count(Int64(1)) AS count(*)`. Der physische Plan dagegen hängt am Speicherstand von 3.x: ein oder zwei Chunks, `input_partitions` 1 oder 2 bei derselben Abfrage. Dort zeigt KuonenSQL den eigenen Plan. Zu bauen: der logische Plan für Abfragen über eine Tabelle. Erledigt 2026-10-24: `EXPLAIN` einer Auswahl über eine Tabelle zeigt den logischen Plan wie DataFusion: 15 Pläne zur Herleitung und eine Prüfreihe plaene2-v3 mit 18 neuen Plänen (14 stimmten sofort, 4 Regeln nachgezogen). Gebaut sind Projektion, Filter, Aggregat, Sortierung, LIMIT/OFFSET und der Scan mit `partial_filters` und `fetch`. Dazu Typangleichung der Literale, `count(*)` als `count(Int64(1)) AS count(*)`, `x = true` als `x` und `lower` über einem Tag als `CAST(… AS Utf8View)`. Der physische Plan zeigt den T-SQL-Text, den KuonenSQL ausführt; der von 3.x hängt am Speicherstand. Joins, Unterabfragen, HAVING und DISTINCT antworten weiter 405. Tests: Logische_Plaene_wie_das_Orakel (33 Pläne). Lauf 1421/1421. Sabotage ok (Projektion nie weggelassen → 3 rot; Tag-Literal ohne Dictionary → 2 rot). Rest: IX37.12j.✅
IX37.12j3.x: EXPLAIN ANALYZE/VERBOSE, Pläne über mehrere TabellenRest aus IX37.12i. Noch offen sind `EXPLAIN ANALYZE` (Plan mit Messwerten, funktionen-v3 X2) und `EXPLAIN VERBOSE` (jeder Schritt des Optimierers, X3). Ebenso der logische Plan für Abfragen über mehr als eine Tabelle, mit Unterabfragen, HAVING oder DISTINCT. Diese antworten heute 405. Erledigt 2026-10-24: gemessen in plaene3-v3 (20 Fälle) und der Prüfreihe plaene4-v3 (12, nach dem Bau gemessen). **Logische Pläne wie DataFusion.** - DISTINCT als Aggregate, HAVING als Filter über der Gruppierung, `count(DISTINCT x)` mit `alias1`. - Ein Alias der Tabelle und WITH als SubqueryAlias. - Unterabfragen im FROM in die äussere gezogen; ein WHERE über einer Gruppierung wird HAVING, eine Teilauswahl verkürzt die innere. - UNION ALL als ein Union (verschachtelte flach), UNION mit Aggregate. - Inner und Left Join, `IN (SELECT …)` als LeftSemi Join mit `__correlated_sq_1`, eine skalare Unterabfrage als Left Join mit `__scalar_sq_1`. **`EXPLAIN VERBOSE`.** - Ohne Tabelle alle Schritte, auch die physischen samt Statistik und Schema. - Über einer Tabelle mit oder ohne WHERE die logischen Schritte (type_coercion, simplify_expressions, push_down_filter, optimize_projections; ein zweiter Optimiererlauf nur nach einer Änderung). - Der physische Teil über Tabellen zeigt den eigenen Plan. - VERBOSE über anderen Formen antwortet 405. **`EXPLAIN ANALYZE [VERBOSE]`.** Dieselben Arten von Zeilen wie 3.x (Plan with Metrics, Plan with Full Metrics, Output Rows, Duration), mit den Messwerten des eigenen Laufs. **Tests.** InfluxApiV3Tests (Logische_Plaene_wie_das_Orakel mit 22 Plänen mehr, Explain_verbose_und_analyze_wie_das_Orakel). Lauf Influx/Flux/Flight/Beispielkatalog 192/192. Sabotage ok (Union nicht flach → rot, immer zwei Optimiererläufe → rot).✅
IX37.12d3.x: _internal und weiches Löschen3.x zeigt die Datenbank `_internal` und behält gelöschte Datenbanken/Tabellen unter `name-<Zeitstempel>` (deleted=true, sql-v3 D4–D10, I4, V4, V5, S33). KuonenSQL löscht hart und zeigt kein `_internal` — entscheiden, ob das nachgebaut werden soll (Marc). Erledigt 2026-10-23 (Entscheid Marc: nachbauen; gemessen loesch-v3, 41/43 Blöcke gleich, die übrigen zeigen Rechner und Token des Orakels). `_internal` ist immer die erste Datenbank, mit den Systemtabellen von 3.x (`system.databases`, `system.tables`, `nodes`, `tokens`, `generation_durations`, `plugin_files` …); Anlegen und Schreiben sind ein Namensfehler, Löschen 500. Gelöscht wird weich: Datenbank und Tabellen heissen `name-JJJJMMTTThhmmss` (echt umbenannt, über alle Zugänge sichtbar) und sind in `influx.geloescht` markiert. Eine gelöschte Datenbank ist aus den Listen verschwunden, InfluxQL SHOW DATABASES zeigt sie mit deleted=true; Schreiben dorthin 400, nochmals Löschen 409. Endgültig weg nach 72 h oder `hard_delete_at` (Aufräumen jede Minute im Hintergrund von 3.x). Eine unbekannte Datenbank liest sich wie eine leere, eine Tabelle darin gibt 404. Listen in der Reihenfolge des Anlegens (sql-v3 V4). Dabei behoben: `sp_rename N'schema.t', N'neu'` liess die Tabelle in dbo landen (am Orakel: bleibt im Schema). sql-v3 D4–D6, D10, I4, V4, V5, S33, S35 und grundlagen-v3 A3, A4 werden jetzt verglichen.✅
IX37.12e3.x: Meldungen des InfluxQL-LesersLesefehler und einige Anweisungsfehler von InfluxQL meldet 3.x mit seinem eigenen Leser (nom: `invalid SELECT statement, expected field at pos 6`, `Parsing Error: Nom(...)`, `is not a valid timestamp`, Regex-Fehler 500; influxql-v3 E1–E4, E10–E12, S21). KuonenSQL meldet die Texte von 1.x hinter dem Vorspann von 3.x. Erledigt 2026-10-24, gemessen an 3-core: influxql-v3 E1–E4, E10–E12, S21 und die neue Fixture lesefehler-v3 L1–L10. Die Lesefehler von InfluxQL meldet KuonenSQL wie der eigene Leser von 3.x (Influx3Lesefehler): - Ohne Feld: `invalid SELECT statement, expected field at pos P`; ohne Quelle: `invalid FROM clause, …`. - Bedingung nach WHERE angefangen: `invalid conditional expression`; leeres ORDER BY: `invalid ORDER BY, expected ASC, DESC or TIME`. - Sonst der Rest ab dem Klauselwort: `invalid InfluxQL statement at pos P. Parsing Error: Nom("Rest", Tag)`. Das gilt für ein unbekanntes Wort, ein leeres WHERE, GROUP BY oder LIMIT, eine Klausel an falscher Stelle, ein Komma ohne Feld und SHOW allein (Many1). - `/*` ohne Ende ist ein Kommentarfehler. - Ein kaputter regulärer Ausdruck gibt 500 mit der Meldung von Rust regex (unclosed/unopened group, unclosed character class) samt Zeiger. - Ein Text als Zeitpunkt scheitert bei `split condition … is not a valid timestamp`. Grundlage ist die Stelle und das Erwartete aus dem Lesefehler von 1.x. Tests: InfluxApiV3Tests.Folge_wie_das_Orakel mit influxql-v3 (ohne die bisherigen Ausnahmen) und lesefehler-v3; Lauf Influx, Flux, Beispielkatalog 175/175. Sabotage ok (Abbildung aus → 2 rot).✅
IX37.12f3.x: Token-Verwaltung und influxdb3Aus IX37.12 offen: `/api/v3/configure/token/admin` (Verwaltertoken anlegen, neu erzeugen) und Ressourcentoken mit Rechten je Datenbank (heute meldet sich 3.x mit `Anmeldename:Kennwort` bzw. dem Kennwort an). Abnahme mit dem Werkzeug `influxdb3` (write, query, create database, create token) unter IX37.16. Erledigt 2026-10-24: Token-Verwaltung von 3.x wie influxdb:3-core (Fixture token-v3). - `POST /api/v3/configure/token/admin` (_admin, Kennung 0, ein zweites Mal 409) und `…/admin/regenerate` (das alte Geheimnis gilt nicht mehr). - `POST …/token/named_admin` mit token_name und expiry_secs (409, 400 serde) sowie `DELETE …/token?token_name=` (404 unbekannt, 405 für _admin). - `system.tokens` mit den 10 gemessenen Spalten. Ein Token `apiv3_…` meldet sich als die Anmeldung an, die es angelegt hat. Gespeichert ist nur der SHA-512 in master (Schema influx3); ein abgelaufenes Token gibt 401. Token verwaltet nur ein Verwalter (sonst 403). Bewusst anders: ohne Anmeldung legt KuonenSQL kein erstes Verwaltertoken an (401). Ressourcentoken gibt es wie in Core nicht (Enterprise, am Orakel 404). Tests InfluxApiV3Tests.Token_… (2), Lauf Influx, Flux, Beispielkatalog 167/167. Sabotage ok (Hash nie gleich → 2 rot; Ablauf verschoben → rot). Die Abnahme mit influxdb3 bleibt bei IX37.16a.✅
IX37.133.x: Arrow Flight SQLgRPC über HTTP/2 am 3.x-Port: Flight SQL (`GetFlightInfo`/`DoGet` für `CommandStatementQuery`, vorbereitete Anweisungen, `GetCatalogs`/`GetDbSchemas`/`GetTables`/`GetSqlInfo`), Arrow-IPC-Ausgabe mit den Typen des Orakels (Zeitstempel in ns, Dictionary-Spalten für Tags), Ticket-Format und Kopfzeile für die Datenbank wie gemessen; Token wie IX37.12. Arrow-IPC-Schreiber und Protobuf-Nachrichten im Eigenbau (kein Paket). Abnahme mit `influxdb3-python`, dem ADBC- und JDBC-Flight-SQL-Treiber und Grafana „FlightSQL“. Umgesetzt (Welle G): HTTP/2 im Eigenbau am 3.x-Port (Vorspann erkannt, ohne TLS oder mit ALPN h2; SETTINGS, HEADERS/CONTINUATION, DATA mit Flusskontrolle, PING, GOAWAY; HPACK mit dynamischer Tabelle und Huffman nach RFC 7541), gRPC-Rahmen, Protobuf, FlatBuffers und Arrow IPC (Schema, RecordBatch; Int64/UInt64/Float64/Boolean/Utf8/Timestamp(ns), Nullbarkeit wie 3.x). Flight: DoGet mit dem JSON-Ticket der Clients von 3.x (sql/influxql, params); Flight SQL: GetFlightInfo/DoGet für CommandStatementQuery (Kopfzeile database), GetCatalogs/GetDbSchemas/GetTables/GetTableTypes, vorbereitete Anweisungen (DoAction), Handshake, ListActions; Fehler als gRPC-Status mit den Meldungen von 3.x. Gemessen mit einem eigenen Flight-Client im Test am Orakel (flight-v3, 18 Aufrufe): 17 gleich. Robustheit: zu grosser Rahmen und kaputter Kopfblock beenden nur die Verbindung. Abnahme: Flight liest, was T-SQL, KSQL, PG, MariaDB und MySQL ändern. Rest IX37.13a.✅
IX37.13a3.x: Flight SQL über die Teilmenge hinausOffen aus IX37.13: GetSqlInfo (dense union), DoPut für Parameter vorbereiteter Anweisungen, komprimierte gRPC-Nachrichten, PollFlightInfo, Nachsatz von DataFusion an Lesefehlern (flight-v3 FL8), Ausgabe in mehreren RecordBatches bei grossen Ergebnissen (heute eine). Abnahme mit influxdb3-python, ADBC/JDBC und Grafana FlightSQL unter IX37.16. Erledigt 2026-10-24: gemessen an influxdb:3-core mit pyarrow.flight und dem Testclient (flight-v3 FX1–FX14, FB1–FB6, FY1–FY9). **Flight SQL über die Teilmenge hinaus.** - `GetSqlInfo`: alle 86 Einträge von 3.x als dichte Union (Referenzdaten flightsql_info.json.gz), ausgewählte Kennungen geordnet, unbekannte fallen weg. - `GetXdbcTypeInfo`: fünf Typen; `GetPrimaryKeys`: leer. `GetTables` mit `include_schema`: das Schema je Tabelle von iox (Tags als Wörterbuch, Metadaten von 3.x). - Parameter vorbereiteter Anweisungen über `DoPut`: die erste Zeile der RecordBatch, Spaltenname ohne `$`. Die Antwort `DoPutPreparedStatementResult` trägt ein neues Handle mit den Werten. Das Schema auch mit offenem Platzhalter; der Fehler kommt erst beim DoGet. - Grosse Ergebnisse in mehreren RecordBatches: 600 Zeilen (nach time oder ungeordnet, InfluxQL), 8192 nach einer anderen Spalte; leere ohne RecordBatch. - Der Nachsatz an Lesefehlern (`: sql parser error: Expected: 1`). - gzip und andere Kodierungen: 12 „Content is compressed with `gzip` which isn't supported“. ListFlights, ListActions, GetSchema, PollFlightInfo und DoExchange: 12 „Not yet implemented…“ wie 3.x. **Befund beim Testen, behoben (Engine).** - MERGE prüfte jede Quell- gegen jede Zielzeile; jetzt gleicht ein ON aus Gleichheiten über Eimer ab, mit der vollen Bedingung je Kandidat. - Die Prüfung des Primärschlüssels sucht über die Spalte mit den wenigsten Treffern statt über die führende. - Schreiben von 20000 Punkten über /api/v3/write_lp: vorher quadratisch (8000 Punkte 152 s), jetzt 5 s. **Tests.** InfluxFlightTests (flight-v3 neu gemessen, FL8 wieder verglichen) und MergeindexTests (am Orakel SQL Server 2025 gemessen). Lauf grün. Sabotage je rot: keine Stapel, SqlInfo ungefiltert, kein Nachsatz, Parameternamen mit `$`, Mergeindex aus, Schlüsselprüfung über die erste Spalte, Leerzeichen am Ende im Schlüssel. **Rest:** IX37.13b.✅
IX37.13b3.x: Flight SQL, Rest aus IX37.13aRest aus IX37.13a (gemessen an influxdb:3-core): - Ein Text als Parameter gegen eine Zahlenspalte (`n = $1` mit 'x'): DataFusion vergleicht als Text (leeres Ergebnis), KuonenSQL meldet den Fehler der Umwandlung (flight-v3 FX13, ausgenommen). - `parameter_schema` in ActionCreatePreparedStatementResult: 3.x leitet die Typen der Platzhalter aus der Abfrage ab (`$1: string`, `$2: int64`). - `GetTables` mit `include_schema` für die Tabellen von information_schema und system: deren Schema mit den Typen von 3.x (Utf8View, Duration, UInt16, Zeitstempel mit UTC); heute ein leeres Schema. - Grenzen der RecordBatches eines ungeordneten Ergebnisses: in 3.x folgen sie den Stücken der Ablage (599/1 …), KuonenSQL teilt nach 600. Erledigt 2026-10-24: gemessen an influxdb:3-core (probe13b2, vergleich-v3, flight-v3 FV1–FV16 und FX6 über alle Tabellen). **Text gegen Zahl.** DataFusion wandelt einen Text im Vergleich mit einer Zahlenspalte ohne Fehler: `n = '2'` trifft, `n = 'x'` und `n = '2.0'` werden NULL und treffen nichts. Das gilt direkt im SQL und als Parameter (HTTP und DoPut). Gegen einen Wahrheitswert ist es ein Fehler der Typen (`type_coercion … Boolean = Utf8`, `<>` als `!=`). Eine Zahl gegen einen Tag vergleicht als Text. **parameter_schema.** - CreatePreparedStatement liefert die Typen der Platzhalter. Sie kommen aus dem Partner eines Vergleichs, aus IN, BETWEEN, Rechnungen, Funktionen und `time`; leer ohne Platzhalter. - Ohne Partner: 13 „Unable to infer the type of parameter“. Zwei Typen: 3 „Conflicting types“ mit den Metadaten der Spalten. - Fehler beim Vorbereiten heissen „Error while planning query“ (ein Fehler des Schemas: INVALID_ARGUMENT). - Die Reihenfolge der Felder ist am Orakel je Lauf anders; der Test ordnet sie. **GetTables mit include_schema.** Die Schemas aller Tabellen von information_schema und system wie 3.x, mit den Typen Utf8View, UInt8, UInt16, Duration(ns) und Timestamp(ns, UTC). **Nicht nachgebaut:** Die Grenzen der RecordBatches eines ungeordneten Ergebnisses folgen in 3.x den Stücken der Schreibpuffer (599/1/…). Sie hängen davon ab, wie geschrieben wurde; KuonenSQL teilt nach 600. Auch die Listen von SqlInfo 517 ordnet 3.x je Lauf anders; der Test ordnet sie. **Tests.** InfluxApiV3Tests.Text_gegen_Zahl_wie_das_Orakel, Flight-Tests (FV1–FV16, FX6 voll, FX13 wieder verglichen). Influx 178/178. Sabotage je rot: Systemschemas leer, IN ohne Typ, Textvergleiche aus.✅
IX37.143.x: Caches für letzte und eindeutige WerteLast Value Cache und Distinct Value Cache (`last_cache()`, `distinct_cache()` im SQL, angelegt über IX37.12), im Schreibpfad gefüllt ohne ihn zu bremsen, Ergebnis gleich wie aus der Tabelle; Grenzen (Anzahl Werte, Alter) wie gemessen. Die Python-Verarbeitung (Processing Engine) von 3.x bleibt aussen vor. Umgesetzt (Welle E): Last- und Distinct-Value-Cache, angelegt/gelöscht über `configure/last_cache&#124;distinct_cache` (Vorgaben, Namen, 409/404/400 wie 3.x), gefüllt im Schreibweg aus dem, was nach dem Anlegen geschrieben wird (count, ttl, max_age, max_cardinality), gelesen über `last_cache()`/`distinct_cache()` im SQL und `system.last_caches&#124;distinct_caches` (Spaltenkennungen in der Reihenfolge der Entstehung). Einstellungen in `influx.caches` der Datenbank. Gemessen sql-v3 C1–C23, gleich bis auf die inneren Kennungen/Zeiten der Anlege-Antwort. Rest IX37.14a.✅
IX37.14a3.x: Inhalt der Caches nach NeustartDie Einstellungen der Caches überdauern einen Neustart, ihr Inhalt nicht (3.x füllt ihn aus dem WAL). Beim Start aus der Ablage füllen (je Schlüssel die letzten count Zeilen, je Kombination die jüngste Zeit). Die Antwort beim Anlegen nennt keine inneren Kennungen und Zeiten wie 3.x. Erledigt 2026-10-23: Nach einem Neustart füllt der erste Zugriff die Caches aus der Ablage (3.x aus seinem WAL): der Last-Value-Cache je Schlüssel mit den letzten count Zeilen, der Distinct-Value-Cache je Kombination mit der jüngsten Zeit (nur aus dem Zeitraum, den er zeigt). Die Antwort beim Anlegen nennt wie 3.x database_id, table_id, die Cache-Kennung und die Spalten als {"tag":i}/{"field":[0,i]}, dazu time_ns und sequence_number. sql-v3 C1, C3, C12 werden jetzt verglichen (Zeit und Folgenummer maskiert); Test Caches_nach_dem_Neustart_aus_der_Ablage.✅
IX37.15Fehler, Grenzen und KennungStatuscodes, Fehlertexte und JSON-Formen je Fassung wie das Orakel (Syntaxfehler mit Stelle in InfluxQL, `database not found`, `authorization failed`, `partial write`, `field type conflict`, `points beyond retention policy`, `max-series-per-database` usw.), Abbruch langer Abfragen (`query-timeout`, `KILL QUERY`), Schutz vor übergrossen Antworten (`max-row-limit`, Chunking). Ehrliche Kennung als KuonenSQL ausser in den Feldern, an denen Clients die Fassung erkennen. Umgesetzt (Welle H) für die Grenzen von 1.x, gemessen an influxdb:1.11 mit gesetzten Grenzen: `max-row-limit` (KUONENSQL_INFLUX_MAX_ROW_LIMIT; Zeilen über die ganze Antwort, gekürzte Serie `partial`, danach nichts mehr; chunked ohne Grenze), `query-timeout` (KUONENSQL_INFLUX_QUERY_TIMEOUT in s; `query-timeout limit exceeded`, folgende Anweisungen `not executed`, SHOW DATABASES ausgenommen), `max-series-per-database` (KUONENSQL_INFLUX_MAX_SERIES; neue Schlüssel je Punkt gezählt, Meldung wie 1.x; gilt für 1.x- und 2.x-Schreibwege). `max-values-per-tag` wirkt in 1.11 (TSI) nicht — gemessen, nichts zu tun. Alle voreingestellt aus wie 1.x. Fixtures grenzen-v1, serien-v1, zeitgrenze-v1 gleich. Fehlertexte, Statuscodes und Kennung je Fassung waren mit IX37.4–IX37.14 schon gemessen; KILL QUERY bricht eine laufende Abfrage noch nicht ab (Rest IX37.15a).✅
IX37.15aAbfragen wirklich abbrechen`KILL QUERY` nimmt eine Abfrage aus SHOW QUERIES, bricht ihre Ausführung aber nicht ab, und `query-timeout` prüft erst nach jeder Anweisung (IX37.15). Den Abbruch der laufenden Abfrage bis in den Ausführer und die Engine tragen (Abbruchmarke der Sitzung). Erledigt 2026-10-23: KILL QUERY und query-timeout brechen eine laufende Leseabfrage ab, nicht erst danach. Das Abbruchzeichen der laufenden Abfrage geht als `AbbruchToken` an die Engine (wie ein Attention über TDS) und an den InfluxQL-Ausführer (Prüfpunkte je Quelle, Gruppe und Zeile der Zusammenführung); die Zeitgrenze läuft über `CancelAfter`. Gemeldet wird wie am Orakel `query interrupted` (KILL) bzw. `query-timeout limit exceeded`, die folgenden Anweisungen `not executed`. Verwaltungsanweisungen führt 1.x ganz aus und meldet danach die Grenze (zeitgrenze-v1). Test `Kill_query_bricht_eine_laufende_Abfrage_ab` (Abfrage wartet auf die Sperre einer anderen Sitzung).✅
IX37.16Treiber und WerkzeugeAbnahme gegen die echten Gegenstellen, je im Container oder Testprojekt: Telegraf (Ausgaben `influxdb` und `influxdb_v2`), Grafana (Datenquelle InfluxDB mit InfluxQL, Flux und SQL), `influx` 1.x und 2.x, `influxdb3`, Client-Bibliotheken (`influxdb-client-python`, `influxdb3-python`, `InfluxDB.Client` und `InfluxDB3.Client` für .NET, `@influxdata/influxdb-client` für Node, Java-Client), Node-RED und Home Assistant (Integration InfluxDB, beide Fassungen). Abgenommen (Welle H) gegen einen Server aus dem Zweig (2.x auf 18196, 3.x auf 18197) mit den echten Werkzeugen im Container: `influx` 2.7 (ping, org list, bucket create/list, write, query mit Flux, auth create/list, v1 dbrp list), `influx` 1.11 (SHOW DATABASES, SELECT über die Wege von 1.x am 2.x-Port), Telegraf 1.32 (Ausgaben influxdb und influxdb_v2, ohne Fehler; Daten über InfluxQL und Flux gelesen), `influxdb3` (show/create databases, write, query SQL, InfluxQL und JSON über Flight), Grafana 11.6 (InfluxQL schon IX37.2b; Flux: Health „6 buckets found“, Abfrage mit aggregateWindow; SQL über Flight SQL/gRPC: Health OK, Daten). Befunde behoben: `sa` fehlte als Benutzer von 2.x (`/api/v2/me`, influx auth create), `buckets()` in Flux fehlte (Health von Grafana). Rest IX37.16a.✅
IX37.16aAbnahme mit Client-Bibliotheken und HeimautomationNoch nicht gegen KuonenSQL abgenommen: influxdb-client-python, influxdb3-python, InfluxDB.Client und InfluxDB3.Client (.NET), @influxdata/influxdb-client (Node), Java-Client, Node-RED und Home Assistant (Integration InfluxDB, beide Fassungen). Braucht Pakete aus PyPI/NuGet/npm im Wegwerf-Container (Freigabe für den Download). Erledigt 2026-10-24: abgenommen gegen einen Server aus dem Zweig (2.x auf 18196, 3.x auf 18197), je Client im Wegwerf-Container. **Abgenommen.** - influxdb-client-python: ping, health, buckets, orgs, me, write (Punkt, Zeile, dict, Stapel), query (Tabellen, CSV, Strom, roh, extern-Parameter, DataFrame), delete, InfluxQL über /query. - influxdb3-python: write lp/Point/dict, SQL und InfluxQL über Flight, pandas, Parameter, Aggregate, reader, Schema, Fehler, SHOW TABLES. - InfluxDB.Client 4.18 samt Linq und InfluxDB3.Client 1.3 (.NET). - @influxdata/influxdb-client und @influxdata/influxdb3-client (Node 22). - influxdb-client-java 7.2 und influxdb3-java 1.2. - Node-RED mit node-red-contrib-influxdb (2.x mit Flux, 1.x über DBRP). - Home Assistant mit der Integration InfluxDB (api_version 2 und 1: Schreiben je Zustandswechsel, Sensor `influxdb` liest mit Flux bzw. InfluxQL zurück). **Befunde, behoben.** - 3.x über Flight: `iox::column::type` an jedem Feld (Tag, Zeit, Feldart), durch Alias, CAST, `::`, Vorzeichen, Unterabfrage, WITH, JOIN, UNION (letzter Teil mit Art) und first_value/last_value. InfluxDB3.Client und influxdb3-java lesen daraus in QueryPoints, was Tag und was Feld ist. - Bei InfluxQL am Schema die Gruppe (`tagKeyColumns`), die Messung und der Serienschlüssel. Am Schema des Plans (FlightInfo, vorbereitete Anweisung) bei SQL über einer Tabelle Messung und Serienschlüssel. - NULL-Fähigkeit wie 3.x: CAST einer Spalte, coalesce mit Literal, `time AS x`, information_schema und system, SHOW (ausser value), die Zeit bei einem einzelnen Selektor und GROUP BY time, count. - `first_value`/`last_value` als Aggregat (vorher „Invalid function“). - 2.x: `org` gilt als Kennung, wenn es sich als ID lesen lässt (Home Assistant schickt die ID). Eine unbekannte Org in /api/v2/query ist 400 invalid „failed to decode request body: …“. **Kein Befund.** - Java mit `params` (nur InfluxDB Cloud; 2.7 meldet dasselbe). - QueryPoints ohne Messung bei `SELECT *` (Orakel ebenso). - Gleichstand bei first() → IX37.16b. **Tests.** InfluxFlightTests (25 Fälle FM1–FM25, Fixture flight-v3 neu gemessen) und InfluxApiV2Tests (OI1–OI10, api2-v2 neu gemessen). Lauf Influx 176/176. Sabotage: Feldmetadaten aus, UNION-Regel vertauscht, first_value-Aggregat aus, Org-Kennung aus → je rot.✅
IX37.16b3.x: Gleichstand bei first/last in InfluxQLBefund der Abnahme IX37.16a (gemessen an influxdb:3-core): bei gleicher Zeit wählt `first()`/`last()` in InfluxQL von 3.x den Punkt der ersten Serie (nach Serienschlüssel; `SELECT first(usage) FROM cpu` über a=0.25, b=2.5, c=11.5 zur selben Zeit → 0.25), 1.x und KuonenSQL den grösseren Wert (11.5, `FloatFirstReduce`). Braucht im gemeinsamen Ausführer von InfluxQL einen Schalter für 3.x (auch für min/max bei gleichem Wert prüfen). Erledigt 2026-10-24: gemessen in gleich-v3 (zwei Läufe an influxdb:3-core). **`first`.** Unter gleichen Zeiten nimmt 3.x immer die erste Serie nach Serienschlüssel, für Float, Integer, Text und Boolean (G1, G3, G5, G6). Am 3.x-Zugang gilt das jetzt auch für KuonenSQL; 1.x und 2.x behalten den grösseren Wert. **`last`.** 3.x wählt unter gleichen Zeiten je Lauf eine andere Serie (Lauf 1: 3.0, Lauf 2: 1.0; je Fenster ebenso). Das ist nicht festgelegt und wird nicht nachgebaut (G2, G9, G10 nicht verglichen). **Tests.** InfluxApiV3Tests.Gleiche_Zeiten_bei_first_wie_das_Orakel; Influx-Lauf 177/177. Sabotage (Schalter aus) → rot.✅
IX37.16cSortierung: Erweiterungen wie ß = ss unter CI_ASBefund beim Messen für IX37.13a (am Orakel SQL Server 2025): unter Latin1_General_CI_AS gilt N'Straße' = N'STRASSE' (Erweiterung ß → ss), in KuonenSQL nicht (ON eines MERGE, WHERE, eindeutiger Schlüssel). Prüfen, welche Sortierungen Erweiterungen kennen (ß, æ, œ …), und die Vergleiche danach richten. Erledigt 2026-10-24: am Orakel SQL Server 2025 gemessen (17 Paare unter neun Sortierungen, dazu ORDER BY, GROUP BY, LIKE, CHARINDEX, PATINDEX, REPLACE und PRIMARY KEY). **Erweiterungen wie SQL Server.** - Unter den Windows-Sortierungen (nvarchar und varchar) gelten ß = ss, æ = ae, œ = oe, fi = fi, þ = th, ij = ij, dž = dž (je auch gross). - Unter SQL_Latin1_General_CP1_* gilt das nur für nvarchar; unter BIN/BIN2 nicht. - Nicht gleich sind ẞ = ß, ß = s, ä = ae (nur PhoneBook) und ʼn = 'n. - Gilt für Gleichheit, Ordnung, GROUP BY/DISTINCT, den Primärschlüssel und den Index. **LIKE, CHARINDEX, PATINDEX, REPLACE.** - Ein erweitertes Zeichen trifft nur ganz: `'Straße' LIKE '%ss%'` und `'Strasse' LIKE 'Straße'` treffen, `'Stras%'` und `'Stra__e'` nicht; `_` nimmt ß als ein Zeichen. - CHARINDEX(N'ss', N'Straße') = 5; REPLACE(N'Straße', N's', N'x') = N'xtraße'. **Tests.** ErweiterungenTests (3). Voller Lauf grün. Sabotage je rot: Erweiterungen aus, LIKE ohne Erweiterung. **Nicht nachgebaut:** German_PhoneBook (ä = ae).✅
IX37.17Gleichwertigkeit und AbnahmeDieselben Messwerte über alle Zugänge (T-SQL, KSQL, PostgreSQL, MariaDB, MySQL, GraphQL) lesbar und bearbeitbar, `JOIN` von Zeitreihen mit Stammdaten; Sicherung und Wiederherstellung samt Aufbewahrungsrichtlinien und Continuous Queries; Prüfstand-Beispiele und Handbuchkapitel InfluxDB (M19), Zeitreihen im Objekt-Explorer des Studios; Leck-Lauf, Absturzprüfung über den Zugang; Schreibrate in M34, Dauerlast in M35. Erledigt 2026-10-23: dieselben Messwerte über T-SQL, KSQL, PostgreSQL, MariaDB, MySQL (echte Drahtprotokolle) und GraphQL lesbar und änderbar (`InfluxZugaengeTests`); JOIN mit Stammdaten ohne COLLATE (IX37.17b, Wahl B); BACKUP/RESTORE unter neuem Namen bringt Messungen, Aufbewahrungsrichtlinien und Continuous Queries zurück (`InfluxSicherungTests`, Gegenprobe ohne RESTORE rot). Handbuch, Objekt-Explorer, Lecklauf und Leistung → IX37.17c.✅
IX37.17cInflux: Handbuch, Studio, DauerlaufRest aus IX37.17: Handbuchkapitel InfluxDB-Zugang (Ports, Fassungen, Anmeldung, Grenzen), Influx-Datenbanken im Objekt-Explorer des Studios, Lecklauf und Absturzprobe unter Schreiblast, Leistungsvergleich nach M34/M35. Teil 2026-10-24 (Playground): Die Webseite kennt die Sprachen InfluxQL (8086), Flux (8086, 2.x) und Influx 3 SQL (8181), mit den üblichen Ports als Konstanten in InfluxEinstellungen. Inhalt: - Beispielkatalog mit 4 InfluxQL-, 2 Flux- und 2 SQL-Beispielen; der Wächter BeispielkatalogTests spielt sie über die echte HTTP-Schnittstelle gegen 1.x, 2.x und 3.x ab, mit dem Testclient InfluxClient. - Zugriffstabelle mit zwei Einträgen, Produktseite, Startseite. - Handbuchabschnitt je Sprache mit der Punktetabelle von M37. Sabotage ok (Schreiben im Client aus → 3 rot). Teil 2026-10-25 (Studio): Im Objekt-Explorer des MultiSQLManager stehen unter den Zugängen InfluxDB und InfluxDB 3 die Datenbanken mit Influx-Katalog, darunter Aufbewahrungsrichtlinien, Messungen, Tags und Felder (MSM `BaumInflux`, Orakeltest `ZugangssprachenTests.Der_Baum_zeigt_Datenbanken_Richtlinien_Messungen_Tags_und_Felder`); ihr Menü öffnet InfluxQL/Flux/SQL mit passender Abfrage, jeder Zugang lässt sich im Menü starten und beenden. Offen bleibt der Dauerlauf (Lecklauf, Absturzprobe, Leistung).🔶
IX37.17aMariaDB/MySQL: Schema und Tabelle in BackticksBefund aus `Influx/InfluxZugaengeTests` (23.10.2026): im MariaDB- und MySQL-Dialekt bricht `SELECT temp FROM `autogen`.`wetter`` mit „Unerwartetes Anweisungstoken '`'“ ab (Spalte des zweiten Bezeichners); ohne Backticks geht es. Am Orakel `mariadb` und `mysql` messen (zweiteilige Namen in Backticks, auch mit Leerzeichen) und im Lexer/Parser des Dialekts beheben; danach den Zugangstest auf Backticks umstellen. Erledigt (Welle G): kein Fehler im Zugang. Der Zugangstest rief die Engine direkt mit gesetztem Dialekt auf — die Übersetzung von MariaDB/MySQL (und PostgreSQL) geschieht aber im Anschluss, nicht in der Sitzung. InfluxZugaengeTests sprechen jetzt MariaDB, MySQL und PostgreSQL über ihre Leitungsprotokolle (MariaTestclient, PgTestclient), mit `` `autogen`.`wetter` `` bzw. `"autogen"."wetter"`: 25/25 grün.✅
IX37.17bBefund: JOIN von Tags mit Stammdaten braucht COLLATETags und Textfelder liegen binär sortiert (Latin1_General_BIN2, wie InfluxDB unterscheidet Gross/Klein). Ein JOIN mit einer Textspalte in der Sortierung der Datenbank gibt wie in SQL Server Fehler 468 (Sortierungskonflikt); mit `COLLATE DATABASE_DEFAULT` geht er (InfluxZugaengeTests.GraphQL_und_JOIN_mit_Stammdaten). Entscheid Marc: so lassen (influxtreu) oder Tags in der Sortierung der Datenbank ablegen. Entscheid Marc 23.10.2026: Tags und Textfelder in der Sortierung der Datenbank. Neue Spalten entstehen ohne COLLATE; `_serie` und der Katalog bleiben binär (Sion und sion sind zwei Serien), InfluxQL/Flux/3.x vergleichen selbst binär, das Löschen über 2.x mit Prädikat vergleicht ausdrücklich binär. JOIN mit Stammdaten ohne COLLATE (InfluxAblageTests, InfluxZugaengeTests). Bestehende Tabellen behalten ihre binären Spalten. Sabotage: Tag-Spalten binär → 3 Tests rot.✅
Meilenstein 38 (optional): MongoDB-Zugang — Wire Protocol, BSON, Abfragen und Aggregation (0/15)
PunktTitelBeschreibungStand
MG38.1Referenzorakel MongoDBContainer `mongo:8` nur auf 127.0.0.1, gestartet und entfernt über `dist\orakel-ref.ps1 -Dialekt mongo` (Download erst bei Bedarf, Replikasatz mit einem Mitglied, Zufallszugang), Messwerkzeug `tools/mongo` (Befehle als Extended JSON, Antworten roh als Fixture, dazu der Mitschnitt der Nachrichten am Weiterleiter); erste Messreihe: Handschlag von `mongosh`, Compass und den vier Treibern, CRUD, Fehler.⬜
MG38.2Zugangsmodul MongoDBZugang `Mongo` als Modul über die gemeinsamen Nahtstellen (`docs/neuer-dialekt.md`, `DialektArchitekturTests`): eigener TCP-Hörer (Vorgabe aus; `KUONENSQL_MONGO_PORT`, üblich 27017), TLS wie die übrigen Zugänge, Nachrichtenrahmen (OP_MSG mit Abschnitten und Prüfsumme, OP_QUERY für den Handschlag, OP_COMPRESSED mit zlib und zstd), `hello`/`isMaster` mit den Feldern, an denen die Treiber Fassung und Topologie erkennen (Replikasatz mit einem Mitglied), `buildInfo`, `ping`, `getParameter`, `hostInfo`; Anmeldung SCRAM-SHA-256 und SCRAM-SHA-1 über die gemeinsamen Anmeldungen (M26), `authSource`. Zugangsverwaltung (`sp_kuonensql_zugang`), Lizenzmerkmal.⬜
MG38.3BSONLeser und Schreiber für alle BSON-Typen (Double, String, Dokument, Array, Binärdaten mit Untertyp, ObjectId, Boolean, Datum, Null, Regex, JavaScript, Int32, Timestamp, Int64, Decimal128, MinKey, MaxKey), Extended JSON (kanonisch und entspannt) für Werkzeuge und Fixtures; Abbildung auf die Typen von KuonenSQL und zurück (ObjectId, Decimal128 und Binärdaten verlustfrei); Vergleichs- und Sortierreihenfolge über gemischte Typen wie MongoDB; Fuzzing des Lesers wie PR27.7.⬜
MG38.4Datenmodell und AbbildungDie Abbildung oben (Mischform, entschieden 23.10.2026): Collection als Tabelle mit `_id` und Dokumentspalte, `_id` erzeugt wie MongoDB (ObjectId), Feldpfade (auch in Arrays und eingebetteten Dokumenten) als Ausdrücke des Kerns, gespeicherte berechnete Spalten für indizierte Pfade, Sicht je Collection für die SQL-Zugänge, flache Dokumente aus gewöhnlichen Tabellen; Katalogeintrag, damit Collections in `sys.tables`, `information_schema` und im Studio als solche erkennbar sind. Vorher am Orakel und in KuonenSQL messen, wie schnell `jsonb` beim Filtern ohne Index ist (Entscheidungsgrundlage zur Dokumentspalte).⬜
MG38.5CRUD und Cursor`insert`, `find` (Filter, Projektion samt `$slice`/`$elemMatch`, `sort`, `skip`, `limit`, `hint`, `collation`, `batchSize`), `getMore`, `killCursors`, `update` mit allen Aktualisierungsoperatoren (`$set`, `$unset`, `$inc`, `$mul`, `$min`, `$max`, `$rename`, `$currentDate`, `$push` mit `$each`/`$slice`/`$sort`/`$position`, `$pull`, `$pullAll`, `$addToSet`, `$pop`, positionale `$`, `$[]`, `$[<id>]` mit `arrayFilters`) und Pipeline-Updates, `upsert`, `delete`, `findAndModify`, `count`, `distinct`, `bulkWrite` (geordnet und ungeordnet), Schreibfehler je Dokument wie das Orakel.⬜
MG38.6AbfrageoperatorenVergleich (`$eq` … `$nin`), logisch (`$and`, `$or`, `$nor`, `$not`), Element (`$exists`, `$type`), Auswertung (`$regex` mit Optionen, `$expr`, `$mod`, `$jsonSchema`, `$text`), Arrays (`$all`, `$elemMatch`, `$size`), Bitoperatoren, Geodaten (`$geoWithin`, `$geoIntersects`, `$near`, `$nearSphere` auf `geometry`/`geography` des Kerns); Semantik der Arrays (ein Filter trifft, wenn ein Element trifft) und von fehlenden Feldern genau wie gemessen.⬜
MG38.7Indizes`createIndexes`/`listIndexes`/`dropIndexes`: einfach, zusammengesetzt, Multikey (Arrays), `unique`, `partialFilterExpression`, `sparse`, TTL (Hintergrunddienst, Takt wie gemessen, nie im Schreibpfad), Text, `2dsphere`, Wildcard, verborgen; Auswahl durch den Optimierer des Kerns; `explain` (`queryPlanner`, `executionStats`) in der Form von MongoDB.⬜
MG38.8Aggregation Pipeline: Grundstufen`aggregate` mit Cursor: `$match`, `$project`, `$addFields`/`$set`, `$unset`, `$group` mit allen Akkumulatoren, `$sort`, `$limit`, `$skip`, `$count`, `$unwind`, `$lookup` (auch mit Pipeline), `$facet`, `$bucket`, `$bucketAuto`, `$sortByCount`, `$replaceRoot`/`$replaceWith`, `$out`, `$merge`; die Ausdrucksoperatoren (Arithmetik, Zeichenketten, Datum mit Zeitzonen, Bedingungen, Arrays samt `$map`/`$filter`/`$reduce`, Typumwandlung `$convert`); so weit möglich in SQL des Kerns übersetzt, damit Optimierer und Indizes greifen.⬜
MG38.9Aggregation Pipeline: weitere Stufen`$graphLookup`, `$setWindowFields`, `$densify`, `$fill`, `$unionWith`, `$sample`, `$redact`, `$geoNear`, `$collStats`, `$indexStats`, `$currentOp`, `$documents`, Ansichten (`create` mit `viewOn`/`pipeline`).⬜
MG38.10Verwaltung, Benutzer und Rechte`listDatabases`, `listCollections`, `create` (Validator, Capped, Zeitreihen-Collections, Kollation), `drop`, `dropDatabase`, `renameCollection`, `collMod`, `dbStats`, `collStats`, `serverStatus`, `currentOp`, `killOp`, `connectionStatus`; `createUser`/`updateUser`/`dropUser`, `createRole`, `grantRolesToUser` und die eingebauten Rollen (`read`, `readWrite`, `dbAdmin`, `userAdmin`, `root` …) auf die gemeinsamen Rechte (M26).⬜
MG38.11Sitzungen und TransaktionenLogische Sitzungen (`lsid`, `endSessions`), Transaktionen über mehrere Dokumente und Collections (`txnNumber`, `startTransaction`, `commitTransaction`, `abortTransaction`) auf den Transaktionen des Kerns, `readConcern`/`writeConcern` wie bei einem Replikasatz mit einem Mitglied, wiederholbare Schreibvorgänge, Fehlerkennzeichen (`TransientTransactionError` …) wie gemessen.⬜
MG38.12Change Streams`$changeStream` auf Collection, Datenbank und Server aus dem Änderungsstrom des Kerns (V14.9c, bestätigte Änderungen): `insert`/`update`/`replace`/`delete`/`drop`/`rename`, `fullDocument` (`updateLookup`, `whenAvailable`), `fullDocumentBeforeChange`, Wiederaufnahme mit `resumeAfter`/`startAfter`/`startAtOperationTime`; auch Änderungen über die anderen Zugänge erscheinen im Strom.⬜
MG38.13Fehler, Grenzen und KennungFehlercodes und -namen (`DuplicateKey` 11000, `WriteConflict`, `Unauthorized` …), Dokumentgrösse 16 MB, Verschachtelungstiefe, `maxTimeMS`, Abbruch langer Abfragen; ehrliche Kennung als KuonenSQL ausser in den Feldern, an denen Treiber die Fassung erkennen.⬜
MG38.14Treiber und WerkzeugeAbnahme gegen die echten Gegenstellen: `mongosh`, MongoDB Compass, `mongodump`/`mongorestore`/`mongoimport`/`mongoexport`, die Treiber für .NET (`MongoDB.Driver`), Java, Node.js (samt Mongoose) und Python (`pymongo`), GridFS über die Treiber, Studio 3T.⬜
MG38.15Gleichwertigkeit und AbnahmeÜber jeden Port auf jede Datenbank (Vorgabe Marc 23.10.2026): Collections über T-SQL, KSQL, PostgreSQL, MariaDB, MySQL, GraphQL und InfluxDB lesbar und änderbar, jede Tabelle der anderen Zugänge über den MongoDB-Port eine Collection, `JOIN` von Dokumenten mit relationalen Daten; der MultiSQLManager verbindet sich über den MongoDB-Port (eigenes Plugin oder Verbindungstyp, Objekt-Explorer, Abfragefenster mit `mongosh`-Syntax); Kreuzmatrix wie `KuonenSQL/tools/zugaenge/kreuz-datenbanken.cs` als Test; Sicherung und Wiederherstellung samt Indizes und Validatoren; Handbuchkapitel MongoDB (M19); Leck-Lauf, Absturzprüfung über den Zugang.⬜
Meilenstein 34: Leistungstests — erst nach dem gesamten Bau (3/9)
PunktTitelBeschreibungStand
L6.1Der Abstand beim LesenNachgemessen am 30.09.2026 (Median dreier Läufe, 10 000 Zeilen, nach P15.4b/P15.5b/P15.5c): Unicode-Sortierung `TOP 100` **2,2×**, Gleichheit **3,8×**, `LIKE` mit Umlaut **1,2×**, `LEN`/`DATALENGTH` **7,6×** (Streuung 4,5–7,7); bei 1 000 Zeilen alle vier zwischen **0,8× und 1,6×**. Gegenüber dem Stand vor diesen drei Paketen (3febaf4, gleiche Messung) besser bei Sortierung (2,4 → 2,2), Gleichheit (4,6 → 3,8), `LIKE` (1,3 → 1,2), nicht bei `LEN`/`DATALENGTH` (5,2 → 7,6, im Rauschen der drei Läufe). Was bleibt, ist die Bauart — Werte in Hüllen und `LEN`, das je Zeile eine Zeichenkette anlegt. **Nachgemessen am 13.10.2026 gegen SQL Server 2025 im Docker (beide im Container, kürzeste von 15–20 Läufen):** Unicode-Reihe höchstens **2,1×**, 250 Spalten über die Leitung **1,2–2,0×**; 100 000 Zeilen: `DISTINCT` 1,95×, Unterabfrage 2,4×, Verbund 2,5×, `TOP 100` 2,5×, Filter 2,6×, Gruppieren 2,8× (Verbund mit Gruppierung 22× → 2,4× durch Hash-Aggregat), Fenster 5,6× → **3,7×**; lange Werte über die Leitung **0,99×** (SqlClient las sie vorher falsch: PLP-Stücke enden jetzt an den Paketgrenzen). Offen: L6.1a.🔶
L6.2Die beiden Schreibausreisser`UPDATE` über eine Tabelle mit 250 Spalten: **132× → 20×**. Zwei Ursachen behoben — die Schlüsselsuche entzifferte je Sondierung die ganze Zeile, und ein Textschlüssel wurde je Sondierung neu normalisiert. **Nachgemessen am 30.09.2026 (Median dreier Läufe, eigener Server ohne Ablage, Orakel 2022):** `UPDATE` einer Spalte **28×** (vor P15.4b/5b/5c: 34×; 20× ließ sich nicht wiederholen), Einfügen der langen Werte **9,7×** (vor den Paketen 11×; die frühere Angabe 0,63× ließ sich auf einem frischen Server nicht wiederholen). Beide Ausreißer sind damit **nicht** geschlossen. **Nachgemessen am 13.10.2026 gegen SQL Server 2025 im Docker:** `UPDATE` einer Spalte über 250 Spalten **13,5× → 6,3×** (14,4 ms), weil geänderte Spalten fester Länge in die gespeicherte Zeile geflickt werden. Offen: L6.2a.🔶
L6.3Native-AOTDer Server läuft als **eine einzige Datei** mit eingebauter Laufzeit (`dist\einzeldatei-bauen.ps1`, 40 MB, sechs Plattformen). Der Verzicht auf den JIT bleibt offen: 53 Stellen im Katalogabzug und 14 im CLR-Laden leben von Reflexion.🔶
L6.1aLesen unter 2×Rest aus L6.1 (13.10.2026): Fenster 3,7×, Summen und Filter über 250 Spalten rund 3×, `DISTINCT` 2,7–3×, Gruppieren 2,8×, `SUM(DATALENGTH(…))` über lange Werte 7,2×. **13.10.2026:** DATALENGTH über lange Werte 15× → 0,58× (liest die gespeicherte Länge). Breite Zeile ohne Felder über alle Spalten: Summen 3,19× → 1,64×, Sortieren 2,23× → 1,11×, DISTINCT 2,69× → 2,19×, Filter 2,75× → 2,41×.⬜
L6.1bDISTINCT und GROUP BY nach der Sortierung der SpalteGefunden 13.10.2026: unter einer Spaltensortierung, die gröber ist als die der Sitzung (Latin1_General_CI_AI: a = ä = A), liefert DISTINCT 2 statt 1 Wert und GROUP BY 3 statt 1 Gruppe; WHERE vergleicht richtig. Der gelesene Wert trägt die Sortierung seiner Spalte nicht mit (SQL_VARIANT_PROPERTY meldet die der Datenbank). Behoben 13.10.2026: GROUP BY und PARTITION BY nahmen den genauen Text (am Orakel CI_AS: a, A, 'a ', b = 2 Gruppen, KuonenSQL 4); jetzt fassen Gruppierung, Fenster und DISTINCT Zeichenketten nach der Sortierung des Werts zusammen (Sortierung.Von: erzwungen, Spalte, Sitzung).✅
L6.1cLangläufer nach der L6.1-Reihe wieder grünERLEDIGT 14.10.2026: der grosse Lauf (volltest -Alle) hatte 8 rote Langläufer, gefunden per Bisektion. 5763a1bb (Paketgrösse des Clients): die Senke schickte erst ab 8 vollen Paketen, mit 8000 statt 4096 Bytes lagen beim Warten auf eine Sperre bis 64 KB beim Server (6 Strom-Tests, der Client sah 2716 statt 2800 Zeilen) - jetzt gehen vor jedem Sperrwarten alle vollen Pakete hinaus, wie bei SQL Server. d699bb2a (GROUP BY im Durchlauf): die Zwischenstände übergingen die Speicherzuteilung (bis 65 536 Gruppen jeder Grösse; der Strom-Test mit 40 000 breiten Gruppen) - sie zählen jetzt auf die Zuteilung, darüber geht es den langen Weg, der auslagert; der Auslagerungstest jeder Art erzwingt diesen Weg mit HAVING wie seine Geschwister. Grosser Lauf 10 254 Tests grün✅
L6.1dSpeicher je kleiner Abfrage wieder unter der GrenzeGefunden 22.10.2026: `BaumdurchlaufTests` (Grenze 78 000 Byte je Abfrage) war im Volltest rot (81 000). Der Wert wuchs über die Tickets von 72 700 (13.10.) auf rund 77 400; eine Speicherspur (EventPipe, GCAllocationTick mit Stapel) zeigte als vermeidbare Posten verpackte Zahlen und Aufzählungen im Baumdurchlauf der Übersetzungsprüfung und die Namenszerlegung der ch-Sichten bei jeder Tabelle. **Erledigt 22.10.2026:** der Durchlauf liest nur Eigenschaften, die einen Knoten enthalten können; `Chsichten.Sicht` zerlegt nur Namen mit „ch“; 71 200 Byte je Abfrage, Grenze auf 76 000 enger gestellt.✅
L6.2aAktualisierer ohne unveränderte SpaltenRest aus L6.2 (6,3×): der Aktualisierer entziffert und kopiert je Zeile alle Spalten; Trigger, berechnete Spalten, Prüfbedingungen, Fremdschlüssel und Indexpflege bekommen heute ein ganzes Wertefeld. Teil 1 (13.10.2026): alte Zeile faul, neue als Auflage über der alten, Richtlinien und Fremdschlüssel je Anweisung - im Prozess 33 → 9,9 MB je UPDATE, am Server 6,49× → 4,46×. Offen: die beiden Abschriften der Bytes, 1,2 KB SqlValue[] je Zeile.⬜
LL25.2Warum acht sättigende Sitzungen einer neunten rund 80 % nehmen**Befund aus LL25.1:** auf 32 Kernen verliert selbst `SELECT 1` 76 bis 79 %, wenn acht Clients (GraphQL oder SQL) den Server sättigen. **Zu tun:** die gemeinsame Engstelle finden (globale Sperre in der Engine, Thread-Pool-Aushungerung, Ablage-Sperre) und messen; **bestanden, wenn** die Reihe aus LL25.1 höchstens 30 % Verlust zeigt⬜
Meilenstein 35: Lasttests — das Allerletzte (6/22)
PunktTitelBeschreibungStand
LL21.1Dauerlauf über Stunden (vorher P15.7)**Werkzeug:** `tools/dauerlast` (`dotnet run -c Release --project tools/dauerlast -- <KuonenSQL.dll> <Arbeitsverzeichnis> 240 50`; startet einen eigenen Server auf eigenen Ports und schreibt alle 60 s CPU-Anteil, Durchsatz, Working Set und offene Verbindungen; vorhanden, bisher 30 Minuten gefahren: `tools/dauerlast/messung-2026-09-30-30min.txt`). **Gegenüber:** keines, es ist ein Selbstvergleich Anfang gegen Ende. **Last:** vier Stunden, TDS-Verbindung mit gemischtem Lesen, Ändern und Einfügen auf 50 Anfragen/s gedrosselt (das Werkzeug fährt daneben eine MariaDB-Verbindung; ausgewertet wird die TDS-Spalte), danach mit Rate 500 für eine zweite Stunde. **Gemessen:** Anfragen und Fehler je Minute, Working Set, offene Verbindungen. **Bestanden, wenn:** 0 Fehler; Durchsatz der letzten Stunde mindestens 90 % der ersten; Working Set steigt nach Abzug des Zeilenwachstums (rund 400 Byte je eingefügter Zeile) zwischen der zweiten Stunde und dem Ende um höchstens 10 %; offene Verbindungen am Ende gleich der Zahl der Clients; die Rohdaten stehen als `tools/dauerlast/messung-<Datum>-4h.txt` im Repository **Teilergebnis 02.10.2026 (v1.11.728, 4 h mit Rate 50, Rohdaten `tools/dauerlast/messung-2026-10-02-4h.txt`):** TDS 720 000 Anfragen, 0 Fehler, Durchsatz letzte Stunde 99,7 % der ersten, Working Set ohne Wachstum über das Zeilenwachstum hinaus, 2 von 2 Verbindungen offen. **Offen:** die zweite Stunde mit Rate 500.⬜
LL21.2Ein Test, der unter Parallellast zweimal rot war**Erledigt (01.10.2026), Ursache gefunden:** keine Last, sondern geteilter Zustand. `DBCC CHECKCONSTRAINTS` ohne Tabelle prüft die ganze Datenbank; der Test lief in `master`, und `MassenkopieTests` (S12.6) legt dort gleichzeitig Tabellen `zz_opt_…` mit absichtlich verletzter Prüfbedingung (`n = 500`, Massenimport ohne `CHECK_CONSTRAINTS`) an — im Mitschnitt stand deren Zeile vor der eigenen. Nur wenn beide Klassen zeitgleich liefen, also im vollen Lauf, kippte er. Behoben: jeder Test von `DbccEinschraenkungenTests` läuft in einer eigenen Datenbank.✅
LL22.1Dauerlauf des MariaDB-Ports über Stunden (Rest von D17.11b und P15.7)**Werkzeug:** `tools/dauerlast` mit der MariaDB-Seite (`tools/dauerlast/maria_last.py`, pymysql), vier Stunden: `dotnet run -c Release --project tools/dauerlast -- <KuonenSQL.dll> <Arbeitsverzeichnis> 240 50` (die 30-Minuten-Fassung ist D17.11b). **Gegenüber:** keines (Selbstvergleich Anfang gegen Ende). **Last:** eine MariaDB-Verbindung, 50 Anfragen/s gemischt (Lesen, Ändern, Einfügen). **Gemessen:** wie LL21.1, Spalte MariaDB. **Bestanden, wenn:** dieselben Schwellen wie LL21.1 (0 Fehler, Durchsatz der letzten Stunde mindestens 90 % der ersten, Working Set höchstens plus 10 % nach Abzug des Zeilenwachstums, offene Verbindungen gleich der Zahl der Clients) **Ergebnis 02.10.2026 (v1.11.728, Release, 4 h, Rohdaten `tools/dauerlast/messung-2026-10-02-4h.txt`): bestanden.** MariaDB 720 000 Anfragen, 0 Fehler; Durchsatz letzte Stunde 99,7 % der ersten (49,86 zu 50,01/s); Working Set 123 MB nach der zweiten Stunde, 158 MB am Ende bei rund 216 000 eingefügten Zeilen (erwartet etwa 86 MB Zuwachs, gemessen 35 MB); 2 offene Verbindungen für 2 Clients.✅
LL22.2`sysbench` gegen einen echten MariaDB, mehrere Verbindungen (Rest von D17.11b)Der Rest von D17.11b, den AGENTS.md den Agenten verbietet. **Werkzeug:** `sysbench oltp_read_write` (aus dem Docker-Bild `severalnines/sysbench` oder lokal) mit `--mysql-host` auf den KuonenSQL-Port 3306. **Gegenüber:** `mariadb:11` im selben Docker-Netz mit demselben Aufruf. **Last:** Tabelle mit 10 000 Zeilen, 16 Threads, 300 s, drei Läufe (Median). **Gemessen:** Transaktionen und Anfragen je Sekunde, 95. Perzentil der Latenz, Fehler. **Bestanden, wenn:** 0 Fehler, die Summenprüfung nach dem Lauf stimmt und der Durchsatz von KuonenSQL liegt bei mindestens einem Drittel des echten MariaDB (Ziel Schreiben ≤ 3× aus M34); jede Abweichung darunter wird als Punkt in M17 oder M34 eingetragen⬜
LL22.3Sperren unter Last (gefunden im ersten Lauf von LL22.2)sysbench `oltp_read_write` mit 16 Threads brach mit 1062 ab (zwei Transaktionen löschen und fügen dieselbe id ein) und blieb im zweiten Lauf stehen. **Ursache:** UPDATE und DELETE fanden eine Zeile, die eine offene fremde Transaktion gelöscht hatte, nicht mehr und warteten nicht auf sie; der Hänger war die Folge (die Verbindung mit dem 1062 hielt ihre offene Transaktion, MariaDB hatte keine Sperrfrist). **Gebaut:** UPDATE und DELETE warten unter der Tabellensperre auf fremd gelöschte oder geänderte Zeilen, deren altes Bild die Bedingung trifft, und lesen danach den neuen Stand (SQL Server 2025 am Orakel gemessen, InnoDB); PostgreSQL bewertet nach dem Warten nur dieselbe Zeile neu (gelöscht oder ersetzt heißt: auslassen, danach 23505); MariaDB meldet das Deadlockopfer als 1213 und wartet höchstens `innodb_lock_wait_timeout` (50 s, `SET SESSION`, danach 1205 bei offener Transaktion); ein gewöhnliches SELECT wartet unter MariaDB und PostgreSQL nicht auf Schreiber (konsistentes Lesen des bestätigten Stands). **Belegt:** Tests über TDS, MariaDB- und PostgreSQL-Port mit zwei Verbindungen sowie `SysbenchArtigeLastTests` (16 Verbindungen, 30 s, 0 Fehler, 0 Hänger, rund 12 000 Transaktionen). Rest: der erneute sysbench-Lauf in LL22.2✅
LL22.3bMariaDB REPEATABLE READ: Lesestand je TransaktionEine MariaDB-Sitzung beginnt wie InnoDB unter REPEATABLE READ (`Dialekt.IsolationsstufeVorgabe`); ein gewöhnliches SELECT in einer Transaktion liest dann den Stand vom ersten Lesen an über den Versionsspeicher von SNAPSHOT, ohne zu warten und ohne zu sperren, sieht aber die eigenen Änderungen (`StandBei`), und DML liest weiter sperrend den neuesten Stand (kein 3960), auch was es selbst liest (`INSERT … SELECT`, Unterabfragen, Trigger: geteilte Sperre bis zum Transaktionsende, Warten auf Schreiber). `SET [SESSION] TRANSACTION ISOLATION LEVEL …` und `SET transaction_isolation = '…'` wirken, `@@transaction_isolation`/`@@tx_isolation` zeigen die Stufe der Sitzung. PostgreSQL unter REPEATABLE READ liest ebenso. `SET TRANSACTION …` ohne `SESSION` gilt wie bei MariaDB nur für die nächste Transaktion, in einer laufenden 1568✅
LL23.1Lastreihe `pgbench -c 16` (vorher P18.11c)**Werkzeug:** `pgbench` aus dem Bild `postgres:17` (`docker run --rm --network <Netz> postgres:17 pgbench -h <KuonenSQL> -p 5432 -c 16 -j 4 -T 300 -U … <Datenbank>`; vorher `pgbench -i -s 10`). Die gedrosselte Einzelverbindung ist P18.11b (`PgGedrosselteLastTests`). **Gegenüber:** ein echtes `postgres:17` im selben Netz mit demselben Aufruf; nebenher dieselbe TDS-Last aus `tools/leistung/Sql-Messung.ps1` gegen denselben KuonenSQL-Prozess. **Last:** 16 Verbindungen, 300 s, TPC-B-Skript, drei Läufe (Median). **Gemessen:** Transaktionen je Sekunde, mittlere Latenz, Fehler, danach die Summen von Konten, Kassen, Filialen und Buchungen; bei der TDS-Last die Verlangsamung gegen den Lauf ohne `pgbench`. **Bestanden, wenn:** 0 Fehler, die Summen stimmen, der Durchsatz von KuonenSQL liegt bei mindestens einem Drittel des echten PostgreSQL (Ziel Schreiben ≤ 3× aus M34), die TDS-Last wird durch `pgbench` um höchstens 30 % langsamer (R13.5: 5 bis 18 %, G16.7: 8 bis 27 %). **Stand 02.10.2026:** der erste Lauf (Rohdaten `orakel/`, nicht eingecheckt) hatte 0 Fehler und stimmende Summen, fiel aber 73 → 59 → 33 tps bei 237 MB → 1,7 GB gegen 487 bis 578 tps von `postgres:17`; Ursache und vier weitere Kosten sind in **P15.9** behoben (im selben Prozess nachgestellt: 171 → 876 tps, drei 3-min-Läufe flach), dazu P15.9b; offen in **P15.9c/d**. Der Container-Lauf ist nach P15.9 noch nicht wiederholt (Docker war nicht verfügbar)⬜
LL23.2Werkzeuge mit Oberfläche: pgAdmin, DBeaver, SSMS (vorher P18.12d)**Werkzeug:** `dpage/pgadmin4` im Container und DBeaver auf dem Rechner, je mit der Anmeldung von psql; in SSMS ein Schema `postgres`. Von Hand zu bedienen, nicht im Testlauf prüfbar. **Gegenüber:** dieselben Schritte gegen `postgres:17`. **Last:** keine, es ist eine Bedienprobe: verbinden, Objektbaum öffnen (Schemas, Tabellen, Sichten, Spalten, Indizes, Funktionen), Abfragefenster mit `SELECT`, Tabellendaten ansehen, eine Zelle ändern und speichern, Tabelle anlegen und löschen, Eigenschaftenfenster einer Tabelle öffnen. **Gemessen:** jeder Schritt gelingt; Fehlermeldung des Werkzeugs und Serverprotokoll. **Bestanden, wenn:** alle Schritte laufen ohne Fehlermeldung und ohne eine vom Server abgelehnte Anweisung; jede Abweichung gegen `postgres:17` steht als eigene Zeile in M18⬜
LL23.3Dauerlauf des PostgreSQL-Ports über Stunden**Werkzeug:** `tools/dauerlast`, dessen Erweiterung Teil dieser Zeile ist: neben der TDS- und der MariaDB-Verbindung eine Npgsql-Verbindung auf Port 5432 (Server-Start mit PostgreSQL-Port, Tabelle `dauerlast`, gleiche Mischung aus Lesen, Ändern und Einfügen, eigene Spalte in der Minutenausgabe); Aufruf `dotnet run -c Release --project tools/dauerlast -- <KuonenSQL.dll> <Arbeitsverzeichnis> 240 50`. **Gegenüber:** keines (Selbstvergleich Anfang gegen Ende). **Last:** eine PostgreSQL-Verbindung, 50 Anfragen/s gemischt, vier Stunden. **Gemessen:** wie LL21.1, Spalte PostgreSQL. **Bestanden, wenn:** dieselben Schwellen wie LL21.1 und LL22.1 (0 Fehler, Durchsatz der letzten Stunde mindestens 90 % der ersten, Working Set höchstens plus 10 % nach Abzug des Zeilenwachstums, offene Verbindungen gleich der Zahl der Clients); Rohdaten als `tools/dauerlast/messung-<Datum>-4h-pg.txt`⬜
LL24.1Redis-Vergleich nach P15.6 nachmessen (vorher P15.6b)**Werkzeug:** `tools/leistung/Redis-Messung.ps1` (`redis-benchmark` im Docker-Netz). **Gegenüber:** `redis:7-alpine` im selben Netz, vom Skript angelegt. **Last:** Standard des Skripts, 200 000 Anfragen, 20 Verbindungen, Bündel 16, Befehle `set,get,incr,lpush,rpush,lpop,rpop,sadd,spop,hset,zadd,mset`, drei Läufe (Median; die Streuung eines Laufs liegt bei ±20 %). **Gemessen:** Anfragen je Sekunde je Befehl, Verhältnis KuonenSQL zu Redis; die Tabelle in `docs/leistung.md` §2 trägt noch die Zahlen vor P15.6 und wird ersetzt. **Bestanden, wenn:** kein Befehl schlechter als der bisher schlechteste Wert (0,29× des echten Redis) abzüglich der Streuung, also nicht unter 0,25×; die neue Tabelle steht in `docs/leistung.md` mit Datum und Fassung **Ergebnis 02.10.2026 (v1.11.728, drei Läufe, Median): bestanden.** KuonenSQL zu `redis:7-alpine` je Befehl: GET 1,12x, HSET 1,10x, INCR 1,17x, LPOP 1,06x, LPUSH 1,12x, MSET 1,45x, RPOP 1,13x, RPUSH 1,07x, SADD 1,16x, SET 1,10x, SPOP 1,07x, ZADD 1,03x; kein Befehl unter 0,25x.✅
LL25.1Die sättigende Messreihe aus G16.7 (vorher G16.9)**Werkzeug:** `tools/leistung/Nebeneinander-GraphQL.ps1 -Nebenlast voll -Wiederholungen 3` (acht Prozesse füttern den GraphQL-Port bis zur Sättigung), dazu derselbe Aufruf mit `-Nebenweg sql` als Vergleich, was jede gleich schwere zweite SQL-Last kostet. **Gegenüber:** die SQL-Last allein (aus demselben Skript) und die gleiche Arbeit als Verbund-`SELECT` über TDS. **Last:** 5000 Zeilen, 60 Durchgänge, Nebenlast sättigend, drei Läufe, Median. **Gemessen:** Verlangsamung der SQL-Seite durch GraphQL daneben; der GraphQL-eigene Anteil gegen die gleich schwere SQL-Nebenlast. **Bestanden, wenn:** die SQL-Seite bei sättigender GraphQL-Last um höchstens 30 % langsamer wird (R13.5: 5 bis 18 %, gewöhnliche GraphQL-Last: 8 bis 27 %) und der GraphQL-eigene Anteil höchstens 10 Prozentpunkte über der gleich schweren SQL-Nebenlast liegt; die Zahlen stehen in `docs/leistung.md` **Gefahren 2026-10-02:** (`docs/leistung.md`, „Die sättigende Reihe“): GraphQL-eigener Anteil höchstens 3,5 Pp über der gleich schweren SQL-Nebenlast (**erfüllt**); SQL-Seite aber −73 bis −93 % statt höchstens −30 % (**verfehlt**), mit SQL-Nebenlast gleich (−73 bis −93 %) — Eigenschaft des Servers, nicht von GraphQL; Rest als LL25.2✅
PR27.1aAbsturz-Prüfstand: 10 000 Abstürze (Lauf für Hauptchat/Nutzer)`powershell -File tools\absturz\lauf.ps1 -Abstuerze 10000` (etwa ein Tag); Rohdaten als `tools/absturz/messung-<Datum>.txt`. **Bestanden:** Rückgabewert 0, kein Befund; jeder Befund ein eigener Fehler mit Test.⬜
PR27.3aKontrollpunkt unter Dauerlast: langer Lauf (Hauptchat/Nutzer)`dotnet build tools\absturz -c Release`, dann `dotnet tools\absturz\bin\Release\net10.0\Absturz.dll <Server.dll> <ordner> 1 1 240` (4 Stunden, 16 Verbindungen). **Bestanden:** Protokoll bleibt unter einer festen Grenze (Schwelle 8 MB + Rest offener Transaktionen), Wiederanlauf unter 10 s, 0 Befunde; Ausgabe als `tools/absturz/messung-<Datum>-dauer.txt`.⬜
PR27.3dWiederanlauf bei langem Bestand nachmessen (Hauptchat/Nutzer)`tools/absturz` mit 1–6 Mio. Zeilen mit Text im Container (Release): Wiederanlaufzeit vor dem Merge (Abbild von `main`) und danach, in `docs/leistung.md` §15 nachtragen. **Bestanden:** unter 10 s (Ziel PR27.3).⬜
PR27.7a24-Stunden-Lauf je Protokoll`dist\fuzzing.ps1 -Sekunden 28800 -Protokoll Tds` (ebenso `MariaDb`, `PostgreSql`) vom Hauptchat oder Nutzer (kein Agent: 72 Stunden Rechenzeit); ein Befund wird ein Fehler mit Test. 45 s je Zeile mit Startwert 77 ohne Befund.⬜
LT35.1Lastlauf über den KSQL-Port (aus K33.15)Mehrere Verbindungen über `Microsoft.Data.SqlClient` auf Port 1333, gemischt Lesen/Schreiben, gleichzeitig mit T-SQL, MariaDB und PostgreSQL auf derselben Datenbank (M26).⬜
LT35.2Lastlauf Oracle (aus OR31.21)Lastlauf mit mehreren Verbindungen über python-oracledb, sobald M31 gebaut ist.⬜
LT35.3Lastlauf Db2 (aus DB32.19)Lastlauf mit mehreren Verbindungen über ibm_db, sobald M32 gebaut ist.⬜
DL28.1Dauerlauf 72 Stunden: T-SQL (TDS)Nur der TDS-Port. `tools/dauerlast` (erweitert um je 16 Verbindungen je Zugang, gemischt Lesen/Schreiben, Verbundabfragen, Transaktionen über mehrere Anweisungen, stündlich eine Sicherung), 72 Stunden ohne Unterbrechung, Release-Fassung im Container mit fester Speichergrenze. **Gemessen:** jede Minute Durchsatz, Fehler, Antwortzeit (Median, 99. Perzentil), Working Set, Protokollgrösse, offene Verbindungen; am Ende Summenprüfung und `DBCC CHECKDB`. **Bestanden, wenn:** 0 Fehler; Durchsatz der letzten Stunde mindestens 90 % der ersten; 99. Perzentil der letzten Stunde höchstens 150 % der ersten; Speicher ohne Wachstum über das Datenwachstum hinaus; Protokoll begrenzt (PR27.3); Summen und `DBCC CHECKDB` fehlerfrei; Rohdaten als `tools/dauerlast/messung-<Datum>-72h-<zugang>.txt` im Repository.⬜
DL28.2Dauerlauf 72 Stunden: MariaDBNur der MariaDB-Port. `tools/dauerlast` (erweitert um je 16 Verbindungen je Zugang, gemischt Lesen/Schreiben, Verbundabfragen, Transaktionen über mehrere Anweisungen, stündlich eine Sicherung), 72 Stunden ohne Unterbrechung, Release-Fassung im Container mit fester Speichergrenze. **Gemessen:** jede Minute Durchsatz, Fehler, Antwortzeit (Median, 99. Perzentil), Working Set, Protokollgrösse, offene Verbindungen; am Ende Summenprüfung und `DBCC CHECKDB`. **Bestanden, wenn:** 0 Fehler; Durchsatz der letzten Stunde mindestens 90 % der ersten; 99. Perzentil der letzten Stunde höchstens 150 % der ersten; Speicher ohne Wachstum über das Datenwachstum hinaus; Protokoll begrenzt (PR27.3); Summen und `DBCC CHECKDB` fehlerfrei; Rohdaten als `tools/dauerlast/messung-<Datum>-72h-<zugang>.txt` im Repository.⬜
DL28.3Dauerlauf 72 Stunden: PostgreSQLNur der PostgreSQL-Port. `tools/dauerlast` (erweitert um je 16 Verbindungen je Zugang, gemischt Lesen/Schreiben, Verbundabfragen, Transaktionen über mehrere Anweisungen, stündlich eine Sicherung), 72 Stunden ohne Unterbrechung, Release-Fassung im Container mit fester Speichergrenze. **Gemessen:** jede Minute Durchsatz, Fehler, Antwortzeit (Median, 99. Perzentil), Working Set, Protokollgrösse, offene Verbindungen; am Ende Summenprüfung und `DBCC CHECKDB`. **Bestanden, wenn:** 0 Fehler; Durchsatz der letzten Stunde mindestens 90 % der ersten; 99. Perzentil der letzten Stunde höchstens 150 % der ersten; Speicher ohne Wachstum über das Datenwachstum hinaus; Protokoll begrenzt (PR27.3); Summen und `DBCC CHECKDB` fehlerfrei; Rohdaten als `tools/dauerlast/messung-<Datum>-72h-<zugang>.txt` im Repository.⬜
DL28.4Dauerlauf 72 Stunden: alle drei Zugänge gleichzeitig auf derselben DatenbankAlle drei Ports zugleich auf denselben Tabellen (Ziel von M26), Sperren und Transaktionen über die Protokolle hinweg. `tools/dauerlast` (erweitert um je 16 Verbindungen je Zugang, gemischt Lesen/Schreiben, Verbundabfragen, Transaktionen über mehrere Anweisungen, stündlich eine Sicherung), 72 Stunden ohne Unterbrechung, Release-Fassung im Container mit fester Speichergrenze. **Gemessen:** jede Minute Durchsatz, Fehler, Antwortzeit (Median, 99. Perzentil), Working Set, Protokollgrösse, offene Verbindungen; am Ende Summenprüfung und `DBCC CHECKDB`. **Bestanden, wenn:** 0 Fehler; Durchsatz der letzten Stunde mindestens 90 % der ersten; 99. Perzentil der letzten Stunde höchstens 150 % der ersten; Speicher ohne Wachstum über das Datenwachstum hinaus; Protokoll begrenzt (PR27.3); Summen und `DBCC CHECKDB` fehlerfrei; Rohdaten als `tools/dauerlast/messung-<Datum>-72h-<zugang>.txt` im Repository. Zusätzlich: kein Sperrring ohne echten Ring, jede Sitzung sieht die bestätigten Änderungen der anderen Zugänge.⬜

Studio MultiSQLManager

SchrittInhaltStand
K1
Repo umbauen, Gerüst, Dokumente

Repo umbauen, Gerüst, Dokumente

✅
K2
Kuonen.Data.Tds

`Kuonen.Data.Tds`: Verbindung (TLS, nur Anmeldung, ohne), Stapel, alle Typen, Meldungen, Fehler; belegt durch LeitungTests, TypenTests, AntwortTests, AnmeldungTests, LeserTests, RobustheitTests, OrakelTests (SQL Server 2025 und KuonenSQL)

✅
K2a
Kleinpunkte aus K2

Kleinpunkte aus K2: Codepage-Tabellen aus Messreihe am Orakel (alle 3445 Sortierungen gegengeprüft), Feldlänge 128 und `Persist Security Info` vor dem Verbinden, Zeitlimit je Wartezeit ohne Wettlauf (`Wartefrist`), Synonym-Tests, `Wert()` bei NULL, Nachlade-Tests, `ConfigureAwait(false)` im gespiegelten Handshake-Strom (Gegenstück siehe K2b), Puffer schrumpft, abgeschnittene Antworten, gesammelte Fehler bei Leitungsabriss, Positivliste, Sende- und Leerpaket-Tests

✅
K2b
Gegenstück-Vermerke in Kuonen.KuonenSQL setzen und die ConfigureAwait(false)-Änderung aus K2a Punkt 7 dort nachziehen (s…

Gegenstück-Vermerke in Kuonen.KuonenSQL setzen und die `ConfigureAwait(false)`-Änderung aus K2a Punkt 7 dort nachziehen (siehe `docs/kuonensql-gegenstueck.md`). Erledigt 2026-10-15: die Vermerke standen, `ConfigureAwait(false)` war im Server schon da; seit Phase 2 des Umzugs (U1) gibt es die Dateien nur noch einmal.

✅
KS1
Befunde für KuonenSQL (Kuonen.KuonenSQL, aus den Orakeltests von Kuonen.Data.Tds und Plugins.SqlServer; Beschreibung in …

Befunde für KuonenSQL (Kuonen.KuonenSQL, aus den Orakeltests von `Kuonen.Data.Tds` und `Plugins.SqlServer`; Beschreibung in `docs/kuonensql-gegenstueck.md`). **Stand 2026-10-14 (K6-Gesamt-Nachbesserung, gezählt mit `--filter "FullyQualifiedName~_Befund_KS1"`): 177 übersprungene Zwillinge – TDS-Treiber 36, SQL-Server-Plugin 61, PG-Treiber 13, PG-Plugin 22, MySQL-Treiber 26, MySQL-Plugin 19 –, alle mit `[Ks1Fact]`/`[Ks1Theory]` und mit `MSM_KS1_ALLE=1` lauffähig; die K6-Befunde ohne KuonenSQL-Nummer stehen als Vorschlag PR27.24a–n in `docs/kuonensql-gegenstueck.md`, „Für die KuonenSQL-Roadmap“.** Früherer Stand: **Zahl der Befunde (Stand 2026-10-11): 131 übersprungene Tests `…_KuonenSql_Befund_KS1`, 36 im TDS-Treiber, 61 im SQL-Server-Plugin, 13 im PG-Treiber und 21 im PG-Plugin, im Quelltext gezählt bei der Nachbesserung nach der K5-Gesamtprüfung (K5 Aufgabe 2: `is_superuser=off` für `sa` trotz `rolsuper`; K5 Aufgabe 3: float NaN/Infinity und Grenzen, `name` und Typlängen, `time 24:00:00`, `xml`-OID, `pg_sleep`/void, Tabellen-OID und Spaltennummer 0 in RowDescription; K5 Aufgabe 4: `CALL` mit OUT/INOUT über Extended Query meldet bei Describe `NoData` und schickt dennoch eine DataRow; K5 Aufgabe 6: `DISCARD ALL` ergibt 42601 – der Treiber verwendet Leitungen zu KuonenSQL darum nicht wieder –, ein neues Schema teilt die OID in `pg_namespace` mit `redis`, `LATERAL` vor einem Funktionsaufruf ergibt 0A000, `timetz` binär mit 14 statt 12 Byte; K5 Aufgabe 8: `CREATE DATABASE` mit Optionen und `CREATE TYPE … AS ENUM` nach einer Kommentarzeile ergeben 42601, `has_database_privilege(oid)` einer fehlenden Datenbank 3D000 statt NULL, `ALTER TABLE "s"."t" OWNER TO` in einem neuen Schema 42P01 und eine Vorgabe mit Typumwandlung als T-SQL-Text, Funktionsnamen in Anführungszeichen werden klein gespeichert, `pg_get_indexdef` ohne Anführungszeichen (sicherheitsrelevant, das Plugin verweigert dann das Skript), Typ- und Argumentnamen in Anführungszeichen ergeben 42601, berechnete Spalte, `timestamptz(3)`, materialisierte Sicht, Teilindex, Sequenzvorrat und `pg_get_functiondef` fehlen im Katalog, `pg_has_role` mit OIDs, `CREATE TABLE … INHERITS`; K5 Aufgabe 9: DDL rollt in einer Transaktion nicht zurück (zwei Tests), Identität ändern 0A000, `SET NOT NULL` verliert die Genauigkeit, Kennwort als Dollarzitat 42601, GRANT nach einer Kommentarzeile still übergangen (sicherheitsrelevant); Nachbesserung: Sortierung einer Spalte fehlt im Katalog; K5 Aufgabe 10: Namen in Anführungszeichen bei VACUUM/ANALYZE/REINDEX, kein `pg_cancel_backend`/`pg_terminate_backend` und `backend_start` nur auf die Minute, `DROP SCHEMA … CASCADE` mit Fremdschlüssel XX000; Flackern-Nachtrag `fe8e71d`: ein Punkt im Namen erscheint als U+FDD0), je einer mit laufendem MsSql-Zwilling (im PG-Treiber mit PostgreSql-Zwilling, bis zum PG-Orakel übersprungen); die Einzelheiten stehen je Aufgabe in `docs/kuonensql-gegenstueck.md`, dort am Ende der vollständige Index aller Testnamen.** `sql_variant` als RPC-Parameter (`TdsRpcWerte`, Typ 0x62); UDT (`geography`, `geometry`, `hierarchyid`, Typ 0xF0) als RPC-Parameter; ENVCHANGE 10, wenn `XACT_ABORT` die Transaktion abbricht; `RAISERROR … WITH NOWAIT` kommt erst am Ende des Stapels beim Client an (sichtbar im Abbruch per ATTENTION); RESETCONNECTION ohne ENVCHANGE 10 (offene Transaktion) und 18 (Bestätigung); mehrere RPCs in einer Nachricht (BatchFlag 0xFF, `DbBatch`): KuonenSQL liest nur den ersten und antwortet mit 8016; MARS (K3 Aufgabe 11): KuonenSQL bestätigt MARS in PRELOGIN nicht (Option 4 mit 0) und spricht kein SMP ([MC-SMP]), mehrere offene Leser auf einer Verbindung gehen nicht (zwölf Tests `…_KuonenSql_Befund_KS1` in `MarsOrakelTests`). Danach in `ParameterTests`, `TransaktionTests`, `AbbruchTests`, `PoolTests`, `BatchTests` und `MarsOrakelTests` die übersprungenen Tests `…_KuonenSql_Befund_KS1` einschalten (fünf plus vier in `BatchTests`); aus K3o dreizehn Tests und ein weiterer Befund (Einzelheiten in `docs/kuonensql-gegenstueck.md`, Abschnitt „K3o“): `INFORMATION_SCHEMA.VIEW_COLUMN_USAGE` ohne Zeilen (`ViewColumns`); Länge und Vorgabe der Glieder von Tabellentypen (`StructuredTypeMembers`); `is_sparse`/`is_column_set` (`ColumnSetColumns`); `INFORMATION_SCHEMA.COLUMNS` (Zeichensatz, `bit`, `sql_variant`, `timestamp`); Länge des Funktionsrückgabewerts in `ProcedureParameters`; `ASSEMBLYPROPERTY` (`UserDefinedTypes`); berechnete Tabellenspalte als nur lesbar; `NO_BROWSETABLE` ohne TABNAME/COLINFO für `SELECT`, ohne Namen der umbenannten Spalte und ohne versteckte Schlüsselspalte; `EXEC` unter `FMTONLY` ohne Spaltenbeschreibung; `SET FMTONLY ON` in `sp_executesql` bleibt stehen; RESETCONNECTION setzt ein rohes `SET FMTONLY ON` nicht zurück (`PoolTests.RESETCONNECTION_setzt_ein_rohes_SET_FMTONLY_ON_zurueck_KuonenSql_Befund_KS1`); `ExecuteNonQuery` einer Prozedur mit innerem `INSERT` liefert -1 statt 3; `ATTENTION` mitten in einer sehr grossen Menge wird erst nach 2,2 bis 5 s bestätigt (SQL Server: wenige ms; über 5 s schliesst der Treiber die Leitung). Danach die Tests `…_KuonenSql_Befund_KS1` in `SchemaTests` (6), `SpaltenschemaTests` (6) und `OrakelTests` (1) einschalten; aus K4 Aufgabe 2: `HAS_PERMS_BY_NAME` prüft immer für dbo und liefert für jede Anmeldung 1 (SQL Server: 0 für eine rechtelose Anmeldung; Einzelheiten in `docs/kuonensql-gegenstueck.md`, Abschnitt „K4 Aufgabe 2“); danach den Test `ErkennungTests.Eine_einfache_Anmeldung_kann_keine_Sitzung_beenden_KuonenSql_Befund_KS1` einschalten; ebenfalls K4 Aufgabe 2: `create_date` in `sys.server_principals` fest (2026-01-01) und in `sys.databases` UTC statt Ortszeit (`OrakelresteTests.…_KuonenSql_Befund_KS1`, Einzelheiten in `docs/kuonensql-gegenstueck.md`); aus K4 Aufgabe 3: `WAITFOR DELAY` in einer neuen Zeile hinter `SELECT 1` ohne Semikolon ergibt Fehler 102 (SQL Server führt es aus; Einzelheiten in `docs/kuonensql-gegenstueck.md`, Abschnitt „K4 Aufgabe 3“); danach den Test `AbfragelaufTests.Ein_WAITFOR_ohne_Semikolon_hinter_SELECT_wird_ausgefuehrt_KuonenSql_Befund_KS1` einschalten (SQL Server führt es aus, belegt durch den laufenden MsSql-Zwilling); KuonenSQL nennt bei einem Fehler in einer temporären Prozedur den Prozedurnamen nicht (`Meldung.Prozedur` leer; Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 3“), danach den Test `AbfragelaufTests.Ein_Fehler_in_Zeile_5_einer_Prozedur_traegt_die_Startzeile_des_aufrufenden_Stapels_KuonenSql_Befund_KS1` einschalten; aus K4 Aufgabe 4: `]]` in Bezeichnern in Klammern (Fehler 102), Spiegelzeilen im Schema `redis` in `sys.tables`, `ALTER DATABASE … SET OFFLINE` ohne Wirkung (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 4“); danach die Tests `BaumTests.…_KuonenSql_Befund_KS1` einschalten; aus K4 Aufgabe 5: Syntaxfehler in `CREATE`/`ALTER PROCEDURE` ohne Prozedurnamen und mit Zeile 1 statt der Zeile im Stapel (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 5“), danach den Test `AbfragelaufTests.Ein_Syntaxfehler_in_einer_im_Stapel_angelegten_Prozedur_zaehlt_ab_dem_Stapelanfang_KuonenSql_Befund_KS1` einschalten (belegt durch den laufenden MsSql-Zwilling); der übersprungene `BaumTeileTests.Sonderzeichen_mit_Klammer_…_KuonenSql_Befund_KS1` hängt am Befund `]]` aus Aufgabe 4; aus K4 Aufgabe 6: der Katalog bewahrt DESC und CLUSTERED eines Schlüssels, die Cachegrösse einer Sequenz und CHECK_POLICY einer Anmeldung nicht, `WITH ENCRYPTION` verbirgt den Text einer Prozedur nicht, `WITH (LEDGER = ON)` wird angenommen, aber `ledger_type` fehlt, und die Spiegelung im Schema `redis` verdoppelt Fremdschlüssel-Spalten (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 6“), danach die vier Tests `SkripteTests.…_KuonenSql_Befund_KS1` einschalten (belegt durch die laufenden MsSql-Zwillinge); aus K4 Aufgabe 7: ein Stern mit Alias in der Select-Liste (`SELECT d.* FROM …`, Fehler „Ungueltiger Spaltenname '*'“), `MODIFY FILE (SIZE, MAXSIZE, FILEGROWTH)` und `ADD FILE` ohne Wirkung im Katalog (`sys.database_files.size` ist 0), `PAGE_VERIFY` ohne Wirkung (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 7“); Aufgabe 8: `DATA_COMPRESSION` und `ROWGUIDCOL` bleiben nicht im Katalog, die Indexoptionen `IGNORE_DUP_KEY`, Sperren und `OPTIMIZE_FOR_SEQUENTIAL_KEY` auch nicht, `NOT FOR REPLICATION` an IDENTITY ist ein Syntaxfehler, `CREATE PARTITION FUNCTION` fehlt, `sys.parameters` meldet den Systemtyp statt des Aliastyps, `SET IDENTITY_INSERT ON` hat keine Wirkung, **`DROP TABLE` in einer Transaktion wird nicht rückgängig gemacht (der Neuaufbau des Designers ist bei KuonenSQL nicht atomar)**, Trigger bleiben nach `DROP TABLE` bestehen und `ALTER TABLE … ADD PRIMARY KEY` über doppelte Werte hängt oder meldet nichts (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 8“); danach die fünf Tests `DialogeOrakelTests.…_KuonenSql_Befund_KS1` und die Tests `…_KuonenSql_Befund_KS1` aus Aufgabe 8 einschalten (belegt durch die laufenden MsSql-Zwillinge) ; K4 Aufgabe 7, Nachbesserung: `DROP TABLE` wird in einer Transaktion nicht zurückgerollt (Befund 8 aus Aufgabe 8), darum lehnt der Designer bei KuonenSQL jeden Neuaufbau ab; nach der Korrektur die Sperre in `DesignerAendern.AnwendenAsync` aufheben und die sieben Neuaufbau-Tests wieder gegen KuonenSQL laufen lassen; Aufgabe 9: `sys.fn_builtin_permissions` liefert nur die Klasse SERVER, ein Recht, das zur Klasse nicht passt, wird angenommen, das erste Benutzerschema teilt die `schema_id` mit `redis`, `GRANT … ON ROLE::` und `ON TYPE::` landen als Klasse 1 mit `major_id` 0, Rechte auf Endpunkte werden nicht gespeichert, die Mitglieder fester Datenbankrollen fehlen im Katalog, `ALTER USER … WITH DEFAULT_SCHEMA`, `… WITH LOGIN` und `… WITH NAME` sind ohne Wirkung, `DENY CONNECT SQL` ersetzt das GRANT nicht, `CHECK_POLICY = OFF`, `CHECK_EXPIRATION`, `DEFAULT_DATABASE`, `DEFAULT_LANGUAGE` und `MUST_CHANGE` sind ohne Wirkung, `CHECK_EXPIRATION` ohne Richtlinie und eine feste Rolle als Mitglied einer Rolle werden angenommen (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 9“); danach die vierzehn Tests `…_KuonenSql_Befund_KS1` aus Aufgabe 9 einschalten (belegt durch die laufenden MsSql-Zwillinge); Aufgabe 10: `BACKUP DATABASE … WITH DIFFERENTIAL` ohne vollständige Sicherung wird angenommen (SQL Server: 3035), eine differenzielle Sicherung lässt sich nicht auf eine mit `MOVE` wiederhergestellte Datenbank im Zustand NORECOVERY einspielen (1834: der Server schreibt in die ursprünglichen Pfade), `RESTORE FILELISTONLY` nennt als `PhysicalName` nur den Dateinamen und `BACKUP DATABASE` einer Datenbank im Zustand RESTORING wird angenommen (SQL Server: 927) (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 10“); danach die vier Tests `…_KuonenSql_Befund_KS1` aus Aufgabe 10 einschalten (belegt durch die laufenden MsSql-Zwillinge); Aufgabe 11: `msdb.dbo.sp_stop_job` fehlt (Fehler 2812), `CREATE LOGIN`/`ALTER LOGIN` mit Kennwort stehen im Klartext in `sys.dm_exec_sql_text` und `sys.dm_exec_query_stats` (SQL Server hält sie heraus), `VIEW SERVER STATE` wird für `sys.dm_os_wait_stats` und `sys.dm_exec_query_stats` nicht geprüft und eine Anmeldung ohne das Recht sieht alle Sitzungen in `sys.dm_exec_sessions`, `sys.dm_exec_sessions.original_login_name` ist NULL (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 11“); dazu in der Nachbesserung: `ALTER LOGIN` mit Kennwort steht ebenfalls im Klartext in `sys.dm_exec_sql_text`, `CONVERT(datetime, N'…T…', 126)` scheitert (`CAST` geht); danach die sieben Tests `…_KuonenSql_Befund_KS1` aus Aufgabe 11 einschalten (belegt durch die laufenden MsSql-Zwillinge); Aufgabe 12: `BACKUP … WITH NAME = @variable` speichert den Text `@n` statt des Werts in `msdb.dbo.backupset.name`, und `IF EXISTS (…) KILL n ELSE …` führt nach dem `KILL` auch den ELSE-Zweig aus (Einzelheiten in `docs/kuonensql-gegenstueck.md`, „K4 Aufgabe 12“); danach die zwei Tests `…_KuonenSql_Befund_KS1` aus Aufgabe 12 einschalten (`SicherungNameOrakelTests`, `AktivitaetOrakelTests`; belegt durch die laufenden MsSql-Zwillinge), in `SicherungRundlaufTests` und `SicherungSchutzTests` die Überprüfung nach der Sicherung auch bei KuonenSQL wieder anschalten (heute `pruefen = 0` für KuonenSQL) und in `AktivitaetOrakelTests.Blockiert_von_zeigt_die_zweite_und_KILL_…` die erwartete Meldung für KuonenSQL entfernen

⬜
K3
Kuonen.Data.Tds

`Kuonen.Data.Tds`: Parameter (RPC), Transaktionen, Abbruch, Pool, `GetSchema`, `DbBatch`, benannte Instanzen, TDS 8 strict, `GetSchemaTable`/`GetColumnSchema`; die SqlClient-Schlüssel, die der Treiber bis dahin nur speichert (MARS, Integrated Security, Authentication, Failover Partner; Pooling ✅), werden ausgewertet

✅ (Rest in K3c/K3d) Aufgabe 2 (Parameter über RPC, Prozeduren, OUTPUT/RETURN, `TdsType`) ✅, Aufgabe 3 (Transaktionen: `TdsTransaction`, Savepoints, Isolationsstufen) ✅, Aufgabe 4 (Abbruch per ATTENTION: `Cancel()`, Zeitlimit Nummer −2, Token, Schreibsperre, Frist 5 s; `CommandTimeout = int.MaxValue` gedeckelt) ✅, Aufgabe 5 (Pool je Zeichenfolge, `Max/Min Pool Size`, `Connection Lifetime`, `ClearPool`/`ClearAllPools`, RESETCONNECTION samt offener Transaktion, `TdsDataSource`) ✅, Aufgabe 6 (`GetSchema`, 20 Sammlungen; am Testserver, an KuonenSQL und am SQL-Server-Orakel gegen SqlClient 6.1.3 Wert für Wert belegt, K3o) ✅, Aufgabe 7 (`GetColumnSchema`, `GetSchemaTable`, `SchemaOnly` über `SET FMTONLY`, `KeyInfo` über `SET NO_BROWSETABLE` mit TABNAME/COLINFO; am Testserver, an KuonenSQL und am SQL-Server-Orakel gegen SqlClient belegt, K3o; die Unterschiede, die nur KuonenSQL hat, siehe KS1) ✅, Aufgabe 8 (`DbBatch`: `TdsBatch`, `TdsBatchCommand`, mehrere RPCs mit BatchFlag 0xFF, `RecordsAffected` und OUTPUT/RETURN je Befehl; am Testserver und am SQL-Server-Orakel belegt (K3o), mehrere RPCs an KuonenSQL siehe KS1; `DbException.BatchCommand` gesetzt) ✅, Aufgabe 9 (benannte Instanzen über den SQL-Browser, UDP 1434 mit Deckeln und Frist; `Encrypt=Strict` = TDS 8 mit ALPN `tds/8.0`, `HostNameInCertificate`, `ServerCertificate`; am Test-Browser, am BCL-TLS-Server und TDS 8 an KuonenSQL belegt; Pin und `HostNameInCertificate` am SQL-Server-Orakel (K3o); TDS 8 und SQL-Browser an einem echten Server siehe K3c) ◐, Aufgabe 10 (Windows-Anmeldung über SSPI mit `NegotiateAuthentication`, Failover Partner samt ENVCHANGE 13, `Authentication` über FEDAUTH: `AccessToken`, `AccessTokenCallback`, Dienstprinzipal, Kennwort und Gerätecode über OAuth bei Entra ID; am Testserver belegt, die Gegenproben am echten Server siehe K3c, Interaktiv/Verwaltete Identität/Standardkette siehe K3d) ◐, Aufgabe 11 (MARS: `MultipleActiveResultSets=true` über SMP ([MC-SMP]); jeder gleichzeitige Befehl in einer eigenen Sitzung (`TdsSmp`, `TdsSmpStrom`, `TdsKanal`), Fenster als Gegendruck, Deckel für Sitzungen, Pakete und Fenster, `TdsException` und Abriss bei jedem Kopf, der nicht passt; ATTENTION, Zeitlimit und Token je Sitzung; die Transaktion gehört der Verbindung (Fehler 3988 und 3981 des Servers), Pool mit RESETCONNECTION, `Encrypt=true`, `Encrypt=false` und TDS 8; am Test-Server und am SQL-Server-Orakel belegt, KuonenSQL bestätigt MARS nicht, siehe KS1) ✅, Aufgabe 12 (Abschluss: Minor-Befunde aus der Prüfung von Aufgabe 11, Belegung je Befehl gesenkt, Messung von 1 Mio. Zeilen mit `Read` und `ReadAsync`, mit und ohne MARS, in `docs/leistung.md`; gefilterter Gesamtlauf mit beiden Orakeln grün, übersprungen sind nur die KS1-Befunde und die drei Fälle von K3c) ✅, UDT-Parameter (`geography`, `geometry`, `hierarchyid`) umgesetzt; Befunde für KuonenSQL stehen in KS1
K3c
Gegenproben am echten Server (Auftraggeber)

Gegenproben am echten Server (Auftraggeber): (a) Windows-Anmeldung an einem SQL Server mit Windows-Anmeldung (Domäne oder lokaler Server; Kerberos und NTLM, Dienstname `MSSQLSvc/<fqdn>:<port>`, benannte Instanz, Channel Binding / Extended Protection gegen einen Server mit „Extended Protection“ an, Anmeldung bei `Encrypt=false`): Testfall `WindowsAnmeldungTests.Windows_Anmeldung_am_echten_Server`, eingeschaltet mit der Umgebungsvariable `MSM_WINDOWS` (Verbindungszeichenfolge, z. B. `Server=host;Integrated Security=true;Encrypt=true;TrustServerCertificate=true`). (b) Azure SQL / Entra ID: Testfall `FedAuthTests.Azure_SQL_am_echten_Server`, Umgebungsvariable `MSM_AZURE` (z. B. `Server=tcp:x.database.windows.net,1433;Database=d;Authentication=Active Directory Service Principal;User ID=<Anwendungs-ID>;Password=<Secret>`); dabei prüfen: Dienstprinzipal, Kennwort (Konto ohne Mehrstufenanmeldung), Gerätecode, `AccessToken`, `AccessTokenCallback`; ob der Workflow-Wert im LOGIN7 (1 für Kennwort und Dienstprinzipal, 3 für Gerätecode und Rückruf) von Azure SQL angenommen wird; ob Azure die Anmeldung mit der Weiterleitung (ENVCHANGE 20, „Routing“) beantwortet und der Treiber sie dann behandeln muss (heute nicht umgesetzt: Anmeldung innerhalb von Azure im Modus „Redirect“); die Anwendungs-ID `2fd908ad-0664-4344-b9be-cd3e8b574c38` für Kennwort und Gerätecode; Mandanten- und Cloud-Kennungen der souveränen Clouds. (c) Spiegelung (Failover Partner) gegen eine echte gespiegelte Datenbank: ENVCHANGE 13 und der Wechsel. (d) Bewusste Abweichung: bei Encrypt=false/Optional wird nie ein Failover-Partner aus ENVCHANGE 13 gemerkt (implizite Spiegelung); SqlClient merkt ihn auch dort. (e) Aus K3o: TDS 8 (`Encrypt=Strict`) an einem SQL Server mit gültigem Zertifikat und „Force Strict Encryption“ (Testfall `StrictTests.Strict_gegen_das_SQL_Server_Orakel_K3c`, übersprungen: der Orakel-Container, SQL Server 2025 auf Linux mit selbst erzeugtem Zertifikat, schliesst jede TDS-8-Verbindung, Fehlerprotokoll: „A valid TLS certificate is not configured to accept strict (TDS 8.0 and above) connections“; das Umstellen ist serverweit und am Orakel verboten); dort auch `Strict` mit `TrustServerCertificate=true` gegen ein selbstsigniertes Zertifikat (SqlClient lehnt ab) und `HostNameInCertificate` unter Strict; ALPN `tds/8.0` unter macOS (ungeprüft); eine benannte Instanz gegen einen echten SQL-Browser (UDP 1434: am Orakel antwortet keiner, die Anfrage wird abgewiesen) mit Antwort für mehrere Instanzen, `np`-Einträgen und mehreren Adressen (IPv6). Zu starten, sobald eine solche Umgebung da ist

⬜
K3d
Authentication=Active Directory Interactive (Browser-Anmeldung mit Rückleitung auf http

`Authentication=Active Directory Interactive` (Browser-Anmeldung mit Rückleitung auf `http://localhost`), `Active Directory Integrated`, `Active Directory Managed Identity` (IMDS / `IDENTITY_ENDPOINT`, beides http im lokalen Netz) und `Active Directory Default` eingebaut; bis dahin liefert der `AccessTokenCallback` den Token, und der Treiber meldet ohne Rückruf `NotSupportedException` mit Hinweis auf K3d. Windows-Kontomanager (WAM) gehört dazu

⬜
K4
Plugin-Host + Plugins.SqlServer (Baum, Skript als, Eigenschaften, Sicherheit, Sicherung, Aktivität)

Plugin-Host + `Plugins.SqlServer` (Baum, Skript als, Eigenschaften, Sicherheit, Sicherung, Aktivität)

✅ Aufgaben 1–12 und die Nachbesserung nach der Gesamtprüfung. Plan: docs/superpowers/plans/2026-10-05-k4-plugin-host-sqlserver.md; Einzelheiten je Aufgabe in `docs/umsetzung.md`, Abschnitte „K4 Aufgabe n“, der frühere ausführliche Text dieser Zeile im Anhang dort. Aufgabe 1: Kern-Gerüst (Datenarten, `PluginRegister`, `Profilspeicher`, `Serververbindung`, Testplugin) ✅. Aufgabe 2: SQL-Server-Plugin, Verbindung und Erkennung der Fähigkeiten mit einer Abfrage ✅. Aufgabe 3: Abfragelauf (Stapel, Mengen, Meldungen, Abbruch) ✅. Aufgabe 4: Objekt-Explorer I (Server, Datenbanken, Schemaobjekte, je Ordner eine Katalogabfrage) ✅. Aufgabe 5: Objekt-Explorer II (Tabellenteile, Sicherheit, Verwaltung, Agent, KuonenSQL-Bereich, Befehle mit Gründen) ✅. Aufgabe 6: Skript als ✅. Aufgabe 7: Eigenschaftsdialoge ✅. Aufgabe 8: Tabellen-Designer ✅. Aufgabe 9: Sicherheit (Anmeldungen, Serverrollen, Benutzer, Rollen, Berechtigungen) ✅. Aufgabe 10: Sicherung, Wiederherstellung, Wartung ✅. Aufgabe 11: Aktivitätsmonitor und Agent-Aufträge samt Nachbesserung (`AgentLaeuft`, `Knoten.Prozess`, Kennwortregel, KILL mit Anmeldezeit) ✅. Aufgabe 12: gemeinsamer Testsatz (sieben Fälle, `TestsatzMsSql` und `TestsatzKuonenSql` laufen an beiden Orakeln ohne Abweichung), Reste am Ende des Laufs, AOT-Probe ohne Warnung, Restpunkte aus Aufgabe 10 und der Nachprüfung von Aufgabe 11, Zeitgrenze des Abbruchtests untersucht, Deadlock in den Sicherungstests behoben ✅. Nachbesserung nach der Gesamtprüfung ✅: jedes datenbankbezogene Skript beginnt mit `USE [db]` und `GO`; Katalogtext in den Vorlagen entschärft; Designer und Sicherheitsdialoge gleichen das Modell mit dem frischen Stand ab; der Kern ist engine-neutral (`Serverkennung.Merkmale`, `IZeilenzuordnung`, `IZustandspruefung`, feste Zahlen der Enums); Regel „je Explorer und je Abfragefenster eine eigene `Serververbindung`“ und Meldung einer offenen Transaktion; Rundweg für den Agent mit eigenem Auftrag; Profiloptionen geprüft; `XACT_ABORT` wiederhergestellt; Sicherungsverlauf der Probedatenbanken entfernt; Hinweis für `msdb`/`model` als Ziel der Wiederherstellung; `Abfragelauf.Wert` kürzt weiter nach dem Lesen (Grenze dokumentiert, K8). Stand der Tests (Gesamtlauf `FullyQualifiedName~Kuonen`, beide Orakel, dreimal hintereinander grün, warnungsfrei): `Core.Tests` 198; `Plugins.SqlServer.Tests` 1691 (1631 grün, 60 übersprungen = KS1-Zwillinge); `Kuonen.Data.Tds.Tests` 1109 (1070 grün, 39 übersprungen = 36 KS1 und 3 K3c). Zwischendurch waren zwei Läufe rot (einmal ein nicht erfasster Einzelfehler, einmal fünf Zeitüberschreitungen von 30 s am SQL-Server-Orakel; Näheres im Bericht `.superpowers/sdd/k4/gesamt-fix-report.md`). **Offen für Teil 2 (Oberfläche) und K5:** Rechte auf Datenbank und Server als eigener Dialog ◐; trägt der letzte Knoten einer Liste das Merkmal `abgeschnitten=1` (über 100 000 Knoten in einem Ordner), zeigt die Oberfläche „Liste gekürzt“; `HatHinweise` (Teil 2 zeigt Hinweise auch bei `Leer`); die neuen Kern-Werte `Knotenart.Sicherungseintrag`, `Knotenart.Prozess`, `Skriptart.Starten`/`Beenden`, `Dialogart.Verlauf` und `Faehigkeit.AgentLaeuft` kennen, einen Prozess aus einer Rasterzeile mit `Knoten.Prozess(sitzung, eigen, anmeldezeit)` machen und den Monitor durch erneutes Öffnen aktualisieren; Skripte von Anmeldungen in `master` öffnen und anzeigen, dass „Wiederherstellen“ die Sitzung in `master` lässt; `AgentLaeuft` ist eine Momentaufnahme der Erkennung (nach dem Start des Dienstes neu erkennen)
K4a
Kleinpunkte aus der Gesamtprüfung von K4 (vor K5)

Kleinpunkte aus der Gesamtprüfung von K4 (vor K5): (1) Synonymziel `base_object_name` in Teile zerlegen und jeden Teil klammern (`SkripteSonstige.cs`); (2) Sitzungszustand des Fensters statt der Explorer-Verbindung: `XACT_ABORT` im Designer-Stapel selbst festhalten und zurückstellen, Datenbank-Rückwechsel im Sicherheitsskript und Hinweis bei der Wiederherstellung; (3) Reste im Kern: Klasse 16 in `Abfragelauf.MeldungAus`, Feldname `eigene_sitzung` in der Doku von `Knoten`; (4) Profiloptionen über einen frischen Builder auflösen und `Data Source`, `Initial Catalog`, `User ID` (auch Alias `User`) und `Failover Partner` gegen das Profil prüfen

✅ alle vier Punkte (Zweig `k4a-kleinpunkte`); Einzelheiten in `docs/umsetzung.md`, Abschnitt „K4a“. Beim Sicherheitsskript entfällt der Rückwechsel der Datenbank (Hinweis „Das Skript wechselt die Datenbank der Sitzung.“), beim Designer hält der Stapel `XACT_ABORT` selbst fest (der Hinweis entfällt). Ein weiterer übersprungener KS1-Test (`]]` im Synonymziel; der Befund `]]` in Bezeichnern ist schon bekannt), Testzahl 97
K5
Kuonen.Data.PostgreSql + Plugins.PostgreSql

`Kuonen.Data.PostgreSql` + `Plugins.PostgreSql`

◐ Aufgabe 10 von 10, offen nur Orakelbelege (Treiber und Plugin sind gebaut; offen sind die Belege am echten PostgreSQL 17 – die SQL-Server-Belege aus Aufgabe 1 sind seit dem 2026-10-11 erledigt –, dazu der KuonenSQL-Befund zu DROP SCHEMA … CASCADE (KS1), aufgelistet in `docs/umsetzung.md`, Abschnitt „K5 offen bis Orakel“, und danach die drei Gesamtläufe an allen Orakeln durch den Hauptchat; erst dann ✅). Plan: docs/superpowers/plans/2026-10-11-k5-postgresql.md; Einzelheiten in `docs/umsetzung.md`, Abschnitte „K5 Aufgabe n“. Aufgabe 1: `Kuonen.Data.Gemeinsam` (Wartefrist, Tlsaufbau, Deckel, `Pool<TLeitung>`) herausgelöst, öffentliche API von `Kuonen.Data.Tds` unverändert ✅ (Belege nachgetragen am 2026-10-11: Tds 1070 grün/39 übersprungen wie vorher, Plugins.SqlServer 1690/61, Core 222, Gemeinsam 67; `mars 3` nicht schlechter, Median 166/162/157/168 ms gegen vorher 166/167/172/164 ms). Aufgabe 2: `Kuonen.Data.PostgreSql` mit `PgConnection` (SSLRequest/TLS nach `SSL Mode`, StartupMessage 3.0, Anmeldung Klartext nur über TLS, MD5, SCRAM-SHA-256 und -PLUS, ParameterStatus, BackendKeyData, ReadyForQuery, ErrorResponse/NoticeResponse mit allen Feldern, `Timeout` je Wartezeit, `Terminate`, Persist Security Info, `IsKuonenSql` über `pg_catalog.version()`; Nachbesserung: strenger Zustandsautomat der Anmeldung, Deckel 1 Mio. Durchgänge mit Frist, `Channel Binding`), `PgConnectionStringBuilder` (Schlüssel und Aliase wie Npgsql, dazu `Persist Security Info` und `Require Auth`), `PgException`, `PgNotice`, `PgProviderFactory`; Testserver `Hilfen/Pgserver` mit verfälschten Nachrichten; `dist\testumgebung.ps1` setzt `MSM_POSTGRESQL`, `dist\kuonensql-testserver.ps1 -PgPort` (15435) `MSM_KUONENSQL_PG` ◐ (offen: alle Orakelbelege am echten PostgreSQL 17 – SCRAM, 28P01, 3D000, Notice, Messung der ParameterStatus, `is_superuser` –, das Orakel 15433 war nicht erreichbar; am KuonenSQL-PG-Zugang grün, ein KS1-Befund). Aufgabe 3: `PgCommand` (Simple Query, ExecuteReader/NonQuery/Scalar, CommandBehavior SingleRow/SingleResult/SchemaOnly über Parse/Describe/KeyInfo mit Katalogfrage/SequentialAccess/CloseConnection) und `PgDataReader` (alle Getter, `GetFieldValue<T>` ohne Einschachteln, GetStream/GetTextReader, GetColumnSchema/GetSchemaTable), Typen in Text- und Binärform (bool, Ganzzahlen, oid, float, numeric, Texttypen, "char", bytea, date/time/timetz/timestamp/timestamptz/interval, uuid, json/jsonb/xml, inet/cidr/macaddr, eindimensionale Arrays, void, unbekannte OIDs als Text), Empfangspuffer mit synchronem heissen Weg (26 720 Byte für 20 000 Zeilen statt 7,4 MB), verfälschte DataRow → PgException und Verbindung geschlossen; `Channel Binding=Require` mit `SSL Mode=Disable` scheitert schon an der Zeichenfolge; Nachbesserung: interval ±infinity, StartupMessage mit `IntervalStyle=postgres` und `extra_float_digits=3`, Meldungsbeleg der DataRow-Längenprüfung ◐ (offen: 23 Zwillinge am echten PostgreSQL 17 und die Npgsql-Messung `bench/Kuonen.Data.PostgreSql.Messung`, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, sechs neue KS1-Befunde). Aufgabe 4: Extended Query (je Anweisung Parse/Bind/Describe/Execute, ein Sync; Fehler bis ReadyForQuery, Antwort in falscher Reihenfolge unlesbar), `PgParameter`/`PgParameterCollection`/`PgDbType` (Typ → OID und Binärform, ohne Typ aus dem CLR-Wert wie Npgsql, string und NULL mit OID 0), Lexer für `@name`/`:name` → `$n` mit Deckeln, mehrere Anweisungen wie Npgsql geteilt, `StoredProcedure` als `CALL` mit maskiertem Namen und OUT/INOUT aus der Zeile, `Prepare` mit benannten Anweisungen (256 je Verbindung, LRU, Close bei Verdrängung, Ergebnisse binär), `PgBatch`/`PgBatchCommand` mit einem Sync, RecordsAffected je Befehl und dem Befehl in der Ausnahme, Fabrik mit Parameter und Stapel; Nachbesserung: OUT im CALL als NULL, COPY FROM STDIN mit CopyFail/Sync, 0A000 mit Close und Neuvorbereitung beim nächsten Aufruf (ohne Wiederholung), numeric NaN/±Infinity, IPNetwork ◐ (offen: 10 Zwillinge am echten PostgreSQL 17, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, ein neuer KS1-Befund: `CALL` mit OUT/INOUT über Extended Query). Aufgabe 5: `PgTransaction` (BEGIN je Isolationsstufe wie Npgsql, Snapshot → REPEATABLE READ, Chaos nicht unterstützt, Commit/Rollback sync und async, Sicherungspunkte mit maskiertem Namen, Dispose rollt zurück, `Transaction`-Eigenschaft nur gehalten wie Npgsql), Zustand aus ReadyForQuery mit Sperre im Zustand E (nur ROLLBACK, sonst 25P02 ohne Rundreise; Commit im Zustand E rollt zurück und meldet es), Abbruch über CancelRequest auf einer zweiten Leitung mit TLS nach `SSL Mode` für `Cancel()`, Token und Command Timeout (bis ReadyForQuery gelesen, Verbindung bleibt), Wachhund mit `Wartefrist`, Wettläufe nach dem TDS-Muster; Nachbesserung: Abbruch nur bei 57014 (sonst gilt das Ergebnis bzw. der Fehler des Servers), Cancel zwischen zwei Rundreisen vorgemerkt, zweiter CancelRequest nach halber Frist, Cancel() wirft nie, COMMIT/END im Zustand E wie PostgreSQL, Transaktion nach rohem Abschluss beendet, Dispose schliesst offenen Leser; Zusatz: `(IPAddress, int)` ohne Typ → inet ◐ (offen: 6 Zwillinge am echten PostgreSQL 17 – Cancel, Token, Zeitlimit, Sicherungspunkt, Zustand E, Isolationsstufe –, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, kein neuer KS1-Befund); Nachträge N1–N3 aus der Nachprüfung (Abbruch ohne 57014 bleibt für die nächste Rundreise vorgemerkt, Dispose der Transaktion wirft nie, gescheiterter zweiter CancelRequest reisst nicht ab). Aufgabe 6: Pool aus `Kuonen.Data.Gemeinsam` (Schlüssel: Zeichenfolge ohne Kennwort samt SHA-256-Hash, `Pooling`, `Minimum/Maximum Pool Size`, `Connection Idle Lifetime`, `ClearPool`/`ClearAllPools`), bei der Rückgabe ROLLBACK einer offenen Transaktion und `DISCARD ALL` vorgemerkt, das mit dem ersten Befehl nach der nächsten Ausleihe in derselben Sendung hinausgeht (verfälschte Antwort → Verbindung zu), Prepare-Cache dabei geleert; `PgDataSource` mit eigenem Pool, Befehlen und Stapeln ohne Verbindung; `GetSchema` mit den 18 Sammlungen wie Npgsql, Einschränkungen als Parameter; Leistung: Formatcache (ab dem zweiten Lauf binär ohne Rundreise) und Auftrag je Befehl nicht neu zerlegt, Messung 1 Mio. Zeilen am KuonenSQL-PG-Zugang vorher/nachher mit Npgsql in `docs/leistung.md` ◐ (offen: 6 PG-Zwillinge und der Vergleich mit Npgsql am echten PostgreSQL 17 – Ziel höchstens 10 % langsamer –, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, drei neue KS1-Befunde: `DISCARD ALL`, doppelte OID in `pg_namespace`, `LATERAL` vor einer Funktion); Nachbesserung: Formatcache robust (Eintrag nach Fehler vergessen, binär gelieferte unbekannte OID als klarer Fehler, bei DISCARD ALL geleert, erst ab dem zweiten Treffer, LRU), Meldung bei gescheitertem DISCARD ALL, Pool-Schlüssel gemerkt, entsorgte DataSource öffnet nicht; Befehle ohne Parameter mit einer Anweisung über Extended Query wie Npgsql (Mehrfachanweisungen und COPY über Simple Query), am KuonenSQL-PG-Zugang gleichauf mit Npgsql (offen: 3 weitere PG-Zwillinge, ein KS1-Befund `timetz` binär). Aufgabe 7: `Plugins.PostgreSql` (Ausgabe `*.Plugin.dll`, lädt über `PluginRegister.LadeAusOrdner`), `ErzeugeVerbindung` (nur Kennwort, Rechner/Port aus dem Feld Server, Optionen über den Builder gegen das Profil geprüft), Erkennung mit einer Abfrage (Prozeduren ab 11, Vektor nur mit `vector`, SitzungBeenden mit Superuser oder `pg_signal_backend`, DENY/Sicherung/Agent/Synonyme mit Grund aus; Merkmale `server_version_num`, `ist_kuonensql`), ein Stapel je Text, Notice mit SQLSTATE, Zeile aus `Position`, Transaktionszustand aus ReadyForQuery; Kern additiv um `IAusnahmemeldung` ergänzt ◐ (offen: 6 PG-Zwillinge am echten PostgreSQL 17, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, kein neuer KS1-Befund) Aufgabe 8: Objekt-Explorer (Server → Datenbanken mit Systemdatenbanken → Tabellen, Sichten auch materialisiert, Programmierbarkeit, Schemas → Tabellenteile; Sicherheit mit Anmeldungen = Rollen mit LOGIN und Rollen ohne), andere Datenbank über eine eigene Verbindung (Kern additiv: `Nebenverbindungen` der `Serververbindung`, `Sitzungskontext.Nebenverbindungen`; Treiber `PgConnection.MitDatenbank` intern), Befehle wie SQL Server mit `LIMIT` statt `TOP`, Skript als für Tabelle, Sicht, Funktion, Prozedur, Trigger, Index, Sequenz, Typ, Schema und Datenbank (aus dem Katalog, `DROP … IF EXISTS`, Kopf `-- Object:` entschärft, Bezeichner immer in Anführungszeichen, Servertexte vor der Übernahme geprüft), DML-Vorlagen, gemeinsamer Testsatz (`TestsatzKuonenSqlPg`, `TestsatzPostgreSql`; Fälle jetzt `virtual`, Stapelprobe überschreibbar) ◐ (offen: 26 PG-Zwillinge und die sieben Fälle des Testsatzes am echten PostgreSQL 17, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, elf neue KS1-Tests); Nachbesserung: `Skripte.Sicher` liest wie der PG-Lexer (Dollar im Wort, E-/U&-Zitate), Kopf von `pg_get_functiondef` geprüft, Erkennung mit `pg_has_role` über Rollennamen, `INHERITS` ohne geerbte Teile, eigene Sequenz mit anderem Namen statt `serial` Aufgabe 9: Eigenschaften (Server nur lesbar aus `pg_settings`; Datenbank: Besitzer, Verbindungsgrenze, ALLOW_CONNECTIONS, IS_TEMPLATE, Einstellungen nur aus der festen Liste), Designer (Neu, Entwerfen mit den kleinsten ALTER-Schritten in einer Transaktion, Neuaufbau bei neuer Reihenfolge mit Erhalt von Schlüsseln, Einschränkungen, Indizes, Triggern, Kommentaren, Besitzer, Rechten und Identität; bei KuonenSQL gesperrt, KS1), Sicherheit (Anmeldungen = Rollen mit LOGIN, Rollen ohne; Merkmale ↔ sysadmin/dbcreator/securityadmin; Rechte-Abbildung aus KuonenSQL abgeleitet, Recht ohne Gegenstück abgelehnt, DENY mit Grund aus; Kennwort nur im Ausführtext), Kennwortregel für Aufgabe 10; Zusatzpunkte N4/N5 aus der Nachprüfung von Aufgabe 8; Nachbesserung: kein stilles Kürzen bei TYPE/Neuaufbau, Nutzungsprüfung vor DROP COLUMN, Vorgaberechte/Sortierung/Trigger-Zustand im Neuaufbau, Erteiler (nicht entziehbar, GRANTED BY ab 16), kein Kommentar in Sicherheitsskripten, Eigenschaften von Tabelle/Sicht nur lesbar, gefährliche Rollen nur mit Bestätigung ◐ (offen: alle PG-Zwillinge am echten PostgreSQL 17 – Wirkungstest, Abbildung, Designer-Rundweg, Identität, Neuaufbau mit Erhalt, DDL-Rollback, Genauigkeit –, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, sechs neue KS1-Befunde); Zusatzpunkte: Bestätigung auch für neue Mitglieder gefährlicher Rollen, Hinweis auf Rundung bei kleinerer Skala/Genauigkeit. Aufgabe 10: Wartung (VACUUM, VACUUM ANALYZE, VACUUM FULL, ANALYZE, REINDEX für Tabelle, Index und Datenbank, Namen maskiert; Anweisungen ohne Transaktionsblock wie VACUUM laufen als eigener Stapel über Simple Query ohne BEGIN), Aktivitätsmonitor (Prozesse aus `pg_stat_activity`, Wartende aus `pg_locks`/`pg_blocking_pids`, Kennwortregel für Abfragetexte), Abbrechen/Beenden nur über PID und `backend_start`, mit Prüfung des Rechts, nie die eigene Sitzung; AOT-Probe mit PostgreSQL ohne Warnung; README mit Abschnitt PostgreSQL ◐ (offen: sechs PG-Zwillinge, Orakel 15433 nicht erreichbar; am KuonenSQL-PG-Zugang grün, zwei neue KS1-Tests); Nachbesserung: bei KuonenSQL Abbrechen/Beenden aus (KS1), Skript nie gegen die Fenstersitzung und mit Beginn als timestamptz in UTC, Rechteprüfung über USAGE, Erkennung um REINDEX SCHEMA, ALTER DATABASE … SET TABLESPACE, DISCARD ALL, COMMIT/ROLLBACK PREPARED u. a. ergänzt, VACUUM in offener Transaktion belegt, Hinweis zur Datenbank des Fensters, GetSchema-Test räumt ohne CASCADE auf (KS1-Zwilling für DROP SCHEMA … CASCADE). Testisolation und 911 (Flackern, Commits `b0ea93c`, `185a892`, `fe8e71d`): TDS-Pooltests leeren nur den eigenen Pool (`ClearAllPools` in eigener Sammlung), PG-Sicherheitstests mit eigenem Präfix, dbcreator-Test nimmt nur die eigene Anmeldung heraus; **Verhaltensänderung im SQL-Server-Plugin:** Fehler 911 gilt in der Zuordnung des Anmeldungsdialogs (`SicherheitAnmeldung.DatenbankFort`) und im Baum (`BaumKataloge.IstGeradeNichtZugaenglich`) als „Datenbank fort“; neuer KS1-Befund U+FDD0 (Einzelheiten in `docs/umsetzung.md`, „K5 Testisolation und 911“). Nachbesserung nach der Gesamtprüfung (Einzelheiten in `docs/umsetzung.md`, „K5 Gesamtprüfung, Nachbesserung“): W1 Testsatz und `PoolOrakelTests` zusätzlich mit `Pooling=true`, Wiederverwendung über den Plugin-Weg am eigenen Testserver belegt (PG-Zwillinge offen bis Orakel, jetzt 123); W2 vor `DROP DATABASE` Nebenverbindung entfernt (Kern additiv `Nebenverbindungen.EntferneAsync`) und Pool der Datenbank geleert, Hinweis im Skript, kein FORCE; W3 KS1-Zahlen 131; K1 Erkennung mit `pg_has_role … 'USAGE'`; K2 serverseitig beendete Nebenverbindung einmal neu geöffnet (nur lesende Katalogabfragen); K3 `Cancel()` kehrt sofort zurück, `CloseAsync`/`DisposeAsync` rollen asynchron zurück; K4 Formatcache nach ALTER/CREATE/DROP/SET/RESET/DISCARD/DEALLOCATE/ROLLBACK geleert; K5 `standard_conforming_strings=off` in Prüfung und Stapelbau berücksichtigt; K8 Plan angeglichen; K6/K7 in K5b.
K5b
Offene Punkte aus der K5-Gesamtprüfung mit Besitzer

Offene Punkte aus der K5-Gesamtprüfung mit Besitzer: (1) **N6/K6 – PG-Plugin, Besitzer: Umsetzer des PG-Plugins vor Teil 2:** Die Bestätigung für gefährliche Rollen (`pg_write_server_files`, `pg_execute_server_program` & Co.) erfasst nur die unmittelbare Mitgliedschaft; wer in eine Rolle aufgenommen wird, die selbst Mitglied einer gefährlichen Rolle ist, erbt deren Wirkung ohne Bestätigung. Vorschlag: Hinweis bzw. Bestätigung über `pg_has_role(…, 'USAGE')` gegen die gefährlichen Rollen (`docs/umsetzung.md`, „K5 Aufgabe 10“, Nachbesserung, N6). (2) **K7 – SQL-Server-Plugin-Tests, Besitzer: Umsetzer der SqlServer-Plugin-Tests (Testisolation), vor dem Abschluss von K5 bzw. bei erneutem Flackern:** Wackelkandidaten 596 („session is in the kill state“; `ALTER DATABASE … SET OFFLINE/SINGLE_USER WITH ROLLBACK IMMEDIATE` in BaumTests, BaumprobeAufraeumenTests und `Baumprobe.LoescheDatenbankAsync` trifft die Sitzung eines parallelen Tests) und 1205 (Deadlock; `DialogAsync`/`AnwendenAsync` der Sicherheitstests ohne Deadlockwiederholung). Vorschlag: `SET OFFLINE WITH NO_WAIT` mit Wiederholung statt `ROLLBACK IMMEDIATE`, Deadlockwiederholung auch für die Dialoge der Sicherheitstests. **Teil (2) 1205 erledigt 13.10.2026:** die Verklemmung lag im Plugin selbst (lesende Katalogabfrage von `SicherheitAnmeldung` als Opfer eines parallelen `DROP DATABASE`); lesende Katalogabfragen (`Skripte.LiesAsync`/`LiesServerAsync`) wiederholen einen Deadlock jetzt höchstens dreimal (`Lesewiederholung`, `LesewiederholungTests` 3); voller Lauf am SQL Server 2025 grün. Offen: (1) und 596

◐
K6
Kuonen.Data.MySql + Plugins.MySql

`Kuonen.Data.MySql` + `Plugins.MySql`

◐ Aufgabe 9 von 9, offen nur Orakelbelege (Treiber und Plugin sind gebaut; offen sind die Belege an echter MariaDB 11.8 und echtem MySQL 8.4, aufgelistet in `docs/umsetzung.md`, Abschnitt „K6 offen bis Orakel“, und danach die Gesamtläufe und die Gesamtprüfung durch den Hauptchat; erst dann ✅). Plugin mit Gerüst, Erkennung, Abfragelauf, Objekt-Explorer, Befehlen, Skript als, Eigenschaften, Designer, Sicherheit, Wartung, Aktivitätsmonitor und Ereignissen (Aufgabe 1: Verbindung: Handshake, TLS, Anmeldung mit native/caching_sha2/ed25519/Klartext, Fehler, Warnungen; Aufgabe 2: `MySqlCommand` über COM_QUERY mit Mehrfachergebnissen, `MySqlDataReader` mit allen Gettern, Typen in Text- und Binärform wie MySqlConnector, Spaltenschema, SchemaOnly über COM_STMT_PREPARE, Gesamtfrist je Aufruf (N3), Belegung unter 4 Byte je Zeile; Aufgabe 3: Parameter (`@name`/`?name`/`?`, Lexer mit Literalen, Backticks, Kommentaren und `/*! */`), Binärprotokoll mit COM_STMT_PREPARE/EXECUTE/CLOSE/RESET/SEND_LONG_DATA, Prepare-Cache LRU 256, 1615 ohne Wiederholung, Prozeduren mit OUT/INOUT über SERVER_PS_OUT_PARAMS, `MySqlBatch`; Aufgabe 4: `MySqlTransaction` (Stufen wie MySqlConnector, Snapshot mit WITH CONSISTENT SNAPSHOT, Sicherungspunkte in Backticks, Ende durch implizites Commit/rohes COMMIT/START TRANSACTION/Deadlock erkannt und beim Commit gemeldet), Abbruch über zweite Verbindung mit SHOW PROCESSLIST und KILL QUERY für Cancel, Token und Command Timeout (nur 1317 als Abbruch, Vormerken, zweiter Versuch, Wachhund, DO SLEEP(0) danach), CloseAsync asynchron; Zusatz N1/N2 aus der Nachprüfung: Host-Vergleich nur bei eigener Adresse mit anderem Port, `@@tx_isolation` für MariaDB vor 11.1; Aufgabe 5: Pool aus `Kuonen.Data.Gemeinsam` (Schlüssel ohne Kennwort, gemerkt; ROLLBACK bei Rückgabe; `COM_RESET_CONNECTION` mit `SET NAMES` und `COM_INIT_DB` vorgemerkt und mit dem ersten Befehl in derselben Sendung, Antworten vor der des Befehls gelesen (H1); `COM_CHANGE_USER` für MySQL < 5.7.3 und MariaDB < 10.2.4; Caches geleert), `MySqlDataSource`, `GetSchema` mit 15 Sammlungen und Einschränkungen als Parameter, Messung 1 Mio. Zeilen gegen MySqlConnector am KuonenSQL-MariaDB-Zugang gleichauf (`docs/leistung.md`); Nachbesserung: KuonenSQL nicht im Pool (KS1 zum Reset), Tests zum offenen KILL, Pool-Schlüssel mit HMAC-SHA256 und Prozessgeheimnis (auch PG); Aufgabe 6: `Plugins.MySql` (Ausgabe `*.Plugin.dll`; `ErzeugeVerbindung` mit Optionsprüfung und den Vorgaben GuidFormat=None, Allow User Variables, Allow Multi Statements, Vorschlag SslMode=VerifyFull; Erkennung mit einer Abfrage: Rollen ab MySQL 8/MariaDB 10.0.5, Sequenzen nur MariaDB ab 10.3, Vektor ab MySQL 9/MariaDB 11.7.1, SitzungBeenden nach CONNECTION_ADMIN/SUPER, Ereignisse statt Agent-Jobs, DENY/Schemas/Sicherung/Synonyme mit Grund aus; Stapel wie der mysql-Client mit DELIMITER; Warnungen, Fehler mit Nummer, SQLSTATE und Zeile aus „at line n“, offene Transaktion aus dem Statusfeld; Kern: Meldungen beim Schliessen des Lesers gehören zu ihrem Stapel; Nachbesserung: Lauf hält beim MySQL-Plugin nach einem Fehler an (Kern `IFehlerverhalten`, `Laufoptionen.BeiFehlerWeiter` als `bool?`), `/*M!` als Code, Text hinter DELIMITER als falscher Trenner, Rechte-Text gedeckelt, keine doppelte späte Fehlermeldung, MariaDB-SUPER bis 11.0); Aufgabe 7: Objekt-Explorer (Datenbanken mit Systemdatenbanken nach lower_case_table_names, Tabellen, Sichten, Programmierbarkeit mit Trigger und Sequenzen, Ereignisse, Tabellenteile mit CHECK ab 8.0.16/10.2.1, Benutzer und Rollen; information_schema mit Parametern, 1049/1146 nur der Ordner), Befehle mit LIMIT und USE, Skript als aus SHOW CREATE mit Schutzprüfung `Skriptpruefung` (MySQL-Lexer, drei Lesarten, /*! als Code und Kommentar, Körper mit BEGIN … END ohne angehängte Anweisung), DELIMITER um Routinen, Trigger und Ereignisse, DROP IF EXISTS, Index aus STATISTICS, DML-Vorlagen, gemeinsamer Testsatz an beiden KuonenSQL-Zugängen; Nachbesserung: bedingte Kommentare in beiden Lesarten, DO nur einmal je WHILE, Rückstrich in Kommentaren und Dollarzitate verweigert, kein USE bei Zeilenumbruch oder Backtick im Datenbanknamen; Aufgabe 8 (Zwischenstand eines unterbrochenen Umsetzers auf `main` übernommen, geprüft, nachgebessert): Eigenschaften (Server über `DialogeServer` von T3a, Datenbank mit Zeichensatz und Kollation nur aus dem Katalog, Tabelle/Sicht nur lesbar), Designer (Neu, Entwerfen mit ADD/MODIFY/CHANGE/DROP COLUMN, RENAME COLUMN nach Fassung, FIRST/AFTER, ganze Definition aus SHOW CREATE TABLE mit Abgleich gegen den Katalog, strikter `sql_mode` für das Skript samt Rückstellung nach Abbruch, keine still mitgelöschten Abhängigen, DDL nicht transaktional als Hinweis), Sicherheit (Benutzer = Anmeldungen, Rollen = Serverrollen, Rechte-Abbildung ABGELEITET aus KuonenSQL S12.19, Recht ohne Gegenstück abgelehnt, Wortsperre, gefährliche Rechte/Rollen nur mit Bestätigung auch über Mitglieder, Entzug mit Hinweis auf weiter geltende Rechte, Kennwort nur im Ausführtext, keine Kommentare); Nachbesserung: Kennwortregel in beiden Lesarten des Rückstrichs, Mitglieder einer im Dialog gefährlich werdenden Rolle, GRANTEE einer MariaDB-Rolle in beiden Formen; Nachbesserung nach der Prüfung (M1 NULL → NOT NULL, M2 Runden nur mit Bestätigung, M3 echte Herabstufung von sysadmin, M4 gefährliche Rollen über Muster, Spalten- und Routinenrechte, M5 keine zu weite Gegenrichtung, M6 Kennwortregel für /*! */, SOURCE/MASTER_PASSWORD, PASSWORD(), N2 Zustandsprüfung auch nach Abbruch der Aufzählung im Kern, N3–N5, N7–N9); Reste der Nachprüfung von Aufgabe 8 vor Aufgabe 9 (M6a: Kennwortregel schwärzt bei jedem bedingten Kommentar ab dem ersten IDENTIFIED/PASSWORD, N12: `--` mit Steuerzeichen, M2a: weitere rundende Typwechsel mit Bestätigung, N10: Fundstelle double → float, N11: Hinweis auf weiter geltende Rollen und Rechte auf mysql beim Abwählen von sysadmin); Aufgabe 9: Wartung einer Tabelle (OPTIMIZE, ANALYZE, CHECK, REPAIR nur für MyISAM/Aria/ARCHIVE/CSV nach der Engine aus dem Katalog, Hinweise zu implizitem Commit, Sperren und Binärprotokoll), Aktivitätsmonitor (PROCESSLIST mit Sitzungsmerkmal Benutzer@Rechner:Port und THREAD_ID aus performance_schema.threads, weil kein Server den Beginn einer Sitzung nennt; Wartende aus sys.innodb_lock_waits bzw. INNODB_LOCK_WAITS; Kennwortregel), KILL QUERY/KILL CONNECTION nur über ein Skript, das ID, Merkmal und das eigene Fenster selbst prüft (PREPARE aus @msm_kill, sonst DO 0), Prüfung vor „Beenden“ am Server, eigene Sitzung gesperrt, fremde Benutzer nur mit Recht; Ereignisse aktivieren/deaktivieren mit ALTER EVENT, Hinweis bei ausgeschaltetem Ereignisplaner (nicht geändert); AOT-Probe mit MySQL ohne Warnung; README-Abschnitt MySQL/MariaDB; Nachbesserung nach der Prüfung (M1 Serverstart im Sitzungsmerkmal gegen KILL nach einem Neustart, Unix-Socket ohne THREAD_ID aus; M2/M5 grobe Kennwortregel für fremde Texte in Monitor und Profiler; N3 bytegenauer Vergleich; N4 eigene Verbindungen und Pool-Leitungen geschützt; N5–N8); 956 Tests im Plugin, 851 grün, 101 übersprungen, 4 rot – alle vier KS1 am MySQL-Zugang (Gesamtprüfung: `CREATE ROLE 'r'@'%'` 1064 neu, dazu Befunde 1, 4, 5 und 9 aus Aufgabe 8 auch dort), seit der Gesamt-Nachbesserung als `…_KuonenSqlMySql_Befund_KS1` übersprungen, damit 0 rot; die übrigen 14 laufen mit dem KuonenSQL-Bau vom 14.10.2026 grün; Plan `docs/superpowers/plans/2026-10-12-k6-mysql.md`, seit dem 14.10.2026 auf `main` (fast-forward; der Abschluss im Plan, „Merge nach `main`“, ist erledigt)); Gesamtprüfung am 14.10.2026 mit Nachbesserung: H1 („Zeilen bearbeiten“ schreibt Werte mit Rückstrich als `_utf8mb4 X'…'`), M1–M7 und N1–N6 (`.superpowers/sdd/k6/gesamt-nachbesserung-report.md`); veraltete KS1-Zwillinge laufen nach jedem KuonenSQL-Bau mit `MSM_KS1_ALLE=1` (Verfahren in `docs/umsetzung.md`, „KS1-Umgehungen im Produkt“). Orakel MariaDB 11.8 (13308) und MySQL 8.4 (13309) brauchen die Kennwörter vom Auftraggeber, bis dahin KuonenSQL-Zugänge (13310/13311) und eigener Testserver; offene Belege (54 Zwillinge im Treiber, 83 im Plugin samt 10 aus T2–T4 und 2 aus H1 – zwei davon ändern am echten Orakel serverweit `wait_timeout` und laufen nur mit `MSM_SERVERWEIT_ERLAUBT=1`, Entscheid des Auftraggebers offen –, Warnungen am Orakel, ed25519 und RSA nur am Testserver, Vergleich mit MySqlConnector im Messprojekt `bench/Kuonen.Data.MySql.Messung`) in `docs/umsetzung.md`, Abschnitt „K6 offen bis Orakel“; KS1-Befunde: keine Warnung 1292 (Aufgabe 1, 2 Tests); TIME ab 24 h, VARCHAR binary, COUNT(*) als INT (Aufgabe 2, 7 Tests); negative TIME, YEAR binär als Text, keine Ausgabemenge nach CALL, information_schema.PARAMETERS am MySQL-Zugang (Aufgabe 3, 6 Tests); kein 1317 nach KILL QUERY, fehlender Sicherungspunkt ohne 1305, kein implizites Commit bei LOCK TABLES und START TRANSACTION (Aufgabe 4, 6 Tests; 1317 nach KILL QUERY am MariaDB-Zugang seit PR27.23h behoben); Reset lässt Benutzer- und Sitzungsvariablen stehen, vorbereitete Anweisungen über information_schema am MySQL-Zugang, PARAMETER_MODE INOUT statt OUT an beiden Zugängen (Aufgabe 5, 4 Tests; die vorbereiteten Kataloge am MySQL-Zugang seit PR27.19b behoben); Syntaxfehler erst nach der Datenbankprüfung (Aufgabe 6, 2 Tests); SHOW CREATE TABLE ohne verdoppelte Backticks, SHOW CREATE TRIGGER ohne Backticks, Sequenzen, Kommentarzeile vor der Anweisung, Nullbarkeit von Sichtspalten, vorbereitete Kataloge am MySQL-Zugang im Baum (Aufgabe 7, 9 Tests; die Kommentarzeile vor CREATE TABLE seit PR27.23b behoben); GRANT auf `db`.`t` in Backticks und auf maskiertes `db\_`.* ohne Wirkung, Rollenrechte auf *.* fehlen in USER_PRIVILEGES, RENAME/CHANGE COLUMN 1105 und FIRST/AFTER bei MODIFY ohne Wirkung, `SET SESSION sql_mode` mit Ausdruck übergangen, `@` im Benutzernamen, Spaltenkommentar bei CREATE TABLE mit Datenbank im Namen, ALL PRIVILEGES ohne CREATE TABLESPACE in USER_PRIVILEGES (Aufgabe 8, 7 Tests; der Spaltenkommentar bei CREATE TABLE mit Datenbank ist behoben); CREATE ROLE mit Rechner 1064 am MySQL-Zugang, Befunde 1, 4, 5 und 9 aus Aufgabe 8 auch dort (Gesamtprüfung, 4 Tests); ENGINE=MyISAM/Aria wird InnoDB, REPAIR an InnoDB meldet am MariaDB-Zugang OK, INNODB_LOCK_WAITS nur mit TABLE_CATALOG, kein performance_schema.threads am MariaDB-Zugang, PROCESSLIST_HOST `localhost` statt Rechner, GLOBAL_STATUS leer (Aufgabe 9, Vorschlag PR27.24a–e in `docs/kuonensql-gegenstueck.md`; alle K6-KS1 ohne KuonenSQL-Nummer als Vorschlag PR27.24a–n unter „Für die KuonenSQL-Roadmap“, Übergabe an KuonenSQL offen, ohne eigene Tests; die Orakeltests folgen dem Katalog)
K7
Kuonen.Data.Sqlite lesend + Plugins.Sqlite

`Kuonen.Data.Sqlite` lesend + `Plugins.Sqlite`

◐ Aufgabe 7 von 10 (ab Aufgabe 5 im Hauptchat ohne Agenten). Aufgabe 7 (Teil 1): ADO.NET-Oberfläche mit den Klassen und Namen von Microsoft.Data.Sqlite, zeilenweises Lesen im Lesestand (Autocommit je Anweisung, Lesetransaktion über mehrere Befehle, BUSY statt Zeilen aus zwei Ständen), Getter, Feldtypen, Schematabelle und Parameterbindung wie Microsoft.Data.Sqlite (am Messprojekt gemessen, Erwartungen committet), Pfadprüfung, Pool der Dateizugänge, GetSchema mit 13 Sammlungen gegen das Manifest, Stapel, Abbruch; Sabotage Pool und UNC rot ✅; Teil 2: Leistung gegen Microsoft.Data.Sqlite (Vollscan schneller als Microsoft.Data.Sqlite, typisiertes Lesen 0,4 B je Zeile, Ziel E13 erfüllt; Lesestand 659 → 75 µs, begrenzte Halde für ORDER BY mit LIMIT, `docs/leistung.md`) und GetStream über 50 MB mit weniger als 1 MB Belegung ✅; offen: Unix-Lauf der FIFO-Prüfung, `SqliteConnection.Dateikopf`, Zwischenspeicher gebundener Anweisungen; Einzelheiten in `docs/umsetzung.md`, „K7 Aufgabe 7“. Aufgabe 6: SELECT mit FROM – Tabellen, `sqlite_schema`, Sichten (als eigene Abfrage eingesetzt, Tiefe ≤ 64, Kreis → Fehler wie SQLite), `pragma_xxx(…)` als Tabellenfunktion samt `arg`/`schema`, VIRTUAL-Spalten berechnet; Spaltenbezüge wie SQLite (mehrdeutig, rowid/oid/_rowid_, USING, Alias in WHERE und ORDER BY, GROUP BY erst Spalte, dann Alias; Spaltennamen und Entdoppelung `a:1` gemessen); verschachtelte Schleifen mit LEFT JOIN; Planer (rowid =, IN, Bereich; Primärschlüssel einer WITHOUT-ROWID-Tabelle; führende Indexfelder nur bei gleicher Kollation und passender Affinität; die Bedingungen werden danach immer vollständig geprüft); Gruppieren mit Deckel (nackte Spalten aus der ersten Zeile der Gruppe, bei min/max aus deren Zeile – gemessen), DISTINCT, Sortieren mit Deckel 1 Mio. Zeilen bzw. 256 MB → SQLITE_TOOBIG und begrenzter Halde bei LIMIT; EXPLAIN QUERY PLAN in der Form von eqp.html (Grundformen wie das Orakel); Abbruch je 256 Zeilen (SQLITE_INTERRUPT bzw. OperationCanceledException). Orakelkatalog `Orakel/Abfragen/abfragen.sql` (214 Abfragen) mit committeter Erwartung auf der neuen Korpusdatei `abfragen.db`; die Wahl des Plans ändert kein Ergebnis; 2000 Bitkipper; Sabotage „Indexsuche ohne Kollationsprüfung“ und „LEFT JOIN als INNER“ rot ✅; Einzelheiten in `docs/umsetzung.md`, „K7 Aufgabe 6“. Aufgabe 5: SQL-Sprache – Parser, Ausdrücke, Funktionen und Aggregate des Umfangs, SELECT ohne FROM, lesende PRAGMAs, Schreiben → SQLITE_READONLY; REAL ↔ Text wie SQLite (Dekker, ABGELEITET); 5000 Zufallsausdrücke und alle Prüffälle gleich wie das Orakel. Aufgabe 1: Gerüst (`Kuonen.Data.Sqlite`, `Plugins.Sqlite` mit `PluginInfo`, Testprojekte, Messprojekt mit Microsoft.Data.Sqlite ausserhalb der Mappe), Orakelweg (`sqlite_orakel.py` über `python -I -S`, Testhilfen `SqliteOrakel`/`Korpusdatei`, `MSM_SQLITE_PYTHON` in `dist\testumgebung.ps1` nur für genau SQLite 3.50.4) und Korpus aus SQLite 3.50.4 (27 Datenbanken samt WAL/Journal, 2 364 224 Byte mit Manifest) ✅; Nachbesserung nach der Prüfung (ATTACH/VACUUM INTO und schreibende Orakelbefehle nur unter Temp, Sollfassung aus dem Manifest, Kindprozess ohne Zugangsdaten, WITHOUT ROWID im Manifest nach Primärschlüssel) ✅; AGENTS.md §6 (E3) offen bis zur Freigabe, Vorschlag in `docs/umsetzung.md`, „K7 Aufgabe 1“. Aufgabe 2: Dateiformat lesen (Kopf mit am Orakel gemessenen Ablehnungscodes, Seitenleser ohne Lock-Byte-Seite, B-Baum-Seiten und -Zeiger für Tabellen und Indizes samt Suche, Nutzlast mit lazy gelesener Overflow-Kette, Datensatz, drei Kodierungen, Freiliste, Ptrmap, Gesamtdurchlauf; öffentliche `SqliteException`), Korpus Wert für Wert gegen das Manifest, gezielte Verfälschungen und 10 000 Bitkipper je Korpusdatei ✅; WAL/Journal folgen mit Aufgabe 3; Einzelheiten in `docs/umsetzung.md`, „K7 Aufgabe 2“. Nachbesserung nach der Prüfung (H1 Freilistenzahl gegen Seitenzahl, M1 Ptrmap neben der Lock-Byte-Seite nach §1.8 – Messung am Orakel offen bis K7g –, M2–M4 Tests für Kreise, Tiefe, Schranken, Spaltenzahl, TOOBIG, Zweig K ≤ X, Kodierungen am Orakel gemessen) ✅. Aufgabe 3: WAL lesend (Kopf, Salz, Prüfsummenkette beider Byte-Reihenfolgen, letzter Commit, Seitentabelle; Kopf und Seitenzahl aus dem WAL), Rollback-Journal (heiss wie am Orakel gemessen: nicht leer und erstes Byte ≠ 0; beobachtet bis zur Frist, dann `SQLITE_READONLY_ROLLBACK`), keine Betriebssystem-Sperren (E4, Vergleich mit SQLite unter Windows und Unix dokumentiert), Momentaufnahme mit optimistischer Prüfung (Kopf, Länge, Zeit, WAL-Kopf und -Ende, Checkpoint-Stand aus der `-shm`, nur gelesen) und Lesetransaktion (höchstens 3 neue Versuche, sonst `SQLITE_BUSY`; Schema-Cookie für Aufgabe 4), keine stillen Nullen beim Schrumpfen der Datei (N7); lebendes WAL, Schemaänderung und Konsistenzprobe (WAL und DELETE) mit dem echten SQLite ✅; Unix-Lauf offen; Einzelheiten in `docs/umsetzung.md`, „K7 Aufgabe 3“. Nachbesserung nach der Prüfung (H1 Schleifendeckel an den vorhandenen Seiten, Seitenzahl mit WAL wie gemessen; M1 Checkpoint hinter dem eigenen mxFrame über gelesene Seiten erkannt; M2 WAL-Zustand ohne zählendes WAL; M3 heisses Journal erst nach 1 s Ruhe, sonst BUSY; M4 WAL nur während einer Lesetransaktion offen; M5 Test des Änderungszählers, Konsistenzprobe auch PERSIST/TRUNCATE) ✅. Aufgabe 4: Schema aus `sqlite_schema` mit Deckeln (eigener Lexer, DDL-Parser für CREATE TABLE/VIRTUAL TABLE/INDEX/VIEW/TRIGGER, Typtext und Vorgaben wie SQLite, Affinität nach `datatype3.html` §3.1), automatische Indizes, Lesen mit Schema (rowid-Alias, ADD-COLUMN-Vorgaben mit Affinität, STORED, WITHOUT ROWID), PRAGMAs table_info/table_xinfo/table_list/index_list/index_info/index_xinfo/foreign_key_list/database_list/collation_list und die Kopfwerte Spalte für Spalte wie das Orakel, ATTACH/DETACH (höchstens 10), Neuladen nach dem Schema-Cookie; kaputte Objekte nur einzeln unlesbar ✅ (offen für Aufgabe 5/6: VIRTUAL-Spalten, `table_info` auf Sichten, Anbindung an `PRAGMA`-Anweisungen und `pragma_xxx(…)`, `function_list`); Einzelheiten in `docs/umsetzung.md`, „K7 Aufgabe 4“. Nachbesserung nach der Prüfung (H1 Sichtspalten gemerkt und höchstens 2000 Spalten, H2 Gesamtdeckel für die Texte von `sqlite_schema`, M1 Leerraum nur ASCII wie gemessen, M2 Handle-Leck, M3 und N1–N8 am Orakel gemessen) ✅; offen für Aufgabe 6: `table_info` auf Sichten, N9; für Aufgabe 7: Pfadprüfung für ATTACH. Plan: docs/superpowers/plans/2026-10-14-k7-sqlite.md (Orakel: echtes SQLite 3.50.4 über das Python-Modul `sqlite3`, Korpus aus Python committet; lesende Engine ohne Betriebssystem-Sperren; SQL-Umfang Stufe 1; offene Entscheidungen E1–E17 im Plan). Regeln: `.superpowers/sdd/k7/umsetzer-regeln.md`
K8
Leistung und Robustheit aller Treiber; dabei die Messung von 1 Mio. Zeilen (docs/leistung.md, K3 Aufgabe 12) auf ruhiger…

Leistung und Robustheit aller Treiber; dabei die Messung von 1 Mio. Zeilen (`docs/leistung.md`, K3 Aufgabe 12) auf ruhiger Maschine wiederholen und die Mehrkosten von MARS beim synchronen Lesen breiter Zeilen untersuchen; AOT-Rauchtest (ein veröffentlichtes `PublishAot`-Programm mit `Kuonen.Data.Tds` verbindet sich, liest und schreibt am Orakel; offen aus der Gesamtprüfung von K3); aus K4 Aufgabe 3: `SequentialAccess` und PLP gestreamt, `GetChars` ohne Volldekodierung, danach gedeckeltes Lesen im Abfragelauf (heute wird ein Wert ganz gelesen und erst dann gekürzt); Ergebnismengen und Meldungen des Abfragelaufs gestreamt melden, also Teilmengen bzw. Meldungen sofort statt erst an Mengengrenzen; aus K6 Aufgabe 5: der Schlüssel im Pool-Verzeichnis von `Kuonen.Data.Tds` (`TdsPool.Schluessel`) enthält die Zeichenfolge samt Kennwort im Klartext – wie bei PostgreSQL und MySQL auf den HMAC-Schlüssel aus `Kuonen.Data.Gemeinsam` umstellen; aus der Prüfung von K6 Aufgabe 8 (N1, Kern, alle Plugins): beim Lauf eines `Aenderungsskript` mit `Ausfuehrtext` steht das Kennwort womöglich in einer Fehlermeldung des Servers (etwa 1064 „…near ''geheim' …'“, ohne das Wort IDENTIFIED, so dass keine Kennwortregel greift) – der Kern soll in jeder Meldung dieses Laufs jedes Kennwort-Literal des Skripts durch `********` ersetzen ; aus K6 Aufgabe 9: die Profiler des PostgreSQL- und des SQL-Server-Plugins (`PgProfiler`, `XeProfiler`, T3d) zeigen fremde SQL-Texte ohne Kennwortregel – wie im Aktivitätsmonitor grob schwärzen (`MyProfiler` erledigt in der Nachbesserung von K6 Aufgabe 9 mit `Kennwortregel.Anzeige`) ; aus der K6-Gesamtprüfung (nur aus der Doku abgeleitet, am Orakel zu belegen): „Zeilen bearbeiten“ im PG-Plugin schreibt `bytea` als `'\x…'` (bei `standard_conforming_strings=off` verfälscht), im SqlServer-Plugin gehen Rückstrich und Zeilenumbruch verloren, wenn sie in `N'…'` direkt aufeinander folgen ; bei einer Anmeldung, die an einem fremden Server scheitert (Port-Konflikt), den antwortenden Server nennen (Produkt aus PRELOGIN/LOGINACK)

⬜
T2a
Teilprojekt 2 (Oberfläche) als VS-Code-Erweiterung, erster Schritt

Teilprojekt 2 (Oberfläche) als VS-Code-Erweiterung, erster Schritt: `src/Kuonen.MultiSQLManager.Dienst` (Hintergrunddienst, zeilenweises JSON über Standard-Ein-/Ausgabe oder `--tcp`; Methoden `plugins`, `profile`, `profilSpeichern`, `profilLoeschen`, `verbinden`, `kinder`, `befehle`, `skript`, `trennen`; Zeilendeckel 16 MB, je Verbindung ein Aufruf zugleich, Kennwort nie in Antworten) und `vscode-erweiterung/` (reines JavaScript ohne npm-Pakete: Objekt-Explorer, Verbindungen anlegen/bearbeiten/entfernen, Kennwort im Schlüsselbund, „Befehle …“ mit „Skript als“ in neuem Editor). F5 „MultiSQLManager: Erweiterung starten“ öffnet ein Testfenster. Belegt durch `Kuonen.MultiSQLManager.Dienst.Tests` (37) und `node --test vscode-erweiterung/test` (5, gegen den echten Dienst). Abgelöst durch das Studio (T2b)

✅
T2b
Studio im Stil von SSMS 22 (Webview, reines JavaScript)

Studio im Stil von SSMS 22 (Webview, reines JavaScript): Menüleiste, Werkzeugleiste mit Datenbankauswahl, Dialog „Mit Server verbinden“ (Servertyp, Authentifizierung, Verschlüsselung, Kennwort im Schlüsselbund), Objekt-Explorer mit eigenen Symbolen und Kontextmenü samt Untermenüs aus den Plugin-Befehlen, Abfragefenster mit eigener Verbindung (Editor mit Hervorhebung und Zeilennummern, F5 ganz oder Auswahl, Abbrechen, Ergebnisraster für 100 000 Zeilen, Meldungen im SSMS-Format mit Sprung zur Fehlerzeile, Abfragestatusleiste), Eigenschafts-/Neu-/Entwerfen-Dialoge aus den Dialogmodellen (Skript in Fenster oder Zwischenablage, OK führt aus), Aktivitätsmonitor mit Aktualisierungsintervall und „Prozess beenden“, hell und dunkel. Dienst: `datenbanken`, `ausfuehren`/`abbrechen` mit Ereignissen, `dialog`/`anwenden`/`skriptAusfuehren` (Modell und Ausführtext bleiben im Dienst), `prozessKnoten`. Belegt durch `Kuonen.MultiSQLManager.Dienst.Tests` (54), `node --test` (10) und eine Durchsicht im Browser am eigenen KuonenSQL-Testserver (Verbinden, Baum, Abfrage mit zwei Mengen, Fehlerzeile, Abbruch, Datenbankeigenschaften mit Ausführung, Monitor mit Sperre der eigenen Sitzung). Installierbar als `.vsix` mit mitgeliefertem Dienst (`vscode-erweiterung/werkzeuge/paketieren.js`, eigener ZIP-Schreiber ohne npm; `test/paket.test.js`). Ergebnisse als Text (Strg+T, zurück mit Strg+D). IntelliSense: `IKatalog` (Spalten der aktuellen Datenbank aus INFORMATION_SCHEMA, in den drei Plugins, Dienst `katalog`), Vorschlagsliste im Editor (nach FROM/JOIN Tabellen, nach Alias/Tabelle mit Punkt deren Spalten, sonst Spalten der Anweisung, Tabellen, Schlüsselwörter; Strg+Leertaste; Katalog nach jeder Ausführung neu), belegt durch `test/vorschlaege.test.js` (9). Offen: Belege an SQL Server 2025, PostgreSQL und MySQL

◐
T2c
Abfrage-Designer wie in SSMS bei „Erste 200 Zeilen bearbeiten“

Abfrage-Designer wie in SSMS bei „Erste 200 Zeilen bearbeiten“: Diagrammbereich (Haken je Spalte, „* (Alle Spalten)“, Schlüssel), Kriterienbereich (Spalte, Alias, Ausgabe, Sortiertyp, Sortierfolge, Gruppieren nach mit Anzahl/Summe/Min/Max/Mittelwert, Filter; Reihenfolge ziehen oder ▲▼), SQL-Bereich (vom Plugin gebaut), Ergebnisbereich bearbeitbar (Doppelklick/F2/Tippen, Strg+0 = NULL, neue Zeile `*`, Entf löscht, nur mit Primärschlüssel und ohne Gruppieren). Kern: `ITabellenbearbeitung`, `Tabellenbau`; umgesetzt in SQL Server, PostgreSQL, MySQL; Dienst: `tabelle`, `entwurf`, `zeileSql`. Belegt durch `TabellenbauTests` (18), `TabellenbearbeitungOrakelTests` der drei Plugins am KuonenSQL-Testserver, `DesignerTests` (7) und eine Durchsicht im Browser (TDS und PostgreSQL: Ändern, Einfügen, Löschen, Ausblenden, Reihenfolge, Filter, Sortierung, Gruppieren). KS1-Befunde in der KuonenSQL-Roadmap PR27.19a–f. Offen: Belege an SQL Server 2025, PostgreSQL 17, MariaDB, MySQL; mehrere Tabellen mit Verknüpfung im Diagramm

◐
T3a
Serverweite Einstellungen wie in SSMS (Vorgabe 2026-10-12)

Serverweite Einstellungen wie in SSMS (Vorgabe 2026-10-12): Servereigenschaften Speicher/Prozessoren/Erweitert über `sp_configure`, KuonenSQL-Einstellungen auch mit `nur_global`, KuonenSQL-Zugänge (Port, Aktiv) über `sp_kuonensql_zugang` — gesperrt mit Grund, solange ein KuonenSQL die Prozedur nicht kennt; seit KuonenSQL PR27.20a (13.10.2026) am Testserver belegt. Belegt durch `SerververwaltungTests` (13) und `DialogeOrakelTests` am KuonenSQL-Testserver; SQL Server 2025 offen. PostgreSQL: Seite „Ändern (ALTER SYSTEM)“ mit gängigen Einstellungen aus `pg_settings` (Grenzen, Neustart-Hinweis, nur für Superuser; `pg_reload_conf()` an jedem Server, seit KuonenSQL PR27.23e auch dort – die Umgehung ist in der K6-Gesamt-Nachbesserung entfernt); MySQL/MariaDB: neue Servereigenschaften mit Seite „Ändern“ (`SET PERSIST` bzw. bei MariaDB `SET GLOBAL` mit Hinweis). Auf Anweisung des Auftraggebers (13.10.2026) am eigenen KuonenSQL-Testserver wirklich ausgeführt und zurückgesetzt (`EigenschaftenOrakelTests`, `ServerdialogOrakelTests`; `ServerdialogTests` 10, `EigenschaftenTests` 6 neue). Belegt an SQL Server 2025, PostgreSQL 17, MySQL 8.4 und MariaDB 11.8 (eigene Referenzcontainer, 13.10.2026); dort auch serverweit ausgeführt und zurückgesetzt (sp_configure, ALTER SYSTEM mit pg_reload_conf, SET PERSIST, SET GLOBAL). Offen: KuonenSQL PR27.23e–g

✅
T3b
IntelliSense wie SSMS

IntelliSense wie SSMS: `IKatalog.RoutinenAsync` (Prozeduren und Funktionen mit Parametern und Rückgabe, `Katalog.Fasse`) in den drei Plugins, Dienst `katalog` liefert Spalten und Routinen; im Editor Liste von selbst nach FROM/JOIN/UPDATE/INTO/EXEC/CALL und beim Tippen, nach EXEC nur Routinen, Parameterinfo bei `(` und `,` mit aktivem Parameter (Strg+Umschalt+Leertaste), QuickInfo (Strg+K Strg+I, Verweilen der Maus), Member auflisten (Strg+J), Wort vervollständigen (Alt+Rechts), lokalen Cache aktualisieren (Strg+Umschalt+R), IntelliSense ein/aus (Strg+B Strg+I), Signaturen eingebauter Funktionen. Belegt durch `KatalogTests` (2), `Dienst.Tests` (63), `node --test` (30), Orakeltests der Routinen am KuonenSQL-Testserver (T-SQL, MariaDB-Zugang; PG-Zugang KS1, KuonenSQL-Roadmap PR27.20c–d) und Durchsicht im Browser. Belegt an SQL Server 2025, PostgreSQL 17, MySQL 8.4 und MariaDB 11.8 (eigene Referenzcontainer, 13.10.2026)

✅
T3c
Ausführungsplan und Clientstatistik wie SSMS

Ausführungsplan und Clientstatistik wie SSMS: `IAusfuehrungsplan` im Kern (Plane, LiesPlan, Planbaum mit Deckeln), `Abfragelauf.PlanAsync` mit Nachlauf auch nach Fehler und Abbruch; SQL Server über `SET SHOWPLAN_XML`/`SET STATISTICS XML` (Leser `Showplan` nach dem öffentlichen Schema, mit fehlenden Indizes), PostgreSQL über `EXPLAIN (FORMAT JSON)` bzw. `(ANALYZE, FORMAT JSON)` je Anweisung, MySQL/MariaDB über `EXPLAIN FORMAT=JSON`, `ANALYZE FORMAT=JSON` (MariaDB) und `EXPLAIN ANALYZE` (MySQL, Baum als Text); Dienst `ausfuehren` mit `plan`; im Studio Reiter „Ausführungsplan“ (Baum von links nach rechts, Pfeilbreite nach Zeilen, Kosten in %, „x von y“, Tooltip, Eigenschaften F4, Zoom, Plan-XML in VS Code, fehlende Indexdetails) und „Clientstatistik“ (zehn Versuche, Durchschnitt, Trend). Belegt durch `AusfuehrungsplanTests` (9), `ShowplanTests` (10), `PgplanTests` (10), `MyplanTests` (12, davon einer am MariaDB-Zugang von KuonenSQL), `StudioTests` (2), `test/plan.test.js` (6) und Durchsicht im Browser am MariaDB-Zugang. Belegt an SQL Server 2025, PostgreSQL 17, MySQL 8.4 und MariaDB 11.8 (eigene Referenzcontainer, 13.10.2026). KuonenSQL: PR27.21a–c behoben und am Testserver belegt (die Erkennung prüft `SET SHOWPLAN_XML`), offen `ANALYZE` einer Änderung PR27.21d

✅
T3d
Profiler mit Live-Daten wie der XEvent Profiler von SSMS

Profiler mit Live-Daten wie der XEvent Profiler von SSMS: `IProfiler`/`IProfilerlauf` im Kern mit `Profilerfilter` (Ereignis, Datenbank, Benutzer, Anwendung, Text, Mindestdauer; 5000 je Abruf) und `Stichprobenvergleich` (Anmeldung, Abmeldung, Abfrage begonnen/beendet mit Dauer, Warten auf Sperre); SQL Server/KuonenSQL über eine eigene Ereignissitzung `msm_profiler_…` mit Ringpuffer (13 Arten wie im SQL Server Profiler, eigene Sitzung ausgeschlossen, beim Beenden gelöscht), PostgreSQL aus Stichproben von `pg_stat_activity`, MySQL/MariaDB aus `performance_schema.events_statements_history` oder Stichproben der Prozessliste; Dienst `profilerArten`/`profilerStarten`/`profilerLesen`/`profilerBeenden` (endet mit der Verbindung); Studio: Startdialog, Live-Raster mit Suche, Datenfeed anhalten, Beenden, Daten löschen, automatisch scrollen, Details, Kopieren, CSV, Text in neuer Abfrage. Belegt durch `ProfilerTests` (5), `StichprobenvergleichTests` (9), `XeProfilerTests` (11, davon einer am KuonenSQL-Testserver), `PgProfilerTests` (2 + 2 übersprungen), `MyProfilerTests` (3 + 3 übersprungen, einer am MariaDB-Zugang), `StudioTests` (1), `test/profiler.test.js` (5) und Durchsicht im Browser (T-SQL- und MySQL-Zugang). Belegt an SQL Server 2025, PostgreSQL 17, MySQL 8.4 und MariaDB 11.8 (eigene Referenzcontainer, 13.10.2026). KuonenSQL PR27.22a–c behoben (MariaDB-Dauer belegt), offen PR27.23c (`pg_sleep(numeric)` im PG-Zwilling)

✅
T3e
Editor-Funktionen wie SSMS

Editor-Funktionen wie SSMS: Schnellsuche/-ersetzung mit Treffermarkierung (Gross/Klein, ganzes Wort, regulärer Ausdruck mit $1/$&, in der Auswahl, 10 000 Treffer, Ersetzen und Alle ersetzen als ein Rückgängig-Schritt), Lesezeichen in der Randleiste (wandern mit eingefügten und gelöschten Zeilen), Gross-/Kleinbuchstaben, Zeilen verschieben, Zeileneinzug, Codeausschnitte und Umschliessen mit (Felder $Name$, Tab springt weiter) und Vorlagen-Explorer mit Vorlagenparametern <Name, Typ, Wert> — eigene Inhalte für T-SQL, PostgreSQL und MySQL; die Vorschau nimmt die Tastenkürzel aus dem Manifest (mit Akkorden). Gliederung (Ausklappen) wie SSMS: Bereiche für Stapel zwischen GO, BEGIN … END (TRY/CATCH, nicht BEGIN TRAN), CASE … END, $$-Körper und Blockkommentare, Knöpfe −/+ neben den Zeilennummern, Gliederungserweiterung umschalten (Strg+Umschalt+[), Alle umschalten (Strg+K Strg+0), Alle aufklappen (Strg+K Strg+J); ein zugeklappter Bereich ist eine Marke im Textfeld, Ausführen, Speichern, Kopieren, Gehe zu Zeile, Fehlerzeilen und Zeilennummern rechnen in den vollen Text um, Rückgängig/Wiederholen über einen Friedhof der Faltungen. Belegt durch `test/bearbeiten.test.js` (10), `test/gliederung.test.js` (9, mit Zufallseingaben) und Durchsicht im Browser (T-SQL-Zugang: Zuklappen, Lauf mit verborgenem Text, Rückgängig/Wiederholen, Alle umschalten, Gehe zu Zeile, Suchen, Fehler im zugeklappten Bereich)

✅
T3f
Explorer und Fenster wie SSMS

Explorer und Fenster wie SSMS: Filter je Ordner (Name, Schema, Besitzer, Erstellungsdatum; „(gefiltert)“ im Baum), Objekt-Explorer-Details (F7: folgt der Auswahl, Pfad, Suche, Spalten aus den Merkmalen, Doppelklick öffnet, Kontextmenü des Baums), Eigenschaftenfenster (F4: Knoten oder Abfragefenster, angedockt mit Teiler; im Plan die Operator-Eigenschaften). Registrierte Server (Strg+Alt+G: Gruppen, Registrierung mit Beschreibung, Testen, Verbinden per Doppelklick, Verschieben, Umbenennen, Löschen, Export/Import ohne Kennwörter; Profil mit `Gruppe`/`Beschreibung`), Fenster nebeneinander (horizontale/vertikale Registerkartengruppe, verschieben). Belegt durch `test/explorer.test.js` (6), `test/registriert.test.js` (5), `ProfilspeicherTests` (3 neue), `DienstTests` (1 neuer) Abfrageoptionen (`IAbfrageoptionen` mit `Abfrageoptionen.Pruefe`; SQL Server wie die Seiten Ausführung/Allgemein, Erweitert, ANSI von SSMS, PostgreSQL und MySQL/MariaDB mit ihren Sitzungsvariablen; Ausführungstimeout, Zeichen je Wert, Ergebnisse verwerfen). Belegt durch `test/explorer.test.js` (6), `test/registriert.test.js` (5), `ProfilspeicherTests` (3 neue), `AbfrageoptionenTests` (9), `TsqlOptionenTests` (4 + Orakel am KuonenSQL-Testserver), `PgOptionenTests` und `MyOptionenTests` (je mit Orakel an den KuonenSQL-Zugängen), `DienstTests` (1 neuer), `StudioTests` (2 neue) und Durchsicht im Browser (MySQL- und T-SQL-Zugang von KuonenSQL). Belegt an SQL Server 2025, PostgreSQL 17, MySQL 8.4 und MariaDB 11.8 (eigene Referenzcontainer, 13.10.2026). Offen: KuonenSQL PR27.23a

✅
T3g
Werkzeugleiste wie SSMS, Rest (Wunsch 2026-10-15). Erledigt 2026-10-15 (0.15.0)

Werkzeugleiste wie SSMS, Rest (Wunsch 2026-10-15). **Erledigt 2026-10-15 (0.15.0):** Analysieren und Ergebnisse in Datei - `Planart.Analysieren` im Kern, SQL Server über `SET PARSEONLY` (auch KuonenSQL ohne Showplan), PostgreSQL/MySQL mit Grund `Planbau.OhneAnalyse`; Studio-Knöpfe mit eigenen Symbolen, Menü „Abfrage“, Strg+F5/Strg+Umschalt+F, Bericht `.rpt`; belegt durch `ShowplanTests` (Orakel SQL Server 2025 und KuonenSQL), `PgplanTests`, `MyplanTests`, Erweiterungstests (85). **Live-Abfragestatistik erledigt (0.16.0):** `ILivestatistik` im Kern (Grund, Sitzungsnummer, laufender Plan), SQL Server über `sys.dm_exec_query_statistics_xml(sitzung)` auf einer zweiten Verbindung alle 500 ms (Showplan-Leser), Dienst `sitzungsnummer`/`livePlan`, Studio-Reiter „Live-Abfragestatistik“ mit Fortschrittsring, „x von y“ je Operator und fliessenden Pfeilen; KuonenSQL nennt den Grund (Server-Roadmap K33.20). **Nachtrag 2026-10-16 (0.19.0):** KuonenSQL ab 1.21.11.0 liefert die Live-Statistik (K33.20), der Grund gilt nur noch für ältere Fassungen (`SqlServerKennung.ErsterMitLivestatistik`); `LivestatistikTests` laufen gegen beide Orakel; belegt durch `LivestatistikTests` (Orakel: blockierte Abfrage ohne Rechenlast), `StudioTests`. Ursprünglich: **Analysieren** (Strg+F5, Haken-Knopf: `SET PARSEONLY ON` bzw. die Entsprechung je Plugin, Meldung „Befehle erfolgreich abgeschlossen“), **Ergebnisse in Datei** (Strg+Umschalt+F: Ergebnisse als Text in eine Datei, Speichern-Dialog), **Live-Abfragestatistik einschliessen** (Reiter, der den Ausführungsplan während des Laufs mit „x von y“, Fortschritt in % und laufenden Pfeilen fortschreibt; SQL Server über `sys.dm_exec_query_profiles`, sonst sauber ausgegraut oder der Plan nach dem Lauf). Ansprechende Darstellung wie die übrigen Reiter (Symbole, Fortschrittsbalken, Hell/Dunkel).

✅
T3h
Datenbankoptimierungsratgeber wie der „Database Engine Tuning Advisor“ (Wunsch 2026-10-15), eingebaut wie der Profiler

Datenbankoptimierungsratgeber wie der „Database Engine Tuning Advisor“ (Wunsch 2026-10-15), eingebaut wie der Profiler: Sitzungsmonitor (Sitzungen mit Name, ID, Erstellungszeit, Status), Reiter „Allgemein“ (Sitzungsname, Arbeitsauslastung aus Datei, Tabelle, Plancache oder Abfragespeicher, Datenbank für die Analyse, Auswahl der Datenbanken und einzelnen Tabellen, Optimierungsprotokoll) und „Optimierungsoptionen“, „Analyse starten“ mit Fortschritt, danach Empfehlungen (Indizes, Statistiken, geschätzte Verbesserung), Berichte und ein Skript zum Anwenden. Je Datenbanktyp, was er kann (SQL Server/KuonenSQL zuerst). Ansprechende Oberfläche statt der grauen SSMS-Dialoge: klare Karten, Fortschritt, Diagramm der erwarteten Verbesserung. Entwurf zuerst mit Marc abstimmen.

⬜
T4
Webseite und Aktualisierung (Vorgabe 2026-10-13, wie der Prüfstand von Kuonen.PDFTools)

Webseite und Aktualisierung (Vorgabe 2026-10-13, wie der Prüfstand von Kuonen.PDFTools): `src/Kuonen.MultiSQLManager.Webseite` (Blazor statisch auf dem Server, ohne PackageReference) mit Produktvorstellung, Download und Versionen; `Veroeffentlichungen` liest die `.vsix` eines Ordners (Name und Version aus `extension/package.json` müssen zum Dateinamen passen, SHA-256, Neuerungen aus `extension/CHANGELOG.md`, ältere Pakete aus dem des neuesten; Grenzen 200 MB je Paket, 1 MB je Eintrag), `GET /api/aktualisierung`, `/api/versionen`, `/download/{Datei}` nur für Namen aus der Liste. Erweiterung: `aktualisierung.js` fragt einmal am Tag (Einstellungen `kuonenMsm.aktualisierung.adresse`/`.automatisch`, https oder http nur für localhost), zeigt eine neuere Version als Meldung und in der Statusleiste, „Aktualisieren“ lädt mit Grössendeckel, prüft Grösse und SHA-256 (Paket nur vom selben Ursprung), installiert über `workbench.extensions.installExtension` und lädt neu; „Diese Version überspringen“; Hilfe → Nach Aktualisierungen suchen. Docker: `Dockerfile` (Bauen und Paketieren auf `$BUILDPLATFORM`, Laufzeit `aspnet:10.0-noble-chiseled`, schreibgeschützt lauffähig), `docker-compose.yml`, `dist/docker-veroeffentlichen.sh` (amd64 und arm64 nach Docker Hub, `--lokal`). Belegt durch `Kuonen.MultiSQLManager.Webseite.Tests` (12, mit Kestrel), `test/aktualisierung.test.js` (9, mit eigenem HTTP-Server und nachgebautem vscode), je eine Sabotage-Probe für Prüfsumme, Ursprung, http, Dateiname, Downloadpfad und Versionspräfix, und einen Lauf gegen den gebauten Container (0.11.0 bekommt 0.12.0 angeboten, Paket geladen und geprüft). Öffentliche Adresse https://multisql.kuonen.org ist Vorgabe der Einstellung (0.12.1). Docker-Hub-Repository `kuomar/kuonen.multisql` (Skript und Compose). 0.12.1 liegt dort für linux/amd64 und linux/arm64 (2026-10-13, gezogen und geprüft: 0.12.0 bekommt 0.12.1 angeboten). In Betrieb unter https://multisql.kuonen.org (Reverse-Proxy mit Let's Encrypt, http leitet auf https um); dort geprüft: alle Seiten und Endpunkte, Sicherheitskopfzeilen, 0.12.0 bekommt 0.12.1 angeboten, Paket geladen, Grösse und SHA-256 stimmen, 0.12.1 bekommt nichts angeboten Image 0.12.7 auf Docker Hub (`kuomar/kuonen.multisql:0.12.7` und `:latest`, amd64 und arm64, 2026-10-14). Image 0.14.0 mit der gemeinsamen Webseite für Server und Studio auf Docker Hub (`kuomar/kuonen.multisql:0.14.0` und `:latest`, amd64 und arm64, 2026-10-15). Image 0.14.1 aus der neuen Ordnerstruktur, mit Server 1.12.576 (2026-10-15).

✅
U1
Umzug ins Repo Kuonen.KuonenSQL (Entscheid 2026-10-14, Plan superpowers/plans/2026-10-15-umzug-nach-kuonensql.md)

Umzug ins Repo Kuonen.KuonenSQL (Entscheid 2026-10-14, Plan `superpowers/plans/2026-10-15-umzug-nach-kuonensql.md`): Phase 1 erledigt 2026-10-15 – Geschichte per filter-branch unter `manager/`, Marken `manager-v*`, Versionsrechnung des Servers zählt nur Commits ausserhalb von `manager/`, `.gitignore`-Ausnahmen, `manager-ci.yml`, Testumgebung findet KuonenSQL über die Wurzel; Version 0.13.0. Phase 2 erledigt 2026-10-15: TdsHeader, TdsMessageType und TdsTlsHandshakeStream nur noch beim Server, `Kuonen.Data.Tds` verlinkt sie mit `MSM_TDS` (Sabotage: Byte-Reihenfolge in TdsHeader macht beide Seiten rot), Manager-Image aus der Repo-Wurzel (`manager/Dockerfile.dockerignore`); der Testserver wird vor dem Start aus dem eigenen Repo gebaut. Wackler unter Volllast (Profiler, PostgreSQL-Anmeldung) in eigener Sitzung behoben (10c80ecf). Offen nur: altes Repo auf GitHub archivieren (Auftraggeber).

◐
U2
Gemeinsame Webseite für Server und Studio (Wunsch 2026-10-15, Spec superpowers/specs/2026-10-15-gemeinsame-webseite-desi…

Gemeinsame Webseite für Server und Studio (Wunsch 2026-10-15, Spec `superpowers/specs/2026-10-15-gemeinsame-webseite-design.md`, Plan `superpowers/plans/2026-10-15-gemeinsame-webseite.md`): der Prüfstand von KuonenSQL zieht ohne Konsole in die Webseite, „Kuonen SQL“ als ein Produkt – Startseite für beide Teile, Server-Seiten (Installation, Zugriff, Handbuch, Beispiele; Reiter als Links, statisch ohne Skript), Studio-Seite, Roadmap und Versionen je mit Abschnitt Server und Studio, Umleitungen /konsole und /version; die Webseite liest Ports, Dialekte, Version und Verlauf aus dem Server-Code; Server-Image und Compose ohne Prüfstand; Version 0.14.0.

✅
U3
Eine Ordnerstruktur für Server und Studio (Wunsch 2026-10-15, Spec superpowers/specs/2026-10-15-ordnerstruktur-design.md…

Eine Ordnerstruktur für Server und Studio (Wunsch 2026-10-15, Spec `superpowers/specs/2026-10-15-ordnerstruktur-design.md`, Plan `superpowers/plans/2026-10-15-ordnerstruktur.md`): Projekte unter `src/Manager`, `src/Global` (Kuonen.Data.Gemeinsam), `tests/Manager` (Messprojekte unter `tests/Manager/Messung`), Doku unter `docs/manager`, Skripte unter `dist/manager` (Testumgebung `. dist\manager\testumgebung.ps1`), Docker unter `docker/manager`, Erweiterung unter `vscode-erweiterung`; `manager/` aufgelöst, Regeln in der Wurzel-`AGENTS.md` §7; gemeinsame Mappe `Kuonen.SQL.slnx`.

✅
T5
Der Manager am KSQL-Port (Server-Roadmap K33.14, 2026-10-16). Erledigt 2026-10-16 (0.20.0)

Der Manager am KSQL-Port (Server-Roadmap K33.14, 2026-10-16). **Erledigt 2026-10-16 (0.20.0):** Orakel `KuonenSqlKsql` (Testserver-Port 14334) in Erkennung, Baum, Abfragelauf, Befehlen, Plänen, Live-Statistik und den Treibertests; der TDS-Treiber löst am KSQL-Zugang (LOGINACK `KuonenSQL`) den SQLSTATE vom Meldungstext (`TdsError.SqlState`, `TdsException.SqlState`), das Plugin gibt ihn als `Meldung.Sqlstate` ins Studio; KuonenSQL gilt für die Schranken des Plugins wenigstens als Stufe 17 (`Erkennung.Hauptfassung(…, istKuonen)`), die angezeigte Fassung bleibt die gemeldete; `GetSchema` (Columns, ProcedureParameters) setzt die Zahlentypen von SQL Server (am Orakel gemessen), weil die Normsichten von KSQL andere liefern.

✅
T6
Der eigene KSQL-Treiber im Manager (Server-Roadmap K33.17, 2026-10-20). Erledigt 2026-10-20 (2.0.1)

Der eigene KSQL-Treiber im Manager (Server-Roadmap K33.17, 2026-10-20). **Erledigt 2026-10-20 (2.0.1):** `Kuonen.Data.Ksql` setzt auf `Kuonen.Data.Tds` auf (`KsqlConnection : TdsConnection`, `KsqlException : TdsException` …; im TDS-Treiber dafür entsiegelt und vier interne Haken: Typ eines Werts, Deklarationstyp, Leitungswert, OUTPUT-Wert, dazu der Wert für `ExecuteScalar`), `SqlServerPlugin.ErzeugeVerbindung` baut eine `KsqlConnection` (ohne Port 1433 wie SqlClient; der KSQL-Treiber nähme 1333), an SQL Server und am T-SQL-Zugang wie bisher, am KSQL-Zugang mit den Typen der Norm (`int ARRAY` statt `master.sys.array`), der ehrlichen Fassung und dem Information Schema; `PluginInfo` nennt `Kuonen.Data.Ksql`. Belegt am eigenen Testserver (T-SQL- und KSQL-Port): alle KuonenSQL-Fälle von `Plugins.SqlServer.Tests` über `KsqlConnection` (auch `Baumprobe` öffnet jetzt so) 282 grün, davon 52 am KSQL-Port; rot nur, was das SQL-Server-Orakel braucht (hier ohne Kennwort), der Zugangsdialog bei abgeschaltetem PostgreSQL-Port und `Sichern_Verlauf_…_STOPAT` am T-SQL-Port (mit `TdsConnection` genauso rot). `Kuonen.Data.Tds.Tests` 841 grün, `Kuonen.Data.Ksql.Tests` 93 grün.

✅
T6a
Anmeldefehler am KSQL-Port mit SQLSTATE (Server-Roadmap D36.2, 2026-10-21). Erledigt 2026-10-21 (2.0.1.1)

Anmeldefehler am KSQL-Port mit SQLSTATE (Server-Roadmap D36.2, 2026-10-21). **Erledigt 2026-10-21 (2.0.1.1):** `Kuonen.Data.Tds` trennte den SQLSTATE vom Meldungstext erst nach dem LOGINACK; der Anmeldefehler 18456 des KSQL-Ports kam darum mit ` (SQLSTATE 28000)` im Text und ohne `SqlState`. Vor dem LOGINACK zählt jetzt die Vorabmeldung (PRELOGIN nennt KuonenSQL). Test `D36_2_Der_Anmeldefehler_von_KuonenSQL_traegt_seinen_SQLSTATE_schon_vor_dem_LOGINACK`; `Kuonen.Data.Tds.Tests` 843 grün, `Kuonen.Data.Ksql.Tests` 93 grün. Keine neue Erweiterung (vierte Stelle).

✅
T7
Handbuchseiten der KSQL-Treiber auf der Webseite (Server-Roadmap D36.8, Teil „Handbuchseite je Treiber“, 2026-10-21). Er…

Handbuchseiten der KSQL-Treiber auf der Webseite (Server-Roadmap D36.8, Teil „Handbuchseite je Treiber“, 2026-10-21). **Erledigt 2026-10-21 (Server 2.0.4.0):** neue Seite `/treiber` (`Components/Pages/Server/Treiber.razor`, Modell `Server/Treiber.cs`) mit einem Reiter je Treiber (.NET, Java, TypeScript, C/ODBC, PHP, C++, Python, Go) und einer Übersicht; Link „Treiber“ in der Navigation und im KSQL-Abschnitt des Handbuchs; die Restzeilen liest die Seite aus der eingebetteten Server-Roadmap. Wächter `TreiberSeitenTests` (8) unter `Data/tests/Kuonen.Data.KuonenSql.Tests/Webseite/`. Keine neue Erweiterung.

✅
T8
Dialog „Löschen…“ für Tabelle und Sicht (Wunsch Marc 2026-10-22, Server-Roadmap PR27.27). Erledigt 2026-10-22 (2.0.2)

Dialog „Löschen…“ für Tabelle und Sicht (Wunsch Marc 2026-10-22, Server-Roadmap PR27.27). **Erledigt 2026-10-22 (2.0.2):** `Loeschdialog` im Plugin SqlServer liest die Abhängigen aus `sys.sql_expression_dependencies` und `sys.foreign_keys` (Sichten auch über Stufen, Funktionen, Prozeduren, Trigger anderer Tabellen, Fremdschlüssel; der eigene Trigger zählt nicht) und zeigt sie als Raster; ohne den Schalter „Abhängige Objekte mitlöschen“ weist das Anwenden ab und nennt sie, mit ihm löscht das Skript von aussen nach innen (Fremdschlüssel, dann jedes Modul nach allem, was darauf zeigt, zuletzt das Objekt) - an SQL Server und KuonenSQL gleich. Neue `Dialogart.Loeschen = 8`; das Studio lädt danach die Datenbank neu. `LoeschdialogTests` (Reihenfolge über zwei Wege), `LoeschdialogOrakelTests` an beiden Orakeln; Sabotage (Anzeige- statt Löschreihenfolge) an KuonenSQL rot.

✅