On the last post I talked about using SQLite's user_version pragma to keep track of your schema version without the need of a separate table. However, how can we do schema migrations and use it in practice?
One of the most elegant schema migration approaches I have found was that of Miniflux.
First you need all your migrations on a single list of transactions:
const schemaVersion = len(migrations) // Order is important. Add new migrations at the end of the list. var migrations = [...]func(tx *sql.Tx) error{ func(tx *sql.Tx) error { sql := ` CREATE TABLE accounts ( username TEXT NOT NULL, password TEXT NOT NULL, PRIMARY KEY (username) ); ` _, err := tx.Exec(sql) return err }, }
Then you have to iterate and execute those statements:
func migrate(db *sql.DB) error { var currentVersion int db.QueryRow(`PRAGMA user_version;`).Scan(¤tVersion) slog.Info("Running database migrations", slog.Int("current_version", currentVersion), slog.Int("latest_version", schemaVersion), ) for version := currentVersion; version < schemaVersion; version++ { newVersion := version + 1 tx, err := db.Begin() if err != nil { return fmt.Errorf("[Migration v%d] %v", newVersion, err) } if err := migrations[version](tx); err != nil { tx.Rollback() return fmt.Errorf("[Migration v%d] %v", newVersion, err) } // PRAGMA is not an expression, so we can't pass a parameter to it cmd := fmt.Sprintf(`PRAGMA user_version = %d;`, newVersion) if _, err := tx.Exec(cmd); err != nil { tx.Rollback() return fmt.Errorf("[Migration v%d] %v", newVersion, err) } if err := tx.Commit(); err != nil { return fmt.Errorf("[Migration v%d] %v", newVersion, err) } } return nil }

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.