Files
rdb/README.md
T
wkh 1c489f6994 Schnellzugriff per Seek, FNV-1a-Hash, Dezimalrechnung für Summen und /act
search, seek, delete und update.rdb lesen bei -mh, -mi, -mr nur Indexeintrag
und Satz. Hashindizes (.h) müssen neu gebaut werden. total, mean, subtotal
und die /act-Befehle rechnen mit BigDecimal, gerundet wird halb auf.
2026-10-30 09:54:05 +01:00

16 KiB
Raw Blame History

rdb

Ein Nachbau von /rdb, dem relationalen Datenbanksystem der Mark Williams Company für COHERENT (um 1991), in Ruby. Das Original-Handbuch und die Man-Pages stammen aus den freigegebenen MWC-Quellen (nesssoftware.com/home/mwc/source.php). Der Programmcode von /rdb selbst war darin nicht enthalten. Alle Befehle hier sind nach dem Handbuch neu geschrieben.

Einrichten

mise trust            # einmal nach dem Clone: mise.toml freigeben
mise install          # Ruby 3.4
mise run install      # legt die Symlinks row, column, total ... in bin/ an
mise run test         # 131 Tests und die Beispiele aus dem Handbuch

Ohne mise trust brechen in einem frisch geklonten Ordner alle mise-Befehle ab.

Wichtig: Nach jedem Clone zuerst die Symlinks anlegen. Im Repository liegt nur bin/rdb. Die Befehle row, column, total ... sind Symlinks darauf und werden nicht mit eingecheckt (.gitignore). Ohne mise run install (oder ohne mise: bin/rdb install) gibt es sie nicht, und Aufrufe wie row ... enden mit command not found. Auch testall und die Tests brauchen sie. Nach neuen Befehlen den Schritt wiederholen.

Danach steht bin/ im PATH, solange man im Projektordner ist (mise.toml). Jeder Befehl ist ein Symlink auf bin/rdb, das am Aufrufnamen erkennt, was zu tun ist. Es geht auch ohne Symlinks: rdb row 'Cost > 5' < demo/inventory.

Vier Namen verdecken im Projektordner Systembefehle: column, rename, display und whatis.

Das Prinzip

Eine Tabelle ist eine Textdatei: Kopfzeile mit Spaltennamen, Strichzeile, dann eine Zeile pro Datensatz, Spalten durch Tabulator getrennt. Jeder Befehl liest eine Tabelle von stdin und schreibt eine Tabelle nach stdout. Befehle lassen sich mit Pipes kombinieren.

cd demo
row 'Cost > 5' < inventory | column Description Cost | sorttable -nr Cost
compute 'Value = Cost * Amount' < inventory | total Value
jointable inventory owners

Dasselbe gilt für Listen (ein Datensatz als Spalte<Tab>Wert-Zeilen, die Datei beginnt mit einer Leerzeile). tabletolist und listtotable wandeln um, die meisten Befehle lesen beides.

column -d Spalte ... löscht Spalten (alle anderen bleiben), column Spalte ... wählt sie aus.

row, compute und validate übergeben ihren Ausdruck an awk, nachdem sie Spaltennamen durch $N ersetzt haben. Es gilt also awk-Syntax.

Spaltennamen unterscheiden Groß- und Kleinschreibung. Mit RDB_ICASE=1 wird sie ignoriert (Details unter „Unterschiede zum Original“):

RDB_ICASE=1 compute 'wert = cost * amount' < inventory

Hilfe

rdb                  alle Befehle
rdb man search       die Original-Man-Page als Text (auch Themen: backup, dbase, dashline ...)
rdb --version        Versionsnummer (Änderungen siehe CHANGELOG.md)
rdb man -g search    dieselbe Seite mit groff formatiert (Beispiele sauber eingerückt)
whatis column        Beschreibung und Syntax
whatwill total       Befehle, die zu einem Stichwort passen
commands             Tabelle aller Befehle

