@@ 13,6 13,10 @@ import (
//go:embed schema.sql
var schema string
+var migrations = []string{
+ "", // migration #0 is reserved for schema initialization
+}
+
var errNoDBRows = sql.ErrNoRows
type DB struct {
@@ 35,15 39,13 @@ func openDB(filename string) (*DB, error) {
}
func (db *DB) init(ctx context.Context) error {
- var n int
- if err := db.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM sqlite_schema").Scan(&n); err != nil {
+ version, err := db.upgrade(ctx)
+ if err != nil {
return err
- } else if n != 0 {
- return nil
}
- if _, err := db.db.ExecContext(ctx, schema); err != nil {
- return err
+ if version > 0 {
+ return nil
}
// TODO: drop this
@@ 54,6 56,44 @@ func (db *DB) init(ctx context.Context) error {
return db.StoreUser(ctx, &defaultUser)
}
+func (db *DB) upgrade(ctx context.Context) (version int, err error) {
+ if err := db.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
+ return 0, fmt.Errorf("failed to query schema version: %v", err)
+ }
+
+ if version == len(migrations) {
+ return version, nil
+ } else if version > len(migrations) {
+ return version, fmt.Errorf("sinwon (version %d) older than schema (version %d)", len(migrations), version)
+ }
+
+ tx, err := db.db.Begin()
+ if err != nil {
+ return version, err
+ }
+ defer tx.Rollback()
+
+ if version == 0 {
+ if _, err := tx.Exec(schema); err != nil {
+ return version, 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 version, 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 version, fmt.Errorf("failed to bump schema version: %v", err)
+ }
+
+ return version, tx.Commit()
+}
+
func (db *DB) Close() error {
return db.db.Close()
}
@@ 1,5 1,3 @@
-PRAGMA user_version = 1;
-
CREATE TABLE User (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,