From 21942591240f66936b1e98d63749e6800125d6f0 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 10 Apr 2020 22:45:02 +0200 Subject: [PATCH] Set up DB migration infrastructure The database is now initialized automatically on first run. The schema version is stored in SQLite's user_version special field. Migrations are stored in an array and applied based on the schema version. --- README.md | 1 - db.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- schema.sql | 29 ------------------ 3 files changed, 86 insertions(+), 32 deletions(-) delete mode 100644 schema.sql diff --git a/README.md b/README.md index 0a4a8cd..1d19739 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ A user-friendly IRC bouncer. ## Usage - sqlite3 soju.db go run ./cmd/soju diff --git a/db.go b/db.go index 782358b..9bb08ab 100644 --- a/db.go +++ b/db.go @@ -48,17 +48,59 @@ type Channel struct { var ErrNoSuchChannel = fmt.Errorf("soju: no such channel") +const schema = ` +CREATE TABLE User ( + username VARCHAR(255) PRIMARY KEY, + password VARCHAR(255) NOT NULL +); + +CREATE TABLE Network ( + id INTEGER PRIMARY KEY, + name VARCHAR(255), + user VARCHAR(255) NOT NULL, + addr VARCHAR(255) NOT NULL, + nick VARCHAR(255) NOT NULL, + username VARCHAR(255), + realname VARCHAR(255), + pass VARCHAR(255), + sasl_mechanism VARCHAR(255), + sasl_plain_username VARCHAR(255), + sasl_plain_password VARCHAR(255), + FOREIGN KEY(user) REFERENCES User(username), + UNIQUE(user, addr, nick) +); + +CREATE TABLE Channel ( + id INTEGER PRIMARY KEY, + network INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + key VARCHAR(255), + FOREIGN KEY(network) REFERENCES Network(id), + UNIQUE(network, name) +); +` + +var migrations = []string{ + "", // migration #0 is reserved for schema initialization +} + type DB struct { lock sync.RWMutex db *sql.DB } func OpenSQLDB(driver, source string) (*DB, error) { - db, err := sql.Open(driver, source) + sqlDB, err := sql.Open(driver, source) if err != nil { return nil, err } - return &DB{db: db}, nil + + db := &DB{db: sqlDB} + if err := db.upgrade(); err != nil { + return nil, err + } + + return db, nil } func (db *DB) Close() error { @@ -67,6 +109,48 @@ func (db *DB) Close() error { return db.Close() } +func (db *DB) upgrade() error { + db.lock.Lock() + defer db.lock.Unlock() + + var version int + if err := db.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil { + return fmt.Errorf("failed to query schema version: %v", err) + } + + if version == len(migrations) { + return nil + } else if version > len(migrations) { + return fmt.Errorf("soju (version %d) older than schema (version %d)", len(migrations), version) + } + + tx, err := db.db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if version == 0 { + if _, err := tx.Exec(schema); err != nil { + return fmt.Errorf("failed to initialize schema: %v", err) + } + } else { + for i := version; i < len(migrations); i++ { + if _, err := tx.Exec(migrations[i]); err != nil { + return fmt.Errorf("failed to execute migration #%v: %v", i, err) + } + } + } + + // For some reason prepared statements don't work here + _, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(migrations))) + if err != nil { + return fmt.Errorf("failed to bump schema version: %v", err) + } + + return tx.Commit() +} + func fromStringPtr(ptr *string) string { if ptr == nil { return "" diff --git a/schema.sql b/schema.sql deleted file mode 100644 index a778b9c..0000000 --- a/schema.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE TABLE User ( - username VARCHAR(255) PRIMARY KEY, - password VARCHAR(255) NOT NULL -); - -CREATE TABLE Network ( - id INTEGER PRIMARY KEY, - name VARCHAR(255), - user VARCHAR(255) NOT NULL, - addr VARCHAR(255) NOT NULL, - nick VARCHAR(255) NOT NULL, - username VARCHAR(255), - realname VARCHAR(255), - pass VARCHAR(255), - sasl_mechanism VARCHAR(255), - sasl_plain_username VARCHAR(255), - sasl_plain_password VARCHAR(255), - FOREIGN KEY(user) REFERENCES User(username), - UNIQUE(user, addr, nick) -); - -CREATE TABLE Channel ( - id INTEGER PRIMARY KEY, - network INTEGER NOT NULL, - name VARCHAR(255) NOT NULL, - key VARCHAR(255), - FOREIGN KEY(network) REFERENCES Network(id), - UNIQUE(network, name) -);