-
Notifications
You must be signed in to change notification settings - Fork 101
/
migration.go
132 lines (122 loc) · 3.5 KB
/
migration.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
package qbs
import (
"database/sql"
"fmt"
"strings"
)
type Migration struct {
db *sql.DB
dbName string
dialect Dialect
Log bool
}
// CreateTableIfNotExists creates a new table and its indexes based on the table struct type
// It will panic if table creation failed, and it will return error if the index creation failed.
func (mg *Migration) CreateTableIfNotExists(structPtr interface{}) error {
model := structPtrToModel(structPtr, true, nil)
sql := mg.dialect.createTableSql(model, true)
if mg.Log {
fmt.Println(sql)
}
sqls := strings.Split(sql, ";")
for _, v := range sqls {
_, err := mg.db.Exec(v)
if err != nil && !mg.dialect.catchMigrationError(err) {
panic(err)
}
}
columns := mg.dialect.columnsInTable(mg, model.table)
if len(model.fields) > len(columns) {
oldFields := []*modelField{}
newFields := []*modelField{}
for _, v := range model.fields {
if _, ok := columns[v.name]; ok {
oldFields = append(oldFields, v)
} else {
newFields = append(newFields, v)
}
}
if len(oldFields) != len(columns) {
panic("Column name has changed, rename column migration is not supported.")
}
for _, v := range newFields {
mg.addColumn(model.table, v)
}
}
var indexErr error
for _, i := range model.indexes {
indexErr = mg.CreateIndexIfNotExists(model.table, i.name, i.unique, i.columns...)
}
return indexErr
}
// this is only used for testing.
func (mg *Migration) dropTableIfExists(structPtr interface{}) {
tn := tableName(structPtr)
_, err := mg.db.Exec(mg.dialect.dropTableSql(tn))
if err != nil && !mg.dialect.catchMigrationError(err) {
panic(err)
}
}
//Can only drop table on database which name has "test" suffix.
//Used for testing
func (mg *Migration) DropTable(strutPtr interface{}) {
if !strings.HasSuffix(mg.dbName, "test") {
panic("Drop table can only be executed on database which name has 'test' suffix")
}
mg.dropTableIfExists(strutPtr)
}
func (mg *Migration) addColumn(table string, column *modelField) {
sql := mg.dialect.addColumnSql(table, *column)
if mg.Log {
fmt.Println(sql)
}
_, err := mg.db.Exec(sql)
if err != nil {
panic(err)
}
}
// CreateIndex creates the specified index on table.
// Some databases like mysql do not support this feature directly,
// So dialect may need to query the database schema table to find out if an index exists.
// Normally you don't need to do it explicitly, it will be created automatically in CreateTableIfNotExists method.
func (mg *Migration) CreateIndexIfNotExists(table interface{}, name string, unique bool, columns ...string) error {
tn := tableName(table)
name = tn + "_" + name
if !mg.dialect.indexExists(mg, tn, name) {
sql := mg.dialect.createIndexSql(name, tn, unique, columns...)
if mg.Log {
fmt.Println(sql)
}
_, err := mg.db.Exec(sql)
return err
}
return nil
}
func (mg *Migration) Close() {
if mg.db != nil {
err := mg.db.Close()
if err != nil {
panic(err)
}
}
}
// Get a Migration instance should get closed like Qbs instance.
func GetMigration() (mg *Migration, err error) {
if driver == "" || dial == nil {
panic("database driver has not been registered, should call Register first.")
}
db, err := sql.Open(driver, driverSource)
if err != nil {
return nil, err
}
return &Migration{db, dbName, dial, false}, nil
}
// A safe and easy way to work with Migration instance without the need to open and close it.
func WithMigration(task func(mg *Migration) error) error {
mg, err := GetMigration()
if err != nil {
return err
}
defer mg.Close()
return task(mg)
}