• 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/sqlcipher/sqlcipher.go
1
package sqlcipher
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
        _ "github.com/mutecomm/go-sqlcipher/v4"
16
)
17

18
func init() {
2✔
19
        database.Register("sqlcipher", &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) {
8✔
43
        if config == nil {
8✔
44
                return nil, ErrNilConfig
×
45
        }
×
46

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

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

55
        mx := &Sqlite{
8✔
56
                db:     instance,
8✔
57
                config: config,
8✔
58
        }
8✔
59
        if err := mx.ensureVersionTable(); err != nil {
8✔
60
                return nil, err
×
61
        }
×
62
        return mx, nil
8✔
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) {
8✔
69
        if err = m.Lock(); err != nil {
8✔
70
                return err
×
71
        }
×
72

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

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

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

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

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

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

108
        noTxWrap := false
6✔
109
        if v := qv.Get("x-no-tx-wrap"); v != "" {
10✔
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{
4✔
117
                DatabaseName:    purl.Path,
4✔
118
                MigrationsTable: migrationsTable,
4✔
119
                NoTxWrap:        noTxWrap,
4✔
120
        })
4✔
121
        if err != nil {
4✔
122
                return nil, err
×
123
        }
×
124
        return mx, nil
4✔
125
}
126

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

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

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

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

172
        return nil
6✔
173
}
174

175
func (m *Sqlite) Lock() error {
26✔
176
        if !m.isLocked.CompareAndSwap(false, true) {
30✔
177
                return database.ErrLocked
4✔
178
        }
4✔
179
        return nil
22✔
180
}
181

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

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

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

202
func (m *Sqlite) executeQuery(query string) error {
22✔
203
        tx, err := m.db.Begin()
22✔
204
        if err != nil {
22✔
205
                return &database.Error{OrigErr: err, Err: "transaction start failed"}
×
206
        }
×
207
        if _, err := tx.Exec(query); err != nil {
22✔
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 {
22✔
214
                return &database.Error{OrigErr: err, Err: "transaction commit failed"}
×
215
        }
×
216
        return nil
22✔
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 {
40✔
227
        tx, err := m.db.Begin()
40✔
228
        if err != nil {
40✔
229
                return &database.Error{OrigErr: err, Err: "transaction start failed"}
×
230
        }
×
231

232
        query := "DELETE FROM " + m.config.MigrationsTable
40✔
233
        if _, err := tx.Exec(query); err != nil {
40✔
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) {
76✔
241
                query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)
36✔
242
                if _, err := tx.Exec(query, version, dirty); err != nil {
36✔
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 {
40✔
251
                return &database.Error{OrigErr: err, Err: "transaction commit failed"}
×
252
        }
×
253

254
        return nil
40✔
255
}
256

257
func (m *Sqlite) Version() (version int, dirty bool, err error) {
32✔
258
        query := "SELECT version, dirty FROM " + m.config.MigrationsTable + " LIMIT 1"
32✔
259
        err = m.db.QueryRow(query).Scan(&version, &dirty)
32✔
260
        if err != nil {
44✔
261
                return database.NilVersion, false, nil
12✔
262
        }
12✔
263
        return version, dirty, nil
20✔
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