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:
209
openspec/changes/archive/2026-09-04-phase-3-isam/design.md
Normal file
209
openspec/changes/archive/2026-09-04-phase-3-isam/design.md
Normal 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 81–89 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.
|
||||
Reference in New Issue
Block a user