Beispiele zu allen Befehlen

demo/testall.cmds enthält zu jedem Befehl ein Beispiel mit kurzer Erklärung. testall führt es in einer Kopie von demo/ aus und zeigt jede Zeile samt Ausgabe:

testall | less                      alle Beispiele ansehen
testall > testall.old               Lauf als Vergleich festhalten
testall | diff testall.old -        später prüfen, was sich geändert hat (mise run testall)

Eine Zeile mit ## erscheint als Überschrift in der Ausgabe, eine mit # ist ein stiller Kommentar. Neue Befehle brauchen hier ein Beispiel, sonst schlägt der Test test_testall_mentions_every_command fehl.

Beispiele aus dem gedruckten Handbuch

doc/handbuch.txt ist der Text des gedruckten Handbuchs „COHERENT /rdb Relational Database Manager“ (Mark Williams, 346 Seiten), per OCR aus dem Scan coherent.pdf. Er ist nicht korrigiert: Tabulatoren fehlen, Tabellen können verrutscht sein. Jede Seite ist mit PDF- und Buchseite markiert (Buchseite = PDF-Seite − 10). Kapitel 1–16 sind ein Lehrbuch, Kapitel 17 enthält dieselben Man-Pages wie man/.

demo/handbuch/ enthält die Beispiele aus dem Lehrbuch mit ihren Tabellen, ein Unterordner je Kapitel. beispiele.txt hält zu jedem Beispiel den Befehl und die Ausgabe, wie sie im Buch steht; test/handbuch_test.rb führt alles aus und vergleicht (ohne Leerraum, das Buch richtet mit Leerzeichen aus). In der Datei bedeutet:

@ kap03       die folgenden Befehle laufen in diesem Unterordner
## S. 26 ...  Überschrift mit Buchseite
$ befehl      darunter die Ausgabe laut Buch
# ...         Kommentar, z. B. ein Druckfehler des Buchs (die Ausgabe ist korrigiert)
! ...         hier weichen wir bewusst ab; nicht verglichen, der Test meldet aber,
              wenn die Ausgabe doch wieder zum Buch passt

Vorlagen: form und entry

  • demo/form ist eine Vorlage für report und letter: sie setzt Spaltenwerte in einen Text ein. Sie hat keine Beschriftungen und nennt Name zweimal.
  • demo/entry ist eine Eingabemaske für screen: jedes Feld kommt einmal vor und hat eine Beschriftung. screen < entry > maske.sh erzeugt das Skript. Starten: CLEAR=$(clear.rdb) sh maske.sh > tabelle 2>&1. Die Tabelle geht auf stderr (wie im Original), daher das 2>&1. Mit form würde die Maske leer und doppelt aussehen.

Eingabemasken: mask

mask ist die Variante von screen mit eigener Oberfläche (nur Ruby, kein ncurses). Es zeichnet die Vorlage (von der Standardeingabe, wie screen; ein Dateiname geht auch) selbst und schreibt die Eingabe als Tabelle auf die Standardausgabe, also pipefähig:

mask -b < demo/maskform                  Rahmen (-b), Felder, Schaltflächen
mask < demo/maskform > neu               Tabelle in eine Datei
mask < demo/maskform | append -h kunden  oder direkt in eine Tabelle
mask -s < demo/maskform                  nur das Bild und die Feldpositionen, ohne Terminal

In der Vorlage: <Spalte> oder <Spalte:20> ist ein Feld (mit Breite), [ Speichern ] eine Schaltfläche, [ Abbrechen ] (oder Cancel, Abort, Quit) bricht ab, <!date!> fügt Befehlsausgabe ein. Kommt eine Spalte mehrfach vor, ist es ein Feld. Tab/Pfeile wechseln, Enter bestätigt, Esc bricht ab (Status 1, keine Ausgabe).

Zeilen auswählen: pick

