Files
TerminalBasic/openspec/changes/archive/2026-09-04-phase-3-isam/design.md
Chili Palmer e49a231a68 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>
2026-09-04 11:38:10 +02:00

9.3 KiB
Raw Blame History

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.