252 lines
15 KiB
Markdown
252 lines
15 KiB
Markdown
# 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.
|
|
|
|
Eine weitere Bindung derselben Datenbank innerhalb einer laufenden Transaktion SHALL deren bisherigen Zustand sehen und MUST ohne implizites Festschreiben oder Selbstblockade entstehen.
|
|
|
|
OPEN und DELETETABLE SHALL relative Datenbankpfade im aktuellen Arbeitsverzeichnis auflösen und kanonisch auflösbare Aliase derselben Datei gemeinsam verwalten. Bereits offene Bindungen MUST nach CHDIR weiterhin auf ihre ursprünglich geöffnete Datenbank zeigen. DELETETABLE SHALL bei Erfolg alle Bindungen der entfernten Tabelle lösen; weitere Zugriffe über diese Nummern MUST Fehler 52 melden. Eine Rücknahme SHALL die gelöschte Tabelle wiederherstellen, ohne geschlossene Bindungen erneut zu öffnen.
|
|
|
|
#### Scenario: Gleicher relativer Name nach Verzeichniswechsel
|
|
- **WHEN** nach OPEN in Verzeichnis A alle Bindungen geschlossen werden und derselbe relative Name in Verzeichnis B geöffnet wird
|
|
- **THEN** betreffen Lesen und Schreiben die Datei in B; die Datei in A bleibt unverändert
|
|
|
|
#### Scenario: Pfadalias in einer Transaktion
|
|
- **WHEN** dieselbe vorhandene Datenbank nach BEGINTRANS und INSERT über einen relativen oder absoluten Pfadalias erneut geöffnet wird
|
|
- **THEN** sieht die neue Bindung die bisherigen Änderungen ohne zweiten Writer; ROLLBACK ALL nimmt die Änderungen zurück
|
|
|
|
#### Scenario: Neuanlage über einen noch nicht auflösbaren Symlink
|
|
- **WHEN** OPEN eine Datenbank über einen Symlink mit bislang fehlender Zieldatei anlegt und die Datenbank danach innerhalb einer Transaktion erneut über Alias oder Zielpfad geöffnet wird
|
|
- **THEN** teilen alle Bindungen denselben Transaktionszustand; Sicherungspunkt-Rücknahme und ROLLBACK ALL funktionieren über beide Pfade
|
|
|
|
#### Scenario: Bindungen nach Tabellenlöschung und Rücknahme
|
|
- **WHEN** eine mehrfach gebundene Tabelle nach SAVEPOINT mit DELETETABLE gelöscht und anschließend zurückgenommen wird
|
|
- **THEN** sind die alten Bindungen geschlossen; ein erneutes OPEN sieht wieder die ursprünglichen Sätze und Indizes
|
|
|
|
#### 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
|
|
|
|
#### Scenario: Weitere Tabelle während einer Transaktion
|
|
- **WHEN** nach BEGINTRANS und INSERT eine zweite Tabelle derselben Datenbank geöffnet und danach ROLLBACK ALL ausgeführt wird
|
|
- **THEN** kehrt OPEN zurück; die eingefügten Daten sind nach Wiederöffnung nicht festgeschrieben
|
|
|
|
### 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.
|
|
|
|
Die Rücknahme auf einen Sicherungspunkt SHALL auch seitdem angelegte oder gelöschte Indizes und Tabellenstrukturen einschließlich ihrer Metadaten zurücknehmen. CLOSE einer Bindung MUST die spätere Rücknahme ihrer Änderungen weiterhin erlauben.
|
|
|
|
#### 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
|
|
|
|
#### Scenario: Indexanlage nach Sicherungspunkt
|
|
- **WHEN** nach SAVEPOINT ein Index angelegt und auf den Sicherungspunkt zurückgesetzt wird
|
|
- **THEN** ist der neue Index nicht vorhanden; SETINDEX mit seinem Namen löst Fehler 83 aus
|
|
|
|
#### Scenario: Geschlossene Tabelle rücknehmen
|
|
- **WHEN** eine geänderte Tabellenbindung vor ROLLBACK auf einen Sicherungspunkt geschlossen wird
|
|
- **THEN** gelingt die Rücknahme und der bei Wiederöffnung sichtbare Satz-/Indexbestand entspricht dem Sicherungspunkt
|
|
|
|
### 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.
|
|
|
|
DELETE SHALL den benötigten Satz-/Undo-Puffer vor der Änderung prüfen. Bei Fehler 89 MUST Satzbestand, Indexbestand und Cursor unverändert bleiben.
|
|
|
|
#### Scenario: Pufferfehler wird benannt
|
|
- **WHEN** eine Operation den verfügbaren ISAM-Puffer überschreitet
|
|
- **THEN** tritt Laufzeitfehler 89 auf
|
|
|
|
#### Scenario: DELETE bei ausgeschöpftem Puffer
|
|
- **WHEN** DELETE auf einem gültigen aktuellen Satz bei unzureichendem ISAM-Puffer ausgeführt wird
|
|
- **THEN** meldet es Fehler 89 ohne den Satz zu löschen, den Cursor zu verschieben oder Undo hinzuzufügen; eine spätere Rücknahme bleibt möglich
|
|
|
|
### 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
|