Skip to content

Commit

Permalink
Merge pull request #7 from josephspurrier/separate-mysql
Browse files Browse the repository at this point in the history
Separate mysql and close a few issues
  • Loading branch information
josephspurrier committed Jan 11, 2019
2 parents 37149ae + 19750ba commit 37a2cca
Show file tree
Hide file tree
Showing 20 changed files with 826 additions and 313 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# OS files
.DS_Store
thumbs.db

# Folders
**/vendor
172 changes: 168 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# rove
# Rove

[![Go Report Card](https://goreportcard.com/badge/github.com/josephspurrier/rove)](https://goreportcard.com/report/github.com/josephspurrier/rove)
[![Build Status](https://travis-ci.org/josephspurrier/rove.svg)](https://travis-ci.org/josephspurrier/rove)
Expand All @@ -8,17 +8,26 @@

This project is based off Liquibase, the database migration tool. It uses a slimmed down version of the DATABASECHANGELOG database table to store the applied changesets. It only supports SQL (no XML) migrations currently. For the most part, you can take your existing SQL migration files and use them with this tool. You can also import this package into your application and apply changesets without having to store the migrations on physical files. This allows you manage DB migrations inside of your application so you can distribute single binary applications.

**Note:** Do not run this tool on a database that already has Liquibase migrations applied - they are not compatiable because the way the checksums are calculated is different. The DATABASECHANGELOG database table which is used for storing the changesets is also different.
**Note:** Do not run this tool on a database that already has Liquibase migrations applied - they are not compatible because the checksums are calculated is different. The DATABASECHANGELOG database table which is used for storing the changesets is also different.

## Dependencies

```
gopkg.in/alecthomas/kingpin.v2
github.com/go-sql-driver/mysql
github.com/jmoiron/sqlx
github.com/stretchr/testify/assert
```

## Quick Start with Docker Compose

You can build a docker image from this repository and set it up along with a MySQL container using docker compose.
You can build a docker image from this repository and set it up along with a MySQL container using Docker Compose.

```bash
# Create a docker image.
docker build -t rove:latest .

# Launch MySQL and the rove with docker compose.
# Launch MySQL and the Rove tool with Docker Compose.
docker-compose up

# The database should now have the sample migrations applied.
Expand Down Expand Up @@ -49,4 +58,159 @@ go install
DB_USERNAME=root DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi rove migrate all testdata/success.sql
# or apply the database migrations with a password.
DB_USERNAME=root DB_PASSWORD=somepassword DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi rove migrate all testdata/success.sql
```

## Rove Usage

The Rove package can be used as a standalone CLI application or you can import the package into your own code.

### Rove via CLI

The Rove CLI app uses [Kingpin](https://github.com/alecthomas/kingpin) to handle command-line parsing. To view the info on any of the commands, you can use `--help` as an argument.

Here are the commands available:

```
usage: rove [<flags>] <command> [<args> ...]
Performs database migration tasks.
Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
Commands:
help [<command>...]
Show help.
migrate all <file>
Apply all changesets to the database.
migrate up <count> <file>
Apply a specific number of changesets to the database.
migrate reset <file>
Run all rollbacks on the database.
migrate down <count> <file>
Apply a specific number of rollbacks to the database.
migrate status
Output the list of migrations already applied to the database.
```

#### Environment Variables

The following environment variables can be read by the CLI app:

```
DB_USERNAME - database username
DB_PASSWORD - database password
DB_HOSTNAME - IP or hostname of the database
DB_PORT - port of the database
DB_DATABASE - name of the database
DB_PARAMETER - parameters to append to the database connection string
```

A full list of MySQL parameters can be found [here](https://github.com/go-sql-driver/mysql#parameters).

#### Example Commands

Here are examples of the commands:

```bash
# Set the environment variables to connect to the database.
export DB_USERNAME=root
export DB_PASSWORD=password
export DB_HOSTNAME=127.0.0.1
export DB_PORT=3306
export DB_DATABASE=webapi
export DB_PARAMETER="collation=utf8mb4_unicode_ci&parseTime=true"

# Apply all of the changes from the SQL file to the database.
rove migrate all testdata/changeset.sql
# Output:
# Changeset applied: josephspurrier:1
# Changeset applied: josephspurrier:2
# Changeset applied: josephspurrier:3

# Try to apply all the changes again.
rove migrate all testdata/changeset.sql
# Output:
# Changeset already applied: josephspurrier:1
# Changeset already applied: josephspurrier:2
# Changeset already applied: josephspurrier:3

# Rollback all of the changes to the database.
rove migrate reset testdata/changeset.sql
# Output:
# Rollback applied: josephspurrier:3
# Rollback applied: josephspurrier:2
# Rollback applied: josephspurrier:1

# Apply only 1 new change to the database.
rove migrate up 1 testdata/success.sql
# Output:
# Changeset applied: josephspurrier:1

# Apply 1 more change to the database.
rove migrate up 1 testdata/changeset.sql
# Output:
# Changeset already applied: josephspurrier:1
# Changeset applied: josephspurrier:2

# Rollback only 1 change to the database.
rove migrate down 1 testdata/changeset.sql
# Output:
# Rollback applied: josephspurrier:2

# Show a list of migrations already applied to the database.
rove migrate status
# Output:
# Changeset: josephspurrier:1
```

### Rove via Package Import

Below is an example of how to include Rove in your own packages.

```go
var changesets = `
--changeset josephspurrier:1
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
CREATE TABLE user_status (
id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT,
status VARCHAR(25) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
--rollback DROP TABLE user_status;
--changeset josephspurrier:2
INSERT INTO user_status (id, status, created_at, updated_at, deleted) VALUES
(1, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0),
(2, 'inactive', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0);
--rollback TRUNCATE TABLE user_status;`

// Create a new MySQL database object.
db, err := database.NewMySQL(&database.Connection{
Hostname: "127.0.0.1",
Username: "root",
Password: "password",
Database: "main",
Port: 3306,
Parameter: "collation=utf8mb4_unicode_ci&parseTime=true",
})
if err != nil {
log.Fatalln(err)
}

// Perform all migrations against the database.
r := rove.NewChangesetMigration(db, changesets)
r.Verbose = true
err = r.Migrate(0)
```
3 changes: 1 addition & 2 deletions cmd/rove/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
rove
rove.exe
rove
59 changes: 38 additions & 21 deletions cmd/rove/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/josephspurrier/rove"
"github.com/josephspurrier/rove/pkg/database"

kingpin "gopkg.in/alecthomas/kingpin.v2"
)
Expand All @@ -27,37 +28,53 @@ var (
cDBDown = cDB.Command("down", "Apply a specific number of rollbacks to the database.")
cDBDownCount = cDBDown.Arg("count", "Number of rollbacks [int].").Required().Int()
cDBDownFile = cDBDown.Arg("file", "Filename of the migration file [string].").Required().String()

cDBStatus = cDB.Command("status", "Output the list of migrations already applied to the database.")
)

func main() {
argList := os.Args[1:]
arg := kingpin.MustParse(app.Parse(argList))

// Create a new MySQL database object.
conn, err := database.NewConnection(*cDBPrefix)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

m, err := database.NewMySQL(conn)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

switch arg {
case cDBAll.FullCommand():
err := rove.Migrate(*cDBAllFile, *cDBPrefix, 0, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
r := rove.NewFileMigration(m, *cDBAllFile)
r.Verbose = true
err = r.Migrate(0)
case cDBUp.FullCommand():
err := rove.Migrate(*cDBUpFile, *cDBPrefix, *cDBUpCount, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
r := rove.NewFileMigration(m, *cDBUpFile)
r.Verbose = true
err = r.Migrate(*cDBUpCount)
case cDBReset.FullCommand():
err := rove.Reset(*cDBResetFile, *cDBPrefix, 0, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

r := rove.NewFileMigration(m, *cDBResetFile)
r.Verbose = true
err = r.Reset(0)
case cDBDown.FullCommand():
err := rove.Reset(*cDBDownFile, *cDBPrefix, *cDBDownCount, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
r := rove.NewFileMigration(m, *cDBDownFile)
r.Verbose = true
err = r.Reset(*cDBDownCount)
case cDBStatus.FullCommand():
r := rove.NewFileMigration(m, *cDBDownFile)
r.Verbose = true
_, err = r.Status()
}

// If there is an error, return with an error code of 1.
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
6 changes: 3 additions & 3 deletions cmd/rove/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"os"
"testing"

"github.com/josephspurrier/rove/pkg/database"
"github.com/josephspurrier/rove/pkg/testutil"

"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
)

Expand All @@ -16,7 +16,7 @@ func TestMigrationAll(t *testing.T) {
testutil.TeardownDatabase(unique)
}

func migrateAll(t *testing.T) (*database.DBW, string) {
func migrateAll(t *testing.T) (*sqlx.DB, string) {
db, unique := testutil.SetupDatabase()

// Set the arguments.
Expand Down Expand Up @@ -95,7 +95,7 @@ func TestMigrationUp(t *testing.T) {
testutil.TeardownDatabase(unique)
}

func migrateUp(t *testing.T) (*database.DBW, string) {
func migrateUp(t *testing.T) (*sqlx.DB, string) {
db, unique := testutil.SetupDatabase()

// Set the arguments.
Expand Down
50 changes: 50 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package rove_test

import (
"log"

"github.com/josephspurrier/rove"
"github.com/josephspurrier/rove/pkg/database"
)

func Example() {
var changesets = `
--changeset josephspurrier:1
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
CREATE TABLE user_status (
id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT,
status VARCHAR(25) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
--rollback DROP TABLE user_status;
--changeset josephspurrier:2
INSERT INTO user_status (id, status, created_at, updated_at, deleted) VALUES
(1, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0),
(2, 'inactive', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0);
--rollback TRUNCATE TABLE user_status;`

// Create a new MySQL database object.
db, err := database.NewMySQL(&database.Connection{
Hostname: "127.0.0.1",
Username: "root",
Password: "password",
Database: "main",
Port: 3306,
Parameter: "collation=utf8mb4_unicode_ci&parseTime=true",
})
if err != nil {
log.Fatalln(err)
}

// Perform all migrations against the database.
r := rove.NewChangesetMigration(db, changesets)
r.Verbose = true
err = r.Migrate(0)
}
Loading

0 comments on commit 37a2cca

Please sign in to comment.