pick zeigt eine Tabelle auf dem Terminal, man scrollt, filtert und wählt eine oder mehrere Zeilen. Die Tabelle kommt von der Standardeingabe, die gewählten Zeilen gehen als Tabelle auf die Standardausgabe, also mitten in der Pipe:

pick < inventory                                  eine Zeile wählen
pick -m < inventory | total Amount Value          mehrere (Leertaste markiert, a alle)
column Item Description < inventory | pick -m | jointable - owners
pick -m -k 'down space down enter' < inventory    Tasten aus einem String (Skripte, Tests)

Pfeile, Bild auf/ab, Pos1/Ende scrollen; / filtert (Enter behält den Filter, Esc löscht ihn); Enter wählt; Esc bricht ab (Status 1, keine Ausgabe). Ohne Markierung gilt die Zeile unter dem Cursor.

Steuertabellen 2026 für tax

demo/ enthält vier Tarife für das Steuerjahr 2026 im Format von tax (income, tax, percent), jeweils für Alleinstehende (Grundtarif). Erzeugt mit ruby tools/gen_tax_tables.rb.

Datei Land Quelle Genauigkeit
taxDE2026 Deutschland, Einkommensteuer § 32a EStG (Grundfreibetrag 12 348 €) Kurvenstücke durch Geraden genähert, Abweichung unter 0,15 € (die Formel rundet ab, tax auf)
taxAT2026 Österreich, Einkommensteuer § 33 EStG (0 % bis 13 539 €, dann 20/30/40/48/50/55 %) exakt, vor Absetzbeträgen
taxCH2026 Schweiz, nur direkte Bundessteuer Art. 36 DBG, ESTV-Rundschreiben 2-215-D-2025 Stufen exakt, das Gesetz rechnet in 100-Franken-Schritten
taxLI2026 Liechtenstein, nur Landessteuer Art. 19 SteG (LR 640.0, Fassung 01.07.2026) exakt
taxCH_AI2026 Kanton Appenzell I.Rh., einfache Staatssteuer Art. 38 StG (ESTV-Kantonsblatt) exakt
taxCH_AR2026 Kanton Appenzell A.Rh., einfache Staatssteuer (Alleinstehendentarif) Art. 39 StG, Beträge ab 2024 exakt
taxCH_SG2026 Kanton St. Gallen, einfache Staatssteuer Art. 50 StG, Beträge ab 2026 exakt
taxCH_GR2026 Kanton Graubünden, einfache Kantonssteuer Art. 39 StG, indexierte Beträge ab 2025 exakt
tax 60000 < demo/taxDE2026         14233
tax 60000 < demo/taxAT2026         15447
tax 60000 < demo/taxCH2026           671
tax 60000 < demo/taxLI2026          1396   (mal 1 + Gemeindezuschlag, 150 % bis 250 %)

Der Betrag ist die Steuer auf das zu versteuernde Einkommen. Abzüge, Sozialversicherung, Absetzbeträge, Kirchensteuer und Soli sind nicht enthalten. Für die Schweiz kommen Kanton und Gemeinde dazu (sie sind meist höher als der Bund), für Liechtenstein der Gemeindezuschlag: gesamt = Landessteuer × (1 + Zuschlag/100). Ehepaar- und Alleinerziehendentarife fehlen. Das ist keine Steuerberatung.

Kantone (AI, AR, SG, GR): Die Dateien tragen den Präfix taxCH_ (Schweiz) vor dem Kantonskürzel; taxCH2026 ist die Bundessteuer. Die Tabellen geben die einfache Steuer wieder. Sie wird mit dem Steuerfuss multipliziert, und Gemeinde und Kirche kommen dazu. Der Kanton setzt den Steuerfuss pro Jahr fest:

Kanton Steuerfuss Kanton 2026 Quelle
AI 96 % Kantonsblatt AI (Grossratsbeschluss 1.12.2025)
SG 105 % Budgetbeschluss des Kantonsrats (Websuche, nicht im Kantonsblatt)
GR 92 % Grosser Rat, Budget 2026 (Websuche)
AR nicht ermittelt AR rechnet in Steuereinheiten, die Zahl für 2026 habe ich nicht gefunden

