-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgormigro.go
executable file
·253 lines (198 loc) · 5.9 KB
/
gormigro.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package gormigro
import (
"fmt"
"log"
"os"
"time"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
const (
InitialSchemaMigrationName = "__initialMigration"
)
// Gormigro is migration tool which allow you to simply handle migrations with gorm.io
type Gormigro struct {
// db gorm database
db *gorm.DB
// options refers to setup of gormigro
options Options
// initialSchemaMigration runs on start, when there's no executed migration
initialSchemaMigration migrationFnc
// migrationsCollection list of migrations
migrationsCollection *Collection
// migrationManager manage migration table
migrationManager MigrationManager
}
var DebugLogger = logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
Colorful: true, // Disable color
})
// NewGormigro return instance of gormigro based on options passed
// work with all collected migrations
func NewGormigro(db *gorm.DB, options Options) *Gormigro {
if options.DebugMode {
db.Logger = DebugLogger
}
return &Gormigro{
db: db,
options: options,
migrationsCollection: DefaultCollector.Export(),
migrationManager: NewMigrationManager(db.Session(&gorm.Session{
NewDB: true,
}), options.MigrationTable),
}
}
// NewGormigro return instance of gormigro based on options passed
// with specified migrations on input
func NewGormigroWithMigrations(db *gorm.DB, options Options, migrations []Migration) *Gormigro {
if options.DebugMode {
db.Logger = DebugLogger
}
return &Gormigro{
db: db,
options: options,
migrationsCollection: NewCollectionWithMigrations(migrations),
migrationManager: NewMigrationManager(db.Session(&gorm.Session{
NewDB: true,
}), options.MigrationTable),
}
}
// AddMigration append migration to list of migrations
func (g *Gormigro) AddMigration(migration Migration) error {
if g.migrationsCollection.Contains(migration) {
return CreateMigrationAlreadyExistsError(migration.ID)
}
g.migrationsCollection.Append(migration)
return nil
}
// RegisterInitialSchemaFunction attach init function to migration process
// It will run on start of migration process if there are no previously executed migration
func (g *Gormigro) RegisterInitialSchemaFunction(initFunc migrationFnc) {
g.initialSchemaMigration = initFunc
}
// Migrate run the migration process
// Retrieve last executed migration and run from it
// If there's no executed migration, it run everything
// If you specify the initial schema function, it'll also run it if no previous executed migration
func (g *Gormigro) Migrate() error {
lastMigration := g.migrationManager.GetLastExecutedMigration()
var err error
if lastMigration == nil && g.options.RunInitSchema && g.initialSchemaMigration != nil {
err = g.runInitialSchemaFunc()
}
if err != nil {
return err
}
if lastMigration == nil {
// start from first
return g.MigrateFrom("")
}
return g.MigrateFrom(lastMigration.MigrationId)
}
// MigrateFrom run migrations from specified one(except the specified)
func (g *Gormigro) MigrateFrom(id string) error {
mc := g.migrationsCollection
// sort collection by migrationId
if g.options.SortMigrationsByIDField {
mc = mc.SortBy(MigrationId, Asc)
}
// if id is not empty, reduce the collection from(not included) migration id
if id != "" {
mc = g.migrationsCollection.SliceFrom(id)
}
if mc.Empty() {
log.Print("No migrations to execute")
}
for _, m := range mc.List() {
log.Printf("Running migration with ID %s\n", m.ID)
// start the transaction per migration
tx := g.db.Session(&gorm.Session{
NewDB: true,
})
if tx.Error != nil {
return tx.Error
}
// run migration
err := m.Migrate(tx)
if err != nil {
log.Printf("Error occured when migration %s run [%s]\n", m.ID, err)
log.Printf("Rollbacking %s\n", m.ID)
m.Rollback(tx)
return err
}
// and insert record to table
g.migrationManager.AddMigration(m.ID)
}
if !mc.Empty() {
log.Printf("%d migrations executed\n", mc.Count())
}
return nil
}
// Clear rollback all migrations and remove record of execution from migration table
func (g *Gormigro) Clear() error {
// you have to rollback in reverse order
sorted := g.migrationsCollection.SortBy(MigrationId, Asc)
lastMigration := g.migrationManager.GetLastExecutedMigration()
if lastMigration != nil {
sorted = g.migrationsCollection.SliceFrom(lastMigration.MigrationId)
}
reverseOrder := sorted.SortBy(MigrationId, Desc)
for _, m := range reverseOrder.List() {
// just rollback the
err := m.Rollback(g.db)
if err != nil {
return CreateUnableToRollbackMigrationError(m.ID)
}
g.migrationManager.RemoveMigration(m.ID)
}
return nil
}
// DropSchema drop the whole database schema
func (g *Gormigro) DropSchema() error {
type Table struct {
Name string
}
rows, err := g.db.Session(&gorm.Session{
NewDB: true,
}).Raw("SHOW TABLES").Rows()
if err != nil {
return err
}
defer rows.Close()
// disable FK check for safe remove
g.db.Exec("SET GLOBAL FOREIGN_KEY_CHECKS=0;")
for rows.Next() {
var table Table
rows.Scan(&table.Name)
log.Printf("Dropping table: %s\n", table.Name)
res := g.db.Exec(fmt.Sprintf("DROP TABLE %s", table.Name))
if res.Error != nil {
return res.Error
}
}
// and enable again
g.db.Exec("SET GLOBAL FOREIGN_KEY_CHECKS=1;")
log.Println("Dropping completed")
return nil
}
// internal usage
func (g *Gormigro) runInitialSchemaFunc() error {
log.Println("Running initial schema migration")
if g.migrationManager.IsMigrationExecuted(InitialSchemaMigrationName) {
return nil
}
// start transaction
tx := g.db.Begin()
// run init func
// rollback if any problem occured
if err := g.initialSchemaMigration(tx); err != nil {
tx.Rollback()
return err
}
// and mark the init func as executed
g.migrationManager.AddMigration(InitialSchemaMigrationName)
return tx.Commit().Error
}