From 8dd032393ec2e9093020d65b67944f5575d102c4 Mon Sep 17 00:00:00 2001 From: mykola2312 <49044616+mykola2312@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:04:42 +0200 Subject: [PATCH] implement small migrations tool --- db/db.go | 84 ++++++++++++++++++++++++++++++++++++++--- go.mod | 3 +- go.sum | 4 ++ main.go | 9 +---- migrations/000_init.sql | 4 ++ migrations/001_test.sql | 4 ++ 6 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 migrations/000_init.sql create mode 100644 migrations/001_test.sql diff --git a/db/db.go b/db/db.go index 46afc2d..29ff235 100644 --- a/db/db.go +++ b/db/db.go @@ -4,8 +4,13 @@ import ( "database/sql" "log" "os" + "path" + "strconv" + "strings" _ "github.com/mattn/go-sqlite3" + "github.com/umpc/go-sortedmap" + "github.com/umpc/go-sortedmap/asc" ) type DBConfig struct { @@ -22,7 +27,6 @@ func Init(_config *DBConfig) { func checkConfig() { if config == nil { 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 '_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 { // create table _, err = db.Exec(` CREATE TABLE _migration ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - seq INTEGER NOT NULL UNIQUE, - name TEXT NOT NULL UNIQUE + _seq INTEGER PRIMARY KEY UNIQUE );`) // can't create migration table - fatal if err != nil { @@ -52,6 +54,77 @@ func open(dbPath string) (*sql.DB, error) { 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) { checkConfig() @@ -59,6 +132,7 @@ func Open() (*sql.DB, error) { if err != nil { return nil, err } + applyMigrations(db) return db, nil } diff --git a/go.mod b/go.mod index b64cfc1..7b870ef 100644 --- a/go.mod +++ b/go.mod @@ -3,5 +3,6 @@ module wargh go 1.23.2 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 ) diff --git a/go.sum b/go.sum index 0de2071..b6e0fcc 100644 --- a/go.sum +++ b/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/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 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/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= diff --git a/main.go b/main.go index c12892b..36b4070 100644 --- a/main.go +++ b/main.go @@ -6,12 +6,10 @@ import ( "wargh/db" ) -const DB_PATH = "wargh.db" - func main() { db.Init(&db.DBConfig{ - DBPath: DB_PATH, - MigrationsPath: "", + DBPath: "wargh.db", + MigrationsPath: "migrations", }) DB, err := db.Open() @@ -20,7 +18,4 @@ func main() { os.Exit(1) } defer DB.Close() - - _, err = DB.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL);") - log.Fatal(err) } diff --git a/migrations/000_init.sql b/migrations/000_init.sql new file mode 100644 index 0000000..0bb8bd3 --- /dev/null +++ b/migrations/000_init.sql @@ -0,0 +1,4 @@ +CREATE TABLE test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + test TEXT NOT NULL UNIQUE +); \ No newline at end of file diff --git a/migrations/001_test.sql b/migrations/001_test.sql new file mode 100644 index 0000000..888a230 --- /dev/null +++ b/migrations/001_test.sql @@ -0,0 +1,4 @@ +CREATE TABLE test2 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + test2 TEXT NOT NULL UNIQUE +); \ No newline at end of file