From e49a231a680682e9340971e4b4e68b78b489f8e7 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Fri, 4 Sep 2026 11:38:10 +0200 Subject: [PATCH] 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) --- .../2026-09-04-phase-3-isam}/.openspec.yaml | 0 .../2026-09-04-phase-3-isam}/design.md | 0 .../2026-09-04-phase-3-isam}/proposal.md | 0 .../specs/isam-datenbank/spec.md | 48 ++-- .../specs/sprach-frontend/spec.md | 0 .../2026-09-04-phase-3-isam}/tasks.md | 98 ++++---- .../umfang-und-signaturen.md | 193 ++++++++++++++++ openspec/specs/isam-datenbank/spec.md | 211 ++++++++++++++++++ openspec/specs/sprach-frontend/spec.md | 26 +++ 9 files changed, 514 insertions(+), 62 deletions(-) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/.openspec.yaml (100%) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/design.md (100%) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/proposal.md (100%) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/specs/isam-datenbank/spec.md (80%) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/specs/sprach-frontend/spec.md (100%) rename openspec/changes/{phase-3-isam => archive/2026-09-04-phase-3-isam}/tasks.md (58%) create mode 100644 openspec/changes/archive/2026-09-04-phase-3-isam/umfang-und-signaturen.md create mode 100644 openspec/specs/isam-datenbank/spec.md diff --git a/openspec/changes/phase-3-isam/.openspec.yaml b/openspec/changes/archive/2026-09-04-phase-3-isam/.openspec.yaml similarity index 100% rename from openspec/changes/phase-3-isam/.openspec.yaml rename to openspec/changes/archive/2026-09-04-phase-3-isam/.openspec.yaml diff --git a/openspec/changes/phase-3-isam/design.md b/openspec/changes/archive/2026-09-04-phase-3-isam/design.md similarity index 100% rename from openspec/changes/phase-3-isam/design.md rename to openspec/changes/archive/2026-09-04-phase-3-isam/design.md diff --git a/openspec/changes/phase-3-isam/proposal.md b/openspec/changes/archive/2026-09-04-phase-3-isam/proposal.md similarity index 100% rename from openspec/changes/phase-3-isam/proposal.md rename to openspec/changes/archive/2026-09-04-phase-3-isam/proposal.md diff --git a/openspec/changes/phase-3-isam/specs/isam-datenbank/spec.md b/openspec/changes/archive/2026-09-04-phase-3-isam/specs/isam-datenbank/spec.md similarity index 80% rename from openspec/changes/phase-3-isam/specs/isam-datenbank/spec.md rename to openspec/changes/archive/2026-09-04-phase-3-isam/specs/isam-datenbank/spec.md index 8329fad..3bf61f3 100644 --- a/openspec/changes/phase-3-isam/specs/isam-datenbank/spec.md +++ b/openspec/changes/archive/2026-09-04-phase-3-isam/specs/isam-datenbank/spec.md @@ -14,8 +14,11 @@ 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 und ausstehende -Änderungen festschreiben. `DELETETABLE` SHALL eine Tabelle samt ihren +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. @@ -27,6 +30,10 @@ Table not found" auslösen. - **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 @@ -37,10 +44,13 @@ 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. Ein unbekannter Indexname MUST -Laufzeitfehler 83 „ISAM - Index not found" auslösen, ein unbekannter -Spaltenname Fehler 84 „ISAM - Invalid column". Nach `SETINDEX` MUST der -Cursor als unpositioniert gelten. +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 @@ -50,6 +60,10 @@ Cursor als unpositioniert gelten. - **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 @@ -77,8 +91,9 @@ 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 ohne aktiven Index MUST -Laufzeitfehler 83 auslösen. +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 @@ -100,18 +115,24 @@ aus einer solchen Variablen überschreiben und `DELETE` ihn entfernen. 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 Cursor als -unpositioniert gelten. Alle Indizes der Tabelle SHALL nach jeder -Satzoperation die neue Ordnung widerspiegeln. +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** nach `SETINDEX` ohne Positionierung `RETRIEVE #1, k` ausgeführt wird +- **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 @@ -129,7 +150,8 @@ 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. +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 diff --git a/openspec/changes/phase-3-isam/specs/sprach-frontend/spec.md b/openspec/changes/archive/2026-09-04-phase-3-isam/specs/sprach-frontend/spec.md similarity index 100% rename from openspec/changes/phase-3-isam/specs/sprach-frontend/spec.md rename to openspec/changes/archive/2026-09-04-phase-3-isam/specs/sprach-frontend/spec.md diff --git a/openspec/changes/phase-3-isam/tasks.md b/openspec/changes/archive/2026-09-04-phase-3-isam/tasks.md similarity index 58% rename from openspec/changes/phase-3-isam/tasks.md rename to openspec/changes/archive/2026-09-04-phase-3-isam/tasks.md index a4d08e3..6da6994 100644 --- a/openspec/changes/phase-3-isam/tasks.md +++ b/openspec/changes/archive/2026-09-04-phase-3-isam/tasks.md @@ -1,78 +1,78 @@ ## 1. Umfang festlegen -- [ ] 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 -- [ ] 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 +- [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 -- [ ] 2.1 ISAM-Anweisungen in die Parser-Grammatik aufnehmen; verifiziert durch Frontend-Test, dass `SETINDEX #1, "Name"` diagnose-frei parst -- [ ] 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 -- [ ] 2.3 Satzargumente gegen den Tabellentyp prüfen; verifiziert durch Frontend-Test, dass `RETRIEVE #1, x$` „Type mismatch" meldet -- [ ] 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 -- [ ] 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" +- [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 -- [ ] 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 -- [ ] 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 -- [ ] 3.3 Satztabelle `satz-id → Satzbytes` mit monoton vergebenen, nie wiederverwendeten IDs; verifiziert durch Test, dass eine nach `DELETE` neu vergebene ID nicht vorkommt -- [ ] 3.4 Satzserialisierung auf den Recordpuffern und der UTF-32-Festtextkodierung aus `datei-eio` aufsetzen; verifiziert durch Roundtrip-Test über alle Feldtypen des Satztyps -- [ ] 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 -- [ ] 3.6 Sortierordnung (Unicode-Codepoints, Groß-/Kleinschreibung unterschieden) in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag -- [ ] 3.7 Datenbankdateiformat in docs/dateiformate.md beschreiben inklusive Formatversion und bewusster Inkompatibilität zum Vorbild; verifiziert durch den Abschnittseintrag +- [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 -- [ ] 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 -- [ ] 4.2 Tabelle beim ersten Öffnen anlegen; verifiziert durch Korpustest gegen eine Datenbank ohne diese Tabelle -- [ ] 4.3 Layoutabgleich bei bestehender Tabelle mit Fehler 88 bei Abweichung; verifiziert durch Korpustest mit unpassendem Typ -- [ ] 4.4 Ungültige Datenbank- und Tabellennamen mit Fehler 81 abweisen; verifiziert durch Korpustest -- [ ] 4.5 `CLOSE` löst die Bindung und schreibt ausstehende Änderungen fest; verifiziert durch Korpustest, dass die Sätze nach erneutem Öffnen vorhanden sind -- [ ] 4.6 `DELETETABLE` entfernt Tabelle samt Indizes, Fehler 82 bei fehlender Tabelle; verifiziert durch Korpustest für beide Fälle +- [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 -- [ ] 5.1 Indextabellen `Schlüsselbytes → satz-id` je Index anlegen; verifiziert durch Test, dass die Einträge in Schlüsselreihenfolge stehen -- [ ] 5.2 `CREATEINDEX` mit einer und mit mehreren Spalten, eindeutig und mehrdeutig, auf- und absteigend; verifiziert durch Korpustest je Variante -- [ ] 5.3 `DELETEINDEX`; verifiziert durch Korpustest, dass der Index danach nicht mehr gesetzt werden kann -- [ ] 5.4 `SETINDEX` setzt den aktiven Index und den Cursor auf unpositioniert, `GETINDEX$` liefert ihn zurück; verifiziert durch Korpustest -- [ ] 5.5 Fehler 83 bei unbekanntem Index und Fehler 84 bei unbekannter Spalte; verifiziert durch Korpustest für beide Fälle +- [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 -- [ ] 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 -- [ ] 6.2 `MOVEFIRST`, `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS` in der Ordnung des aktiven Index; verifiziert durch Korpustest, dass ein Durchlauf die Sätze in Indexreihenfolge liefert -- [ ] 6.3 Bewegung ohne aktiven Index folgt der Einfügereihenfolge; verifiziert durch Korpustest -- [ ] 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 -- [ ] 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 -- [ ] 6.6 Erfolglose Suche setzt `EOF` und lässt den Cursor unpositioniert; verifiziert durch Korpustest -- [ ] 6.7 Suche ohne aktiven Index löst Fehler 83 aus; verifiziert durch Korpustest +- [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 -- [ ] 7.1 `INSERT` und `RETRIEVE`; verifiziert durch Korpustest, dass ein eingefügter Satz per `SEEKEQ` gefunden und feldgleich gelesen wird -- [ ] 7.2 `UPDATE` und `DELETE`, Cursor nach `DELETE` unpositioniert; verifiziert durch Korpustest -- [ ] 7.3 Fehler 85 bei `RETRIEVE`/`UPDATE`/`DELETE` ohne positionierten Cursor; verifiziert durch je einen Korpustest -- [ ] 7.4 Eindeutigkeitsprüfung mit Fehler 86 und unverändertem Satzbestand; verifiziert durch Korpustest, der nach dem Fehler den Bestand nachzählt -- [ ] 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 +- [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 -- [ ] 8.1 `BEGINTRANS`/`COMMITTRANS` auf einer Schreibtransaktion; verifiziert durch Korpustest, dass die Änderungen nach erneutem Öffnen vorhanden sind -- [ ] 8.2 Rücknahmeprotokoll der Änderungen innerhalb einer Transaktion; verifiziert durch Unit-Test, dass jede Satzoperation einen Protokolleintrag erzeugt -- [ ] 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 -- [ ] 8.4 `SAVEPOINT` als Funktion (liefert Kennung) und `ROLLBACK kennung`; verifiziert durch Korpustest, dass Änderungen vor dem Punkt erhalten bleiben +- [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 -- [ ] 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 -- [ ] 9.3 Vorgabewert und Bedeutung von `SETMEM` sowie die Abweichung vom DOS-Speichermodell in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag -- [ ] 9.4 Indexordnung gegen den Stringvergleich des Dialekts prüfen; verifiziert durch Korpustest, dass Indexreihenfolge und `<`-Vergleich übereinstimmen +- [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 -- [ ] 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 -- [ ] 10.2 `docs/bibliothek.md` um die ISAM-Elemente erweitern; verifiziert dadurch, dass jedes umgesetzte Element dort einen Eintrag hat -- [ ] 10.3 Korpus-ISAM-Programme in einem temporären Arbeitsverzeichnis ausführen; verifiziert dadurch, dass nach `cargo test` `git status` sauber ist -- [ ] 10.4 Vollständige Suite auf allen Zielplattformen ausführen; verifiziert dadurch, dass `cargo test --workspace` grün ist -- [ ] 10.5 PLAN.md Phase 3 als abgeschlossen markieren; verifiziert durch den aktualisierten Planabschnitt +- [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 diff --git a/openspec/changes/archive/2026-09-04-phase-3-isam/umfang-und-signaturen.md b/openspec/changes/archive/2026-09-04-phase-3-isam/umfang-und-signaturen.md new file mode 100644 index 0000000..b2196b5 --- /dev/null +++ b/openspec/changes/archive/2026-09-04-phase-3-isam/umfang-und-signaturen.md @@ -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. diff --git a/openspec/specs/isam-datenbank/spec.md b/openspec/specs/isam-datenbank/spec.md new file mode 100644 index 0000000..8afbd40 --- /dev/null +++ b/openspec/specs/isam-datenbank/spec.md @@ -0,0 +1,211 @@ +# isam-datenbank Specification + +## 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. + +## 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 diff --git a/openspec/specs/sprach-frontend/spec.md b/openspec/specs/sprach-frontend/spec.md index fa0cd77..5df0c90 100644 --- a/openspec/specs/sprach-frontend/spec.md +++ b/openspec/specs/sprach-frontend/spec.md @@ -173,3 +173,29 @@ bleiben. #### Scenario: Leerraum an den Rändern - **WHEN** ein Modul `DATA ab cd ` enthält und gelesen wird - **THEN** lautet der Wert `ab cd` + +### 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