Skip to content
This repository has been archived by the owner on Jun 28, 2018. It is now read-only.

adapted the sqlite driver for v3 (#165) #238

Merged
merged 2 commits into from
Jun 5, 2017
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SOURCE ?= file go-bindata github aws-s3 google-cloud-storage
DATABASE ?= postgres mysql redshift
DATABASE ?= postgres mysql redshift sqlite3
VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-)
TEST_FLAGS ?=
REPO_OWNER ?= $(shell cd .. && basename "$$(pwd)")
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Database drivers run migrations. [Add a new database?](database/driver.go)
* [Redshift](database/redshift)
* [Ql](database/ql)
* [Cassandra](database/cassandra) ([todo #164](https://github.com/mattes/migrate/issues/164))
* [SQLite](database/sqlite) ([todo #165](https://github.com/mattes/migrate/issues/165))
* [SQLite](database/sqlite3)
* [MySQL/ MariaDB](database/mysql)
* [Neo4j](database/neo4j) ([todo #167](https://github.com/mattes/migrate/issues/167))
* [MongoDB](database/mongodb) ([todo #169](https://github.com/mattes/migrate/issues/169))
Expand Down
7 changes: 7 additions & 0 deletions cli/build_sqlite3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build sqlite3

package main

import (
_ "github.com/mattes/migrate/database/sqlite3"
)
File renamed without changes.
1 change: 1 addition & 0 deletions database/sqlite3/migration/33_create_table.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS pets;
3 changes: 3 additions & 0 deletions database/sqlite3/migration/33_create_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CREATE TABLE pets (
name string
);
1 change: 1 addition & 0 deletions database/sqlite3/migration/44_alter_table.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS pets;
1 change: 1 addition & 0 deletions database/sqlite3/migration/44_alter_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE pets ADD predator bool;
214 changes: 214 additions & 0 deletions database/sqlite3/sqlite3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package sqlite3

import (
"database/sql"
"fmt"
"github.com/mattes/migrate"
"github.com/mattes/migrate/database"
_ "github.com/mattn/go-sqlite3"
"io"
"io/ioutil"
nurl "net/url"
"strings"
)

func init() {
database.Register("sqlite3", &Sqlite{})
}

var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
)

type Config struct {
MigrationsTable string
DatabaseName string
}

type Sqlite struct {
db *sql.DB
isLocked bool

config *Config
}

func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}

if err := instance.Ping(); err != nil {
return nil, err
}
if len(config.MigrationsTable) == 0 {
config.MigrationsTable = DefaultMigrationsTable
}

mx := &Sqlite{
db: instance,
config: config,
}
if err := mx.ensureVersionTable(); err != nil {
return nil, err
}
return mx, nil
}

func (m *Sqlite) ensureVersionTable() error {

query := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);
CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);
`, DefaultMigrationsTable, DefaultMigrationsTable)

if _, err := m.db.Exec(query); err != nil {
return err
}
return nil
}

func (m *Sqlite) Open(url string) (database.Driver, error) {
purl, err := nurl.Parse(url)
if err != nil {
return nil, err
}
dbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), "sqlite3://", "", 1)
db, err := sql.Open("sqlite3", dbfile)
if err != nil {
return nil, err
}

migrationsTable := purl.Query().Get("x-migrations-table")
if len(migrationsTable) == 0 {
migrationsTable = DefaultMigrationsTable
}
mx, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
})
if err != nil {
return nil, err
}
return mx, nil
}

func (m *Sqlite) Close() error {
return m.db.Close()
}

func (m *Sqlite) Drop() error {
query := `SELECT name FROM sqlite_master WHERE type = 'table';`
tables, err := m.db.Query(query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
defer tables.Close()
tableNames := make([]string, 0)
for tables.Next() {
var tableName string
if err := tables.Scan(&tableName); err != nil {
return err
}
if len(tableName) > 0 {
tableNames = append(tableNames, tableName)
}
}
if len(tableNames) > 0 {
for _, t := range tableNames {
query := "DROP TABLE " + t
err = m.executeQuery(query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := m.ensureVersionTable(); err != nil {
return err
}
query := "VACUUM"
_, err = m.db.Query(query)
if err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}

return nil
}

func (m *Sqlite) Lock() error {
if m.isLocked {
return database.ErrLocked
}
m.isLocked = true
return nil
}

func (m *Sqlite) Unlock() error {
if !m.isLocked {
return nil
}
m.isLocked = false
return nil
}

func (m *Sqlite) Run(migration io.Reader) error {
migr, err := ioutil.ReadAll(migration)
if err != nil {
return err
}
query := string(migr[:])

return m.executeQuery(query)
}

func (m *Sqlite) executeQuery(query string) error {
tx, err := m.db.Begin()
if err != nil {
return &database.Error{OrigErr: err, Err: "transaction start failed"}
}
if _, err := tx.Exec(query); err != nil {
tx.Rollback()
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if err := tx.Commit(); err != nil {
return &database.Error{OrigErr: err, Err: "transaction commit failed"}
}
return nil
}

func (m *Sqlite) SetVersion(version int, dirty bool) error {
tx, err := m.db.Begin()
if err != nil {
return &database.Error{OrigErr: err, Err: "transaction start failed"}
}

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

if version >= 0 {
query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (%d, '%t')`, m.config.MigrationsTable, version, dirty)
if _, err := tx.Exec(query); err != nil {
tx.Rollback()
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}

if err := tx.Commit(); err != nil {
return &database.Error{OrigErr: err, Err: "transaction commit failed"}
}

return nil
}

func (m *Sqlite) Version() (version int, dirty bool, err error) {
query := "SELECT version, dirty FROM " + m.config.MigrationsTable + " LIMIT 1"
err = m.db.QueryRow(query).Scan(&version, &dirty)
if err != nil {
return database.NilVersion, false, nil
}
return version, dirty, nil
}
61 changes: 61 additions & 0 deletions database/sqlite3/sqlite3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package sqlite3

import (
"database/sql"
"fmt"
"github.com/mattes/migrate"
dt "github.com/mattes/migrate/database/testing"
_ "github.com/mattes/migrate/source/file"
_ "github.com/mattn/go-sqlite3"
"io/ioutil"
"os"
"path/filepath"
"testing"
)

func Test(t *testing.T) {
dir, err := ioutil.TempDir("", "sqlite3-driver-test")
if err != nil {
return
}
defer func() {
os.RemoveAll(dir)
}()
fmt.Printf("DB path : %s\n", filepath.Join(dir, "sqlite3.db"))
p := &Sqlite{}
addr := fmt.Sprintf("sqlite3://%s", filepath.Join(dir, "sqlite3.db"))
d, err := p.Open(addr)
if err != nil {
t.Fatalf("%v", err)
}

db, err := sql.Open("sqlite3", filepath.Join(dir, "sqlite3.db"))
if err != nil {
return
}
defer func() {
if err := db.Close(); err != nil {
return
}
}()
dt.Test(t, d, []byte("CREATE TABLE t (Qty int, Name string);"))
driver, err := WithInstance(db, &Config{})
if err != nil {
t.Fatalf("%v", err)
}
if err := d.Drop(); err != nil {
t.Fatal(err)
}

m, err := migrate.NewWithDatabaseInstance(
"file://./migration",
"ql", driver)
if err != nil {
t.Fatalf("%v", err)
}
fmt.Println("UP")
err = m.Up()
if err != nil {
t.Fatalf("%v", err)
}
}