1package migrate
2
3import (
4 "database/sql"
5 _ "embed"
6 "fmt"
7 "time"
8)
9
10//go:embed sql/001_initial.sql
11var sql001 string
12
13//go:embed sql/002_conflicts.sql
14var sql002 string
15
16//go:embed sql/003_wcache_mode.sql
17var sql003 string
18
19//go:embed sql/004_pack_delta.sql
20var sql004 string
21
22//go:embed sql/005_zstd_dict.sql
23var sql005 string
24
25//go:embed sql/006_file_locks.sql
26var sql006 string
27
28//go:embed sql/007_wcache_dirty.sql
29var sql007 string
30
31//go:embed sql/008_shelves.sql
32var sql008 string
33
34//go:embed sql/009_watcher_pid.sql
35var sql009 string
36
37type migration struct {
38 version int
39 sql string
40}
41
42var all = []migration{
43 {1, sql001},
44 {2, sql002},
45 {3, sql003},
46 {4, sql004},
47 {5, sql005},
48 {6, sql006},
49 {7, sql007},
50 {8, sql008},
51 {9, sql009},
52}
53
54func Run(db *sql.DB) error {
55 if err := ensureMigrationsTable(db); err != nil {
56 return err
57 }
58
59 applied, err := appliedVersions(db)
60 if err != nil {
61 return err
62 }
63
64 for _, m := range all {
65 if applied[m.version] {
66 continue
67 }
68 if err := applyMigration(db, m); err != nil {
69 return fmt.Errorf("migrate v%d: %w", m.version, err)
70 }
71 }
72 return nil
73}
74
75func ensureMigrationsTable(db *sql.DB) error {
76 _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
77 version INTEGER PRIMARY KEY,
78 applied_at INTEGER NOT NULL
79 )`)
80 return err
81}
82
83func appliedVersions(db *sql.DB) (map[int]bool, error) {
84 rows, err := db.Query("SELECT version FROM schema_migrations")
85 if err != nil {
86 return nil, err
87 }
88 defer rows.Close()
89
90 m := make(map[int]bool)
91 for rows.Next() {
92 var v int
93 if err := rows.Scan(&v); err != nil {
94 return nil, err
95 }
96 m[v] = true
97 }
98 return m, rows.Err()
99}
100
101func applyMigration(db *sql.DB, m migration) error {
102 tx, err := db.Begin()
103 if err != nil {
104 return err
105 }
106
107 if _, err := tx.Exec(m.sql); err != nil {
108 tx.Rollback()
109 return fmt.Errorf("exec: %w", err)
110 }
111
112 if _, err := tx.Exec(
113 "INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)",
114 m.version, time.Now().Unix(),
115 ); err != nil {
116 tx.Rollback()
117 return fmt.Errorf("record: %w", err)
118 }
119
120 return tx.Commit()
121}