Beispiel AI bei 60 000: tax 60000 < demo/taxCH_AI2026 = 3770, mal 0,96 = 3619 (Kantonssteuer, ohne Bezirk, Gemeinde und Kirche). Oberhalb einer Grenze gilt bei allen vier Kantonen ein fester Satz auf das ganze Einkommen (AI ab 200 000 mit 8 %, AR ab 260 800 mit 2,6 %, SG ab 264 500 mit 8,5 %, GR ab 780 440 mit 11 %); in der Tabelle ist das die letzte Zeile.

Befehle

Gruppe Befehle
Auswahl row select column project sorttable record number Number
Spalten compute addcol rename
Mengen, Verbund jointable union intersect difference paste.rdb splittable
Auswertung total mean maximum minimum subtotal width widest precision datatype schema dbdict
Schnellzugriff index search seek append delete replace enter lock unlock vilock update.rdb ve vindex
Prüfen check.rdb domain validate fd searchtree tableorlist
Format justify trim compresss pad rmblank blank headon headoff insertdash display see
Text cap uppercase lowercase substitute trimblank padstring length filesize ascii chr word prompt not path
Umwandeln tabletolist listtotable listtosh tabletofix fixtotable tabletostruct tabletotbl tabletofact tabletorule tabletom4 tabletosed translate uniondict
Datum, Namen julian gregorian computedate todaysdate soundex like hashkey
Berichte report reportwriter screen mask pick label letter
Terminal, Hilfe termput tset clear.rdb cursor menu rdb commands helpme whatis whatwill howmany man
Betrieb testall testsearch timesearch rmcore cpdir.rdb
Buchhaltung (/act) act chartdup foot adjust balance close consolidate getjournal fillform calculate tax cashflow calcpay w2 bom explode sale invoice cstate update.inv

Schnellzugriff

Wie im Original sucht search über Byte-Offsets. Indizes bauen:

Option Methode Datei
-ms sequentiell (Vorgabe) keine
-mb binär: index sortiert die Tabelle selbst keine
-mr Datensatznummer tabelle.r
-mi invertiert (Schlüssel und Offset, sortiert) tabelle.i
-mh Hash, doppelt so viele Plätze wie Zeilen tabelle.h
index -mi inventory Description
echo scales | search -mi inventory Description
echo cl | search -mi -x inventory Description     # Teiltreffer, Groß/klein egal
echo 5 | seek -mi inventory Item                   # von bis xvon xbis

delete überschreibt einen Satz mit Leerzeichen, ohne die Datei zu verschieben. replace schreibt an dieselbe Stelle zurück und hängt den Satz hinten an, wenn er nicht mehr passt (außer mit -a). Beide halten die Indizes aktuell. rmblank räumt später auf.

