Preliminary MySQL support. Barely tested.

Requirements:
MySQL/MariaDB need to be configured with this settings:

  innodb_file_format = Barracuda
  innodb_file_per_table = On
  innodb_large_prefix = On

to support key prefix length up to 3042 bytes.

MySQL/MariaDB DSN will need this parameter:

  parseTime=true

as per [1]

The migration system itself mostly inspired by Rails (ActiveRecord),
but it still rough at the edges. Could use some inputs.

Next Todo: more testing!

[1]  https://github.com/go-sql-driver/mysql#parsetime
This commit is contained in:
Nurahmadie 2014-03-14 02:14:16 +07:00
parent 5903eb8f04
commit 7cf4f2eb89
8 changed files with 96 additions and 35 deletions

View file

@ -51,7 +51,9 @@ func Init(name, datasource string) error {
} }
migration := migrate.New(db) migration := migrate.New(db)
migration.All().Migrate() if err := migration.All().Migrate(); err != nil {
return err
}
return nil return nil
} }

View file

@ -52,7 +52,7 @@ func (r *rev1st) Up(mg *MigrationDriver) error {
if _, err := mg.CreateTable("repos", []string{ if _, err := mg.CreateTable("repos", []string{
t.Integer("id", PRIMARYKEY, AUTOINCREMENT), t.Integer("id", PRIMARYKEY, AUTOINCREMENT),
t.Varchar("slug", 1024, UNIQUE), t.String("slug", UNIQUE),
t.String("host"), t.String("host"),
t.String("owner"), t.String("owner"),
t.String("name"), t.String("name"),
@ -113,7 +113,7 @@ func (r *rev1st) Up(mg *MigrationDriver) error {
} }
_, err := mg.CreateTable("settings", []string{ _, err := mg.CreateTable("settings", []string{
t.Integer("id", PRIMARYKEY), t.Integer("id", PRIMARYKEY, AUTOINCREMENT),
t.String("github_key"), t.String("github_key"),
t.String("github_secret"), t.String("github_secret"),
t.String("bitbucket_key"), t.String("bitbucket_key"),

View file

@ -13,11 +13,11 @@ func (r *rev2nd) Up(mg *MigrationDriver) error {
return err return err
} }
if _, err := mg.AddIndex("members", []string{"team_id"}, ""); err != nil { if _, err := mg.AddIndex("members", []string{"team_id"}); err != nil {
return err return err
} }
if _, err := mg.AddIndex("members", []string{"user_id"}, ""); err != nil { if _, err := mg.AddIndex("members", []string{"user_id"}); err != nil {
return err return err
} }
@ -25,27 +25,27 @@ func (r *rev2nd) Up(mg *MigrationDriver) error {
return err return err
} }
if _, err := mg.AddIndex("commits", []string{"repo_id"}, ""); err != nil { if _, err := mg.AddIndex("commits", []string{"repo_id"}); err != nil {
return err return err
} }
if _, err := mg.AddIndex("commits", []string{"repo_id", "branch"}, ""); err != nil { if _, err := mg.AddIndex("commits", []string{"repo_id", "branch"}); err != nil {
return err return err
} }
if _, err := mg.AddIndex("repos", []string{"team_id"}, ""); err != nil { if _, err := mg.AddIndex("repos", []string{"team_id"}); err != nil {
return err return err
} }
if _, err := mg.AddIndex("repos", []string{"user_id"}, ""); err != nil { if _, err := mg.AddIndex("repos", []string{"user_id"}); err != nil {
return err return err
} }
if _, err := mg.AddIndex("builds", []string{"commit_id"}, ""); err != nil { if _, err := mg.AddIndex("builds", []string{"commit_id"}); err != nil {
return err return err
} }
_, err := mg.AddIndex("builds", []string{"commit_id", "slug"}, "") _, err := mg.AddIndex("builds", []string{"commit_id", "slug"})
return err return err
} }

View file

@ -22,7 +22,7 @@ type Operation interface {
RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error) RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error)
AddIndex(tableName string, columns []string, flag string) (sql.Result, error) AddIndex(tableName string, columns []string, flags ...string) (sql.Result, error)
DropIndex(tableName string, columns []string) (sql.Result, error) DropIndex(tableName string, columns []string) (sql.Result, error)
} }

View file

@ -28,7 +28,7 @@ import (
const migrationTableStmt = ` const migrationTableStmt = `
CREATE TABLE IF NOT EXISTS migration ( CREATE TABLE IF NOT EXISTS migration (
revision NUMBER PRIMARY KEY revision BIGINT PRIMARY KEY
) )
` `

View file

@ -2,7 +2,8 @@ package migrate
import ( import (
"database/sql" "database/sql"
"errors" "fmt"
"strings"
) )
type mysqlDriver struct { type mysqlDriver struct {
@ -13,41 +14,96 @@ func MySQL(tx *sql.Tx) *MigrationDriver {
return &MigrationDriver{ return &MigrationDriver{
Tx: tx, Tx: tx,
Operation: &mysqlDriver{Tx: tx}, Operation: &mysqlDriver{Tx: tx},
T: &columnType{
AttrMap: map[int]string{AUTOINCREMENT: "AUTO_INCREMENT"},
},
} }
} }
func (m *mysqlDriver) CreateTable(tableName string, args []string) (sql.Result, error) { func (m *mysqlDriver) CreateTable(tableName string, args []string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("CREATE TABLE %s (%s) ROW_FORMAT=DYNAMIC", tableName, strings.Join(args, ", ")))
} }
func (m *mysqlDriver) RenameTable(tableName, newName string) (sql.Result, error) { func (m *mysqlDriver) RenameTable(tableName, newName string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s", tableName, newName))
} }
func (m *mysqlDriver) DropTable(tableName string) (sql.Result, error) { func (m *mysqlDriver) DropTable(tableName string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName))
} }
func (m *mysqlDriver) AddColumn(tableName, columnSpec string) (sql.Result, error) { func (m *mysqlDriver) AddColumn(tableName, columnSpec string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("ALTER TABLE %s ADD COLUMN (%s)", tableName, columnSpec))
} }
func (m *mysqlDriver) ChangeColumn(tableName, columnName, newSpecs string) (sql.Result, error) { func (m *mysqlDriver) ChangeColumn(tableName, columnName, newSpecs string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("ALTER TABLE %s MODIFY %s %s", tableName, columnName, newSpecs))
} }
func (m *mysqlDriver) DropColumns(tableName string, columnsToDrop []string) (sql.Result, error) { func (m *mysqlDriver) DropColumns(tableName string, columnsToDrop []string) (sql.Result, error) {
return nil, errors.New("not implemented yet") for k, v := range columnsToDrop {
columnsToDrop[k] = fmt.Sprintf("DROP %s", v)
}
return m.Tx.Exec(fmt.Sprintf("ALTER TABLE %s %s", tableName, strings.Join(columnsToDrop, ", ")))
} }
func (m *mysqlDriver) RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error) { func (m *mysqlDriver) RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error) {
return nil, errors.New("not implemented yet") var columns []string
tableSQL, err := m.getTableDefinition(tableName)
if err != nil {
return nil, err
}
columns, err = fetchColumns(tableSQL)
if err != nil {
return nil, err
}
var colspec []string
for k, v := range columnChanges {
for _, col := range columns {
col = strings.Trim(col, " \n")
cols := strings.SplitN(col, " ", 2)
if quote(k) == cols[0] {
colspec = append(colspec, fmt.Sprintf("CHANGE %s %s %s", k, v, cols[1]))
break
}
}
}
return m.Tx.Exec(fmt.Sprintf("ALTER TABLE %s %s", tableName, strings.Join(colspec, ", ")))
} }
func (m *mysqlDriver) AddIndex(tableName string, columns []string, flag string) (sql.Result, error) { func (m *mysqlDriver) AddIndex(tableName string, columns []string, flags ...string) (sql.Result, error) {
return nil, errors.New("not implemented yet") flag := ""
if len(flags) > 0 {
switch strings.ToUpper(flags[0]) {
case "UNIQUE":
fallthrough
case "FULLTEXT":
fallthrough
case "SPATIAL":
flag = flags[0]
}
}
return m.Tx.Exec(fmt.Sprintf("CREATE %s INDEX %s ON %s (%s)", flag,
indexName(tableName, columns), tableName, strings.Join(columns, ", ")))
} }
func (m *mysqlDriver) DropIndex(tableName string, columns []string) (sql.Result, error) { func (m *mysqlDriver) DropIndex(tableName string, columns []string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return m.Tx.Exec(fmt.Sprintf("DROP INDEX %s on %s", indexName(tableName, columns), tableName))
}
func (m *mysqlDriver) getTableDefinition(tableName string) (string, error) {
var name, def string
st := fmt.Sprintf("SHOW CREATE TABLE %s", tableName)
if err := m.Tx.QueryRow(st).Scan(&name, &def); err != nil {
return "", err
}
return def, nil
}
func quote(name string) string {
return fmt.Sprintf("`%s`", name)
} }

