Skip to content

Commit

Permalink
Make the migration table configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
elwinar committed Oct 31, 2016
1 parent 4b04113 commit cf984bb
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 37 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ build: ## Build the binary for the local architecture

.PHONY: fetch
fetch: ## Fetch the dependencies
go get ./...
go get -d ./...

.PHONY: help
help: ## Get help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-10s\033[0m %s\n", $$1, $$2}'

.PHONY: release
release: ## Build the release files
go get github.com/karalabe/xgo
xgo --targets=$(TARGETS) --ldflags=$(LDFLAGS) $(PKG)

.PHONY: test
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Migrations are executed in alphabetical order, thus a versionning scheme of the
* `201409272258_Added_table_foo.sql`
* `01_First_migration.sql`

The migrations applied to the database are stored in a table named `migration` (can be changed with the `table` configuration option).

### Configuration

Rambler configuration is lightweight: just dump the credentials of your database and the path to your migrations' directory into a JSON file, and you're done. Here is an example or JSON configuration file with the default values of rambler:
Expand All @@ -54,7 +56,8 @@ Rambler configuration is lightweight: just dump the credentials of your database
"user": "root",
"password": "",
"database": "",
"directory": "."
"directory": ".",
"table": "migrations"
}
```

Expand Down Expand Up @@ -120,6 +123,7 @@ Environments configuration are derived from the default configuration of rambler
"password": "",
"database": "rambler_default",
"directory": "migrations",
"table": "migrations",
"environments": {
"development": {
"database": "rambler_development"
Expand Down
5 changes: 5 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ Server = http://archlinux.mirror.root.lu/$repo/os/$arch
echo "export GOPATH=~
export PATH=\$GOPATH/bin:\$PATH
" >> /home/vagrant/.bashrc
pacman -S --noconfirm docker
gpasswd -a vagrant docker
systemctl enable docker
systemctl start docker
EOS
end
5 changes: 3 additions & 2 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ type Configuration struct {
// Load open, read and parse the given configuration file
func Load(filename string) (Configuration, error) {
var c Configuration
c.Table = "migrations"

raw, err := ioutil.ReadFile(filename)
if err != nil {
return c, err
return Configuration{}, err
}

err = hjson.Unmarshal(raw, &c)
if err != nil {
return c, err
return Configuration{}, err
}

return c, nil
Expand Down
7 changes: 6 additions & 1 deletion configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func TestLoad(t *testing.T) {
Password: "",
Database: "rambler_default",
Directory: ".",
Table: "migrations",
},
Environments: map[string]Environment{
"testing": {
Expand Down Expand Up @@ -61,6 +62,7 @@ func TestLoad(t *testing.T) {
Password: "",
Database: "rambler_default",
Directory: ".",
Table: "migrations",
},
Environments: map[string]Environment{
"testing": {
Expand All @@ -85,7 +87,7 @@ func TestLoad(t *testing.T) {
}

if !reflect.DeepEqual(cfg, c.output) {
t.Error("case", n, "got unexpected output:", cfg)
t.Error("case", n, "got unexpected output: wanted", c.output, "got", cfg)
}
}
}
Expand Down Expand Up @@ -113,6 +115,7 @@ func TestConfigurationEnv(t *testing.T) {
Password: "",
Database: "rambler_default",
Directory: ".",
Table: "migrations",
},
},
{
Expand All @@ -127,6 +130,7 @@ func TestConfigurationEnv(t *testing.T) {
Password: "",
Database: "rambler_testing",
Directory: ".",
Table: "migrations",
},
},
}
Expand All @@ -142,6 +146,7 @@ func TestConfigurationEnv(t *testing.T) {
Password: "",
Database: "rambler_default",
Directory: ".",
Table: "migrations",
},
Environments: map[string]Environment{
"testing": {
Expand Down
6 changes: 3 additions & 3 deletions driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

// Driver is the interface used by the program to initialize the database connection.
type Driver interface {
New(dns, schema string) (Conn, error)
New(dns, schema, table string) (Conn, error)
}

var drivers = make(map[string]Driver)
Expand All @@ -26,13 +26,13 @@ func Register(name string, driver Driver) error {
}

// Get initialize a driver from the given environment
func Get(drv, dsn, schema string) (Conn, error) {
func Get(drv, dsn, schema, table string) (Conn, error) {
driver, found := drivers[drv]
if !found {
return nil, fmt.Errorf(`driver "%s" not registered`, drv)
}

conn, err := driver.New(dsn, schema)
conn, err := driver.New(dsn, schema, table)
if err != nil {
return nil, err
}
Expand Down
16 changes: 8 additions & 8 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
)

type MockDriver struct {
new func(string, string) (Conn, error)
new func(string, string, string) (Conn, error)
}

func (d *MockDriver) New(dsn, schema string) (Conn, error) {
return d.new(dsn, schema)
func (d *MockDriver) New(dsn, schema, table string) (Conn, error) {
return d.new(dsn, schema, table)
}

func Test_Register_NilDriver(t *testing.T) {
Expand Down Expand Up @@ -52,7 +52,7 @@ func Test_Register_OK(t *testing.T) {
func Test_Get_NotRegistered(t *testing.T) {
drivers = make(map[string]Driver)

_, err := Get("test", "", "")
_, err := Get("test", "", "", "migrations")
if err == nil {
t.Fail()
}
Expand All @@ -62,7 +62,7 @@ func Test_Get_InitializeError(t *testing.T) {
drivers = make(map[string]Driver)

driver := &MockDriver{}
driver.new = func(_, _ string) (Conn, error) {
driver.new = func(_, _, _ string) (Conn, error) {
return nil, errors.New("initialize error")
}

Expand All @@ -71,7 +71,7 @@ func Test_Get_InitializeError(t *testing.T) {
t.Fail()
}

_, err = Get("test", "", "")
_, err = Get("test", "", "", "migrations")
if err == nil {
t.Fail()
}
Expand All @@ -80,7 +80,7 @@ func Test_Get_InitializeError(t *testing.T) {
func Test_Get_OK(t *testing.T) {
drivers = make(map[string]Driver)
driver := &MockDriver{}
driver.new = func(_, _ string) (Conn, error) {
driver.new = func(_, _, _ string) (Conn, error) {
return nil, nil
}

Expand All @@ -89,7 +89,7 @@ func Test_Get_OK(t *testing.T) {
t.Fail()
}

_, err = Get("test", "", "")
_, err = Get("test", "", "", "migrations")
if err != nil {
t.Fail()
}
Expand Down
15 changes: 9 additions & 6 deletions driver/mysql/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mysql

import (
"database/sql"
"fmt"

"github.com/elwinar/rambler/driver"
_ "github.com/go-sql-driver/mysql" // Where are working with the go-sql-driver/mysql driver for database/sql.
Expand All @@ -14,7 +15,7 @@ func init() {
// Driver is the type that initialize new connections.
type Driver struct{}

func (d Driver) New(dsn, schema string) (driver.Conn, error) {
func (d Driver) New(dsn, schema, table string) (driver.Conn, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
Expand All @@ -23,19 +24,21 @@ func (d Driver) New(dsn, schema string) (driver.Conn, error) {
return Conn{
db: db,
schema: schema,
table: table,
}, nil
}

// Conn holds a connection to a MySQL database schema.
type Conn struct {
db *sql.DB
schema string
table string
}

// HasTable check if the schema has the migration table needed for Rambler to operate on it.
func (c Conn) HasTable() (bool, error) {
var table string
err := c.db.QueryRow(`select table_name from information_schema.tables where table_schema = ? and table_name = ?`, c.schema, "migrations").Scan(&table)
err := c.db.QueryRow(`select table_name from information_schema.tables where table_schema = ? and table_name = ?`, c.schema, c.table).Scan(&table)
if err != nil && err != sql.ErrNoRows {
return false, err
}
Expand All @@ -49,13 +52,13 @@ func (c Conn) HasTable() (bool, error) {

// CreateTable create the migration table using a MySQL-compatible syntax.
func (c Conn) CreateTable() error {
_, err := c.db.Exec(`CREATE TABLE migrations ( migration VARCHAR(255) NOT NULL ) DEFAULT CHARSET=utf8`)
_, err := c.db.Exec(fmt.Sprintf(`CREATE TABLE %s ( migration VARCHAR(255) NOT NULL ) DEFAULT CHARSET=utf8`, c.table))
return err
}

// GetApplied returns the list of already applied migrations.
func (c Conn) GetApplied() ([]string, error) {
rows, err := c.db.Query(`SELECT migration FROM migrations ORDER BY migration ASC`)
rows, err := c.db.Query(fmt.Sprintf(`SELECT migration FROM %s ORDER BY migration ASC`, c.table))
if err != nil {
return nil, err
}
Expand All @@ -77,13 +80,13 @@ func (c Conn) GetApplied() ([]string, error) {

// AddApplied record that a migration was applied.
func (c Conn) AddApplied(migration string) error {
_, err := c.db.Exec(`INSERT INTO migrations (migration) VALUES (?)`, migration)
_, err := c.db.Exec(fmt.Sprintf(`INSERT INTO %s (migration) VALUES (?)`, c.table), migration)
return err
}

// RemoveApplied record that a migration was reversed.
func (c Conn) RemoveApplied(migration string) error {
_, err := c.db.Exec(`DELETE FROM migrations WHERE migration = ?`, migration)
_, err := c.db.Exec(fmt.Sprintf(`DELETE FROM %s WHERE migration = ?`, c.table), migration)
return err
}

Expand Down
14 changes: 8 additions & 6 deletions driver/postgresql/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func init() {
type Driver struct{}

// New returns a new connection.
func (d Driver) New(dsn, schema string) (driver.Conn, error) {
func (d Driver) New(dsn, schema, table string) (driver.Conn, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
Expand All @@ -25,6 +25,7 @@ func (d Driver) New(dsn, schema string) (driver.Conn, error) {
c := &Conn{
db: db,
schema: schema,
table: table,
}

return c, nil
Expand All @@ -34,12 +35,13 @@ func (d Driver) New(dsn, schema string) (driver.Conn, error) {
type Conn struct {
db *sql.DB
schema string
table string
}

// HasTable check if the schema has the migration table.
func (c *Conn) HasTable() (bool, error) {
var name string
err := c.db.QueryRow(fmt.Sprintf(`SELECT table_name FROM information_schema.tables WHERE table_catalog = '%s' AND table_name = 'migrations'`, c.schema)).Scan(&name)
err := c.db.QueryRow(`SELECT table_name FROM information_schema.tables WHERE table_catalog = $1 AND table_name = $2`, c.schema, c.table).Scan(&name)
if err != nil && err != sql.ErrNoRows {
return false, err
}
Expand All @@ -51,13 +53,13 @@ func (c *Conn) HasTable() (bool, error) {

// CreateTable create the migration table using a PostgreSQL-compatible syntax.
func (c *Conn) CreateTable() error {
_, err := c.db.Exec(`CREATE TABLE migrations ( migration VARCHAR(255) NOT NULL );`)
_, err := c.db.Exec(fmt.Sprintf(`CREATE TABLE %s ( migration VARCHAR(255) NOT NULL );`, c.table))
return err
}

// GetApplied returns the list of applied migrations.
func (c *Conn) GetApplied() ([]string, error) {
rows, err := c.db.Query(`SELECT migration FROM migrations ORDER BY migration ASC`)
rows, err := c.db.Query(fmt.Sprintf(`SELECT migration FROM %s ORDER BY migration ASC`, c.table))
if err != nil {
return nil, err
}
Expand All @@ -79,13 +81,13 @@ func (c *Conn) GetApplied() ([]string, error) {

// AddApplied records a migration as applied.
func (c *Conn) AddApplied(migration string) error {
_, err := c.db.Exec(`INSERT INTO migrations (migration) VALUES ($1)`, migration)
_, err := c.db.Exec(fmt.Sprintf(`INSERT INTO %s (migration) VALUES ($1)`, c.table), migration)
return err
}

// RemoveApplied records a migration as reversed.
func (c *Conn) RemoveApplied(migration string) error {
_, err := c.db.Exec(`DELETE FROM migrations WHERE migration = $1`, migration)
_, err := c.db.Exec(fmt.Sprintf(`DELETE FROM %s WHERE migration = $1`, c.table), migration)
return err
}

Expand Down
Loading

0 comments on commit cf984bb

Please sign in to comment.