package store import ( "context" "database/sql" "fmt" ) // migrations are ordered, forward-only schema steps applied after schema.sql. // Index i (1-based) is the user_version the step at migrations[i-1] brings the // DB TO; there is no step 0 — schema.sql is the idempotent baseline (version 0). // An empty slice is a clean no-op that leaves user_version at 0. // // To add migration #1 (e.g. the sqlcipher rekey), append its SQL: // // var migrations = []string{ // `ALTER TABLE ...;`, // #1 // } var migrations = []string{} // migrate applies every migration with a number greater than the DB's current // user_version, each in its own transaction that also bumps user_version. Fails // closed: the first erroring step aborts and leaves prior steps committed. func migrate(ctx context.Context, db *sql.DB) error { var current int if err := db.QueryRowContext(ctx, "PRAGMA user_version").Scan(¤t); err != nil { return fmt.Errorf("read user_version: %w", err) } for i := current; i < len(migrations); i++ { version := i + 1 // 1-based: migrations[i] brings DB to `version` tx, err := db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("migration %d begin: %w", version, err) } if _, err := tx.ExecContext(ctx, migrations[i]); err != nil { _ = tx.Rollback() return fmt.Errorf("migration %d: %w", version, err) } // PRAGMA user_version can't be parameterized; version is our own int. if _, err := tx.ExecContext(ctx, fmt.Sprintf("PRAGMA user_version = %d", version)); err != nil { _ = tx.Rollback() return fmt.Errorf("migration %d bump: %w", version, err) } if err := tx.Commit(); err != nil { return fmt.Errorf("migration %d commit: %w", version, err) } } return nil }