OpenSpec: phase-3-isam archiviert, Delta-Specs gesynct

Neue Capability isam-datenbank mit 9 Anforderungen und 25 Szenarien:
Tabellenbindung an eine Dateinummer, Indexverwaltung, Cursorbewegung,
Schluesselsuche, Satzoperationen, Transaktionen mit Ruecknahme,
Vergleichsreihenfolge, Pufferverwaltung und das eigene Dateiformat.

sprach-frontend um eine Anforderung erweitert: ISAM-Anweisungen und
-Funktionen in Grammatik und Signaturpruefung.

Die Delta-Spec wurde vor dem Archivieren an die Original-Hilfe
angeglichen -- Cursorlage nach SETINDEX und DELETE, Fehlercode der Suche
ueber den NULL-Index. Die Change-Notiz umfang-und-signaturen.md haelt
Umfangsabgleich, Argumentformen mit Quellenangabe je Themenseite und die
Verifikationsbefunde fest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 11:38:10 +02:00
parent 9b58e0ec43
commit e49a231a68
9 changed files with 514 additions and 62 deletions

View File

@@ -0,0 +1,209 @@
## Context
Siehe proposal.md — Why. Technisch relevant ist der Zustand nach
`phase-3-runtime-bildschirm`:
- Die Dateitabelle, die Recordpuffer und die UTF-32-Serialisierung fester
Textfelder existieren (Capability `datei-eio`) und sind die Grundlage,
auf der ISAM die Sätze ablegt.
- Die Laufzeitfehler 8189 sind in `tb_runtime::errors` bereits
vorhanden; dieser Change liefert ihre Auslöser.
- Vom ISAM-Sprachumfang existiert heute nur die Parser-Klausel
`OPEN … FOR ISAM` (`parser.rs`). Die ISAM-Anweisungen haben weder
Grammatik noch Signatur — sie enden in „unbekannter Bezeichner".
- Das Sprachinventar (`docs/inventar.md`) führt die ISAM-Elemente mit
Status `offen` und ist der maßgebliche Umfangsnachweis.
## Goals / Non-Goals
**Goals:**
- Der volle dokumentierte ISAM-Sprachumfang läuft, nicht eine Auswahl.
- Die Cursor-Semantik des Vorbilds ist exakt abgebildet — sie ist der
Teil, den keine Fremdbibliothek liefert.
- Speicher, Indizes und Transaktionen kommen aus einer erprobten
Bibliothek statt aus Eigenbau.
- Phase 3 ist mit diesem Change abgeschlossen.
**Non-Goals:**
- Leistungsoptimierung des Satzzugriffs — ISAM-Programme des Vorbilds
arbeiten auf Datenmengen, die jede heutige Speicherschicht mühelos
trägt.
- Nebenläufiger Zugriff mehrerer Prozesse auf dieselbe Datenbankdatei
über die Satzsperren aus `datei-eio` hinaus.
- Lesen von Datenbankdateien des Vorbilds.
## Decisions
### D1: `redb` als Speicherschicht
Die Datenbankdatei ist eine `redb`-Datei. Sätze und Indexeinträge liegen
darin als Key-Value-Paare; `redb` liefert B-Baum, Transaktionen und
Crash-Sicherheit.
*Warum nicht Eigenbau:* Seitenverwaltung, B-Baum und crash-sichere
Transaktionen sind gelöste Probleme mit langen Fehlerkatalogen. Der
projektspezifische Teil ist die ISAM-Semantik, nicht der Baum darunter.
*Warum nicht SQLite:* Zöge eine C-Bibliothek in jedes von `tbc build
--exe` erzeugte Binary. Der eigentliche Grund ist aber semantisch: ISAM
hat einen *stabilen, benannten Cursor*, der Änderungen überlebt und sich
vorwärts wie rückwärts bewegt — ein SQL-Cursor ist ein
Ergebnismengen-Iterator. Die Abbildung wäre aufwendiger als der Gewinn.
Die für Stufe 2 geplante SQL-Erweiterung bleibt davon unberührt; sie ist
eine eigene Sprachebene und kann später ihren eigenen Speicher wählen.
*Was `redb` nicht liefert und wir bauen:* Sekundärindizes,
Savepoints innerhalb einer Transaktion, die Cursor-Semantik.
### D2: Datenmodell — Sätze und Indizes als getrennte Tabellen
Je Tabelle eine Satztabelle `satz-id → Satzbytes` und je Index eine
Tabelle `Schlüsselbytes → satz-id`. Satz-IDs sind monoton vergebene
Zahlen und werden nie wiederverwendet.
*Warum getrennt:* `SETINDEX` wechselt die Durchlaufordnung zur Laufzeit;
mit Indextabellen ist das ein Wechsel der zu lesenden Tabelle, ohne die
Sätze anzufassen. `MOVEPREVIOUS` und die `SEEK`-Familie sind dann
Bereichsabfragen auf einer geordneten Tabelle.
*Warum IDs nicht wiederverwendet werden:* Der Cursor merkt sich eine
Satz-ID. Würde sie nach `DELETE` neu vergeben, könnte ein Cursor still
auf einen fremden Satz zeigen.
### D3: Schlüsselkodierung muss ordnungserhaltend sein
Indexschlüssel werden so kodiert, dass die Byte-Reihenfolge der Kodierung
der fachlichen Reihenfolge entspricht — Zahlen mit vorzeichenrichtiger
Kodierung fester Breite, Texte nach der in D4 festgelegten Ordnung,
mehrspaltige Schlüssel als Verkettung mit eindeutigem Trenner.
Absteigende Spalten werden durch Invertierung der Bytes abgebildet.
*Warum das die kritische Stelle ist:* `redb` ordnet nach Bytes. Jede
Ordnungsverletzung in der Kodierung äußert sich als falsche
Satzreihenfolge — ein Fehler, der erst bei bestimmten Datenwerten
sichtbar wird. Ein Eigenschaftstest über zufällige Wertepaare
(Kodierungsvergleich = fachlicher Vergleich) gehört deshalb zur
Umsetzung.
*Mehrspaltige Präfixsuche* fällt aus dieser Kodierung ab: Suche mit
weniger Werten als Spalten ist eine Bereichsabfrage über das
Schlüsselpräfix.
### D4: Sortierordnung explizit festlegen
Texte werden nach Unicode-Codepoints geordnet, Groß- und Kleinschreibung
unterschieden. Es ist dieselbe Ordnung, die der Vergleichsoperator des
Dialekts auf Strings anwendet.
`TEXTCOMP` gibt es in der Original-Hilfe nicht (Befund aus Aufgabe 1.1,
2026-09-03); die Ordnung braucht deshalb keine eigene Vergleichsfunktion,
sondern muss nur mit dem Stringvergleich der Sprache übereinstimmen.
*Warum nicht sprachabhängige Sortierung:* Sie machte die Reihenfolge
plattform- und locale-abhängig und damit die Korpustests unreproduzierbar.
Das Vorbild ordnete nach Zeichencodes einer festen Codepage; Codepoints
sind die direkte Entsprechung unter der bereits getroffenen
Unicode-Entscheidung. Die Ordnung wird in docs/sprachreferenz.md
festgehalten, weil sie beobachtbar ist.
### D5: Cursor als Wert, nicht als Bibliotheks-Iterator
Der Cursor je Dateinummer ist ein eigener Zustand: aktiver Index,
zuletzt besuchte Satz-ID und ein Kennzeichen „unpositioniert". Jede
Bewegung ist eine frische Bereichsabfrage ab dem gemerkten Schlüssel.
*Warum keine gehaltenen Iteratoren:* Der ISAM-Cursor überlebt
Satzänderungen und Transaktionsgrenzen; ein gehaltener Iterator über eine
Transaktion tut das nicht. Ein Cursor als Wert bildet außerdem
„unpositioniert" (Fehler 85) direkt ab, statt es aus einem
Iteratorzustand ableiten zu müssen.
*Preis:* Jede Bewegung kostet eine Suche statt eines Schritts. Bei den
Datenmengen dieser Programme irrelevant; siehe Non-Goals.
### D6: Savepoints als Rücknahmeprotokoll
`BEGINTRANS` öffnet eine Schreibtransaktion. Innerhalb davon führt die
Laufzeit ein Protokoll der Änderungen; die Funktion `SAVEPOINT` merkt
sich eine Position darin und liefert deren Kennung, `ROLLBACK` mit dieser
Kennung spielt das Protokoll bis dorthin rückwärts ab. `ROLLBACK ALL` und
`COMMITTRANS` nutzen die Transaktion der Bibliothek direkt.
*Warum ein eigenes Protokoll:* Verschachtelte Rücknahmepunkte gehören
nicht zum Funktionsumfang von `redb`. Das Protokoll ist der kleinste
Weg dorthin und bleibt auf den Umfang einer laufenden Transaktion
begrenzt.
### D7: Fehler 89 und `SETMEM` bekommen eine echte Grenze
`SETMEM` ist eine Funktion: sie ändert die Obergrenze des ISAM-Puffers um
den übergebenen Betrag und liefert die danach verfügbare Größe.
Überschreiten der Grenze löst Fehler 89 aus. Ohne Grenze wäre der Code nie
erreichbar und die Anweisung eine Attrappe.
*Abweichung:* Die Bedeutung in Bytes entspricht nicht dem
DOS-Speichermodell des Vorbilds und wird in docs/sprachreferenz.md
dokumentiert.
### D8: Reihenfolge der Umsetzung
```
1. Frontend: Grammatik + Signaturen -> Elemente werden benannt
2. Speicher: Datei, Satztabelle, Schluesselkodierung (+ Eigenschaftstest)
3. Indizes: CREATEINDEX/SETINDEX/GETINDEX$/DELETEINDEX
4. Cursor: MOVE-Familie, SEEK-Familie, EOF
5. Saetze: INSERT/RETRIEVE/UPDATE/DELETE, Eindeutigkeit
6. Transaktionen: BEGINTRANS/COMMITTRANS/ROLLBACK[ ALL]/SAVEPOINT
7. Rest: SETMEM, DELETETABLE, BOF, Formatversion
```
Schritt 1 zuerst, weil er allein schon den Guiding-Principle-Verstoß
behebt: ISAM-Programme werden ab da namentlich abgewiesen statt mit
„unbekannter Bezeichner". Schritt 2 vor allem anderen, weil die
Schlüsselkodierung jede spätere Ordnung bestimmt.
## Risks / Trade-offs
- **Fehlerhafte Schlüsselkodierung fällt erst bei bestimmten Daten auf**
→ Eigenschaftstest über zufällige Wertepaare je Spaltentyp:
Byte-Vergleich der Kodierung MUSS dem fachlichen Vergleich entsprechen.
Läuft vor allem Weiteren.
- **Der genaue ISAM-Sprachumfang stammt aus der Original-Hilfe und ist
bislang nur aus zweiter Hand belegt** → Das Inventar aus
`phase-3-runtime-bildschirm` ist die maßgebliche Liste; findet die
Erhebung dort weitere Elemente, gehören sie in diesen Change. Die
Aufzählung im Proposal ist ein Mindestumfang, keine Obergrenze.
- **Cursorverhalten an den Rändern ist die häufigste Fehlerquelle**
(`EOF` nach `MOVENEXT` am Ende, Cursor nach `DELETE`, nach `SETINDEX`,
nach `ROLLBACK`) → je ein Korpustest pro Randfall, nicht ein
Sammeltest.
- **Neue Abhängigkeit in `tb-runtime`, die bisher keine hatte** →
bewusst; die Alternative wäre ein eigener transaktionaler B-Baum.
Die Abhängigkeit bleibt auf das ISAM-Modul beschränkt.
- **Das Rücknahmeprotokoll kann bei langen Transaktionen wachsen** →
begrenzt auf die laufende Transaktion; `COMMITTRANS` beendet sie. Falls
eine Grenze nötig wird, ist Fehler 89 der dafür vorgesehene Ausgang.
- **Eigenes Dateiformat heißt: keine Vorbild-Datenbanken lesbar** →
bewusst entschieden, dieselbe Linie wie UTF-32-Records; steht in
docs/dateiformate.md und in der Sprachreferenz.
## Migration Plan
Rein additiv — es gibt heute keine ISAM-Funktionalität und keine
Datenbankdateien im Feld. Die einzige sichtbare Änderung an bestehendem
Verhalten: `OPEN … FOR ISAM` löst nicht mehr Laufzeitfehler 73 aus,
sondern arbeitet.
Die Datenbankdatei trägt von der ersten Fassung an eine Formatversion,
damit spätere Änderungen abgewiesen statt fehlinterpretiert werden.
## Open Questions
- Genaue Argumentformen einzelner ISAM-Anweisungen (etwa die Schreibweise
der Spaltenliste bei `CREATEINDEX`): bei der Umsetzung aus der
Original-Hilfe zu übernehmen. Berührt weder Datenmodell noch
Aufgabenschnitt.
- Konkreter Vorgabewert der `SETMEM`-Grenze: bei der Umsetzung zu wählen
und zu dokumentieren.