diff --git a/CHANGELOG.md b/CHANGELOG.md index d706d8ee..fa2dfd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [v3.23.3](https://github.com/markbates/pop/tree/v3.23.3) (2017-06-27) +[Full Changelog](https://github.com/markbates/pop/compare/v3.23.2...v3.23.3) + +**Closed issues:** + +- How to use callbacks? [\#83](https://github.com/markbates/pop/issues/83) + ## [v3.23.2](https://github.com/markbates/pop/tree/v3.23.2) (2017-06-19) [Full Changelog](https://github.com/markbates/pop/compare/v3.23.1...v3.23.2) @@ -278,13 +285,13 @@ [Full Changelog](https://github.com/markbates/pop/compare/v3.5.1.1...v3.6.0) ## [v3.5.1.1](https://github.com/markbates/pop/tree/v3.5.1.1) (2016-08-26) -[Full Changelog](https://github.com/markbates/pop/compare/v3.5.2...v3.5.1.1) - -## [v3.5.2](https://github.com/markbates/pop/tree/v3.5.2) (2016-08-26) -[Full Changelog](https://github.com/markbates/pop/compare/v3.5.1...v3.5.2) +[Full Changelog](https://github.com/markbates/pop/compare/v3.5.1...v3.5.1.1) ## [v3.5.1](https://github.com/markbates/pop/tree/v3.5.1) (2016-08-26) -[Full Changelog](https://github.com/markbates/pop/compare/v3.5.0...v3.5.1) +[Full Changelog](https://github.com/markbates/pop/compare/v3.5.2...v3.5.1) + +## [v3.5.2](https://github.com/markbates/pop/tree/v3.5.2) (2016-08-26) +[Full Changelog](https://github.com/markbates/pop/compare/v3.5.0...v3.5.2) ## [v3.5.0](https://github.com/markbates/pop/tree/v3.5.0) (2016-08-25) [Full Changelog](https://github.com/markbates/pop/compare/v3.4.1...v3.5.0) diff --git a/connection_details.go b/connection_details.go index ef4d60c9..290a3d35 100644 --- a/connection_details.go +++ b/connection_details.go @@ -3,6 +3,7 @@ package pop import ( "fmt" "net/url" + "regexp" "strconv" "strings" "time" @@ -34,13 +35,21 @@ type ConnectionDetails struct { Options map[string]string } +var dialectX = regexp.MustCompile(`\s+:\/\/`) + // Finalize cleans up the connection details by normalizing names, // filling in default values, etc... func (cd *ConnectionDetails) Finalize() error { if cd.URL != "" { - u, err := url.Parse(cd.URL) + ul := cd.URL + if cd.Dialect != "" { + if !dialectX.MatchString(ul) { + ul = cd.Dialect + "://" + ul + } + } + u, err := url.Parse(ul) if err != nil { - return errors.Wrapf(err, "couldn't parse %s", cd.URL) + return errors.Wrapf(err, "couldn't parse %s", ul) } cd.Dialect = u.Scheme cd.Database = u.Path diff --git a/file_migrator.go b/file_migrator.go new file mode 100644 index 00000000..97fac5d8 --- /dev/null +++ b/file_migrator.go @@ -0,0 +1,107 @@ +package pop + +import ( + "bytes" + "html/template" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/markbates/pop/fizz" + "github.com/pkg/errors" +) + +// FileMigrator is a migrator for SQL and Fizz +// files on disk at a specified path. +type FileMigrator struct { + Migrator + Path string +} + +// NewFileMigrator for a path and a Connection +func NewFileMigrator(path string, c *Connection) (FileMigrator, error) { + fm := FileMigrator{ + Migrator: NewMigrator(c), + Path: path, + } + + err := fm.findMigrations() + if err != nil { + return fm, errors.WithStack(err) + } + + return fm, nil +} + +func (fm *FileMigrator) findMigrations() error { + dir := filepath.Base(fm.Path) + if _, err := os.Stat(dir); err != nil { + // directory doesn't exist + return nil + } + filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { + if !info.IsDir() { + matches := mrx.FindAllStringSubmatch(info.Name(), -1) + if matches == nil || len(matches) == 0 { + return nil + } + m := matches[0] + mf := Migration{ + Path: p, + Version: m[1], + Name: m[2], + Direction: m[3], + Type: m[4], + Runner: func(mf Migration, tx *Connection) error { + f, err := os.Open(p) + if err != nil { + return errors.WithStack(err) + } + content, err := migrationContent(mf, tx, f) + if err != nil { + return errors.Wrapf(err, "error processing %s", mf.Path) + } + + if content == "" { + return nil + } + + err = tx.RawQuery(content).Exec() + if err != nil { + return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content) + } + return nil + }, + } + fm.Migrations[mf.Direction] = append(fm.Migrations[mf.Direction], mf) + } + return nil + }) + return nil +} + +func migrationContent(mf Migration, c *Connection, r io.Reader) (string, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return "", nil + } + + content := string(b) + + t := template.Must(template.New("sql").Parse(content)) + var bb bytes.Buffer + err = t.Execute(&bb, c.Dialect.Details()) + if err != nil { + return "", errors.Wrapf(err, "could not execute migration template %s", mf.Path) + } + content = bb.String() + + if mf.Type == "fizz" { + content, err = fizz.AString(content, c.Dialect.FizzTranslator()) + if err != nil { + return "", errors.Wrapf(err, "could not fizz the migration %s", mf.Path) + } + } + return content, nil +} diff --git a/migration.go b/migration.go index 935efc74..70834349 100644 --- a/migration.go +++ b/migration.go @@ -3,35 +3,15 @@ package pop import ( "fmt" "io/ioutil" + "log" "os" "path/filepath" - "regexp" - "sort" + "runtime" "time" - "text/tabwriter" - - "github.com/markbates/pop/fizz" "github.com/pkg/errors" ) -var mrx = regexp.MustCompile("(\\d+)_(.+)\\.(up|down)\\.(sql|fizz)") - -func init() { - MapTableName("schema_migrations", "schema_migration") - MapTableName("schema_migration", "schema_migration") -} - -var schemaMigrations = fizz.Table{ - Name: "schema_migration", - Columns: []fizz.Column{ - {Name: "version", ColType: "string"}, - }, - Indexes: []fizz.Index{ - {Name: "version_idx", Columns: []string{"version"}, Unique: true}, - }, -} - func MigrationCreate(path, name, ext string, up, down []byte) error { n := time.Now().UTC() s := n.Format("20060102150405") @@ -58,190 +38,66 @@ func MigrationCreate(path, name, ext string, up, down []byte) error { return nil } +// MigrateUp is deprecated, and will be removed in a future version. Use FileMigrator#Up instead. func (c *Connection) MigrateUp(path string) error { - now := time.Now() - defer c.dumpMigrationSchema(path) - defer printTimer(now) - - err := c.createSchemaMigrations() - if err != nil { - return errors.Wrap(err, "migration up: problem creating schema migrations") + warningMsg := "Connection#MigrateUp is deprecated, and will be removed in a future version. Use FileMigrator#Up instead." + _, file, no, ok := runtime.Caller(1) + if ok { + warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) } - return findMigrations(path, "up", 0, func(m migrationFile) error { - exists, err := c.Where("version = ?", m.Version).Exists("schema_migration") - if err != nil || exists { - return errors.Wrapf(err, "problem checking for migration version %s", m.Version) - } - err = c.Transaction(func(tx *Connection) error { - err := m.Execute(tx) - if err != nil { - return err - } - _, err = tx.Store.Exec(fmt.Sprintf("insert into schema_migration (version) values ('%s')", m.Version)) - return errors.Wrapf(err, "problem inserting migration version %s", m.Version) - }) - if err == nil { - fmt.Printf("> %s\n", m.FileName) - } - return err - }, -1) -} + log.Println(warningMsg) -func (c *Connection) dumpMigrationSchema(path string) error { - f, err := os.Create(filepath.Join(path, "schema.sql")) + mig, err := NewFileMigrator(path, c) if err != nil { return errors.WithStack(err) } - err = c.Dialect.DumpSchema(f) - if err != nil { - - return errors.WithStack(err) - } - return nil + return mig.Up() } +// MigrateDown is deprecated, and will be removed in a future version. Use FileMigrator#Down instead. func (c *Connection) MigrateDown(path string, step int) error { - now := time.Now() - defer c.dumpMigrationSchema(path) - defer printTimer(now) - - err := c.createSchemaMigrations() - if err != nil { - return errors.Wrap(err, "migration down: problem creating schema migrations") + warningMsg := "Connection#MigrateDown is deprecated, and will be removed in a future version. Use FileMigrator#Down instead." + _, file, no, ok := runtime.Caller(1) + if ok { + warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) } + log.Println(warningMsg) - //increase skip by - count, err := c.Count("schema_migration") + mig, err := NewFileMigrator(path, c) if err != nil { - return errors.Wrap(err, "migration down: unable count existing migration") + return errors.WithStack(err) } - - return findMigrations(path, "down", count, func(m migrationFile) error { - exists, err := c.Where("version = ?", m.Version).Exists("schema_migration") - if err != nil || !exists { - return errors.Wrapf(err, "problem checking for migration version %s", m.Version) - } - err = c.Transaction(func(tx *Connection) error { - err := m.Execute(tx) - if err != nil { - return err - } - err = tx.RawQuery("delete from schema_migration where version = ?", m.Version).Exec() - return errors.Wrapf(err, "problem deleting migration version %s", m.Version) - }) - if err == nil { - fmt.Printf("< %s\n", m.FileName) - } - return err - }, step) + return mig.Down(step) } +// MigrateStatus is deprecated, and will be removed in a future version. Use FileMigrator#Status instead. func (c *Connection) MigrateStatus(path string) error { + warningMsg := "Connection#MigrateStatus is deprecated, and will be removed in a future version. Use FileMigrator#Status instead." + _, file, no, ok := runtime.Caller(1) + if ok { + warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) + } + log.Println(warningMsg) - err := c.createSchemaMigrations() + mig, err := NewFileMigrator(path, c) if err != nil { - return errors.Wrap(err, "migration down: problem creating schema migrations") + return errors.WithStack(err) } - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.TabIndent) - fmt.Fprintln(w, "Version\tName\tStatus\t") - findMigrations(path, "up", 0, func(m migrationFile) error { - exists, err := c.Where("version = ?", m.Version).Exists("schema_migration") - if err != nil { - return errors.Wrapf(err, "problem with migration") - } - if exists { - fmt.Fprintln(w, m.Version+"\t"+m.Name+"\tApplied\t") - } else { - fmt.Fprintln(w, m.Version+"\t"+m.Name+"\tPending\t") - } - return nil - }, -1) - w.Flush() - return nil + return mig.Status() } +// MigrateReset is deprecated, and will be removed in a future version. Use FileMigrator#Reset instead. func (c *Connection) MigrateReset(path string) error { + warningMsg := "Connection#MigrateReset is deprecated, and will be removed in a future version. Use FileMigrator#Reset instead." + _, file, no, ok := runtime.Caller(1) + if ok { + warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) + } + log.Println(warningMsg) + err := c.MigrateDown(path, -1) if err != nil { return err } return c.MigrateUp(path) } - -func (c *Connection) createSchemaMigrations() error { - err := c.Open() - if err != nil { - return errors.Wrap(err, "could not open connection") - } - _, err = c.Store.Exec("select * from schema_migration") - if err == nil { - return nil - } - - return c.Transaction(func(tx *Connection) error { - smSQL, err := c.Dialect.FizzTranslator().CreateTable(schemaMigrations) - if err != nil { - return errors.Wrap(err, "could not build SQL for schema migration table") - } - return errors.Wrap(tx.RawQuery(smSQL).Exec(), "could not create schema migration table") - }) -} - -func findMigrations(dir string, direction string, runned int, fn func(migrationFile) error, step int) error { - if _, err := os.Stat(dir); err != nil { - // directory doesn't exist - return nil - } - mfs := migrationFiles{} - filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { - if !info.IsDir() { - matches := mrx.FindAllStringSubmatch(info.Name(), -1) - if matches == nil || len(matches) == 0 { - return nil - } - m := matches[0] - mf := migrationFile{ - Path: p, - FileName: m[0], - Version: m[1], - Name: m[2], - Direction: m[3], - FileType: m[4], - } - if mf.Direction == direction { - mfs = append(mfs, mf) - } - } - return nil - }) - if direction == "down" { - sort.Sort(sort.Reverse(mfs)) - // skip all runned migration - if len(mfs) > runned { - mfs = mfs[len(mfs)-runned:] - } - // run only required steps - if step > 0 && len(mfs) >= step { - mfs = mfs[:step] - } - } else { - sort.Sort(mfs) - } - for _, mf := range mfs { - err := fn(mf) - if err != nil { - return errors.Wrap(err, "error from called function") - } - } - return nil -} - -func printTimer(timerStart time.Time) { - diff := time.Now().Sub(timerStart).Seconds() - if diff > 60 { - fmt.Printf("\n%.4f minutes\n", diff/60) - } else { - fmt.Printf("\n%.4f seconds\n", diff) - } -} diff --git a/migration_box.go b/migration_box.go new file mode 100644 index 00000000..c534adea --- /dev/null +++ b/migration_box.go @@ -0,0 +1,68 @@ +package pop + +import ( + "github.com/gobuffalo/packr" + "github.com/pkg/errors" +) + +// MigrationBox is a wrapper around packr.Box and Migrator. +// This will allow you to run migrations from a packed box +// inside of a compiled binary. +type MigrationBox struct { + Migrator + Box packr.Box +} + +// NewMigrationBox from a packr.Box and a Connection. +func NewMigrationBox(box packr.Box, c *Connection) (MigrationBox, error) { + fm := MigrationBox{ + Migrator: NewMigrator(c), + Box: box, + } + + err := fm.findMigrations() + if err != nil { + return fm, errors.WithStack(err) + } + + return fm, nil +} + +func (fm *MigrationBox) findMigrations() error { + return fm.Box.Walk(func(p string, f packr.File) error { + info, err := f.FileInfo() + if err != nil { + return errors.WithStack(err) + } + matches := mrx.FindAllStringSubmatch(info.Name(), -1) + if matches == nil || len(matches) == 0 { + return nil + } + m := matches[0] + mf := Migration{ + Path: p, + Version: m[1], + Name: m[2], + Direction: m[3], + Type: m[4], + Runner: func(mf Migration, tx *Connection) error { + content, err := migrationContent(mf, tx, f) + if err != nil { + return errors.Wrapf(err, "error processing %s", mf.Path) + } + + if content == "" { + return nil + } + + err = tx.RawQuery(content).Exec() + if err != nil { + return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content) + } + return nil + }, + } + fm.Migrations[mf.Direction] = append(fm.Migrations[mf.Direction], mf) + return nil + }) +} diff --git a/migration_file.go b/migration_file.go deleted file mode 100644 index 6500a710..00000000 --- a/migration_file.go +++ /dev/null @@ -1,76 +0,0 @@ -package pop - -import ( - "bytes" - "html/template" - "io/ioutil" - "path" - - "github.com/markbates/pop/fizz" - "github.com/pkg/errors" -) - -type migrationFile struct { - Path string - FileName string - Version string - Name string - Direction string - FileType string -} - -type migrationFiles []migrationFile - -func (mfs migrationFiles) Len() int { - return len(mfs) -} - -func (mfs migrationFiles) Less(i, j int) bool { - return mfs[i].Version < mfs[j].Version -} - -func (mfs migrationFiles) Swap(i, j int) { - mfs[i], mfs[j] = mfs[j], mfs[i] -} - -func (m migrationFile) Content(c *Connection) (string, error) { - b, err := ioutil.ReadFile(m.Path) - if err != nil { - return "", nil - } - content := string(b) - ext := path.Ext(m.FileName) - - t := template.Must(template.New("sql").Parse(content)) - var bb bytes.Buffer - err = t.Execute(&bb, c.Dialect.Details()) - if err != nil { - return "", errors.Wrapf(err, "could not execute migration template %s", m.Path) - } - content = bb.String() - - if ext == ".fizz" { - content, err = fizz.AString(content, c.Dialect.FizzTranslator()) - if err != nil { - return "", errors.Wrapf(err, "could not fizz the migration %s", m.Path) - } - } - return content, nil -} - -func (m migrationFile) Execute(c *Connection) error { - content, err := m.Content(c) - if err != nil { - return errors.Wrapf(err, "error processing %s", m.FileName) - } - - if content == "" { - return nil - } - - err = c.RawQuery(content).Exec() - if err != nil { - return errors.Wrapf(err, "error executing %s, sql: %s", m.FileName, content) - } - return nil -} diff --git a/migration_info.go b/migration_info.go new file mode 100644 index 00000000..5787e94d --- /dev/null +++ b/migration_info.go @@ -0,0 +1,41 @@ +package pop + +import "github.com/pkg/errors" + +type Migration struct { + // Path to the migration (./migrations/123_create_widgets.up.sql) + Path string + // Version of the migration (123) + Version string + // Name of the migration (create_widgets) + Name string + // Direction of the migration (up) + Direction string + // Type of migration (sql) + Type string + // Runner function to run/execute the migration + Runner func(Migration, *Connection) error +} + +// Run the migration. Returns an error if there is +// no mf.Runner defined. +func (mf Migration) Run(c *Connection) error { + if mf.Runner == nil { + return errors.Errorf("no runner defined for %s", mf.Path) + } + return mf.Runner(mf, c) +} + +type Migrations []Migration + +func (mfs Migrations) Len() int { + return len(mfs) +} + +func (mfs Migrations) Less(i, j int) bool { + return mfs[i].Version < mfs[j].Version +} + +func (mfs Migrations) Swap(i, j int) { + mfs[i], mfs[j] = mfs[j], mfs[i] +} diff --git a/migrator.go b/migrator.go new file mode 100644 index 00000000..395f4664 --- /dev/null +++ b/migrator.go @@ -0,0 +1,210 @@ +package pop + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "sort" + "text/tabwriter" + "time" + + "github.com/pkg/errors" +) + +var mrx = regexp.MustCompile("(\\d+)_(.+)\\.(up|down)\\.(sql|fizz)") + +func init() { + MapTableName("schema_migrations", "schema_migration") + MapTableName("schema_migration", "schema_migration") +} + +// NewMigrator returns a new "blank" migrator. It is recommended +// to use something like MigrationBox or FileMigrator. A "blank" +// Migrator should only be used as the basis for a new type of +// migration system. +func NewMigrator(c *Connection) Migrator { + return Migrator{ + Connection: c, + Migrations: map[string]Migrations{ + "up": Migrations{}, + "down": Migrations{}, + }, + } +} + +// Migrator forms the basis of all migrations systems. +// It does the actual heavy lifting of running migrations. +// When building a new migration system, you should embed this +// type into your migrator. +type Migrator struct { + Connection *Connection + SchemaPath string + Migrations map[string]Migrations +} + +// Up runs pending "up" migrations and applies them to the database. +func (m Migrator) Up() error { + c := m.Connection + return m.exec(func() error { + mfs := m.Migrations["up"] + sort.Sort(mfs) + for _, mi := range mfs { + exists, err := c.Where("version = ?", mi.Version).Exists("schema_migration") + if err != nil || exists { + return errors.Wrapf(err, "problem checking for migration version %s", mi.Version) + } + err = c.Transaction(func(tx *Connection) error { + err := mi.Run(tx) + if err != nil { + return err + } + _, err = tx.Store.Exec(fmt.Sprintf("insert into schema_migration (version) values ('%s')", mi.Version)) + return errors.Wrapf(err, "problem inserting migration version %s", mi.Version) + }) + if err != nil { + return errors.WithStack(err) + } + fmt.Printf("> %s\n", mi.Name) + } + return nil + }) +} + +// Down runs pending "down" migrations and rolls back the +// database by the specfied number of steps. +func (m Migrator) Down(step int) error { + c := m.Connection + return m.exec(func() error { + count, err := c.Count("schema_migration") + if err != nil { + return errors.Wrap(err, "migration down: unable count existing migration") + } + mfs := m.Migrations["down"] + sort.Sort(sort.Reverse(mfs)) + // skip all runned migration + if len(mfs) > count { + mfs = mfs[len(mfs)-count:] + } + // run only required steps + if step > 0 && len(mfs) >= step { + mfs = mfs[:step] + } + for _, mi := range mfs { + exists, err := c.Where("version = ?", mi.Version).Exists("schema_migration") + if err != nil || !exists { + return errors.Wrapf(err, "problem checking for migration version %s", mi.Version) + } + err = c.Transaction(func(tx *Connection) error { + err := mi.Run(tx) + if err != nil { + return err + } + err = tx.RawQuery("delete from schema_migration where version = ?", mi.Version).Exec() + return errors.Wrapf(err, "problem deleting migration version %s", mi.Version) + }) + if err == nil { + fmt.Printf("< %s\n", mi.Name) + } + return err + } + return nil + }) +} + +// Reset the database by runing the down migrations followed by the up migrations. +func (m Migrator) Reset() error { + err := m.Down(-1) + if err != nil { + return errors.WithStack(err) + } + return m.Up() +} + +// CreateSchemaMigrations sets up a table to track migrations. This is an idempotent +// operation. +func (m Migrator) CreateSchemaMigrations() error { + c := m.Connection + err := c.Open() + if err != nil { + return errors.Wrap(err, "could not open connection") + } + _, err = c.Store.Exec("select * from schema_migration") + if err == nil { + return nil + } + + return c.Transaction(func(tx *Connection) error { + smSQL, err := c.Dialect.FizzTranslator().CreateTable(schemaMigrations) + if err != nil { + return errors.Wrap(err, "could not build SQL for schema migration table") + } + err = tx.RawQuery(smSQL).Exec() + if err != nil { + return errors.WithStack(errors.Wrap(err, smSQL)) + } + return nil + }) +} + +// Status prints out the status of applied/pending migrations. +func (m Migrator) Status() error { + err := m.CreateSchemaMigrations() + if err != nil { + return errors.WithStack(err) + } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.TabIndent) + fmt.Fprintln(w, "Version\tName\tStatus\t") + for _, mf := range m.Migrations["up"] { + exists, err := m.Connection.Where("version = ?", mf.Version).Exists("schema_migration") + if err != nil { + return errors.Wrapf(err, "problem with migration") + } + state := "Pending" + if exists { + state = "Applied" + } + fmt.Fprintf(w, "%s\t%s\t%s\t\n", mf.Version, mf.Name, state) + } + return w.Flush() +} + +// DumpMigrationSchema will generate a file of the current database schema +// based on the value of Migrator.SchemaPath +func (m Migrator) DumpMigrationSchema() error { + if m.SchemaPath == "" { + return nil + } + c := m.Connection + f, err := os.Create(filepath.Join(m.SchemaPath, "schema.sql")) + if err != nil { + return errors.WithStack(err) + } + err = c.Dialect.DumpSchema(f) + if err != nil { + + return errors.WithStack(err) + } + return nil +} + +func (m Migrator) exec(fn func() error) error { + now := time.Now() + defer m.DumpMigrationSchema() + defer printTimer(now) + + err := m.CreateSchemaMigrations() + if err != nil { + return errors.Wrap(err, "Migrator: problem creating schema migrations") + } + return fn() +} + +func printTimer(timerStart time.Time) { + diff := time.Now().Sub(timerStart).Seconds() + if diff > 60 { + fmt.Printf("\n%.4f minutes\n", diff/60) + } else { + fmt.Printf("\n%.4f seconds\n", diff) + } +} diff --git a/mysql.go b/mysql.go index c5af2221..1e18fa69 100644 --- a/mysql.go +++ b/mysql.go @@ -26,6 +26,9 @@ func (m *mysql) Details() *ConnectionDetails { func (m *mysql) URL() string { c := m.ConnectionDetails + if m.ConnectionDetails.URL != "" { + return m.ConnectionDetails.URL + } s := "%s:%s@(%s:%s)/%s?parseTime=true&multiStatements=true&readTimeout=1s" return fmt.Sprintf(s, c.User, c.Password, c.Host, c.Port, c.Database) } @@ -56,7 +59,7 @@ func (m *mysql) SelectMany(s store, models *Model, query Query) error { func (m *mysql) CreateDB() error { c := m.ConnectionDetails - cmd := exec.Command("mysql", "-u", c.User, "-p"+c.Password, "-h", c.Host, "-P", c.Port, "-e", fmt.Sprintf("create database %s", c.Database)) + cmd := exec.Command("mysql", "-u", c.User, "-p"+c.Password, "-h", c.Host, "-P", c.Port, "-e", fmt.Sprintf("create database `%s`", c.Database)) Log(strings.Join(cmd.Args, " ")) cmd.Stdin = os.Stdin cmd.Stdout = os.Stderr @@ -71,7 +74,7 @@ func (m *mysql) CreateDB() error { func (m *mysql) DropDB() error { c := m.ConnectionDetails - cmd := exec.Command("mysql", "-u", c.User, "-p"+c.Password, "-h", c.Host, "-P", c.Port, "-e", fmt.Sprintf("drop database %s", c.Database)) + cmd := exec.Command("mysql", "-u", c.User, "-p"+c.Password, "-h", c.Host, "-P", c.Port, "-e", fmt.Sprintf("drop database `%s`", c.Database)) Log(strings.Join(cmd.Args, " ")) cmd.Stdin = os.Stdin cmd.Stdout = os.Stderr @@ -147,6 +150,9 @@ func (m *mysql) TruncateAll(tx *Connection) error { if err != nil { return err } + if len(stmts) == 0 { + return nil + } qs := []string{} for _, x := range stmts { qs = append(qs, x.Stmt) diff --git a/schema_migrations.go b/schema_migrations.go new file mode 100644 index 00000000..33a88c2e --- /dev/null +++ b/schema_migrations.go @@ -0,0 +1,15 @@ +// +build !appengine + +package pop + +import "github.com/markbates/pop/fizz" + +var schemaMigrations = fizz.Table{ + Name: "schema_migration", + Columns: []fizz.Column{ + {Name: "version", ColType: "string"}, + }, + Indexes: []fizz.Index{ + {Name: "version_idx", Columns: []string{"version"}, Unique: true}, + }, +} diff --git a/schema_migrations_appengine.go b/schema_migrations_appengine.go new file mode 100644 index 00000000..4cfca388 --- /dev/null +++ b/schema_migrations_appengine.go @@ -0,0 +1,13 @@ +// +build appengine + +package pop + +import "github.com/markbates/pop/fizz" + +var schemaMigrations = fizz.Table{ + Name: "schema_migration", + Columns: []fizz.Column{ + {Name: "version", ColType: "string"}, + }, + Indexes: []fizz.Index{}, +} diff --git a/soda/cmd/migrate.go b/soda/cmd/migrate.go index 717fc53c..376cb652 100644 --- a/soda/cmd/migrate.go +++ b/soda/cmd/migrate.go @@ -3,6 +3,8 @@ package cmd import ( "os" + "github.com/markbates/pop" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -17,8 +19,11 @@ var migrateCmd = &cobra.Command{ return os.MkdirAll(migrationPath, 0766) }, RunE: func(cmd *cobra.Command, args []string) error { - c := getConn() - return c.MigrateUp(migrationPath) + mig, err := pop.NewFileMigrator(migrationPath, getConn()) + if err != nil { + return errors.WithStack(err) + } + return mig.Up() }, } diff --git a/soda/cmd/migrate_down.go b/soda/cmd/migrate_down.go index faa18589..fd6d3c2f 100644 --- a/soda/cmd/migrate_down.go +++ b/soda/cmd/migrate_down.go @@ -1,6 +1,10 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/markbates/pop" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) var migrationStep int @@ -8,8 +12,11 @@ var migrateDownCmd = &cobra.Command{ Use: "down", Short: "Apply one or more of the 'down' migrations.", RunE: func(cmd *cobra.Command, args []string) error { - c := getConn() - return c.MigrateDown(migrationPath, migrationStep) + mig, err := pop.NewFileMigrator(migrationPath, getConn()) + if err != nil { + return errors.WithStack(err) + } + return mig.Down(migrationStep) }, } diff --git a/soda/cmd/migrate_reset.go b/soda/cmd/migrate_reset.go index 3ab1b745..33287a7a 100644 --- a/soda/cmd/migrate_reset.go +++ b/soda/cmd/migrate_reset.go @@ -1,13 +1,20 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/markbates/pop" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) var migrateResetCmd = &cobra.Command{ Use: "reset", Short: "The equivalent of running `migrate down` and then `migrate up`", RunE: func(cmd *cobra.Command, args []string) error { - c := getConn() - return c.MigrateReset(migrationPath) + mig, err := pop.NewFileMigrator(migrationPath, getConn()) + if err != nil { + return errors.WithStack(err) + } + return mig.Reset() }, } diff --git a/soda/cmd/migrate_status.go b/soda/cmd/migrate_status.go index 3b2ccce1..0380342a 100644 --- a/soda/cmd/migrate_status.go +++ b/soda/cmd/migrate_status.go @@ -1,13 +1,20 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/markbates/pop" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) var migrateStatusCmd = &cobra.Command{ Use: "status", Short: "Displays the status of all migrations.", RunE: func(cmd *cobra.Command, args []string) error { - c := getConn() - return c.MigrateStatus(migrationPath) + mig, err := pop.NewFileMigrator(migrationPath, getConn()) + if err != nil { + return errors.WithStack(err) + } + return mig.Status() }, } diff --git a/soda/cmd/migrate_up.go b/soda/cmd/migrate_up.go index bee1a5f3..8f2a1581 100644 --- a/soda/cmd/migrate_up.go +++ b/soda/cmd/migrate_up.go @@ -1,13 +1,20 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/markbates/pop" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) var migrateUpCmd = &cobra.Command{ Use: "up", Short: "Apply all of the 'up' migrations.", RunE: func(cmd *cobra.Command, args []string) error { - c := getConn() - return c.MigrateUp(migrationPath) + mig, err := pop.NewFileMigrator(migrationPath, getConn()) + if err != nil { + return errors.WithStack(err) + } + return mig.Up() }, } diff --git a/soda/cmd/version.go b/soda/cmd/version.go index f299815b..d32acf06 100644 --- a/soda/cmd/version.go +++ b/soda/cmd/version.go @@ -1,3 +1,3 @@ package cmd -const Version = "3.23.2" +const Version = "3.24.0" diff --git a/sqlite.go b/sqlite.go index 5b5e32c7..926e00b4 100644 --- a/sqlite.go +++ b/sqlite.go @@ -1,4 +1,4 @@ -// +build !nosqlite +// +build !nosqlite,!appengine,!appenginevm package pop @@ -166,13 +166,13 @@ func (m *sqlite) TruncateAll(tx *Connection) error { if err != nil { return err } + if len(names) == 0 { + return nil + } stmts := []string{} for _, n := range names { stmts = append(stmts, fmt.Sprintf("DELETE FROM %s", n.Name)) } - if len(stmts) == 0 { - return nil - } return tx.RawQuery(strings.Join(stmts, "; ")).Exec() } diff --git a/sqlite_shim.go b/sqlite_shim.go index d5eb70ed..0cb07272 100644 --- a/sqlite_shim.go +++ b/sqlite_shim.go @@ -1,4 +1,4 @@ -// +build nosqlite +// +build nosqlite appengine appenginevm package pop