Skip to content

Commit 14efdaa

Browse files
Prometheus2677FPiety0521
authored andcommitted
Merge pull request #350 from saj/saj/sqlite-no-implicit-tx
sqlite3: Allow users to disable implicit transactions
2 parents e406d40 + e1abbd0 commit 14efdaa

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

database/sqlite3/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# sqlite3
2+
3+
`sqlite3://path/to/database?query`
4+
5+
Unlike other migrate database drivers, the sqlite3 driver will automatically wrap each migration in an implicit transaction by default. Migrations must not contain explicit `BEGIN` or `COMMIT` statements. This behaviour may change in a future major release. (See below for a workaround.)
6+
7+
Refer to [upstream documentation](https://github.com/mattn/go-sqlite3/blob/master/README.md#connection-string) for a complete list of query parameters supported by the sqlite3 database driver. The auxiliary query parameters listed below may be supplied to tailor migrate behaviour. All auxiliary query parameters are optional.
8+
9+
| URL Query | WithInstance Config | Description |
10+
|------------|---------------------|-------------|
11+
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table. Defaults to `schema_migrations`. |
12+
| `x-no-tx-wrap` | `NoTxWrap` | Disable implicit transactions when `true`. Migrations may, and should, contain explicit `BEGIN` and `COMMIT` statements. |
13+

database/sqlite3/sqlite3.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"io/ioutil"
88
nurl "net/url"
9+
"strconv"
910
"strings"
1011

1112
"github.com/golang-migrate/migrate/v4"
@@ -28,6 +29,7 @@ var (
2829
type Config struct {
2930
MigrationsTable string
3031
DatabaseName string
32+
NoTxWrap bool
3133
}
3234

3335
type Sqlite struct {
@@ -100,13 +102,25 @@ func (m *Sqlite) Open(url string) (database.Driver, error) {
100102
return nil, err
101103
}
102104

103-
migrationsTable := purl.Query().Get("x-migrations-table")
105+
qv := purl.Query()
106+
107+
migrationsTable := qv.Get("x-migrations-table")
104108
if len(migrationsTable) == 0 {
105109
migrationsTable = DefaultMigrationsTable
106110
}
111+
112+
noTxWrap := false
113+
if v := qv.Get("x-no-tx-wrap"); v != "" {
114+
noTxWrap, err = strconv.ParseBool(v)
115+
if err != nil {
116+
return nil, fmt.Errorf("x-no-tx-wrap: %s", err)
117+
}
118+
}
119+
107120
mx, err := WithInstance(db, &Config{
108121
DatabaseName: purl.Path,
109122
MigrationsTable: migrationsTable,
123+
NoTxWrap: noTxWrap,
110124
})
111125
if err != nil {
112126
return nil, err
@@ -180,6 +194,9 @@ func (m *Sqlite) Run(migration io.Reader) error {
180194
}
181195
query := string(migr[:])
182196

197+
if m.config.NoTxWrap {
198+
return m.executeQueryNoTx(query)
199+
}
183200
return m.executeQuery(query)
184201
}
185202

@@ -200,6 +217,13 @@ func (m *Sqlite) executeQuery(query string) error {
200217
return nil
201218
}
202219

220+
func (m *Sqlite) executeQueryNoTx(query string) error {
221+
if _, err := m.db.Exec(query); err != nil {
222+
return &database.Error{OrigErr: err, Query: []byte(query)}
223+
}
224+
return nil
225+
}
226+
203227
func (m *Sqlite) SetVersion(version int, dirty bool) error {
204228
tx, err := m.db.Begin()
205229
if err != nil {

database/sqlite3/sqlite3_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"path/filepath"
99
"testing"
1010

11+
"github.com/stretchr/testify/assert"
12+
1113
"github.com/golang-migrate/migrate/v4"
1214
dt "github.com/golang-migrate/migrate/v4/database/testing"
1315
_ "github.com/golang-migrate/migrate/v4/source/file"
@@ -116,3 +118,45 @@ func TestMigrationTable(t *testing.T) {
116118
t.Fatal(err)
117119
}
118120
}
121+
122+
func TestNoTxWrap(t *testing.T) {
123+
dir, err := ioutil.TempDir("", "sqlite3-driver-test")
124+
if err != nil {
125+
return
126+
}
127+
defer func() {
128+
if err := os.RemoveAll(dir); err != nil {
129+
t.Error(err)
130+
}
131+
}()
132+
t.Logf("DB path : %s\n", filepath.Join(dir, "sqlite3.db"))
133+
p := &Sqlite{}
134+
addr := fmt.Sprintf("sqlite3://%s?x-no-tx-wrap=true", filepath.Join(dir, "sqlite3.db"))
135+
d, err := p.Open(addr)
136+
if err != nil {
137+
t.Fatal(err)
138+
}
139+
// An explicit BEGIN statement would ordinarily fail without x-no-tx-wrap.
140+
// (Transactions in sqlite may not be nested.)
141+
dt.Test(t, d, []byte("BEGIN; CREATE TABLE t (Qty int, Name string); COMMIT;"))
142+
}
143+
144+
func TestNoTxWrapInvalidValue(t *testing.T) {
145+
dir, err := ioutil.TempDir("", "sqlite3-driver-test")
146+
if err != nil {
147+
return
148+
}
149+
defer func() {
150+
if err := os.RemoveAll(dir); err != nil {
151+
t.Error(err)
152+
}
153+
}()
154+
t.Logf("DB path : %s\n", filepath.Join(dir, "sqlite3.db"))
155+
p := &Sqlite{}
156+
addr := fmt.Sprintf("sqlite3://%s?x-no-tx-wrap=yeppers", filepath.Join(dir, "sqlite3.db"))
157+
_, err = p.Open(addr)
158+
if assert.Error(t, err) {
159+
assert.Contains(t, err.Error(), "x-no-tx-wrap")
160+
assert.Contains(t, err.Error(), "invalid syntax")
161+
}
162+
}

0 commit comments

Comments
 (0)