View file

@ -44,7 +44,7 @@ func (p *postgresqlDriver) RenameColumns(tableName string, columnChanges map[str
return nil, errors.New("not implemented yet") return nil, errors.New("not implemented yet")
} }
func (p *postgresqlDriver) AddIndex(tableName string, columns []string, flag string) (sql.Result, error) { func (p *postgresqlDriver) AddIndex(tableName string, columns []string, flags ...string) (sql.Result, error) {
return nil, errors.New("not implemented yet") return nil, errors.New("not implemented yet")
} }

View file

@ -19,19 +19,19 @@ func SQLite(tx *sql.Tx) *MigrationDriver {
} }
func (s *sqliteDriver) CreateTable(tableName string, args []string) (sql.Result, error) { func (s *sqliteDriver) CreateTable(tableName string, args []string) (sql.Result, error) {
return s.Tx.Exec(fmt.Sprintf("CREATE TABLE %s (%s);", tableName, strings.Join(args, ", "))) return s.Tx.Exec(fmt.Sprintf("CREATE TABLE %s (%s)", tableName, strings.Join(args, ", ")))
} }
func (s *sqliteDriver) RenameTable(tableName, newName string) (sql.Result, error) { func (s *sqliteDriver) RenameTable(tableName, newName string) (sql.Result, error) {
return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s;", tableName, newName)) return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s", tableName, newName))
} }
func (s *sqliteDriver) DropTable(tableName string) (sql.Result, error) { func (s *sqliteDriver) DropTable(tableName string) (sql.Result, error) {
return s.Tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s;", tableName)) return s.Tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName))
} }
func (s *sqliteDriver) AddColumn(tableName, columnSpec string) (sql.Result, error) { func (s *sqliteDriver) AddColumn(tableName, columnSpec string) (sql.Result, error) {
return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s;", tableName, columnSpec)) return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", tableName, columnSpec))
} }
func (s *sqliteDriver) ChangeColumn(tableName, columnName, newType string) (sql.Result, error) { func (s *sqliteDriver) ChangeColumn(tableName, columnName, newType string) (sql.Result, error) {
@ -173,7 +173,7 @@ func (s *sqliteDriver) DropColumns(tableName string, columnsToDrop []string) (sq
} }
// Move data from old table // Move data from old table
if result, err = s.Tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT %s FROM %s;", tableName, if result, err = s.Tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT %s FROM %s", tableName,
strings.Join(selectName(preparedColumns), ", "), proxy)); err != nil { strings.Join(selectName(preparedColumns), ", "), proxy)); err != nil {
return result, err return result, err
} }
@ -291,9 +291,12 @@ func (s *sqliteDriver) RenameColumns(tableName string, columnChanges map[string]
return result, err return result, err
} }
func (s *sqliteDriver) AddIndex(tableName string, columns []string, flag string) (sql.Result, error) { func (s *sqliteDriver) AddIndex(tableName string, columns []string, flags ...string) (sql.Result, error) {
if strings.ToUpper(flag) != "UNIQUE" { flag := ""
flag = "" if len(flags) > 0 {
if strings.ToUpper(flags[0]) == "UNIQUE" {
flag = flags[0]
}
} }
return s.Tx.Exec(fmt.Sprintf("CREATE %s INDEX %s ON %s (%s)", flag, indexName(tableName, columns), return s.Tx.Exec(fmt.Sprintf("CREATE %s INDEX %s ON %s (%s)", flag, indexName(tableName, columns),
tableName, strings.Join(columns, ", "))) tableName, strings.Join(columns, ", ")))
@ -305,7 +308,7 @@ func (s *sqliteDriver) DropIndex(tableName string, columns []string) (sql.Result
func (s *sqliteDriver) getTableDefinition(tableName string) (string, error) { func (s *sqliteDriver) getTableDefinition(tableName string) (string, error) {
var sql string var sql string
query := `SELECT sql FROM sqlite_master WHERE type='table' and name=?;` query := `SELECT sql FROM sqlite_master WHERE type='table' and name=?`
err := s.Tx.QueryRow(query, tableName).Scan(&sql) err := s.Tx.QueryRow(query, tableName).Scan(&sql)
if err != nil { if err != nil {
return "", err return "", err
@ -316,7 +319,7 @@ func (s *sqliteDriver) getTableDefinition(tableName string) (string, error) {
func (s *sqliteDriver) getIndexDefinition(tableName string) ([]string, error) { func (s *sqliteDriver) getIndexDefinition(tableName string) ([]string, error) {
var sqls []string var sqls []string
query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name=?;` query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name=?`
rows, err := s.Tx.Query(query, tableName) rows, err := s.Tx.Query(query, tableName)
if err != nil { if err != nil {
return sqls, err return sqls, err