More tests for alter columns migration

Also change the way we handle columns rename migration.
SQLite restrict column addition not to have PRIMARY KEY and/or
UNIQUE attribute, so we have to change from:

add new column -> migrate data from old column to new column ->
rename old table -> create new table with old columns removed ->
migrate data from old table to the new table -> drop old table

to directly:
rename old table -> create new table with renamed columns ->
migrate data from old table to the new table -> drop old table
This commit is contained in:
Nurahmadie 2014-02-16 00:56:03 +07:00
parent 4465b2654d
commit 8ce87f0d2c
3 changed files with 100 additions and 24 deletions

View file

@ -146,7 +146,7 @@ func (m *Migration) up(target, current int64) error {
// loop through and execute revisions
for _, rev := range m.revs {
if rev.Revision() > current {
if rev.Revision() > current && rev.Revision() <= target {
current = rev.Revision()
// execute the revision Upgrade.
if err := rev.Up(op); err != nil {

View file

@ -85,7 +85,7 @@ func (s *SQLiteDriver) DropColumns(tableName string, columnsToDrop []string) (sq
}
// Clean up proxy table
return s.Tx.Exec(fmt.Sprintf("DROP TABLE %s;", proxyName))
return s.DropTable(proxyName)
}
func (s *SQLiteDriver) RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error) {
@ -99,30 +99,46 @@ func (s *SQLiteDriver) RenameColumns(tableName string, columnChanges map[string]
return nil, err
}
var oldColumns []string
// We need a list of columns name to migrate data to the new table
var oldColumnsName = selectName(columns)
// newColumns will be used to create the new table
var newColumns []string
for k, column := range selectName(columns) {
for k, column := range oldColumnsName {
added := false
for Old, New := range columnChanges {
if column == Old {
columnToAdd := strings.Replace(columns[k], Old, New, 1)
if results, err := s.AddColumn(tableName, columnToAdd); err != nil {
return results, err
}
oldColumns = append(oldColumns, Old)
newColumns = append(newColumns, New)
newColumns = append(newColumns, columnToAdd)
added = true
break
}
}
if !added {
newColumns = append(newColumns, columns[k])
}
}
statement := fmt.Sprintf("UPDATE %s SET %s;", tableName, setForUpdate(oldColumns, newColumns))
if results, err := s.Tx.Exec(statement); err != nil {
return results, err
// Rename current table
proxyName := fmt.Sprintf("%s_%s", tableName, uniuri.NewLen(16))
if result, err := s.RenameTable(tableName, proxyName); err != nil {
return result, err
}
return s.DropColumns(tableName, oldColumns)
// Create new table with the new columns
if result, err := s.CreateTable(tableName, newColumns); err != nil {
return result, err
}
// Migrate data
if result, err := s.Tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT %s FROM %s", tableName,
strings.Join(oldColumnsName, ", "), proxyName)); err != nil {
return result, err
}
// Clean up proxy table
return s.DropTable(proxyName)
}
func (s *SQLiteDriver) getDDLFromTable(tableName string) (string, error) {

View file

@ -24,14 +24,10 @@ type AddColumnSample struct {
ID int64 `meddler:"id,pk"`
Imel string `meddler:"imel"`
Name string `meddler:"name"`
Url string `meddler:"url"`
Num int64 `meddler:"num"`
}
type RemoveColumnSample struct {
ID int64 `meddler:"id,pk"`
Name string `meddler:"name"`
}
// ---------- revision 1
type revision1 struct{}
@ -84,12 +80,12 @@ func (r *revision3) Up(op Operation) error {
if _, err := op.AddColumn("samples", "url VARCHAR(255)"); err != nil {
return err
}
_, err := op.AddColumn("samples", "likes INTEGER")
_, err := op.AddColumn("samples", "num INTEGER")
return err
}
func (r *revision3) Down(op Operation) error {
_, err := op.DropColumns("samples", []string{"likes", "url"})
_, err := op.DropColumns("samples", []string{"num", "url"})
return err
}
@ -99,6 +95,30 @@ func (r *revision3) Revision() int64 {
// ---------- end of revision 3
// ---------- revision 4
type revision4 struct{}
func (r *revision4) Up(op Operation) error {
_, err := op.RenameColumns("samples", map[string]string{
"imel": "email",
})
return err
}
func (r *revision4) Down(op Operation) error {
_, err := op.RenameColumns("samples", map[string]string{
"email": "imel",
})
return err
}
func (r *revision4) Revision() int64 {
return 4
}
// ----------
var db *sql.DB
var testSchema = `
@ -163,7 +183,7 @@ func TestMigrateRenameTable(t *testing.T) {
}
if sample.Imel != "foo@bar.com" {
t.Errorf("Column doesn't match\n\texpect:\t%s\n\tget:\t%s", "foo@bar.com", sample.Imel)
t.Errorf("Column doesn't match. Expect: %s, got: %s", "foo@bar.com", sample.Imel)
}
}
@ -198,6 +218,17 @@ func TestMigrateAddRemoveColumns(t *testing.T) {
t.Errorf("Expect length columns: %d\nGot: %d", 5, len(columns))
}
var row = AddColumnSample{
ID: 33,
Name: "Foo",
Imel: "foo@bar.com",
Url: "http://example.com",
Num: 42,
}
if err := meddler.Save(db, "samples", &row); err != nil {
t.Errorf("Can not save into database: %q", err)
}
if err := mgr.MigrateTo(1); err != nil {
t.Errorf("Can not migrate: %q", err)
}
@ -208,7 +239,36 @@ func TestMigrateAddRemoveColumns(t *testing.T) {
}
if len(another_columns) != 3 {
t.Errorf("Expect length columns: %d\nGot: %d", 3, len(columns))
t.Errorf("Expect length columns = %d, got: %d", 3, len(columns))
}
}
func TestRenameColumn(t *testing.T) {
defer tearDown()
if err := setUp(); err != nil {
t.Fatalf("Error preparing database: %q", err)
}
Driver = SQLite
mgr := New(db)
if err := mgr.Add(&revision1{}).Add(&revision4{}).MigrateTo(1); err != nil {
t.Errorf("Can not migrate: %q", err)
}
loadFixture(t)
if err := mgr.MigrateTo(4); err != nil {
t.Errorf("Can not migrate: %q", err)
}
row := RenameSample{}
if err := meddler.QueryRow(db, &row, `SELECT * FROM samples WHERE id = 3;`); err != nil {
t.Errorf("Can not query database: %q", err)
}
if row.Email != "crash@bandicoot.io" {
t.Errorf("Expect %s, got %s", "crash@bandicoot.io", row.Email)
}
}