Unterschiede zum Original

  • Es gibt kein COHERENT-Programmmaterial, daher sind die Ausgaben nur anhand der Man-Pages und dem gedruckten Handbuch geprüft, nicht gegen das echte /rdb. Die Beispiele aus den Man-Pages sind als Tests enthalten (test/rdb_test.rb), die aus dem Lehrbuch in demo/handbuch/ (test/handbuch_test.rb).
  • Die Indexdateien sind dem Original nachgebaut, aber nicht gleich (Handbuch S. 103–108): die Hashtabelle .h hat 2n statt 2n+1 Plätze (Hashfunktion FNV-1a), und in .i steht der Schlüssel vor dem Offset statt dahinter. Alte /rdb-Indizes und .h-Dateien früherer Versionen dieses Nachbaus muss man neu bauen.
  • Summen, Mittelwerte und /act-Beträge sind Dezimalzahlen (BigDecimal), gerundet wird halb auf.
  • Dateien, die kein UTF-8 sind, liest rdb als Latin-1 und schreibt sie als UTF-8. Fehler erscheinen als eine Zeile; mit RDB_DEBUG=1 kommt der Ruby-Backtrace. Werte mit Tab oder Zeilenumbruch lehnen append, enter, replace und ve ab.
  • lock/unlock legen ihre Sperrdateien in /tmp/rdb-locks-<UID>/ ab (Modus 0700, flock), nicht als /tmp/L<Tabelle> wie in den Man-Pages. Eine Sperre eines abgestürzten Prozesses wird nicht erkannt; unlock hebt sie auf.
  • vilock sperrt atomar über einen Hardlink; EDITOR darf Argumente haben (nvim -f). Nach kill -9 bleibt LOCK<Tabelle> liegen und muss von Hand zurückbenannt werden.
  • report: Spaltenwerte in <!befehl <Spalte>!> werden für die Shell maskiert und kommen als ein Wort an; nicht in Anführungszeichen setzen. screen verlangt Spaltennamen, die gültige Shell-Variablennamen sind (Buchstaben, Ziffern, _).
  • sorttable lässt Zeilen mit gleichem Schlüssel in ihrer Reihenfolge; das Original ordnet sie nach der ganzen Zeile, wie sort (Handbuch S. 94).
  • subtotal schreibt bei einer Summe von null ein leeres Feld; das Handbuch zeigt 0 und die Kontonummern als 101.0 (S. 30).
  • Spaltennamen unterscheiden wie im Original Groß- und Kleinschreibung. Mit RDB_ICASE=1 (z. B. in mise.toml unter [env]) wird sie ignoriert: ein exakter Treffer gewinnt, mehrere ähnliche sind ein Fehler. In row/compute/validate bleiben awk-Wörter und Funktionsaufrufe unberührt (eine Spalte Length verdeckt also nicht length()). Die Man-Pages von row, compute, column, sorttable, jointable, rename, validate und rdb haben dazu den Abschnitt „Case of Column Names“; er ist die einzige Ergänzung gegenüber den Original-Seiten.
  • select ist in bash, zsh und ksh ein reserviertes Wort. select 'Cost > 5' < inventory endet dort mit syntax error near unexpected token '<', weil die Shell das Schlüsselwort vor dem PATH auswertet. Es gibt dasselbe unter dem Namen row. Außerdem gehen \select ..., command select ... und rdb select ....
  • jointable braucht keine vorsortierten Tabellen.
  • ve ist ein zeilenorientierter Editor, kein Bildschirmeditor.
  • soundex nutzt den Standard-Algorithmus. Die Beispielwerte im Handbuch weichen davon ab.
  • Die /act-Befehle arbeiten auf Tabellen im aktuellen Ordner. Welche das sind und welche Spalten sie brauchen, steht jeweils in der Man-Page. Das Paket ist wie im Original nur ein Beispiel für Datenbankprogrammierung. Die Befehle, zu denen das Handbuch nichts sagt (post, purchase, ship ...), fehlen.

Aufbau

bin/rdb            das eine Programm (die Symlinks darauf legt `rdb install` an, nicht im Repo)
lib/rdb.rb         Registry der Befehle
lib/rdb/table.rb   Tabellen und Listen lesen und schreiben
lib/rdb/fast.rb    Schnellzugriff über Byte-Offsets
lib/rdb/cmd/*.rb   die Befehle, nach Gruppen
man/               Man-Pages (troff-Quellen aus den MWC-Archiven)
demo/              Beispieltabellen; testall.cmds = je ein Beispiel pro Befehl, testall.old = letzter Lauf
demo/handbuch/     die Beispiele aus dem gedruckten Handbuch, je Kapitel ein Ordner
doc/handbuch.txt   Text des gedruckten Handbuchs (OCR)
test/              Tests

Die Man-Pages und die Beispieltabellen stammen aus den MWC-Quellen. Lizenz der Originale: BSD, Copyright 1977-1995 Robert Swartz.