• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

golang-migrate / migrate / 19729155206

27 Nov 2025 07:58AM UTC coverage: 54.432% (+0.4%) from 54.037%
19729155206

Pull #1322

github

leonklingele
chore: bring back unused util.go file as removing it is a breaking change
Pull Request #1322: chore: remove dependency on "hashicorp/go-multierror"

1 of 68 new or added lines in 22 files covered. (1.47%)

19 existing lines in 2 files now uncovered.

4378 of 8043 relevant lines covered (54.43%)

48.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.24
/database/sqlite/sqlite.go
1
package sqlite
2

3
import (
4
        "database/sql"
5
        "errors"
6
        "fmt"
7
        "io"
8
        nurl "net/url"
9
        "strconv"
10
        "strings"
11
        "sync/atomic"
12

13
        "github.com/golang-migrate/migrate/v4"
14
        "github.com/golang-migrate/migrate/v4/database"
15
        _ "modernc.org/sqlite"
16
)
17

18
func init() {
2✔
19
        database.Register("sqlite", &Sqlite{})
2✔
20
}
2✔
21

22
var DefaultMigrationsTable = "schema_migrations"
23
var (
24
        ErrDatabaseDirty  = fmt.Errorf("database is dirty")
25
        ErrNilConfig      = fmt.Errorf("no config")
26
        ErrNoDatabaseName = fmt.Errorf("no database name")
27
)
28

29
type Config struct {
30
        MigrationsTable string
31
        DatabaseName    string
32
        NoTxWrap        bool
33
}
34

35
type Sqlite struct {
36
        db       *sql.DB
37
        isLocked atomic.Bool
38

39
        config *Config
40
}
41

42
func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
10✔
43
        if config == nil {
10✔
44
                return nil, ErrNilConfig
×
45
        }
×
46

47
        if err := instance.Ping(); err != nil {
10✔
48
                return nil, err
×
49
        }
×
50

51
        if len(config.MigrationsTable) == 0 {
12✔
52
                config.MigrationsTable = DefaultMigrationsTable
2✔
53
        }
2✔
54

55
        mx := &Sqlite{
10✔
56
                db:     instance,
10✔
57
                config: config,
10✔
58
        }
10✔
59
        if err := mx.ensureVersionTable(); err != nil {
10✔
60
                return nil, err
×
61
        }
×
62
        return mx, nil
10✔
63
}
64

65
// ensureVersionTable checks if versions table exists and, if not, creates it.
66
// Note that this function locks the database, which deviates from the usual
67
// convention of "caller locks" in the Sqlite type.
68
func (m *Sqlite) ensureVersionTable() (err error) {
10✔
69
        if err = m.Lock(); err != nil {
10✔
70
                return err
×
71
        }
×
72

73
        defer func() {
20✔
74
                if e := m.Unlock(); e != nil {
10✔
NEW
75
                        err = errors.Join(err, e)
×
76
                }
×
77
        }()
78

79
        query := fmt.Sprintf(`
10✔
80
        CREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);
10✔
81
  CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);
10✔
82
  `, m.config.MigrationsTable, m.config.MigrationsTable)
10✔
83

10✔
84
        if _, err := m.db.Exec(query); err != nil {
10✔
85
                return err
×
86
        }
×
87
        return nil
10✔
88
}
89

90
func (m *Sqlite) Open(url string) (database.Driver, error) {
8✔
91
        purl, err := nurl.Parse(url)
8✔
92
        if err != nil {
8✔
93
                return nil, err
×
94
        }
×
95
        dbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), "sqlite://", "", 1)
8✔
96
        db, err := sql.Open("sqlite", dbfile)
8✔
97
        if err != nil {
8✔
98
                return nil, err
×
99
        }
×
100

101
        qv := purl.Query()
8✔
102

8✔
103
        migrationsTable := qv.Get("x-migrations-table")
8✔
104
        if len(migrationsTable) == 0 {
16✔
105
                migrationsTable = DefaultMigrationsTable
8✔
106
        }
8✔
107

108
        noTxWrap := false
8✔
109
        if v := qv.Get("x-no-tx-wrap"); v != "" {
12✔
110
                noTxWrap, err = strconv.ParseBool(v)
4✔
111
                if err != nil {
6✔
112
                        return nil, fmt.Errorf("x-no-tx-wrap: %s", err)
2✔
113
                }
2✔
114
        }
115

116
        mx, err := WithInstance(db, &Config{
6✔
117
                DatabaseName:    purl.Path,
6✔
118
                MigrationsTable: migrationsTable,
6✔
119
                NoTxWrap:        noTxWrap,
6✔
120
        })
6✔
121
        if err != nil {
6✔
122
                return nil, err
×
123
        }
×
124
        return mx, nil
6✔
125
}
126

127
func (m *Sqlite) Close() error {
×
128
        return m.db.Close()
×
129
}
×
130

