-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a6a6ba4
commit ba9f499
Showing
9 changed files
with
524 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package basemigrate | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
const ( | ||
sqlChangelog = `CREATE TABLE IF NOT EXISTS databasechangelog ( | ||
id varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, | ||
author varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, | ||
filename varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, | ||
dateexecuted datetime NOT NULL, | ||
orderexecuted int(11) NOT NULL, | ||
md5sum varchar(35) COLLATE utf8mb4_unicode_ci DEFAULT NULL, | ||
description varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, | ||
tag varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, | ||
version varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL | ||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;` | ||
) | ||
|
||
const ( | ||
appVersion = "1.0" | ||
elementChangeset = "--changeset " | ||
elementRollback = "--rollback " | ||
) | ||
|
||
var ( | ||
// ErrInvalidHeader is when the changeset header is invalid. | ||
ErrInvalidHeader = errors.New("invalid changeset header") | ||
// ErrInvalidFormat is when a changeset is not found. | ||
ErrInvalidFormat = errors.New("invalid changeset format") | ||
) | ||
|
||
// ParseFile will parse a file into changesets. | ||
func ParseFile(filename string) ([]*Changeset, error) { | ||
f, err := os.Open(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer f.Close() | ||
|
||
return parse(f, filename) | ||
} | ||
|
||
// Migrate will migrate a file. | ||
func Migrate(filename string, verbose bool) (err error) { | ||
db, err := connect() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Create the DATABASECHANGELOG. | ||
_, err = db.Exec(sqlChangelog) | ||
|
||
// Get the changesets. | ||
arr, err := ParseFile(filename) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Loop through each changeset. | ||
for _, cs := range arr { | ||
checksum := "" | ||
newChecksum := cs.Checksum() | ||
|
||
// Determine if the changeset was already applied. | ||
// Count the number of rows. | ||
err = db.Get(&checksum, `SELECT md5sum | ||
FROM databasechangelog | ||
WHERE id = ? | ||
AND author = ?`, cs.id, cs.author) | ||
if err == nil { | ||
// Determine if the checksums match. | ||
if checksum != newChecksum { | ||
return fmt.Errorf("checksum does not match - existing changeset %v:%v has checksum %v, but new changeset has checksum %v", | ||
cs.author, cs.id, checksum, newChecksum) | ||
} | ||
|
||
if verbose { | ||
fmt.Printf("Changeset already applied: %v:%v\n", cs.author, cs.id) | ||
} | ||
continue | ||
} else if err != nil && err != sql.ErrNoRows { | ||
return err | ||
} | ||
|
||
arrQueries := strings.Split(cs.Changes(), ";") | ||
// Loop through each change. | ||
for _, q := range arrQueries { | ||
if len(q) == 0 { | ||
continue | ||
} | ||
|
||
// Execute the query. | ||
_, err = db.Exec(q) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Count the number of rows. | ||
count := 0 | ||
err = db.Get(&count, `SELECT COUNT(*) FROM databasechangelog`) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Insert the record. | ||
_, err = db.Exec(` | ||
INSERT INTO databasechangelog | ||
(id,author,filename,dateexecuted,orderexecuted,md5sum,description,version) | ||
VALUES(?,?,?,NOW(),?,?,?,?) | ||
`, cs.id, cs.author, cs.filename, count+1, newChecksum, cs.description, cs.version) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if verbose { | ||
fmt.Printf("Changeset applied: %v:%v\n", cs.author, cs.id) | ||
} | ||
} | ||
|
||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package basemigrate_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"app/webapi/pkg/basemigrate" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMigration(t *testing.T) { | ||
err := basemigrate.Migrate("testdata/success.sql", false) | ||
assert.Nil(t, err) | ||
} | ||
|
||
func TestMigrationFailDuplicate(t *testing.T) { | ||
err := basemigrate.Migrate("testdata/fail-duplicate.sql", false) | ||
assert.Contains(t, err.Error(), "checksum does not match") | ||
} | ||
|
||
func TestParse(t *testing.T) { | ||
arr, err := basemigrate.ParseFile("testdata/success.sql") | ||
assert.Nil(t, err) | ||
assert.Equal(t, 5, len(arr)) | ||
|
||
//basemigrate.Debug(arr) | ||
|
||
/*for _, v := range arr { | ||
fmt.Println(v.Changes()) | ||
fmt.Println(v.Rollbacks()) | ||
fmt.Println("MD5:", v.Checksum()) | ||
break | ||
} | ||
fmt.Println("Total:", len(arr))*/ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package basemigrate | ||
|
||
import ( | ||
"strings" | ||
) | ||
|
||
// Changeset is a SQL changeset. | ||
type Changeset struct { | ||
id string | ||
author string | ||
filename string | ||
md5 string | ||
description string | ||
version string | ||
|
||
change []string | ||
rollback []string | ||
} | ||
|
||
// ParseHeader will parse the header information. | ||
func (cs *Changeset) ParseHeader(line string) error { | ||
arr := strings.Split(line, ":") | ||
if len(arr) != 2 { | ||
return ErrInvalidHeader | ||
} | ||
|
||
cs.author = arr[0] | ||
cs.id = arr[1] | ||
|
||
return nil | ||
} | ||
|
||
// SetFileInfo will set the file information. | ||
func (cs *Changeset) SetFileInfo(filename string, description string, version string) { | ||
cs.filename = filename | ||
cs.description = description | ||
cs.version = version | ||
} | ||
|
||
// AddRollback will add a rollback command. | ||
func (cs *Changeset) AddRollback(line string) { | ||
if len(cs.rollback) == 0 { | ||
cs.rollback = make([]string, 0) | ||
} | ||
cs.rollback = append(cs.rollback, line) | ||
} | ||
|
||
// AddChange will add a change command. | ||
func (cs *Changeset) AddChange(line string) { | ||
if len(cs.change) == 0 { | ||
cs.change = make([]string, 0) | ||
} | ||
cs.change = append(cs.change, line) | ||
} | ||
|
||
// Changes will return all the changes. | ||
func (cs *Changeset) Changes() string { | ||
return strings.Join(cs.change, "\n") | ||
} | ||
|
||
// Rollbacks will return all the rollbacks. | ||
func (cs *Changeset) Rollbacks() string { | ||
return strings.Join(cs.rollback, "\n") | ||
} | ||
|
||
// Checksum returns an MD5 checksum for the changeset. | ||
func (cs *Changeset) Checksum() string { | ||
return md5sum(cs.Changes()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package basemigrate | ||
|
||
import ( | ||
"bytes" | ||
"crypto/md5" | ||
"fmt" | ||
"io" | ||
|
||
"app/webapi/pkg/database" | ||
|
||
"github.com/jmoiron/sqlx" | ||
) | ||
|
||
// connect will connect to the database. | ||
func connect() (*sqlx.DB, error) { | ||
dbc := new(database.Connection) | ||
dbc.Hostname = "127.0.0.1" | ||
dbc.Port = 3306 | ||
dbc.Username = "root" | ||
dbc.Password = "" | ||
dbc.Database = "webapitest" | ||
dbc.Parameter = "parseTime=true&allowNativePasswords=true" | ||
|
||
return dbc.Connect(true) | ||
} | ||
|
||
// md5sum will return a checksum from a string. | ||
func md5sum(s string) string { | ||
h := md5.New() | ||
r := bytes.NewReader([]byte(s)) | ||
_, _ = io.Copy(h, r) | ||
return fmt.Sprintf("%x", h.Sum(nil)) | ||
} | ||
|
||
// Debug will return the SQL file. | ||
func Debug(arr []*Changeset) { | ||
for _, cs := range arr { | ||
fmt.Printf("%v%v:%v\n", elementChangeset, cs.author, cs.id) | ||
fmt.Println(cs.Changes()) | ||
fmt.Printf("%v%v\n", elementRollback, cs.Rollbacks()) | ||
fmt.Println("--md5", cs.Checksum()) | ||
break | ||
} | ||
|
||
fmt.Println("Total:", len(arr)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package basemigrate | ||
|
||
import ( | ||
"bufio" | ||
"io" | ||
"path" | ||
"strings" | ||
) | ||
|
||
// parse will split the SQL migration into pieces. | ||
func parse(r io.Reader, filename string) ([]*Changeset, error) { | ||
scanner := bufio.NewScanner(r) | ||
scanner.Split(bufio.ScanLines) | ||
|
||
// Array of changesets. | ||
arr := make([]*Changeset, 0) | ||
|
||
for scanner.Scan() { | ||
// Get the line without leading or trailing spaces. | ||
line := strings.TrimSpace(scanner.Text()) | ||
|
||
// Skip blank lines. | ||
if len(line) == 0 { | ||
continue | ||
} | ||
|
||
// Start recording the changeset. | ||
if strings.HasPrefix(line, elementChangeset) { | ||
// Create a new changeset. | ||
cs := new(Changeset) | ||
cs.ParseHeader(strings.TrimLeft(line, elementChangeset)) | ||
cs.SetFileInfo(path.Base(filename), "sql", appVersion) | ||
arr = append(arr, cs) | ||
continue | ||
} | ||
|
||
// If the length of the array is 0, then the first changeset is missing. | ||
if len(arr) == 0 { | ||
return nil, ErrInvalidFormat | ||
} | ||
|
||
// Determine if the line is a rollback. | ||
if strings.HasPrefix(line, elementRollback) { | ||
cs := arr[len(arr)-1] | ||
cs.AddRollback(strings.TrimLeft(line, elementRollback)) | ||
continue | ||
} | ||
|
||
// Determine if the line is comment, ignore it. | ||
if strings.HasPrefix(line, "--") { | ||
continue | ||
} | ||
|
||
// Add the line as a changeset. | ||
cs := arr[len(arr)-1] | ||
cs.AddChange(line) | ||
} | ||
|
||
return arr, nil | ||
} |
Oops, something went wrong.