Ein Softwareprojekt mit ganzheitlichem Anspruch.

Hier baue ich Software von den Grundlagen an. Also genau die Teile, die man sonst fertig einkauft und nie wieder anschaut.

Das erste Stück, das fertig genug zum Herzeigen ist, ist eine Datenbank. Sie steht weiter unten, mit Code zum Ausprobieren.

Was ganzheitlich hier heißt

Ich fange unter dem Framework an. Wie legt ein Programm seine Daten ab? Was ist davon noch da, wenn mitten im Schreiben der Strom ausfällt?

Und jede Aussage über so ein Stück soll sich prüfen lassen, mit einem Test, einem Befehl oder einer Zahl. Wenn etwas noch nicht geht, steht das genauso da wie das, was schon geht.

Das erste Stück

datumujo

Eine eingebettete Datenbank für Go

  • v0.1.0
  • EUPL-1.2
  • nur Go-Standardbibliothek

Kein Server, nichts zu installieren. Du holst datumujo wie jede andere Go-Bibliothek, und deine Daten liegen in einer Datei neben deinem Programm. Der Name ist Esperanto und heißt ungefähr Datenbehälter.

Das kann v0.1.0:

  • Transaktionen, die einen Absturz oder Stromausfall überstehen. Ist ein Commit zurück, liegt er auf der Platte.
  • Ein Schreiber zur Zeit, und Leser warten nie auf ihn.
  • Tabellen mit Primärschlüssel, Indizes (auch eindeutige), Scans in beide Richtungen und Zähler.
  • Eine Prüfsumme auf jeder Seite der Datei.
  • Sicherung, während weiter geschrieben wird.

So sieht das aus

Tabelle anlegen, eine Zeile schreiben, alles wieder lesen. Das Programm läuft so gegen v0.1.0:

package main

import (
	"fmt"

	"github.com/dyadik-eu/datumujo"
)

func main() {
	db, err := datumujo.Open("notizen.db", datumujo.Options{})
	if err != nil {
		panic(err)
	}
	defer db.Close()

	err = db.Update(func(tx *datumujo.Tx) error {
		err := tx.CreateTable(datumujo.Def{
			Name: "notiz",
			Columns: []datumujo.Column{
				{Name: "id", Type: datumujo.Int64},
				{Name: "text", Type: datumujo.String},
			},
			Key: []string{"id"},
		})
		if err != nil {
			return err
		}
		return tx.Insert("notiz", datumujo.Row{int64(1), "Hallo, Leibniz"})
	})
	if err != nil {
		panic(err)
	}

	err = db.Read(func(v *datumujo.View) error {
		rows, err := v.Scan("notiz", datumujo.ScanOptions{})
		if err != nil {
			return err
		}
		for rows.Next() {
			fmt.Println(rows.Row())
		}
		return rows.Err()
	})
	if err != nil {
		panic(err)
	}
}

Ausgabe:

[1 Hallo, Leibniz]

Und wenn ein Byte kippt?

Dann bekommst du einen Fehler mit der Seitennummer. Falsche Daten bekommst du nicht. Probiert an einer kleinen Datenbank mit einer Tabelle: eine Kopie, darin ein einziges Byte auf Seite 2 überschrieben.

$ datumujo check notizen.db
file: 12288 bytes, log: 28 bytes
pages: 3 of 4096 bytes, 0 free, 1 in the catalog
schema version: 1
table notiz: 1 rows, 1 pages
intact
$ cp notizen.db kaputt.db
$ printf '\xff' | dd of=kaputt.db bs=1 seek=8292 conv=notrunc status=none
$ datumujo check kaputt.db
fault: page 2: checksum does not match
file: 12288 bytes, log: 0 bytes
pages: 3 of 4096 bytes, 0 free, 0 in the catalog
schema version: 0
1 faults

datumujo check endet dann mit Exit-Code 1. Bei einer heilen Datei ist es 0, und 2, wenn es gar nicht prüfen konnte.

Was noch fehlt

SQL. Daran arbeite ich gerade, als zweite Stufe. Bis v1 können sich API und Dateiformat noch ändern, das steht auch so im Changelog.

Code, Doku und Fahrplan auf GitHub

Der Name

0  1  10  11  100  101  110  111  1000  1001

Dyadik ist Leibniz' eigenes deutsches Wort für das Rechnen mit Null und Eins. Er hat es in Hannover ausgearbeitet und aufgeschrieben.

Für ein Projekt, das bei den Grundlagen anfängt, passt kaum ein Name besser.

Gottfried Wilhelm Leibniz, De progressione dyadica, Hannover, 15. März 1679.

Kontakt

Fragen, Ideen oder einen Fehler gefunden? Schreib mir an kontakt@dyadik.eu oder mach ein Issue auf GitHub auf.