Kuonen SQL

Handbuch

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

Der Server spricht PostgreSQL (M18) - nur diese Konsole noch nicht (U19.10b). Port einschalten mit KUONENSQL_POSTGRESQL_PORT=5432, dann etwa: psql -h localhost -U sa -d <datenbank>.

Punkt Beschreibung Stand
P18.1 Der Handschlag Port 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.2 Die 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.2b Anmeldung 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.3 Einfache 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.3b Fehlerfelder und Fehlercode-Tabelle ERLEDIGT (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.3c Anfragetext in PostgreSQL-Syntax ERLEDIGT (`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 Tabellen ERLEDIGT (`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.4 Das 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`: Restposten ERLEDIGT (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.4c Parametertypen aus dem Zusammenhang, Fehler bei Parse ERLEDIGT (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.4e Parse-Zeitpunkt: der Rest ERLEDIGT (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.4f Parse-Zeitpunkt: Uhrzeit und Verbund ERLEDIGT (`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.5 Der PostgreSQL-Parser Die 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.5b Rest der PostgreSQL-Schreibweise: gebaut Gegen `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.5d Rest der PostgreSQL-Schreibweise ERLEDIGT 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 Wert ERLEDIGT (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 Stellen Nach 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.5g Grenzen des `numeric`-Typmodifizierers ERLEDIGT (`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.5c PostgreSQL im gemeinsamen Parser Ein 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.6 DDL 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.6b DDL: Rest, gebaut Gegen `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.6e DDL: Rest ERLEDIGT (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.6f DDL: Rest 2 ERLEDIGT (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.6g DDL: benannte Grenzen von P18.6f ERLEDIGT 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.6c Engine: vier Fehler aus dem Port Am 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.6d Engine: 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.6h DDL: `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.6i DDL: Rest 3 ERLEDIGT (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.7 DML im PostgreSQL-Stil Gegen `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.7b DML: Rest ERLEDIGT (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.7c DML: 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.8 Funktionsbibliothek Gegen `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.8b Funktionsbibliothek: Arrays, `jsonb`, `interval`, Wahrheitswerte Gegen `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.8c Funktionsbibliothek: Rest ERLEDIGT (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.8d Arrays, `jsonb`, `interval`, Datumsgrenzen: Rest Nach 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.8j Arrays und `jsonb`: Spalten und Operatoren ERLEDIGT (`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.8h Datumsgrenzen und `interval`: der Rest Nach 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 Ära ERLEDIGT (`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.9 Katalog 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: Wirkung Gebaut (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 2 ERLEDIGT (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.10 Funktionen, Prozeduren, Trigger Gegen `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.10b PL/pgSQL: Rest 1 ERLEDIGT (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.10c PL/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.10d PL/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.10e PL/pgSQL: Rest 3, erster Teil ERLEDIGT (`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.11 Der Nachweis, dass es den SQL-Server nicht bremst Kurzmessung 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.11b Nachweis: gedrosselte Lastreihe ERLEDIGT (`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.11c Nachweis: Lastreihe mit vielen Verbindungen Nach M35 verschoben (**LL23.1**): wartet nur auf einen langen Lauf, einen Container oder Handarbeit, nicht auf Bauarbeit ✅
P18.12 Treiber-Abnahme Die 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.12c Treiber-Abnahme: Npgsql mit EF Core ERLEDIGT (`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.12d Treiber-Abnahme: `pg_dump`-Rest ERLEDIGT (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.13 App-Abnahme PostgreSQL Fremde 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.14 App-Abnahme PostgreSQL Runde 2 Keycloak 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.14b App-Abnahme PostgreSQL Runde 2: Rest ERLEDIGT (`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.14c Katalogobjekte 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.14d Katalogobjekte: die Spalten ERLEDIGT (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.14e Katalogobjekte: Namen in der Ausgabe ERLEDIGT (`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.15 Zeilensicherheit PostgreSQL ERLEDIGT (`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.16 Vordefinierte Rollen PostgreSQL ERLEDIGT (`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.16b Rechtefunktionen und übrige vordefinierte Rollen ERLEDIGT (`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.17 Fehlende Rechenfunktionen PostgreSQL ERLEDIGT 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.5c1 Syntaxfehler 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.5c2 Der 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.5c3 Namensauflö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.5c4 Typen und Aggregate Die 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.5c5 Typwissen für die Übersetzung Was 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.5c6 Die Übersetzung aus dem Baum Der Ü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.5c6a Befund: die Engine braucht T-SQL-Text Erkundet 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.5c6b Vorbedingung: der Baum trägt, was die Übersetzung braucht Gebaut: `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.5c6b2 Der Leser für DDL und die übrigen Formen Gebaut: `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.5c6c Scheibe SELECT aus dem Baum Offen (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.5c6c1 SELECT: die Namen der Ergebnisspalten aus dem Baum Gebaut: `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.5c6c2 SELECT: GROUP BY mit Stelle aus dem Baum Gebaut: `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.5c6c3 SELECT: Anfragen an leere Katalogtabellen aus dem Baum Gebaut: `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.5c6c4 Der Schreiber: Operatoren aus dem Baum Gebaut: 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.5c6c5 Der Schreiber: Mustervergleiche aus dem Baum Gebaut: `~`, `~*`, `!~`, `!~*` (`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.5c6c6 Der Schreiber: Formen mit Schlüsselwörtern aus dem Baum Gebaut: `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.5c6c7 Der Schreiber: grosse Zahlkonstanten aus dem Baum Gebaut: 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.5c6c8 Der Schreiber: FILTER aus dem Baum Gebaut: `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.5c6c9 Der Schreiber: NULLS FIRST/LAST aus dem Baum Gebaut: 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.5c6c10 Der Schreiber: LIMIT, OFFSET und FETCH aus dem Baum Gebaut: 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.5c6c11 Der Schreiber: IS TRUE/FALSE/UNKNOWN aus dem Baum Gebaut: `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.5c6c12 Der Schreiber: Typen aus c4, erste Nutzerin EXTRACT Gebaut: 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.5c6c13 Der Schreiber: die Uhr der Sitzung aus dem Baum Gebaut: `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.5c6c14 Der Schreiber: Literale und Umwandlungen aus dem Baum Gebaut: `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.5c6c15 Der Schreiber: Verkettung, Felder, jsonb und Zeitrechnung aus dem Baum Gebaut (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.5c6c15a Der Schreiber: Verkettung, Zeit- und Intervallrechnung aus dem Baum Gebaut: `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.5c6c15b Der Schreiber: Arrays, ANY/ALL und die Operatoren über jsonb aus dem Baum Gebaut: `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.5c6c15c Der Schreiber: die Operatoren über geometrischen Werten aus dem Baum Gebaut: 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.5c6c15d Der Schreiber: Zeilen aus dem Baum Gebaut: `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.5c6c15e Der Schreiber: Typen ohne die ganze Analyse, wo der Knoten sie selbst sagt Gebaut: `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.5c6c16 Der Schreiber: Funktionen aus dem Baum Gebaut (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.5c6c16a Der Schreiber: die Namen der Funktionsspalten aus dem Baum Gebaut: 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.5c6c16b Der Schreiber: die Bibliothek der Funktionsnamen am Aufruf Gebaut: 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.5c6c16c Der Schreiber: Funktionen des Nutzers am Aufruf Gebaut: 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.5c6c16d Der Schreiber: Feldfunktionen und Sammler am Aufruf Gebaut: 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.5c6c16e Der Schreiber: OPERATOR(schema.op) und format(…, true) am Knoten Gebaut: 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.5c6c16f Der Schreiber: Rangfunktionen ohne Sortierung am Aufruf Gebaut: `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.5c6c16g Der Schreiber: nextval, currval, lastval am Aufruf Gebaut: `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.5c6c16h Der Schreiber: Sitzungsfunktionen am Knoten Gebaut: `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.5c6c16i Der Schreiber: Reihen am Aufruf Gebaut: `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.5c6c16j Der Schreiber: Katalogfunktionen am Aufruf Gebaut: 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.5c6c16k Der Schreiber: COLLATE am Knoten Gebaut: 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.5c6c16l Der Schreiber: Registerumwandlungen am Knoten Gebaut: `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.5c6c16m Der Schreiber: current_schemas am Aufruf Gebaut: `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.5c6c17a Der Schreiber: Wahrheitswerte am Knoten Gebaut: 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.5c6c17b Der Schreiber: Typen der Arme einer Menge Gebaut: 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.5c6c17c Der Schreiber: Zeichentypen von CASE, COALESCE, GREATEST, LEAST, NULLIF Gebaut: 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.5c6c17d Der Schreiber: Typen der Aggregate Gebaut: `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.5c6c17e Der Schreiber: Typen der Konstanten Gebaut: 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.5c6c17f Spaltenarten aus dem Baum Gebaut: 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.5c6c17g Der Schreiber: Rechnungen der Auswahlliste, Fehler über Aufzählungen und json Gebaut: 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.5c6c17 Der Schreiber: Bedingung als Wert und Typen der Auswahlliste aus dem Baum Gebaut (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.5c6c18a Der Schreiber: die Sperrklausel Gebaut: 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.5c6c18b Der Schreiber: WITH RECURSIVE und MATERIALIZED Gebaut: 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.5c6c18c Der Schreiber: LATERAL Gebaut: 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.5c6c18d Der Schreiber: VALUES in einer Menge Gebaut: `… 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.5c6c18e Der Schreiber: USING und NATURAL Gebaut: 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.5c6c18f Abgelehntes im Schreiber Gebaut: 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.5c6c18g Der Schreiber: Funktionen als Quelle Gebaut: 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.5c6c18h Der Schreiber: unnest und json-Zeilenfunktionen Gebaut: `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.5c6c18i Der Schreiber: pg_settings Gebaut: 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.5c6c18j Korrektur: `CAST(x AS bit)` ist die Bitfolge Am 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.5c6c18k Ausdrücke hinter OFFSET und FETCH Gebaut: 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.5c6c18l Der Schreiber: ONLY Gebaut: `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.5c6c18m Der Schreiber: der Stand einer Sequenz für pg_dump Gebaut: `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.5c6c18n Vorgabeschema beim Zerlegen Gebaut: `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.5c6c18o Der Schreiber: Katalognamen Gebaut: 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.5c6c18p Der Schreiber: temporäre Tabellen und Suchpfad Gebaut: 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.5c6c18q Der Schreiber: CREATE TEMP TABLE Gebaut: 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.5c6c18 Der Schreiber: Quellen, Verbunde und Namen aus dem Baum Gebaut (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.5c6d1 Der Schreiber: Ziele von SET mit Index und Feld Gebaut: 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.5c6d2 Der Schreiber: Felder in der Spaltenliste von INSERT, row_to_json Gebaut: 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.5c6d3 Der Schreiber: weniger Werte als Spalten, ON CONFLICT als MERGE Gebaut: 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.5c6d4 Der Schreiber: MERGE … THEN DO NOTHING Gebaut: 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.5c6d5 Der Schreiber: RETURNING als OUTPUT Gebaut: 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.5c6d6 Der Schreiber: UPDATE … FROM, DELETE … USING, Alias am Ziel Gebaut: 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.5c6d7 Der 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.5c6d8 Schreibende CTEs aus dem Baum Gebaut: 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.5c6d9 Orakelbefunde: Anzahl in LIMIT/OFFSET, Unterabfrage in RETURNING Am 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.5c6d Der Schreiber: DML aus dem Baum Gebaut (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.5c6e1 Leser für DDL: die Relationen einer Anweisung, erster Nutzer TRUNCATE Gebaut: `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.5c6e2 Der Schreiber: das Ereignis für Anweisungstrigger Gebaut: 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.5c6e3 CREATE TABLE … AS aus dem Baum Gebaut: 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.5c6e4a DROP aus dem Baum Gebaut: 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.5c6e4b CREATE SEQUENCE und CREATE SCHEMA aus dem Baum Gebaut: 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.5c6e4c CREATE INDEX aus dem Baum Gebaut: 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.5c6e4d CREATE TABLE aus dem Baum Gebaut: 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.5c6e4e ALTER TABLE aus dem Baum, der Durchgang Ddl entfällt Gebaut: 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.5c6e Scheibe DDL aus dem Baum Gebaut (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.5c6f Scheibe PL/pgSQL aus dem Baum Offen: 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.5c6f1 PL/pgSQL: Variablen aus dem Baum Gebaut: 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.5c6f2 Portale aus dem Baum Gebaut: `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.5c6f3 CREATE TRIGGER aus dem Baum Gebaut: 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.5c6f4 CREATE FUNCTION/PROCEDURE aus dem Baum Gebaut: 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.5c6f5 DO, CALL, DROP FUNCTION, Sicherungspunkte aus dem Baum Gebaut: 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.5c6g1 Die Schreibweise in der Ausgabe Gebaut: 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.5c6g2 Umwandlungen ganz im Schreiber Gebaut: 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.5c6g3 Die Tabellen der Anweisung im Schreiber aus dem Baum Gebaut: Ü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.5c6h1 Orakelbefunde postgres:17: LIMIT float8, Prüfnamen, ADD COLUMN UNIQUE, nextval Gebaut: `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.5c6h2 ZeilenSterne und Tabellenaliase aus dem Baum Gebaut: `(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.5c6h3 AlleTabellen 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.5c6h4 Der PL/pgSQL-Rumpf als Baum, die Breite von RETURN QUERY Gebaut: 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.5c6h5 Wahrheitswert 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. ✅