/
database.go
129 lines (108 loc) · 3.49 KB
/
database.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
package manageddb
import (
"database/sql"
"fmt"
"log"
"sync"
_ "github.com/mattn/go-sqlite3"
)
// ManagedDB has all information pertaining to the database that is being managed.
// This should probably all be opaque to the user.
type ManagedDB struct {
DB *sql.DB
dbLock *sync.Mutex
migrations map[int]DBMigration
currentMigration int
}
// NewManagedDB creates and initializes a new ManagedDB with the given file path,
// datatbase driver and migrations to apply to the db.
func NewManagedDB(dbPath string, driver string, migrations map[int]DBMigration) *ManagedDB {
newDB := new(ManagedDB)
var err error
newDB.DB, err = sql.Open(driver, dbPath)
if err != nil {
panic(fmt.Sprintf("err opening database: %v", err))
}
newDB.dbLock = new(sync.Mutex)
// Figure out what the current migration is
newDB.currentMigration = newDB.getCurrentMigration()
log.Printf("Current migration level: %d.", newDB.currentMigration)
newDB.migrations = migrations
newDB.databaseMigrate(-1)
return newDB
}
func (mdb ManagedDB) getCurrentMigration() int {
var current int
rows, err := mdb.DB.Query("select migration from db_metadata")
if err == nil {
defer rows.Close()
rows.Next()
if err = rows.Scan(¤t); err != nil {
log.Fatalf("Error getting current migration from db: %v", err)
panic(err)
}
}
return current
}
func (mdb ManagedDB) setCurrentMigration(level int) {
_, err := mdb.DB.Exec("update db_metadata set migration = ?", level)
if err != nil {
log.Fatalf("unable to update database migration level: %v", err)
panic(err)
}
}
func (mdb ManagedDB) databaseMigrate(toMigration int) {
// If desired migration level is -1, it means go to the latest
// migration.
if toMigration == -1 {
toMigration = len(mdb.migrations)
}
var dbErr error
if mdb.currentMigration > toMigration {
// Migrating down.
for mdb.currentMigration > toMigration {
mdb.currentMigration--
dbErr = mdb.migrations[mdb.currentMigration].Down(mdb.DB)
if dbErr != nil {
panic(fmt.Sprintf("db migration %d down failed: %v", mdb.currentMigration, dbErr))
mdb.currentMigration++
} else {
mdb.setCurrentMigration(mdb.currentMigration)
}
}
} else if mdb.currentMigration < toMigration {
// Migrating up.
for mdb.currentMigration < toMigration {
mdb.currentMigration++
dbErr = mdb.migrations[mdb.currentMigration].Up(mdb.DB)
if dbErr != nil {
panic(fmt.Sprintf("db migration %d up failed: %v", mdb.currentMigration, dbErr))
mdb.currentMigration--
} else {
mdb.setCurrentMigration(mdb.currentMigration)
}
}
log.Printf("Migrated up to level %d.", mdb.currentMigration)
} else {
log.Printf("No migrations to perform.")
}
}
type ManagedDBWriteFunc func(db *sql.DB) error
// DoWrite executes the provided ManagedDBWriteFunc in a safely
// single-threaded way. All writes to the underlying DB should happen
// in this way.
func (mdb ManagedDB) DoWrite(writeFunc ManagedDBWriteFunc) error {
mdb.dbLock.Lock()
writeErr := writeFunc(mdb.DB)
mdb.dbLock.Unlock()
return writeErr
}
// DBMigrationFunction gives the signature of functions that can perform
// database migrations.
type DBMigrationFunction func(db *sql.DB) error
// DBMigration contains two functions, Up and Down that together perform and undo
// a set of changes to the database.
type DBMigration struct {
Up DBMigrationFunction
Down DBMigrationFunction
}