@@ -105,33 +105,10 @@ func verifyMigrations(tx *sqlx.Tx, migrations []NamedMigration) (firstUnappliedM
105105 return firstUnappliedMigrationIndex , nil
106106}
107107
108- func doMigrations (tx * sqlx.Tx , migrations []NamedMigration , startIndex int ) error {
109- for index := startIndex ; index < len (migrations ); index ++ {
110- migration := migrations [index ]
111- log .Printf ("Performing migration %d (%q)" , index , migration .Name )
112- err := migrations [index ].Migration .DoMigration (tx )
113- if err != nil {
114- return fmt .Errorf ("Error performing migration %d (%q): %w" , index , migration .Name , err )
115- }
116- _ , err = tx .Exec (`INSERT INTO migration ("index", name) VALUES ($1, $2)` , index , migration .Name )
117- if err != nil {
118- return fmt .Errorf ("Error recording migration %d (%q): %w" , index , migration .Name , err )
119- }
120- }
121-
122- return nil
123- }
124-
125- // Rollback runs the Reverse migrations for all the input migrations with index >= rollBackThroughIndex.
126- // The input migrations must include all migrations, not just the ones to roll back.
127- func Rollback (db * sqlx.DB , migrations []NamedMigration , rollBackThroughIndex int ) error {
128- err := ensureMigrationsTableExists (db )
129- if err != nil {
130- return err
131- }
108+ func migrateOne (db * sqlx.DB , migrations []NamedMigration ) (bool , error ) {
132109 tx , err := db .Beginx ()
133110 if err != nil {
134- return fmt .Errorf ("Error starting migrations transaction: %w" , err )
111+ return false , fmt .Errorf ("Error starting migrations transaction: %w" , err )
135112 }
136113 committed := false
137114 defer func () {
@@ -145,57 +122,43 @@ func Rollback(db *sqlx.DB, migrations []NamedMigration, rollBackThroughIndex int
145122
146123 _ , err = tx .Exec ("LOCK TABLE migration" )
147124 if err != nil {
148- return fmt .Errorf ("Error locking migration table: %w" , err )
125+ return false , fmt .Errorf ("Error locking migration table: %w" , err )
149126 }
150127
151128 firstUnappliedIndex , err := verifyMigrations (tx , migrations )
152129 if err != nil {
153- return err
130+ return false , err
131+ }
132+ if firstUnappliedIndex >= len (migrations ) {
133+ return false , nil
154134 }
155135
156- if rollBackThroughIndex < 0 {
157- return fmt .Errorf ("Invalid target index %d" , rollBackThroughIndex )
136+ migration := migrations [firstUnappliedIndex ]
137+ log .Printf ("Performing migration %d (%q)" , firstUnappliedIndex , migration .Name )
138+ err = migrations [firstUnappliedIndex ].Migration .DoMigration (tx )
139+ if err != nil {
140+ return false , fmt .Errorf ("Error performing migration %d (%q): %w" , firstUnappliedIndex , migration .Name , err )
158141 }
159- if rollBackThroughIndex >= firstUnappliedIndex {
160- return fmt .Errorf ("Migration %d has not been applied yet" , rollBackThroughIndex )
142+ _ , err = tx .Exec (`INSERT INTO migration ("index", name) VALUES ($1, $2)` , firstUnappliedIndex , migration .Name )
143+ if err != nil {
144+ return false , fmt .Errorf ("Error recording migration %d (%q): %w" , firstUnappliedIndex , migration .Name , err )
161145 }
162-
163- for index := firstUnappliedIndex - 1 ; index >= rollBackThroughIndex ; index -- {
164- migration := migrations [index ]
165- if migration .Reverse == nil {
166- return fmt .Errorf ("No Reverse for migration %d (%q)" , index , migration .Name )
167- }
168- log .Printf ("Reversing migration %d (%q)" , index , migration .Name )
169- err := migrations [index ].Reverse .DoMigration (tx )
170- if err != nil {
171- return fmt .Errorf ("Error reversing migration %d (%q): %w" , index , migration .Name , err )
172- }
173- _ , err = tx .Exec (`DELETE FROM migration WHERE "index"=$1` , index )
174- if err != nil {
175- return fmt .Errorf ("Error deleting migration row %d (%q): %w" , index , migration .Name , err )
176- }
146+ if err != nil {
147+ return false , err
177148 }
178149
150+ committed = true
179151 err = tx .Commit ()
180152 if err != nil {
181- return fmt .Errorf ("Error committing migrations: %w" , err )
153+ return false , fmt .Errorf ("Error committing migrations: %w" , err )
182154 }
183- committed = true
184- return nil
155+ return true , nil
185156}
186157
187- // Migrate does the following:
188- // 1. Verifies that the `migration` table exists, and creates it if it does not.
189- // 2. Verifies that the existing migrations recorded in the database match (by name and order) the migrations given as the argument.
190- // 3. Performs any migrations that are not yet recorded in the database.
191- func Migrate (db * sqlx.DB , migrations []NamedMigration ) error {
192- err := ensureMigrationsTableExists (db )
193- if err != nil {
194- return err
195- }
158+ func rollbackOne (db * sqlx.DB , migrations []NamedMigration , rollBackThroughIndex int ) (rolledBackIndex int , err error ) {
196159 tx , err := db .Beginx ()
197160 if err != nil {
198- return fmt .Errorf ("Error starting migrations transaction: %w" , err )
161+ return - 1 , fmt .Errorf ("Error starting migrations transaction: %w" , err )
199162 }
200163 committed := false
201164 defer func () {
@@ -209,23 +172,78 @@ func Migrate(db *sqlx.DB, migrations []NamedMigration) error {
209172
210173 _ , err = tx .Exec ("LOCK TABLE migration" )
211174 if err != nil {
212- return fmt .Errorf ("Error locking migration table: %w" , err )
175+ return - 1 , fmt .Errorf ("Error locking migration table: %w" , err )
213176 }
214177
215178 firstUnappliedIndex , err := verifyMigrations (tx , migrations )
216179 if err != nil {
217- return err
180+ return - 1 , err
218181 }
219182
220- err = doMigrations (tx , migrations , firstUnappliedIndex )
183+ if rollBackThroughIndex < 0 {
184+ return - 1 , fmt .Errorf ("Invalid target index %d" , rollBackThroughIndex )
185+ }
186+ if rollBackThroughIndex >= firstUnappliedIndex {
187+ return - 1 , fmt .Errorf ("Migration %d has not been applied yet" , rollBackThroughIndex )
188+ }
189+
190+ index := firstUnappliedIndex - 1
191+ migration := migrations [index ]
192+ if migration .Reverse == nil {
193+ return - 1 , fmt .Errorf ("No Reverse for migration %d (%q)" , index , migration .Name )
194+ }
195+ log .Printf ("Reversing migration %d (%q)" , index , migration .Name )
196+ err = migrations [index ].Reverse .DoMigration (tx )
221197 if err != nil {
222- return err
198+ return - 1 , fmt .Errorf ("Error reversing migration %d (%q): %w" , index , migration .Name , err )
199+ }
200+ _ , err = tx .Exec (`DELETE FROM migration WHERE "index"=$1` , index )
201+ if err != nil {
202+ return - 1 , fmt .Errorf ("Error deleting migration row %d (%q): %w" , index , migration .Name , err )
223203 }
224204
205+ committed = true
225206 err = tx .Commit ()
226207 if err != nil {
227- return fmt .Errorf ("Error committing migrations: %w" , err )
208+ return - 1 , fmt .Errorf ("Error committing migrations: %w" , err )
209+ }
210+ return index , nil
211+ }
212+
213+ // Rollback runs the Reverse migrations for all the input migrations with index >= rollBackThroughIndex.
214+ // The input migrations must include all migrations, not just the ones to roll back.
215+ func Rollback (db * sqlx.DB , migrations []NamedMigration , rollBackThroughIndex int ) error {
216+ err := ensureMigrationsTableExists (db )
217+ if err != nil {
218+ return err
219+ }
220+ for {
221+ rolledBackIndex , err := rollbackOne (db , migrations , rollBackThroughIndex )
222+ if err != nil {
223+ return err
224+ }
225+ if rolledBackIndex == rollBackThroughIndex {
226+ return nil
227+ }
228+ }
229+ }
230+
231+ // Migrate does the following:
232+ // 1. Verifies that the `migration` table exists, and creates it if it does not.
233+ // 2. Verifies that the existing migrations recorded in the database match (by name and order) the migrations given as the argument.
234+ // 3. Performs any migrations that are not yet recorded in the database.
235+ func Migrate (db * sqlx.DB , migrations []NamedMigration ) error {
236+ err := ensureMigrationsTableExists (db )
237+ if err != nil {
238+ return err
239+ }
240+ for {
241+ migrated , err := migrateOne (db , migrations )
242+ if err != nil {
243+ return err
244+ }
245+ if ! migrated {
246+ return nil
247+ }
228248 }
229- committed = true
230- return nil
231249}
0 commit comments