131
func (m *Sqlite) Drop() (err error) {
8✔
132
        query := `SELECT name FROM sqlite_master WHERE type = 'table';`
8✔
133
        tables, err := m.db.Query(query)
8✔
134
        if err != nil {
8✔
135
                return &database.Error{OrigErr: err, Query: []byte(query)}
×
136
        }
×
137
        defer func() {
16✔
138
                if errClose := tables.Close(); errClose != nil {
8✔
NEW
139
                        err = errors.Join(err, errClose)
×
140
                }
×
141
        }()
142

143
        tableNames := make([]string, 0)
8✔
144
        for tables.Next() {
24✔
145
                var tableName string
16✔
146
                if err := tables.Scan(&tableName); err != nil {
16✔
147
                        return err
×
148
                }
×
149
                if len(tableName) > 0 {
32✔
150
                        tableNames = append(tableNames, tableName)
16✔
151
                }
16✔
152
        }
153
        if err := tables.Err(); err != nil {
8✔
154
                return &database.Error{OrigErr: err, Query: []byte(query)}
×
155
        }
×
156

157
        if len(tableNames) > 0 {
16✔
158
                for _, t := range tableNames {
24✔
159
                        query := "DROP TABLE " + t
16✔
160
                        err = m.executeQuery(query)
16✔
161
                        if err != nil {
16✔
162
                                return &database.Error{OrigErr: err, Query: []byte(query)}
×
163
                        }
×
164
                }
165
                query := "VACUUM"
8✔
166
                _, err = m.db.Query(query)
8✔
167
                if err != nil {
8✔
168
                        return &database.Error{OrigErr: err, Query: []byte(query)}
×
169
                }
×
170
        }
171

172
        return nil
8✔
173
}
174

175
func (m *Sqlite) Lock() error {
34✔
176
        if !m.isLocked.CompareAndSwap(false, true) {
40✔
177
                return database.ErrLocked
6✔
178
        }
6✔
179
        return nil
28✔
180
}
181

182
func (m *Sqlite) Unlock() error {
28✔
183
        if !m.isLocked.CompareAndSwap(true, false) {
28✔
184
                return database.ErrNotLocked
×
185
        }
×
186
        return nil
28✔
187
}
188

189
func (m *Sqlite) Run(migration io.Reader) error {
14✔
190
        migr, err := io.ReadAll(migration)
14✔
191
        if err != nil {
14✔
192
                return err
×
193
        }
×
194
        query := string(migr[:])
14✔
195

14✔
196
        if m.config.NoTxWrap {
16✔
197
                return m.executeQueryNoTx(query)
2✔
198
        }
2✔
199
        return m.executeQuery(query)
12✔
200
}
201

202
func (m *Sqlite) executeQuery(query string) error {
28✔
203
        tx, err := m.db.Begin()
28✔
204
        if err != nil {
28✔
205
                return &database.Error{OrigErr: err, Err: "transaction start failed"}
×
206
        }
×
207
        if _, err := tx.Exec(query); err != nil {
28✔
208
                if errRollback := tx.Rollback(); errRollback != nil {
×
NEW
209
                        err = errors.Join(err, errRollback)
×
210
                }
×
211
                return &database.Error{OrigErr: err, Query: []byte(query)}
×
212
        }
213
        if err := tx.Commit(); err != nil {
28✔
214
                return &database.Error{OrigErr: err, Err: "transaction commit failed"}
×
215
        }
×
216
        return nil
28✔
217
}
218

219
func (m *Sqlite) executeQueryNoTx(query string) error {
2✔
220
        if _, err := m.db.Exec(query); err != nil {
2✔
221
                return &database.Error{OrigErr: err, Query: []byte(query)}
×
222
        }
×
223
        return nil
2✔
224
}
225

226
func (m *Sqlite) SetVersion(version int, dirty bool) error {
52✔
227
        tx, err := m.db.Begin()
52✔
228
        if err != nil {
52✔
229
                return &database.Error{OrigErr: err, Err: "transaction start failed"}
×
230
        }
×
231

232
        query := "DELETE FROM " + m.config.MigrationsTable
52✔
233
        if _, err := tx.Exec(query); err != nil {
52✔
234
                return &database.Error{OrigErr: err, Query: []byte(query)}
×
235
        }
×
236

237
        // Also re-write the schema version for nil dirty versions to prevent
238
        // empty schema version for failed down migration on the first migration
239
        // See: https://github.com/golang-migrate/migrate/issues/330
240
        if version >= 0 || (version == database.NilVersion && dirty) {
98✔
241
                query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)
46✔
242
                if _, err := tx.Exec(query, version, dirty); err != nil {
46✔
243
                        if errRollback := tx.Rollback(); errRollback != nil {
×
NEW
244
                                err = errors.Join(err, errRollback)
×
245
                        }
×
246
                        return &database.Error{OrigErr: err, Query: []byte(query)}
×
247
                }
248
        }
249

250
        if err := tx.Commit(); err != nil {
52✔
251
                return &database.Error{OrigErr: err, Err: "transaction commit failed"}
×
252
        }
×
253

254
        return nil
52✔
255
}
256

257
func (m *Sqlite) Version() (version int, dirty bool, err error) {
46✔
258
        query := "SELECT version, dirty FROM " + m.config.MigrationsTable + " LIMIT 1"
46✔
259
        err = m.db.QueryRow(query).Scan(&version, &dirty)
46✔
260
        if err != nil {
62✔
261
                return database.NilVersion, false, nil
16✔
262
        }
16✔
263
        return version, dirty, nil
30✔
264
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc