ISAM-Datenbankunterstuetzung

Setzt den OpenSpec-Change phase-3-isam um (49/49 Aufgaben) und schliesst
damit Phase 3 ab. Alle 22 ISAM-Elemente des Inventars sind implementiert;
der Abdeckungsstand steigt auf 217 implementiert / 15 offen / 53
Non-Feature.

Frontend
- ISAM-Anweisungen als eigener Zweig im Parser: NAME [#]n [, arg ...],
  Sonderform ROLLBACK ALL ueber eine Sentinel-Kennung
- 22 Signaturen in builtin_stmt/builtin_fn, neue Argumentart R
  (Satzvariable eines benutzerdefinierten Typs)
- Satzargumente von INSERT/RETRIEVE/UPDATE werden gegen den Typ der
  Dateinummer geprueft, sofern beide literal bekannt sind
- Die Unsupported-Absenkung von OPEN ... FOR ISAM entfaellt

Speicherschicht (tb-runtime::isam, einziger Ort mit redb)
- Je Tabelle eine Satztabelle satz-id -> Satzbytes, je Index eine Tabelle
  Schluesselbytes -> satz-id. Satz-IDs sind monoton und werden nie
  wiederverwendet: der Cursor merkt sich eine ID, eine neu vergebene
  koennte still auf einen fremden Satz zeigen
- Satzbytes entstehen mit fileio::wert_schreiben/wert_lesen, also mit den
  Recordpuffern und der UTF-32-Festtextkodierung aus datei-eio
- Ordnungserhaltende Schluesselkodierung je Spaltentyp; Text als UTF-8,
  dessen Bytereihenfolge die Codepoint-Reihenfolge ist und damit dieselbe
  Ordnung wie CmpStr der VM. Ein Eigenschaftstest ueber 5000 zufaellige
  Wertepaare je Typ haelt fest, dass der Bytevergleich dem fachlichen
  Vergleich entspricht -- eine Ordnungsverletzung faellt sonst erst bei
  bestimmten Datenwerten auf
- Cursor als Wert (aktiver Index, letzte Satz-ID, unpositioniert), nicht
  als gehaltener Iterator: er ueberlebt Satzaenderungen und
  Transaktionsgrenzen
- Sicherungspunkte ueber ein eigenes Ruecknahmeprotokoll; ROLLBACK ALL
  bricht die Bibliothekstransaktion direkt ab

Semantik durchweg aus der Original-Hilfe
- Die Argumentformen stammen erstmals aus den Einzelseiten, nicht nur aus
  der Themenliste. Das korrigierte eine Annahme des Entwurfs: die
  Spaltenliste von CREATEINDEX ist keine Zeichenkette mit Trennzeichen,
  sondern eine Folge einzelner Stringargumente
- SEEKEQ mit unvollstaendigem Schluessel schlaegt immer fehl, SEEKGT mit
  Teilschluessel positioniert wie SEEKGE
- Nach SETINDEX ist der erste Satz der neuen Ordnung aktuell, nach DELETE
  der folgende
- Suche ueber den NULL-Index meldet 87, nicht 83; damit hat jeder Code von
  81 bis 89 einen Ausloeser
- Einzige Erweiterung: ein - vor dem Spaltennamen ordnet absteigend. Die
  Original-Hilfe kennt bei CREATEINDEX keine Sortierrichtung, die
  Anforderung verlangt sie. Kollisionsfrei, weil ein TYPE-Feldname nie mit
  - beginnen kann

CLOSE beendet keine Transaktion
- Erst schrieb es sie fest, womit das Schliessen irgendeiner Dateinummer
  die Transaktion aller anderen mit beendete und ein folgendes ROLLBACK
  ALL ins Leere lief. Ausstehende Aenderungen sind ohnehin festgeschrieben,
  weil jede Operation ausserhalb einer Transaktion fuer sich eine ist
- Ueber das Ende entscheiden allein COMMITTRANS und ROLLBACK ALL; eine
  beim Programmende offene Transaktion verfaellt, in beiden Wegen gleich

SETMEM und Fehler 89
- Ohne DOS-Speichermodell ist SETMEM die Obergrenze des ISAM-Puffers
  (Vorgabe 65536 Bytes); ohne echte Grenze waere Code 89 nie erreichbar
  und die Anweisung eine Attrappe

Tests
- 18 Einheitentests (Kodierung, Formatversion, Satz-IDs, Roundtrip ueber
  alle Feldtypen, Indexordnung, Cursor, Protokoll)
- 6 Korpusprogramme: Tabellen, Indizes, Cursor, Saetze, Transaktionen,
  Puffer und Vergleichsordnung -- je in einem temporaeren Arbeits-
  verzeichnis, der Projektbaum bleibt sauber
- Der VM-Test zu Fehler 73 prueft ISAM nicht mehr, sondern SETUEVENT;
  dafuer belegt ein neuer Test, dass OPEN ... FOR ISAM arbeitet

Dokumentation: Datenbankdateiformat in dateiformate.md, Abschnitt 10a
samt Sortierordnung und SETMEM-Abweichung in sprachreferenz.md,
ISAM-Abschnitt in bibliothek.md, Inventar auf implementiert, PLAN.md
schaerft die Leitplanke Referenzverhalten -- sie gilt auch gegenueber den
eigenen Spezifikationen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 11:37:55 +02:00
parent da114d86e9
commit 9b58e0ec43
30 changed files with 3615 additions and 50 deletions

View File

@@ -259,11 +259,87 @@ solange ein Menü-Control den Fokus hat. Für Terminal Basic relevant:
`LOC(n)`, `LOF(n)`.
- Verwaltung: `NAME alt$ AS neu$`, `KILL datei$`, `FILES [muster$]`,
`CHDIR`, `MKDIR`, `RMDIR`, `FILEATTR`, `FREEFILE`.
- **ISAM** (Professional-Edition des Vorbilds): wird unterstützt
(Entscheidung 2026-09-02) — `OPEN … FOR ISAM`, Tabellen/Indizes,
`SEEKGT`-Familie usw.; Umfang wird mit dem Inventar (Phase 3) erfasst.
- Pfade: plattformneutral; `/` und `\` werden akzeptiert.
## 10a. ISAM (indizierter Satzzugriff)
ISAM der Professional-Edition des Vorbilds wird unterstützt (Entscheidung
2026-09-02). Das Dateiformat beschreibt
[dateiformate.md](dateiformate.md#isam-datenbank-open--for-isam).
- `OPEN datenbank$ FOR ISAM typname tabellenname$ AS [#]n` bindet eine
Tabelle an eine Dateinummer; die Felder des benannten `TYPE` sind ihre
Spalten. Die Tabelle entsteht beim ersten Öffnen. `CLOSE` löst die
Bindung; jede Operation außerhalb einer Transaktion ist für sich schon
festgeschrieben. Eine laufende Transaktion lässt `CLOSE` unberührt —
über ihr Ende entscheiden `COMMITTRANS` und `ROLLBACK ALL`; eine beim
Programmende offene Transaktion bleibt wirkungslos.
- Indizes: `CREATEINDEX [#]n, indexname$, eindeutig%, spalte$[, spalte$]…`,
`DELETEINDEX [#]n, indexname$`, `SETINDEX [#]n[, indexname$]`,
`GETINDEX$(n)`. `SETINDEX` ohne Namen wählt den NULL-Index
(Einfügereihenfolge); `GETINDEX$` liefert dafür einen leeren String.
- Sätze: `INSERT`, `RETRIEVE`, `UPDATE` (je `[#]n, satzvariable`) und
`DELETE [#]n` auf dem aktuellen Satz.
- Cursor: `MOVEFIRST`, `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS` (je `[#]n`)
in der Ordnung des aktiven Index; `EOF(n)` und `BOF(n)` melden das
Überlaufen am Ende bzw. am Anfang.
- Suche: `SEEKEQ`, `SEEKGT`, `SEEKGE` (je `[#]n, schlüsselwert[, …]`).
Ein mehrspaltiger Index lässt sich mit einem Präfix seiner Spalten
durchsuchen; `SEEKEQ` mit unvollständigem Schlüssel schlägt dabei immer
fehl (wie im Vorbild), `SEEKGE` und `SEEKGT` positionieren auf dem ersten
passenden Satz. Über den NULL-Index lässt sich nicht suchen (Fehler 87).
- Tabellen: `DELETETABLE datenbank$, tabellenname$`.
- Transaktionen: `BEGINTRANS`, `COMMITTRANS`, `SAVEPOINT` (Funktion,
liefert die Kennung), `ROLLBACK [kennung]` und `ROLLBACK ALL`.
- `LOF(n)` liefert für eine ISAM-Dateinummer die Satzzahl der Tabelle,
`LOC(n)` die Kennung des aktuellen Satzes (0 = unpositioniert).
- Cursorlage wie im Vorbild: nach `SETINDEX` ist der erste Satz der neuen
Ordnung der aktuelle; nach `DELETE` der auf den gelöschten folgende, und
war der gelöschte der letzte, steht der Cursor am Ende der Tabelle ohne
aktuellen Satz.
- Fehlercodes 8189 (`ERR`): 81 ungültiger Name, 82 Tabelle nicht
gefunden, 83 Index nicht gefunden, 84 ungültige Spalte, 85 kein aktueller
Satz, 86 Verstoß gegen einen eindeutigen Index, 87 unzulässige Operation
auf dem NULL-Index (Suche), 88 Datenbank inkonsistent, 89 ISAM-Puffer zu
klein.
**Sortierordnung der Indizes.** Textspalten ordnen nach
**Unicode-Codepoints**; Groß- und Kleinschreibung werden **unterschieden**.
Es ist dieselbe Ordnung, die der Vergleichsoperator des Dialekts auf
Strings anwendet — ein Programm kann die Satzreihenfolge damit
vorhersagen. Praktisch heißt das: alle Großbuchstaben `A``Z` stehen vor
allen Kleinbuchstaben `a``z`, und Zeichen jenseits von ASCII (`Ä`, `ß`,
`€`) stehen nach beiden, in der Reihenfolge ihrer Codepoints. Beispiel:
`Apfel` < `Birne` < `apfel` < `birne` < `Äpfel`.
Eine sprachabhängige (locale-abhängige) Sortierung gibt es bewusst nicht:
sie machte die Reihenfolge plattformabhängig. Das Vorbild ordnete nach den
Zeichencodes einer festen Codepage; Codepoints sind die direkte
Entsprechung unter der Unicode-Entscheidung dieses Projekts.
**Absteigende Indexspalten** (Erweiterung). Die Original-Hilfe kennt bei
`CREATEINDEX` keine Sortierrichtung. Terminal Basic ordnet eine Spalte
absteigend, wenn ihrem Namen ein `-` vorangestellt ist:
```basic
CREATEINDEX #1, "NachPreis", 0, "Nachname", "-Preis"
```
Das ist kollisionsfrei, weil ein Feldname einer `TYPE`-Anweisung nie mit
`-` beginnen kann; Programme des Vorbilds bleiben unverändert gültig.
**`SETMEM` und Fehler 89** (Abweichung). Im Vorbild veränderte
`SETMEM(n&)` den DOS-Fern-Heap um `n` Bytes. Ein DOS-Speichermodell gibt
es hier nicht; stattdessen ist `SETMEM` die Obergrenze des ISAM-Puffers:
sie wird um `n` Bytes verändert (negativ verkleinert, positiv vergrößert)
und die danach verfügbare Größe geliefert. Der Vorgabewert ist **65536
Bytes**. Gegen die Grenze zählen die Satzbytes einer laufenden Operation
und das Rücknahmeprotokoll einer offenen Transaktion. Reicht der Puffer
für eine Operation nicht aus, tritt Laufzeitfehler 89 („ISAM -
Insufficient ISAM buffers") auf — nicht ein generischer Fehler und kein
stiller Abbruch.
## 11. Eingebaute Funktionen (Katalog)
**Strings:** `LEN`, `LEFT$`, `RIGHT$`, `MID$` (Funktion **und** Anweisung),