implement small migrations tool
This commit is contained in:
parent
f58d75ebe8
commit
8dd032393e
6 changed files with 95 additions and 13 deletions
84
db/db.go
84
db/db.go
|
|
@ -4,8 +4,13 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/umpc/go-sortedmap"
|
||||||
|
"github.com/umpc/go-sortedmap/asc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
|
|
@ -22,7 +27,6 @@ func Init(_config *DBConfig) {
|
||||||
func checkConfig() {
|
func checkConfig() {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
log.Fatal("DBConfig not initialized! Call db.Init!")
|
log.Fatal("DBConfig not initialized! Call db.Init!")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,14 +39,12 @@ func open(dbPath string) (*sql.DB, error) {
|
||||||
/* if database is empty. we must create
|
/* if database is empty. we must create
|
||||||
'_migration` table and apply all releveant migrations
|
'_migration` table and apply all releveant migrations
|
||||||
*/
|
*/
|
||||||
_, err = db.Query("SELECT id FROM `_migration` LIMIT 1;")
|
_, err = db.Query("SELECT `_seq` FROM `_migration` LIMIT 1;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// create table
|
// create table
|
||||||
_, err = db.Exec(`
|
_, err = db.Exec(`
|
||||||
CREATE TABLE _migration (
|
CREATE TABLE _migration (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
_seq INTEGER PRIMARY KEY UNIQUE
|
||||||
seq INTEGER NOT NULL UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE
|
|
||||||
);`)
|
);`)
|
||||||
// can't create migration table - fatal
|
// can't create migration table - fatal
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,6 +54,77 @@ func open(dbPath string) (*sql.DB, error) {
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyMigrations(db *sql.DB) {
|
||||||
|
entries, err := os.ReadDir(config.MigrationsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load migrations from directory
|
||||||
|
migrations := sortedmap.New(len(entries), asc.Int)
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := entry.Name()
|
||||||
|
split := strings.Split(name, "_")
|
||||||
|
if len(split) < 2 {
|
||||||
|
log.Printf("%s does not follow 000_name.sql convention\n", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
seq, err := strconv.Atoi(split[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations.Insert(name, seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
if migrations.Len() == 0 {
|
||||||
|
log.Fatal("no migrations!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// detect last database migration
|
||||||
|
var seq int = -1
|
||||||
|
row := db.QueryRow("SELECT `_seq` FROM `_migration` ORDER BY `_seq` DESC LIMIT 1;")
|
||||||
|
if row.Err() == nil {
|
||||||
|
row.Scan(&seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply migrations starting from seq
|
||||||
|
iter, err := migrations.BoundedIterCh(false, seq+1, nil)
|
||||||
|
if err != nil {
|
||||||
|
// no migrations to apply
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
for m := range iter.Records() {
|
||||||
|
path := path.Join(config.MigrationsPath, m.Key.(string))
|
||||||
|
bytes, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql := string(bytes[:])
|
||||||
|
_, err = db.Exec(sql)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// successfuly applied migration, so bump seq and insert in table
|
||||||
|
seq = m.Val.(int)
|
||||||
|
_, err = db.Exec("INSERT INTO `_migration` (_seq) VALUES (?);", seq)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Applied migration %d\n", seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Open() (*sql.DB, error) {
|
func Open() (*sql.DB, error) {
|
||||||
checkConfig()
|
checkConfig()
|
||||||
|
|
||||||
|
|
@ -59,6 +132,7 @@ func Open() (*sql.DB, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
applyMigrations(db)
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -3,5 +3,6 @@ module wargh
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4
|
||||||
)
|
)
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -1,4 +1,8 @@
|
||||||
|
github.com/elliotchance/orderedmap/v2 v2.4.0 h1:6tUmMwD9F998FNpwFxA5E6NQvSpk2PVw7RKsVq3+2Cw=
|
||||||
|
github.com/elliotchance/orderedmap/v2 v2.4.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 h1:qk1XyC6UGfPa51PGmsTQJavyhfMLScqw97pEV3sFClI=
|
||||||
|
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4/go.mod h1:X6iKjXCleSyo/LZzKZ9zDF/ZB2L9gC36I5gLMf32w3M=
|
||||||
|
|
|
||||||
9
main.go
9
main.go
|
|
@ -6,12 +6,10 @@ import (
|
||||||
"wargh/db"
|
"wargh/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DB_PATH = "wargh.db"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
db.Init(&db.DBConfig{
|
db.Init(&db.DBConfig{
|
||||||
DBPath: DB_PATH,
|
DBPath: "wargh.db",
|
||||||
MigrationsPath: "",
|
MigrationsPath: "migrations",
|
||||||
})
|
})
|
||||||
|
|
||||||
DB, err := db.Open()
|
DB, err := db.Open()
|
||||||
|
|
@ -20,7 +18,4 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer DB.Close()
|
defer DB.Close()
|
||||||
|
|
||||||
_, err = DB.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL);")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
migrations/000_init.sql
Normal file
4
migrations/000_init.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
CREATE TABLE test (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
test TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
4
migrations/001_test.sql
Normal file
4
migrations/001_test.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
CREATE TABLE test2 (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
test2 TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
Loading…
Add table
Reference in a new issue