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:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-03
|
||||
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.
|
||||
104
openspec/changes/archive/2026-09-04-phase-3-isam/proposal.md
Normal file
104
openspec/changes/archive/2026-09-04-phase-3-isam/proposal.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Phase 3 (Abschluss) — ISAM-Datenbankunterstützung
|
||||
|
||||
## Why
|
||||
|
||||
Die Leitplanke „Vollständigkeit ist das Soll" und die Entscheidung vom
|
||||
2026-09-02 legen fest: ISAM wird implementiert, es ist kein Non-Feature.
|
||||
Heute existiert davon nur eine Parser-Klausel — `OPEN … FOR ISAM typname
|
||||
tabellenname AS #n` wird erkannt und auf den „nicht verfügbar"-Marker
|
||||
abgesenkt. Die eigentlichen ISAM-Anweisungen und -Funktionen
|
||||
(`CREATEINDEX`, `SETINDEX`, `INSERT`, `RETRIEVE`, `UPDATE`, `DELETE`, die
|
||||
`MOVE`- und `SEEK`-Familien, die Transaktionsanweisungen) besitzen im
|
||||
Frontend **keine Signatur**. Sie enden damit in „unbekannter Bezeichner"
|
||||
bzw. einem generischen Syntaxfehler — genau der Fall, den der Guiding
|
||||
Principle als Bug definiert: ein dokumentiertes Feature wird still und
|
||||
ohne Namensnennung abgewiesen.
|
||||
|
||||
Die Laufzeitfehler 81–89 des ISAM-Bereichs sind bereits als
|
||||
`tb_runtime::errors` implementiert und warten auf ihre Auslöser.
|
||||
|
||||
Dieser Change schließt Phase 3 ab. Der vorausgehende Change
|
||||
`phase-3-runtime-bildschirm` liefert die Datei-E/A-Grundlage
|
||||
(Dateitabelle, Recordpuffer, UTF-32-Festtextfelder), auf der ISAM
|
||||
aufsetzt, sowie das Inventar, in dem die ISAM-Elemente ihren Status
|
||||
führen.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **ISAM-Elemente erhalten Frontend-Signaturen**: Sämtliche in der
|
||||
Original-Hilfe dokumentierten ISAM-Anweisungen und -Funktionen werden
|
||||
in Grammatik und Signaturprüfung aufgenommen, damit ihre Verwendung
|
||||
namentlich diagnostiziert statt als unbekannter Bezeichner behandelt
|
||||
wird. Den maßgeblichen Umfang bestimmt das Sprachinventar aus
|
||||
`phase-3-runtime-bildschirm`; nach heutigem Stand umfasst er
|
||||
mindestens `CREATEINDEX`, `DELETEINDEX`, `SETINDEX`, `GETINDEX$`,
|
||||
`INSERT`, `RETRIEVE`, `UPDATE`, `DELETE`, `DELETETABLE`, `MOVEFIRST`,
|
||||
`MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS`, `SEEKEQ`, `SEEKGT`, `SEEKGE`,
|
||||
`BEGINTRANS`, `COMMITTRANS`, `ROLLBACK`/`ROLLBACK ALL`, `SAVEPOINT`
|
||||
(Funktion), `SETMEM` (Funktion), `BOF` sowie das ISAM-Verhalten von
|
||||
`EOF`, `DELETE`, `LOF` und `LOC`.
|
||||
- **ISAM-Datenbanken und Tabellen**: `OPEN … FOR ISAM typname
|
||||
tabellenname AS #n` bindet eine Tabelle einer Datenbankdatei an eine
|
||||
Dateinummer; das Satzlayout ergibt sich aus dem benannten
|
||||
benutzerdefinierten Typ. Tabellen entstehen beim ersten Öffnen,
|
||||
`DELETETABLE` entfernt sie.
|
||||
- **Indizes**: benannte Indizes über eine oder mehrere Spalten, eindeutig
|
||||
oder mehrdeutig, mit auf- und absteigender Ordnung; `SETINDEX` wählt
|
||||
den aktiven Index und bestimmt damit die Satzreihenfolge, `GETINDEX$`
|
||||
liefert ihn zurück.
|
||||
- **Satzoperationen und Cursor**: `INSERT`, `RETRIEVE`, `UPDATE`,
|
||||
`DELETE` arbeiten auf dem aktuellen Satz; die `MOVE`-Familie bewegt den
|
||||
Cursor in der Ordnung des aktiven Index, die `SEEK`-Familie positioniert
|
||||
über Schlüsselvergleiche (gleich, größer, größer-gleich). `EOF` und
|
||||
`BOF` melden das Überlaufen des Cursors am Ende bzw. am Anfang.
|
||||
- **Transaktionen**: `BEGINTRANS`, `COMMITTRANS`, `ROLLBACK` und
|
||||
`ROLLBACK ALL` mit Rücknahme aller Änderungen seit Transaktionsbeginn
|
||||
bzw. seit einem Sicherungspunkt; `SAVEPOINT` ist eine **Funktion** und
|
||||
liefert die Kennung des gesetzten Sicherungspunkts.
|
||||
- **Vergleichsreihenfolge**: Die Sortierordnung der Indizes wird unter
|
||||
Unicode festgelegt und dokumentiert.
|
||||
- **ISAM-Fehlersemantik**: Die vorhandenen Fehlercodes 81–89 werden von
|
||||
den entsprechenden Situationen ausgelöst (ungültiger Name, Tabelle
|
||||
nicht gefunden, Index nicht gefunden, ungültige Spalte, kein aktueller
|
||||
Satz, Verstoß gegen einen eindeutigen Index, ungültige Operation auf
|
||||
NULL-Index, inkonsistente Datenbank, zu wenig Puffer).
|
||||
- **Eigenes Datenbankdateiformat**: Die Datenbankdatei ist bewusst nicht
|
||||
binärkompatibel zum Vorbild — dieselbe Linie wie bei den UTF-32-
|
||||
Records. Das Format wird in `docs/dateiformate.md` beschrieben.
|
||||
|
||||
**Non-Goals:** Ein Konvertierungswerkzeug für Datenbankdateien des
|
||||
Vorbilds; die eingebettete SQL-Erweiterung samt `WHILE SELECT`
|
||||
(Stufe 2, Ideenspeicher); Mehrprozess-Zugriff über
|
||||
Betriebssystemgrenzen hinweg jenseits der Satzsperren aus `datei-eio`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `isam-datenbank`: indizierter Satzzugriff — Datenbank- und
|
||||
Tabellenbindung an Dateinummern, Indexverwaltung, Cursorbewegung und
|
||||
Schlüsselsuche, Satzoperationen, Transaktionen, Vergleichsreihenfolge
|
||||
und die ISAM-Fehlersemantik (Codes 81–89).
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `sprach-frontend`: Die Grammatik und die Signaturprüfung SHALL die
|
||||
ISAM-Anweisungen und -Funktionen umfassen, sodass ihre Verwendung
|
||||
namentlich diagnostiziert wird statt als unbekannter Bezeichner.
|
||||
|
||||
## Impact
|
||||
|
||||
- `crates/tb-frontend`: neue Anweisungsgrammatik und Signaturen für die
|
||||
ISAM-Elemente; die bisherige `Unsupported`-Absenkung von `OPEN … FOR
|
||||
ISAM` entfällt.
|
||||
- `crates/tb-runtime`: neues ISAM-Modul (Speicher, Indizes, Cursor,
|
||||
Transaktionen) auf der Dateitabelle und den Recordpuffern aus
|
||||
`phase-3-runtime-bildschirm`.
|
||||
- `crates/tb-vm`: neue Builtin-Indizes für die ISAM-Anweisungen.
|
||||
- `tests/compat`: neue Korpusprogramme für Indexordnung, Schlüsselsuche,
|
||||
Eindeutigkeitsverletzung, Cursorenden und Transaktionsrücknahme.
|
||||
- `docs/`: `docs/dateiformate.md` um das Datenbankdateiformat erweitert;
|
||||
`docs/bibliothek.md` um die ISAM-Elemente; `docs/inventar.md` wechselt
|
||||
deren Status auf `implementiert`; `docs/sprachreferenz.md` hält die
|
||||
Sortierordnung und die Inkompatibilität des Dateiformats fest.
|
||||
- PLAN.md: Phase 3 gilt mit diesem Change als abgeschlossen.
|
||||
@@ -0,0 +1,209 @@
|
||||
## Purpose
|
||||
Die ISAM-Unterstützung bietet indizierten Satzzugriff auf Tabellen einer
|
||||
Datenbankdatei: Sätze werden über benannte Indizes geordnet durchlaufen
|
||||
und über Schlüsselvergleiche gesucht, Änderungen laufen in Transaktionen
|
||||
und Verstöße melden die ISAM-Fehlercodes des Vorbilds.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Datenbank- und Tabellenbindung an eine Dateinummer
|
||||
`OPEN datenbank$ FOR ISAM typname tabellenname AS #n` SHALL eine Tabelle
|
||||
einer Datenbankdatei an eine Dateinummer binden. Das Satzlayout SHALL
|
||||
sich aus dem benannten benutzerdefinierten Typ ergeben; dessen Felder
|
||||
sind die Spalten der Tabelle. Existiert die Tabelle noch nicht, SHALL sie
|
||||
mit diesem Layout angelegt werden; existiert sie mit abweichendem Layout,
|
||||
MUST Laufzeitfehler 88 „ISAM - Database inconsistent" auftreten. Ein
|
||||
ungültiger Tabellen- oder Datenbankname MUST Fehler 81 „ISAM - Invalid
|
||||
name" auslösen. `CLOSE` SHALL die Bindung lösen; ausstehende Änderungen
|
||||
sind dabei bereits festgeschrieben, weil jede Operation außerhalb einer
|
||||
Transaktion für sich eine Transaktion ist. Eine laufende Transaktion MUST
|
||||
`CLOSE` unberührt lassen — über ihr Ende entscheiden allein `COMMITTRANS`
|
||||
und `ROLLBACK ALL`. `DELETETABLE` SHALL eine Tabelle samt ihren
|
||||
Indizes entfernen; eine nicht vorhandene Tabelle MUST Fehler 82 „ISAM -
|
||||
Table not found" auslösen.
|
||||
|
||||
#### Scenario: Tabelle wird beim ersten Öffnen angelegt
|
||||
- **WHEN** eine Datenbank ohne die Tabelle `Kunden` mit `OPEN "db" FOR ISAM KundeTyp "Kunden" AS #1` geöffnet wird
|
||||
- **THEN** entsteht die Tabelle mit den Feldern des Typs und die Dateinummer ist nutzbar
|
||||
|
||||
#### Scenario: Abweichendes Satzlayout
|
||||
- **WHEN** eine bestehende Tabelle mit einem Typ geöffnet wird, dessen Felder nicht zu ihren Spalten passen
|
||||
- **THEN** tritt Laufzeitfehler 88 auf
|
||||
|
||||
#### Scenario: CLOSE lässt eine laufende Transaktion stehen
|
||||
- **WHEN** innerhalb einer Transaktion eine andere Dateinummer mit `CLOSE` geschlossen wird
|
||||
- **THEN** läuft die Transaktion weiter und ein folgendes `ROLLBACK ALL` nimmt ihre Änderungen zurück
|
||||
|
||||
#### Scenario: Entfernte Tabelle
|
||||
- **WHEN** `DELETETABLE` für eine nicht vorhandene Tabelle aufgerufen wird
|
||||
- **THEN** tritt Laufzeitfehler 82 auf
|
||||
|
||||
### Requirement: Indexverwaltung
|
||||
`CREATEINDEX` SHALL einen benannten Index über eine oder mehrere Spalten
|
||||
anlegen, wahlweise eindeutig oder mehrdeutig und je Spalte auf- oder
|
||||
absteigend geordnet. `DELETEINDEX` SHALL einen Index entfernen.
|
||||
`SETINDEX` SHALL den aktiven Index einer Dateinummer setzen und damit die
|
||||
Reihenfolge bestimmen, in der Sätze durchlaufen werden; `GETINDEX$` SHALL
|
||||
den Namen des aktiven Index liefern. `SETINDEX` ohne Indexnamen (oder mit
|
||||
leerem Namen) SHALL den NULL-Index wählen, also die Einfügereihenfolge;
|
||||
`GETINDEX$` liefert dafür einen leeren String. Ein unbekannter Indexname
|
||||
MUST Laufzeitfehler 83 „ISAM - Index not found" auslösen, ein unbekannter
|
||||
Spaltenname Fehler 84 „ISAM - Invalid column". Nach `SETINDEX` SHALL der
|
||||
erste Satz in der Ordnung des gewählten Index der aktuelle Satz sein; ist
|
||||
die Tabelle leer, gibt es keinen aktuellen Satz.
|
||||
|
||||
#### Scenario: Aktiver Index bestimmt die Reihenfolge
|
||||
- **WHEN** ein Index über die Spalte `Name` gesetzt ist und die Sätze mit `MOVEFIRST` und `MOVENEXT` durchlaufen werden
|
||||
- **THEN** erscheinen sie in aufsteigender Reihenfolge der Spalte `Name`
|
||||
|
||||
#### Scenario: Unbekannter Index
|
||||
- **WHEN** `SETINDEX #1, "GibtsNicht"` ausgeführt wird
|
||||
- **THEN** tritt Laufzeitfehler 83 auf
|
||||
|
||||
#### Scenario: Aktueller Satz nach SETINDEX
|
||||
- **WHEN** ein Index gesetzt wird, dessen Ordnung mit einem anderen Satz beginnt als die bisherige
|
||||
- **THEN** ist dieser Satz ohne weitere Bewegung der aktuelle Satz
|
||||
|
||||
#### Scenario: Index über unbekannte Spalte
|
||||
- **WHEN** `CREATEINDEX` eine Spalte nennt, die der Satztyp nicht führt
|
||||
- **THEN** tritt Laufzeitfehler 84 auf
|
||||
|
||||
### Requirement: Cursorbewegung in der Ordnung des aktiven Index
|
||||
`MOVEFIRST`, `MOVELAST`, `MOVENEXT` und `MOVEPREVIOUS` SHALL den Cursor
|
||||
in der Ordnung des aktiven Index bewegen. Läuft der Cursor über das Ende
|
||||
hinaus, SHALL `EOF` für diese Dateinummer wahr werden; läuft er über den
|
||||
Anfang hinaus, SHALL `BOF` wahr werden. In beiden Fällen SHALL der Cursor
|
||||
als unpositioniert gelten. Eine Bewegung ohne aktiven Index SHALL der
|
||||
Einfügereihenfolge folgen.
|
||||
|
||||
#### Scenario: Cursor läuft über das Ende hinaus
|
||||
- **WHEN** nach dem letzten Satz `MOVENEXT` ausgeführt wird
|
||||
- **THEN** liefert `EOF` für diese Dateinummer wahr
|
||||
|
||||
#### Scenario: Rückwärts über den Anfang hinaus
|
||||
- **WHEN** auf dem ersten Satz `MOVEPREVIOUS` ausgeführt wird
|
||||
- **THEN** liefert `BOF` für diese Dateinummer wahr
|
||||
|
||||
### Requirement: Schlüsselsuche
|
||||
`SEEKEQ`, `SEEKGT` und `SEEKGE` SHALL den Cursor auf den ersten Satz
|
||||
setzen, dessen Schlüssel im aktiven Index gleich, größer bzw.
|
||||
größer-gleich dem angegebenen Wert ist. Die Zahl der Schlüsselwerte MUST
|
||||
höchstens der Spaltenzahl des aktiven Index entsprechen; ein
|
||||
mehrspaltiger Index SHALL mit einem Präfix der Spalten durchsucht werden
|
||||
können. Findet die Suche keinen Satz, SHALL `EOF` wahr werden und der
|
||||
Cursor unpositioniert bleiben. Eine Suche über den NULL-Index MUST
|
||||
Laufzeitfehler 87 „ISAM - Invalid operation on NULL index" auslösen — er
|
||||
führt keine Schlüssel, sondern nur die Einfügereihenfolge.
|
||||
|
||||
#### Scenario: Suche nach größerem Schlüssel
|
||||
- **WHEN** Sätze mit den Schlüsseln 10, 20 und 30 vorliegen und `SEEKGT #1, 20` ausgeführt wird
|
||||
- **THEN** steht der Cursor auf dem Satz mit Schlüssel 30
|
||||
|
||||
#### Scenario: Erfolglose Suche
|
||||
- **WHEN** `SEEKGT #1, 30` bei größtem Schlüssel 30 ausgeführt wird
|
||||
- **THEN** liefert `EOF` wahr
|
||||
|
||||
#### Scenario: Suche über Präfix eines mehrspaltigen Index
|
||||
- **WHEN** der aktive Index über `Nachname` und `Vorname` geht und mit nur einem Wert gesucht wird
|
||||
- **THEN** positioniert die Suche auf dem ersten Satz mit passendem `Nachname`
|
||||
|
||||
### Requirement: Satzoperationen auf dem aktuellen Satz
|
||||
`INSERT` SHALL einen Satz aus einer Variablen des Tabellentyps einfügen,
|
||||
`RETRIEVE` den aktuellen Satz in eine solche Variable lesen, `UPDATE` ihn
|
||||
aus einer solchen Variablen überschreiben und `DELETE` ihn entfernen.
|
||||
`RETRIEVE`, `UPDATE` und `DELETE` ohne positionierten Cursor MUST
|
||||
Laufzeitfehler 85 „ISAM - No current record" auslösen. Ein `INSERT` oder
|
||||
`UPDATE`, das einen als eindeutig angelegten Index verletzen würde, MUST
|
||||
Laufzeitfehler 86 „ISAM - Duplicate value for unique index" auslösen und
|
||||
den Satzbestand unverändert lassen. Nach `DELETE` SHALL der auf den
|
||||
gelöschten folgende Satz der aktuelle sein; war der gelöschte der letzte
|
||||
in der Ordnung des aktiven Index, SHALL der Cursor am Ende der Tabelle
|
||||
stehen und es SHALL keinen aktuellen Satz geben. Alle Indizes der Tabelle
|
||||
SHALL nach jeder Satzoperation die neue Ordnung widerspiegeln.
|
||||
|
||||
#### Scenario: Einfügen und Zurücklesen
|
||||
- **WHEN** ein Satz eingefügt, per `SEEKEQ` gesucht und mit `RETRIEVE` gelesen wird
|
||||
- **THEN** stimmen alle Feldwerte mit den eingefügten überein
|
||||
|
||||
#### Scenario: Operation ohne aktuellen Satz
|
||||
- **WHEN** hinter dem letzten Satz — also nach `MOVENEXT` am Ende — `RETRIEVE #1, k` ausgeführt wird
|
||||
- **THEN** tritt Laufzeitfehler 85 auf
|
||||
|
||||
#### Scenario: Aktueller Satz nach DELETE
|
||||
- **WHEN** ein Satz gelöscht wird, auf den in der Ordnung des aktiven Index ein weiterer folgt
|
||||
- **THEN** ist dieser folgende Satz der aktuelle
|
||||
|
||||
#### Scenario: Verstoß gegen eindeutigen Index
|
||||
- **WHEN** ein Satz eingefügt wird, dessen Schlüssel in einem eindeutigen Index bereits vorkommt
|
||||
- **THEN** tritt Laufzeitfehler 86 auf und der Satzbestand bleibt unverändert
|
||||
|
||||
#### Scenario: Index folgt der Änderung
|
||||
- **WHEN** die indizierte Spalte eines Satzes per `UPDATE` geändert wird
|
||||
- **THEN** erscheint der Satz beim nächsten Durchlauf an seiner neuen Position in der Indexordnung
|
||||
|
||||
### Requirement: Transaktionen mit Rücknahme
|
||||
`BEGINTRANS` SHALL eine Transaktion eröffnen, `COMMITTRANS` alle
|
||||
Änderungen seit ihrem Beginn festschreiben und `ROLLBACK` sie
|
||||
zurücknehmen. `SAVEPOINT` SHALL als **Funktion** einen Sicherungspunkt
|
||||
setzen und dessen Kennung liefern; `ROLLBACK` mit einer solchen Kennung
|
||||
SHALL auf diesen Punkt zurücksetzen, ohne die Transaktion aufzugeben.
|
||||
`ROLLBACK ALL` SHALL alle Änderungen seit `BEGINTRANS` zurücknehmen und
|
||||
die Transaktion beenden. Nach jeder Rücknahme MUST der Satzbestand
|
||||
einschließlich aller Indizes dem Zustand zum Bezugspunkt entsprechen und
|
||||
der Cursor als unpositioniert gelten. Eine beim Programmende nicht
|
||||
festgeschriebene Transaktion MUST wirkungslos bleiben.
|
||||
|
||||
#### Scenario: Rücknahme stellt den Bestand wieder her
|
||||
- **WHEN** nach `BEGINTRANS` Sätze eingefügt und gelöscht werden und dann `ROLLBACK` folgt
|
||||
- **THEN** entspricht der Bestand beim erneuten Durchlauf dem Zustand vor `BEGINTRANS`
|
||||
|
||||
#### Scenario: Rücknahme auf einen Sicherungspunkt
|
||||
- **WHEN** innerhalb einer Transaktion `s = SAVEPOINT` ausgewertet, danach geändert und mit `ROLLBACK s` zurückgesetzt wird
|
||||
- **THEN** bleiben die Änderungen vor dem Sicherungspunkt erhalten und die danach sind zurückgenommen
|
||||
|
||||
#### Scenario: ROLLBACK ALL beendet die Transaktion
|
||||
- **WHEN** nach mehreren Sicherungspunkten `ROLLBACK ALL` ausgeführt wird
|
||||
- **THEN** entspricht der Bestand dem Zustand vor `BEGINTRANS` und die Transaktion ist beendet
|
||||
|
||||
### Requirement: Vergleichsreihenfolge für Indizes
|
||||
Textspalten SHALL in Indizes nach einer festgelegten, dokumentierten
|
||||
Sortierordnung geordnet werden. Diese Ordnung MUST dieselbe sein, die der
|
||||
Vergleichsoperator des Dialekts auf Strings anwendet, damit ein Programm
|
||||
die Satzreihenfolge vorhersagen kann. Die gewählte Ordnung MUST in
|
||||
docs/sprachreferenz.md festgehalten sein, einschließlich der Behandlung
|
||||
von Groß- und Kleinschreibung und von Zeichen jenseits von ASCII.
|
||||
|
||||
#### Scenario: Indexordnung entspricht dem Stringvergleich
|
||||
- **WHEN** zwei Texte als Schlüssel indiziert und dieselben Werte mit `<` verglichen werden
|
||||
- **THEN** entspricht ihre Reihenfolge im Index dem Ergebnis des Vergleichs
|
||||
|
||||
### Requirement: Pufferverwaltung mit benanntem Fehler
|
||||
`SETMEM` SHALL als **Funktion** die für ISAM verfügbare Puffergröße um
|
||||
den übergebenen Betrag ändern und die danach verfügbare Größe liefern.
|
||||
Reicht
|
||||
der verfügbare Puffer für eine Operation nicht aus, MUST Laufzeitfehler 89
|
||||
„ISAM - Insufficient ISAM buffers" auftreten statt eines generischen
|
||||
Fehlers oder eines stillen Abbruchs. Weicht die Bedeutung von `SETMEM`
|
||||
mangels DOS-Speichermodell vom Vorbild ab, MUST die Abweichung in
|
||||
docs/sprachreferenz.md dokumentiert sein.
|
||||
|
||||
#### Scenario: Pufferfehler wird benannt
|
||||
- **WHEN** eine Operation den verfügbaren ISAM-Puffer überschreitet
|
||||
- **THEN** tritt Laufzeitfehler 89 auf
|
||||
|
||||
### Requirement: Eigenes Datenbankdateiformat
|
||||
Die Datenbankdatei SHALL ein eigenes, in docs/dateiformate.md
|
||||
beschriebenes Format verwenden und ist bewusst nicht binärkompatibel zu
|
||||
Datenbankdateien des Vorbilds — dieselbe Linie wie bei den
|
||||
UTF-32-Festtextfeldern. Das Format SHALL eine Formatversion tragen; eine
|
||||
unbekannte Version MUST mit einem benannten Fehler abgewiesen werden
|
||||
statt fehlinterpretiert zu werden. Eine strukturell beschädigte Datei
|
||||
MUST Laufzeitfehler 88 auslösen.
|
||||
|
||||
#### Scenario: Unbekannte Formatversion
|
||||
- **WHEN** eine Datenbankdatei mit einer höheren Formatversion geöffnet wird
|
||||
- **THEN** wird sie mit einem benannten Fehler abgewiesen und nicht gelesen
|
||||
|
||||
#### Scenario: Beschädigte Datei
|
||||
- **WHEN** eine strukturell beschädigte Datenbankdatei geöffnet wird
|
||||
- **THEN** tritt Laufzeitfehler 88 auf
|
||||
@@ -0,0 +1,27 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: ISAM-Anweisungen und -Funktionen in Grammatik und Signaturprüfung
|
||||
Der Parser SHALL die ISAM-Anweisungen und -Funktionen des Vorbilds als
|
||||
eigene Konstrukte erkennen, und die Semantik SHALL für jede eine Signatur
|
||||
führen (Argumentanzahl und -typen). Der maßgebliche Umfang ergibt sich
|
||||
aus dem Sprachinventar; er umfasst mindestens `CREATEINDEX`,
|
||||
`DELETEINDEX`, `SETINDEX`, `GETINDEX$`, `INSERT`, `RETRIEVE`, `UPDATE`,
|
||||
`DELETE`, `DELETETABLE`, `MOVEFIRST`, `MOVELAST`, `MOVENEXT`,
|
||||
`MOVEPREVIOUS`, `SEEKEQ`, `SEEKGT`, `SEEKGE`, `BEGINTRANS`,
|
||||
`COMMITTRANS`, `ROLLBACK`, `ROLLBACK ALL`, `SAVEPOINT`, `SETMEM` und
|
||||
`BOF`. Eine fehlerhafte Verwendung MUST eine Diagnose erzeugen, die
|
||||
das Element namentlich nennt; ein ISAM-Element MUST NOT als unbekannter
|
||||
Bezeichner oder mit einem generischen Syntaxfehler abgewiesen werden
|
||||
(Guiding Principle).
|
||||
|
||||
#### Scenario: ISAM-Anweisung wird als solche erkannt
|
||||
- **WHEN** ein Modul `SETINDEX #1, "NachName"` enthält
|
||||
- **THEN** entsteht keine Diagnose „unbekannter Bezeichner" und die Anweisung wird als ISAM-Anweisung erfasst
|
||||
|
||||
#### Scenario: Falsche Argumentanzahl wird namentlich gemeldet
|
||||
- **WHEN** ein Modul `SEEKGT #1` ohne Schlüsselwert enthält
|
||||
- **THEN** nennt die Diagnose `SEEKGT` und die erwartete Argumentanzahl
|
||||
|
||||
#### Scenario: Typprüfung des Satzarguments
|
||||
- **WHEN** `RETRIEVE #1, x$` mit einem String statt einer Variablen des Tabellentyps geprüft wird
|
||||
- **THEN** wird „Type mismatch" mit Quelltextposition gemeldet
|
||||
78
openspec/changes/archive/2026-09-04-phase-3-isam/tasks.md
Normal file
78
openspec/changes/archive/2026-09-04-phase-3-isam/tasks.md
Normal file
@@ -0,0 +1,78 @@
|
||||
## 1. Umfang festlegen
|
||||
|
||||
- [x] 1.1 ISAM-Abschnitt der Original-Hilfe auswerten und alle Anweisungen, Funktionen und Klauseln gegen die ISAM-Einträge in `docs/inventar.md` abgleichen; verifiziert dadurch, dass jedes gefundene Element im Inventar mit Status `offen` steht und im Change notiert ist, falls es über die Aufzählung im Proposal hinausgeht
|
||||
- [x] 1.2 Argumentformen je Element aus der Original-Hilfe festhalten (insbesondere die Schreibweise der Spaltenliste bei `CREATEINDEX`); verifiziert durch die Notiz im Change, die jede Signatur mit ihrer Quelle nennt
|
||||
|
||||
## 2. Frontend: Grammatik und Signaturen
|
||||
|
||||
- [x] 2.1 ISAM-Anweisungen in die Parser-Grammatik aufnehmen; verifiziert durch Frontend-Test, dass `SETINDEX #1, "Name"` diagnose-frei parst
|
||||
- [x] 2.2 Signaturen (Argumentanzahl und -typen) für alle ISAM-Elemente in der Semantik führen; verifiziert durch Frontend-Test, dass `SEEKGT #1` ohne Schlüsselwert eine Diagnose erzeugt, die `SEEKGT` namentlich nennt
|
||||
- [x] 2.3 Satzargumente gegen den Tabellentyp prüfen; verifiziert durch Frontend-Test, dass `RETRIEVE #1, x$` „Type mismatch" meldet
|
||||
- [x] 2.4 `Unsupported`-Absenkung von `OPEN … FOR ISAM` entfernen und durch die reguläre Absenkung ersetzen; verifiziert dadurch, dass ein ISAM-Programm nicht mehr mit Laufzeitfehler 73 endet
|
||||
- [x] 2.5 Prüfen, dass kein ISAM-Element mehr als unbekannter Bezeichner behandelt wird; verifiziert durch Frontend-Test über ein Programm, das jedes Element einmal verwendet, ohne Diagnose „unbekannter Bezeichner"
|
||||
|
||||
## 3. Speicherschicht
|
||||
|
||||
- [x] 3.1 `redb` als Abhängigkeit aufnehmen, auf das ISAM-Modul in `tb-runtime` beschränkt; verifiziert dadurch, dass `cargo build -p tb-runtime` durchläuft
|
||||
- [x] 3.2 Datenbankdatei mit Formatversion anlegen und öffnen; verifiziert durch Test, dass eine Datei mit höherer Formatversion mit benanntem Fehler abgewiesen und eine strukturell beschädigte Datei mit Laufzeitfehler 88 abgelehnt wird
|
||||
- [x] 3.3 Satztabelle `satz-id → Satzbytes` mit monoton vergebenen, nie wiederverwendeten IDs; verifiziert durch Test, dass eine nach `DELETE` neu vergebene ID nicht vorkommt
|
||||
- [x] 3.4 Satzserialisierung auf den Recordpuffern und der UTF-32-Festtextkodierung aus `datei-eio` aufsetzen; verifiziert durch Roundtrip-Test über alle Feldtypen des Satztyps
|
||||
- [x] 3.5 Ordnungserhaltende Schlüsselkodierung je Spaltentyp (Zahlen vorzeichenrichtig fester Breite, Text nach Codepoints, mehrspaltig verkettet mit eindeutigem Trenner, absteigend durch Byte-Invertierung); verifiziert durch Eigenschaftstest über zufällige Wertepaare, dass der Byte-Vergleich der Kodierung dem fachlichen Vergleich entspricht
|
||||
- [x] 3.6 Sortierordnung (Unicode-Codepoints, Groß-/Kleinschreibung unterschieden) in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag
|
||||
- [x] 3.7 Datenbankdateiformat in docs/dateiformate.md beschreiben inklusive Formatversion und bewusster Inkompatibilität zum Vorbild; verifiziert durch den Abschnittseintrag
|
||||
|
||||
## 4. Tabellen
|
||||
|
||||
- [x] 4.1 `OPEN … FOR ISAM typname tabellenname AS #n` an die Dateitabelle binden, Satzlayout aus dem benannten Typ ableiten; verifiziert durch Korpustest, dass die Dateinummer danach nutzbar ist
|
||||
- [x] 4.2 Tabelle beim ersten Öffnen anlegen; verifiziert durch Korpustest gegen eine Datenbank ohne diese Tabelle
|
||||
- [x] 4.3 Layoutabgleich bei bestehender Tabelle mit Fehler 88 bei Abweichung; verifiziert durch Korpustest mit unpassendem Typ
|
||||
- [x] 4.4 Ungültige Datenbank- und Tabellennamen mit Fehler 81 abweisen; verifiziert durch Korpustest
|
||||
- [x] 4.5 `CLOSE` löst die Bindung und lässt eine laufende Transaktion unberührt; verifiziert durch Korpustest, dass die Sätze nach erneutem Öffnen vorhanden sind
|
||||
- [x] 4.6 `DELETETABLE` entfernt Tabelle samt Indizes, Fehler 82 bei fehlender Tabelle; verifiziert durch Korpustest für beide Fälle
|
||||
|
||||
## 5. Indizes
|
||||
|
||||
- [x] 5.1 Indextabellen `Schlüsselbytes → satz-id` je Index anlegen; verifiziert durch Test, dass die Einträge in Schlüsselreihenfolge stehen
|
||||
- [x] 5.2 `CREATEINDEX` mit einer und mit mehreren Spalten, eindeutig und mehrdeutig, auf- und absteigend; verifiziert durch Korpustest je Variante
|
||||
- [x] 5.3 `DELETEINDEX`; verifiziert durch Korpustest, dass der Index danach nicht mehr gesetzt werden kann
|
||||
- [x] 5.4 `SETINDEX` setzt den aktiven Index und stellt den Cursor auf den ersten Satz seiner Ordnung, `GETINDEX$` liefert ihn zurück; verifiziert durch Korpustest
|
||||
- [x] 5.5 Fehler 83 bei unbekanntem Index und Fehler 84 bei unbekannter Spalte; verifiziert durch Korpustest für beide Fälle
|
||||
|
||||
## 6. Cursor
|
||||
|
||||
- [x] 6.1 Cursor je Dateinummer als Wert führen (aktiver Index, letzte Satz-ID, Kennzeichen unpositioniert); verifiziert durch Unit-Test, dass der Cursor eine Satzänderung überlebt
|
||||
- [x] 6.2 `MOVEFIRST`, `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS` in der Ordnung des aktiven Index; verifiziert durch Korpustest, dass ein Durchlauf die Sätze in Indexreihenfolge liefert
|
||||
- [x] 6.3 Bewegung ohne aktiven Index folgt der Einfügereihenfolge; verifiziert durch Korpustest
|
||||
- [x] 6.4 `EOF` am Ende und `BOF` am Anfang, Cursor danach jeweils unpositioniert; verifiziert durch je einen Korpustest für `MOVENEXT` am Ende und `MOVEPREVIOUS` am Anfang
|
||||
- [x] 6.5 `SEEKEQ`, `SEEKGT`, `SEEKGE` inklusive Präfixsuche über einen mehrspaltigen Index; verifiziert durch Korpustest mit den Schlüsseln 10, 20, 30 und einer Präfixsuche
|
||||
- [x] 6.6 Erfolglose Suche setzt `EOF` und lässt den Cursor unpositioniert; verifiziert durch Korpustest
|
||||
- [x] 6.7 Suche über den NULL-Index löst Fehler 87 aus; verifiziert durch Korpustest
|
||||
|
||||
## 7. Satzoperationen
|
||||
|
||||
- [x] 7.1 `INSERT` und `RETRIEVE`; verifiziert durch Korpustest, dass ein eingefügter Satz per `SEEKEQ` gefunden und feldgleich gelesen wird
|
||||
- [x] 7.2 `UPDATE` und `DELETE`, nach `DELETE` wird der folgende Satz der aktuelle (am Ende: kein aktueller Satz); verifiziert durch Korpustest
|
||||
- [x] 7.3 Fehler 85 bei `RETRIEVE`/`UPDATE`/`DELETE` ohne positionierten Cursor; verifiziert durch je einen Korpustest
|
||||
- [x] 7.4 Eindeutigkeitsprüfung mit Fehler 86 und unverändertem Satzbestand; verifiziert durch Korpustest, der nach dem Fehler den Bestand nachzählt
|
||||
- [x] 7.5 Alle Indizes nach jeder Satzoperation nachführen; verifiziert durch Korpustest, dass ein Satz nach Änderung der indizierten Spalte an neuer Position im Durchlauf erscheint
|
||||
|
||||
## 8. Transaktionen
|
||||
|
||||
- [x] 8.1 `BEGINTRANS`/`COMMITTRANS` auf einer Schreibtransaktion; verifiziert durch Korpustest, dass die Änderungen nach erneutem Öffnen vorhanden sind
|
||||
- [x] 8.2 Rücknahmeprotokoll der Änderungen innerhalb einer Transaktion; verifiziert durch Unit-Test, dass jede Satzoperation einen Protokolleintrag erzeugt
|
||||
- [x] 8.3 `ROLLBACK ALL` stellt Sätze und Indizes wieder her, beendet die Transaktion und setzt den Cursor auf unpositioniert; verifiziert durch Korpustest mit Einfügen, Löschen und anschließendem Durchlauf
|
||||
- [x] 8.4 `SAVEPOINT` als Funktion (liefert Kennung) und `ROLLBACK kennung`; verifiziert durch Korpustest, dass Änderungen vor dem Punkt erhalten bleiben
|
||||
|
||||
## 9. Übrige Elemente
|
||||
|
||||
- [x] 9.2 `SETMEM` als Funktion mit echter Puffergrenze und Fehler 89 beim Überschreiten; verifiziert durch Korpustest, der die Grenze niedrig setzt und den Fehler auslöst
|
||||
- [x] 9.3 Vorgabewert und Bedeutung von `SETMEM` sowie die Abweichung vom DOS-Speichermodell in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag
|
||||
- [x] 9.4 Indexordnung gegen den Stringvergleich des Dialekts prüfen; verifiziert durch Korpustest, dass Indexreihenfolge und `<`-Vergleich übereinstimmen
|
||||
|
||||
## 10. Abschluss
|
||||
|
||||
- [x] 10.1 ISAM-Einträge in `docs/inventar.md` auf `implementiert` setzen; verifiziert dadurch, dass der Abgleichstest aus `phase-3-runtime-bildschirm` grün bleibt
|
||||
- [x] 10.2 `docs/bibliothek.md` um die ISAM-Elemente erweitern; verifiziert dadurch, dass jedes umgesetzte Element dort einen Eintrag hat
|
||||
- [x] 10.3 Korpus-ISAM-Programme in einem temporären Arbeitsverzeichnis ausführen; verifiziert dadurch, dass nach `cargo test` `git status` sauber ist
|
||||
- [x] 10.4 Vollständige Suite auf allen Zielplattformen ausführen; verifiziert dadurch, dass `cargo test --workspace` grün ist
|
||||
- [x] 10.5 PLAN.md Phase 3 als abgeschlossen markieren; verifiziert durch den aktualisierten Planabschnitt
|
||||
@@ -0,0 +1,193 @@
|
||||
# Umfang und Argumentformen der ISAM-Elemente (Aufgaben 1.1, 1.2)
|
||||
|
||||
**Quelle.** `bas7advr.hlp` (BASIC 7 Advisor), Themenseiten unter
|
||||
https://dos-help.soulsphere.org/bas7advr.hlp/ (abgerufen 2026-09-04).
|
||||
Die Themenliste selbst ist bereits in
|
||||
`openspec/changes/archive/2026-09-04-phase-3-runtime-bildschirm/rohliste-original-hilfe.md`
|
||||
wortgetreu festgehalten; hier kommen die Syntaxzeilen der Einzelthemen
|
||||
dazu.
|
||||
|
||||
## 1.1 Umfangsabgleich
|
||||
|
||||
Der ISAM-Abschnitt der Original-Hilfe führt genau diese Themen (Zeilen der
|
||||
Rohliste in Klammern):
|
||||
|
||||
| Thema der Rohliste | Zeile | Elemente |
|
||||
|---|---|---|
|
||||
| `BEGINTRANS Statement` | 26 | `BEGINTRANS` |
|
||||
| `BOF Function` | 28 | `BOF` |
|
||||
| `COMMITTRANS Statement` | 47 | `COMMITTRANS` |
|
||||
| `CREATEINDEX Statement` | 51 | `CREATEINDEX` |
|
||||
| `DELETE Statement` | 64 | `DELETE` |
|
||||
| `DELETEINDEX Statement` | 65 | `DELETEINDEX` |
|
||||
| `DELETETABLE Statement` | 66 | `DELETETABLE` |
|
||||
| `GETINDEX$ Function` | 99 | `GETINDEX$` |
|
||||
| `INSERT Statement` | 110 | `INSERT` |
|
||||
| `MOVEFIRST, MOVELAST, MOVENEXT, MOVEPREVIOUS Statements` | 145 | 4 Elemente |
|
||||
| `RETRIEVE Statement` | 195 | `RETRIEVE` |
|
||||
| `ROLLBACK, ROLLBACK ALL Statements` | 200 | `ROLLBACK` |
|
||||
| `SAVEPOINT Function` | 206 | `SAVEPOINT` |
|
||||
| `SEEKGT, SEEKGE, SEEKEQ Statements` | 211 | 3 Elemente |
|
||||
| `SETINDEX Statement` | 213 | `SETINDEX` |
|
||||
| `SETMEM Function` | 214 | `SETMEM` |
|
||||
| `UPDATE Statement` | 259 | `UPDATE` |
|
||||
|
||||
Das sind **22 Elemente**. `docs/inventar.md` führt in der Gruppe `ISAM`
|
||||
genau dieselben 22 Namen, alle mit Status `offen`. Der Umfang geht damit
|
||||
**nicht** über die Aufzählung im Proposal hinaus — die Liste im Proposal
|
||||
ist deckungsgleich.
|
||||
|
||||
**Kein eigenes Element:** `TEXTCOMP` kommt in der Themenliste nicht vor
|
||||
(bestätigt den Befund aus `design.md`, D4). Ebenso wenig ein Thema für
|
||||
Sicherungspunkte außer `SAVEPOINT` oder für Datenbankwartung.
|
||||
|
||||
**Elemente mit zusätzlichem ISAM-Verhalten**, die schon anderweitig
|
||||
implementiert sind und deshalb keinen eigenen Inventareintrag der Gruppe
|
||||
`ISAM` haben: `OPEN` (Klausel `FOR ISAM`), `CLOSE`, `EOF`, `LOF`, `LOC`.
|
||||
|
||||
## 1.2 Argumentformen je Element
|
||||
|
||||
Syntaxzeilen wortgetreu aus der jeweiligen Themenseite der Original-Hilfe.
|
||||
|
||||
| Element | Syntax (Original-Hilfe) | Themenseite |
|
||||
|---|---|---|
|
||||
| `OPEN … FOR ISAM` | `OPEN database$ FOR ISAM tabletype tablename$ AS [#]filenumber%` | `x_dot_opfior.html` |
|
||||
| `CREATEINDEX` | `CREATEINDEX [#]filenumber%,indexname$,unique%,columnname$[,columnname$]` | `x_dot_CREATEINDEXr.html` |
|
||||
| `DELETEINDEX` | `DELETEINDEX [#]filenumber%,indexname$` | `x_dot_DELETEINDEXr.html` |
|
||||
| `SETINDEX` | `SETINDEX [#]filenumber%[,indexname$]` | `x_dot_SETINDEXr.html` |
|
||||
| `GETINDEX$` | `GETINDEX$ (filenumber%)` | `x_dot_GETINDEX$r.html` |
|
||||
| `INSERT` | `INSERT [#]filenumber%,recordvariable` | `x_dot_INSERTr.html` |
|
||||
| `RETRIEVE` | `RETRIEVE [#]filenumber%,recordvariable` | `x_dot_RETRIEVEr.html` |
|
||||
| `UPDATE` | `UPDATE [#]filenumber%,recordvariable` | `x_dot_UPDATEr.html` |
|
||||
| `DELETE` | `DELETE [#]filenumber%` | `x_dot_DELETEr.html` |
|
||||
| `DELETETABLE` | `DELETETABLE database$,tablename$` | `x_dot_DELETETABLEr.html` |
|
||||
| `MOVEFIRST` u. a. | `MOVEFIRST [#]filenumber%` (ebenso `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS`) | `x_dot_MOVEFIRSTr.html` |
|
||||
| `SEEKGT`/`SEEKGE`/`SEEKEQ` | `SEEKGT [#]filenumber% ,keyvalue [,keyvalue]…` | `x_dot_seekisamr.html` |
|
||||
| `BEGINTRANS` | `BEGINTRANS` | `x_dot_BEGINTRANSr.html` |
|
||||
| `COMMITTRANS` | `COMMITTRANS` | `x_dot_COMMITTRANSr.html` |
|
||||
| `ROLLBACK` | `ROLLBACK [savepoint%]` bzw. `ROLLBACK ALL` | `x_dot_ROLLBACKr.html` |
|
||||
| `SAVEPOINT` | `SAVEPOINT` (Funktion, liefert eine ganze Zahl) | `x_dot_SAVEPOINTr.html` |
|
||||
| `SETMEM` | `SETMEM(numeric-expression&)` | `x_dot_setmemr.html` |
|
||||
| `BOF` | `BOF(filenumber%)` | `x_dot_BOFr.html` |
|
||||
|
||||
### Befund zur offenen Frage aus design.md
|
||||
|
||||
Die Spaltenliste bei `CREATEINDEX` ist **keine** Zeichenkette mit
|
||||
Trennzeichen, sondern eine **Folge einzelner Stringargumente**:
|
||||
`columnname$[,columnname$]…`. Die Absenkung übernimmt diese Form
|
||||
unverändert; `CREATEINDEX` ist damit variadisch (4 bis 12 Argumente,
|
||||
also bis zu neun Indexspalten).
|
||||
|
||||
### Ergänzung über die Original-Hilfe hinaus: absteigende Spalten
|
||||
|
||||
Die Original-Hilfe kennt bei `CREATEINDEX` **keine** Angabe der
|
||||
Sortierrichtung — Indizes sind dort immer aufsteigend. Die Anforderung
|
||||
„Indexverwaltung" dieses Changes verlangt aber auf- **und** absteigende
|
||||
Spalten. Gewählte Schreibweise: ein vorangestelltes `-` am Spaltennamen.
|
||||
|
||||
```basic
|
||||
CREATEINDEX #1, "NachPreis", 0, "Nachname", "-Preis"
|
||||
```
|
||||
|
||||
Sie ist eindeutig und kollisionsfrei, weil ein Feldname einer
|
||||
`TYPE`-Anweisung nie mit `-` beginnen kann; Programme des Vorbilds
|
||||
bleiben dadurch unverändert gültig. Festgehalten in
|
||||
docs/sprachreferenz.md und docs/bibliothek.md.
|
||||
|
||||
### Cursor-Semantik folgt dem Vorbild
|
||||
|
||||
Die Delta-Spezifikation schrieb ursprünglich an zwei Stellen eine andere
|
||||
Cursorlage vor, als die Original-Hilfe beschreibt. Entscheidung vom
|
||||
2026-09-04: **bei Semantik und Syntax gewinnt das Vorbild, wo immer das
|
||||
möglich ist.** Die Spezifikation wurde daraufhin angeglichen, nicht die
|
||||
Umsetzung:
|
||||
|
||||
| Situation | Original-Hilfe (und jetzt auch Terminal Basic) |
|
||||
|---|---|
|
||||
| nach `SETINDEX` | „the current record is the first record according to that index" — der erste Satz der neuen Ordnung ist der aktuelle |
|
||||
| nach `DELETE` | „the record following the deleted record becomes the current record"; war der gelöschte der letzte, steht der Cursor am Ende der Tabelle ohne aktuellen Satz |
|
||||
|
||||
`SETINDEX` ohne Indexnamen bzw. mit `""` wählt den NULL-Index: „If you
|
||||
omit the `indexname$` argument or specify double quotes for it, the
|
||||
current index is the NULL index" — er „represents the order in which
|
||||
records were added to the file".
|
||||
|
||||
### Präfixsuche: Verhalten je Suchart
|
||||
|
||||
Die Original-Hilfe unterscheidet die Sucharten bei zu wenigen
|
||||
Schlüsselwerten ausdrücklich; die Umsetzung übernimmt das:
|
||||
|
||||
- `SEEKEQ` mit unvollständigem Schlüssel **schlägt immer fehl**
|
||||
(„SEEKEQ with insufficient keyvalues always fails").
|
||||
- `SEEKGE` sucht mit den vorhandenen Werten als Präfix.
|
||||
- `SEEKGT` mit unvollständigem Schlüssel positioniert auf dem **ersten
|
||||
passenden** Satz — also wie `SEEKGE`, nicht hinter der Präfixgruppe.
|
||||
|
||||
### Fehler 87: Auslöser ist die Suche über den NULL-Index
|
||||
|
||||
Der Proposal-Abschnitt „ISAM-Fehlersemantik" nennt Code 87 („ISAM -
|
||||
Invalid operation on NULL index"). Die Themenseite zur `SEEK`-Familie
|
||||
schweigt zur Lage ohne aktiven Index — der Fehlerkatalog des Vorbilds
|
||||
benennt sie aber genau: der NULL-Index führt keine Schlüssel, sondern nur
|
||||
die Einfügereihenfolge, eine Schlüsselsuche über ihn ist deshalb die
|
||||
„unzulässige Operation auf dem NULL-Index".
|
||||
|
||||
Die Delta-Spezifikation nannte hier zunächst Fehler 83; sie ist mit der
|
||||
Entscheidung vom 2026-09-04 („bei Semantik gewinnt das Vorbild") auf 87
|
||||
angeglichen. **83** bleibt dem wirklich unbekannten Indexnamen vorbehalten
|
||||
und wird von `SETINDEX` und `DELETEINDEX` ausgelöst. Damit hat jeder Code
|
||||
von 81 bis 89 einen Auslöser.
|
||||
|
||||
Bewegung über den NULL-Index bleibt zulässig (`MOVE`-Familie in
|
||||
Einfügereihenfolge) — die Original-Hilfe beschreibt den NULL-Index
|
||||
ausdrücklich als Reihenfolge, nicht als Fehlerzustand.
|
||||
|
||||
## Befunde der Verifikation (2026-09-04)
|
||||
|
||||
Vier Befunde aus `/opsx:verify`, alle umgesetzt.
|
||||
|
||||
### `CLOSE` beendet keine Transaktion
|
||||
|
||||
Ursprünglich schrieb `CLOSE` eine laufende Transaktion fest, um Aufgabe 4.5
|
||||
(„schreibt ausstehende Änderungen fest") zu erfüllen. Das war zu weit
|
||||
gegriffen und hatte zwei beobachtbare Folgen:
|
||||
|
||||
```basic
|
||||
BEGINTRANS : INSERT #1, p : CLOSE #1 ' Satz blieb erhalten
|
||||
BEGINTRANS : INSERT #1, p : END ' Satz verfiel
|
||||
```
|
||||
|
||||
und, schlimmer, über Dateinummern hinweg:
|
||||
|
||||
```basic
|
||||
OPEN … AS #1 : OPEN … AS #2
|
||||
BEGINTRANS : INSERT #1, p : CLOSE #2 ' schrieb die Änderung an #1 fest
|
||||
ROLLBACK ALL ' → Fehler 5, Satz überlebte
|
||||
```
|
||||
|
||||
Aufgabe 4.5 ist auch ohne diese Festschreibung erfüllt, weil jede Operation
|
||||
außerhalb einer Transaktion für sich eine Transaktion ist. `CLOSE` löst
|
||||
seither nur die Bindung; über das Ende einer Transaktion entscheiden allein
|
||||
`COMMITTRANS` und `ROLLBACK ALL`, und eine beim Programmende offene
|
||||
Transaktion verfällt — in beiden Wegen gleich. Delta-Spec, Aufgabe 4.5 und
|
||||
die Referenzen sind angeglichen; zwei Einheitentests halten es fest.
|
||||
|
||||
### `CREATEINDEX` ohne Obergrenze der Spaltenzahl
|
||||
|
||||
Die Signatur begrenzte den Index auf neun Spalten. Die Original-Hilfe nennt
|
||||
keine Obergrenze, also darf die Signatur auch keine setzen: die
|
||||
Spaltenargumente werden jetzt wie bei `INSTR` gesondert geprüft (ab dem
|
||||
vierten Argument je ein String), die Argumentzahl ist offen.
|
||||
|
||||
### Kein Index-Torso nach Fehler 86
|
||||
|
||||
Scheiterte `CREATEINDEX` an der Eindeutigkeit, blieb innerhalb einer
|
||||
Transaktion eine halb gefüllte Indextabelle zurück, die keine
|
||||
Indexdefinition mehr nannte. `index_anlegen` bildet jetzt erst alle
|
||||
Schlüssel und prüft die Eindeutigkeit, bevor es schreibt — dieselbe
|
||||
Reihenfolge wie in `satz_schreiben`.
|
||||
|
||||
### Toter Klon in `RETRIEVE`
|
||||
|
||||
`satz_lesen` klonte das Tabellenlayout je Aufruf, ohne es zu benutzen.
|
||||
Entfernt.
|
||||
Reference in New Issue
Block a user