Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sqlite3: Allow users to disable implicit transactions #350

Merged
merged 3 commits into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions database/sqlite3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# sqlite3

`sqlite3://path/to/database?query`

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.)

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.

| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table. Defaults to `schema_migrations`. |
| `x-no-tx-wrap` | `NoTxWrap` | Disable implicit transactions when `true`. Migrations may, and should, contain explicit `BEGIN` and `COMMIT` statements. |

26 changes: 25 additions & 1 deletion database/sqlite3/sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
nurl "net/url"
"strconv"
"strings"

"github.com/golang-migrate/migrate/v4"
Expand All @@ -28,6 +29,7 @@ var (
type Config struct {
MigrationsTable string
DatabaseName string
NoTxWrap bool
}

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

migrationsTable := purl.Query().Get("x-migrations-table")
qv := purl.Query()

migrationsTable := qv.Get("x-migrations-table")
if len(migrationsTable) == 0 {
migrationsTable = DefaultMigrationsTable
}

noTxWrap := false
if v := qv.Get("x-no-tx-wrap"); v != "" {
noTxWrap, err = strconv.ParseBool(v)
if err != nil {
return nil, fmt.Errorf("x-no-tx-wrap: %s", err)
}
}

mx, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
NoTxWrap: noTxWrap,
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -180,6 +194,9 @@ func (m *Sqlite) Run(migration io.Reader) error {
}
query := string(migr[:])

if m.config.NoTxWrap {
return m.executeQueryNoTx(query)
}
return m.executeQuery(query)
}

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

func (m *Sqlite) executeQueryNoTx(query string) error {
if _, err := m.db.Exec(query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return nil
}

func (m *Sqlite) SetVersion(version int, dirty bool) error {
tx, err := m.db.Begin()
if err != nil {
Expand Down
44 changes: 44 additions & 0 deletions database/sqlite3/sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"

"github.com/golang-migrate/migrate/v4"
dt "github.com/golang-migrate/migrate/v4/database/testing"
_ "github.com/golang-migrate/migrate/v4/source/file"
Expand Down Expand Up @@ -116,3 +118,45 @@ func TestMigrationTable(t *testing.T) {
t.Fatal(err)
}
}

func TestNoTxWrap(t *testing.T) {
dir, err := ioutil.TempDir("", "sqlite3-driver-test")
if err != nil {
return
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Error(err)
}
}()
t.Logf("DB path : %s\n", filepath.Join(dir, "sqlite3.db"))
p := &Sqlite{}
addr := fmt.Sprintf("sqlite3://%s?x-no-tx-wrap=true", filepath.Join(dir, "sqlite3.db"))
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
// An explicit BEGIN statement would ordinarily fail without x-no-tx-wrap.
// (Transactions in sqlite may not be nested.)
dt.Test(t, d, []byte("BEGIN; CREATE TABLE t (Qty int, Name string); COMMIT;"))
}

func TestNoTxWrapInvalidValue(t *testing.T) {
dir, err := ioutil.TempDir("", "sqlite3-driver-test")
if err != nil {
return
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Error(err)
}
}()
t.Logf("DB path : %s\n", filepath.Join(dir, "sqlite3.db"))
p := &Sqlite{}
addr := fmt.Sprintf("sqlite3://%s?x-no-tx-wrap=yeppers", filepath.Join(dir, "sqlite3.db"))
_, err = p.Open(addr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "x-no-tx-wrap")
assert.Contains(t, err.Error(), "invalid syntax")
}
}