diff options
author | xengineering <me@xengineering.eu> | 2024-10-13 20:34:29 +0200 |
---|---|---|
committer | xengineering <me@xengineering.eu> | 2024-10-15 20:25:38 +0200 |
commit | 1904f50084660ce587a67d49939fd0734fd8a582 (patch) | |
tree | eda737f7fa845866fd4401ef443df0cdda92539f | |
parent | 5ca945112b5cd7c92eb3b9da81597fcba5256b56 (diff) | |
download | ceres-1904f50084660ce587a67d49939fd0734fd8a582.tar ceres-1904f50084660ce587a67d49939fd0734fd8a582.tar.zst ceres-1904f50084660ce587a67d49939fd0734fd8a582.zip |
model: Handle schema versions internally by integers
This references the database schemas known from the past with integer
values internally. 0 is used for an empty database and 1 for what was
used in ceres version 0.4.0.
The old approach to write the `git describe` output into the database
worked well for released versions but was problematic during
development. A numerical and separate database schema versioning is
easier to handle.
-rw-r--r-- | model/database.go | 127 |
1 files changed, 58 insertions, 69 deletions
diff --git a/model/database.go b/model/database.go index d3fc115..7490f7b 100644 --- a/model/database.go +++ b/model/database.go @@ -2,6 +2,7 @@ package model import ( "database/sql" + "fmt" "log" _ "github.com/mattn/go-sqlite3" @@ -46,35 +47,30 @@ func (db *DB) Transaction(f func(*sql.Tx) error) error { return tx.Commit() } -func (db *DB) IsEmpty() bool { +func (db *DB) IsEmpty(tx *sql.Tx) (bool, error) { var number int - _ = db.Transaction(func(tx *sql.Tx) error { - cmd := `SELECT COUNT(*) FROM sqlite_master WHERE type='table'` - rows, err := tx.Query(cmd) - if err != nil { - log.Fatal(err) - } - defer rows.Close() - - if !rows.Next() { - log.Fatalf("No rows on request of database table number") - } + cmd := `SELECT COUNT(*) FROM sqlite_master WHERE type='table'` + rows, err := tx.Query(cmd) + if err != nil { + return false, fmt.Errorf("Select call failed: %w", err) + } + defer rows.Close() - err = rows.Scan(&number) - if err != nil { - log.Fatalf("Failed to scan number of database tables to integer") - } + if !rows.Next() { + return false, fmt.Errorf("Result set is empty") + } - return nil - }) + err = rows.Scan(&number) + if err != nil { + return false, fmt.Errorf("Failed to scan numerical value: %w", err) + } - return number == 0 + return number == 0, nil } -func (db *DB) setupMinimal(execVersion string) error { - return db.Transaction(func(tx *sql.Tx) error { - cmd := ` +func (db *DB) setupMinimal(tx *sql.Tx, execVersion string) error { + cmd := ` CREATE TABLE metadata ( key TEXT PRIMARY KEY, value TEXT @@ -84,66 +80,59 @@ INSERT INTO metadata VALUES ('version', ?); ` - _, err := tx.Exec(cmd, execVersion) - return err - }) + _, err := tx.Exec(cmd, execVersion) + return err } -func (db *DB) Version() string { - var version string - - _ = db.Transaction(func(tx *sql.Tx) error { - rows, err := tx.Query(`SELECT value FROM metadata WHERE key='version';`) - if err != nil { - log.Fatal(err) - } - defer rows.Close() +func (db *DB) SchemaVersion(tx *sql.Tx) (int, error) { + empty, err := db.IsEmpty(tx) + if err != nil { + return 0, fmt.Errorf("Failed to check if DB is empty: %w", err) + } - if !rows.Next() { - log.Fatalf("No rows on request of database version") - } + if empty { + return 0, nil + } - err = rows.Scan(&version) - if err != nil { - log.Fatalf("Failed to scan database version to string") - } + rows, err := tx.Query(`SELECT value FROM metadata WHERE key='version';`) + if err != nil { + return 0, fmt.Errorf("Select call failed: %w", err) + } + defer rows.Close() - return nil - }) + if rows.Next() { + return 1, nil // version field was only present in one schema version + } - return version + return 0, fmt.Errorf("Unknown schema version") } -func (db *DB) Migrate(execVersion string) { - err := db.Transaction(func(tx *sql.Tx) error { - if db.IsEmpty() { - log.Println("Starting with empty database") - err := db.setupMinimal(execVersion) +func (db *DB) Migrate(execVersion string) error { + return db.Transaction(func(tx *sql.Tx) error { + for { + schema, err := db.SchemaVersion(tx) if err != nil { - log.Fatalf("Failed to setup minimal database schema: %v", err) + return fmt.Errorf("Failed to get DB schema version: %w", err) } - - log.Println("Executing initial migration") - err = migrations.Migration001(tx) - if err != nil { - return err + switch schema { + case 0: + log.Println("Starting with empty database") + err := db.setupMinimal(tx, execVersion) + if err != nil { + return fmt.Errorf("Failed to setup minimal database schema: %w", err) + } + log.Println("Executing initial migration") + err = migrations.Migration001(tx) + if err != nil { + return err + } + case 1: + return nil + default: + return fmt.Errorf("Cannot migrate database to a matching schema version") } } - - dbVersion := db.Version() - if dbVersion != execVersion { - log.Fatalf( - "Database version '%s' does not match executable version '%s'", - dbVersion, - execVersion, - ) - } - - return nil }) - if err != nil { - log.Fatalf("Fatal: Database migration failed") - } } func (db *DB) CreateExamples() { |