diff --git a/pkg/database/migrate/migrate.go b/pkg/database/migrate/migrate.go index cc96f7ed..f4a286d7 100644 --- a/pkg/database/migrate/migrate.go +++ b/pkg/database/migrate/migrate.go @@ -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 { diff --git a/pkg/database/migrate/sqlite.go b/pkg/database/migrate/sqlite.go index d79e76de..512a1e2a 100644 --- a/pkg/database/migrate/sqlite.go +++ b/pkg/database/migrate/sqlite.go @@ -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) { diff --git a/pkg/database/migrate/sqlite_test.go b/pkg/database/migrate/sqlite_test.go index b9b4d9e1..17ffd4fe 100644 --- a/pkg/database/migrate/sqlite_test.go +++ b/pkg/database/migrate/sqlite_test.go @@ -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) } }