diff --git a/database/console/driver/sqlite.go b/database/console/driver/sqlite.go index 72e796850..b38dd3bba 100644 --- a/database/console/driver/sqlite.go +++ b/database/console/driver/sqlite.go @@ -85,7 +85,7 @@ func (m *Sqlite) ensureVersionTable() (err error) { CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version); `, m.config.MigrationsTable, m.config.MigrationsTable) - if _, err := m.db.Exec(query); err != nil { + if _, err = m.db.Exec(query); err != nil { return err } return nil @@ -147,27 +147,37 @@ func (m *Sqlite) Drop() (err error) { tableNames := make([]string, 0) for tables.Next() { var tableName string - if err := tables.Scan(&tableName); err != nil { + if err = tables.Scan(&tableName); err != nil { return err } if len(tableName) > 0 { tableNames = append(tableNames, tableName) } } - if err := tables.Err(); err != nil { + if err = tables.Err(); err != nil { return &database.Error{OrigErr: err, Query: []byte(query)} } if len(tableNames) > 0 { for _, t := range tableNames { - query := "DROP TABLE " + t + // SQLite has a sqlite_sequence table and it cannot be dropped + if t == "sqlite_sequence" { + _, err = m.db.Exec("DELETE FROM sqlite_sequence;") + if err != nil { + return &database.Error{OrigErr: err, Query: []byte("DELETE FROM sqlite_sequence;")} + } + + continue + } + + query = "DROP TABLE " + t err = m.executeQuery(query) if err != nil { return &database.Error{OrigErr: err, Query: []byte(query)} } } - query := "VACUUM" - _, err = m.db.Query(query) + query = "VACUUM" + _, err = m.db.Exec(query) if err != nil { return &database.Error{OrigErr: err, Query: []byte(query)} } @@ -208,13 +218,13 @@ func (m *Sqlite) executeQuery(query string) error { if err != nil { return &database.Error{OrigErr: err, Err: "transaction start failed"} } - if _, err := tx.Exec(query); err != nil { + if _, err = tx.Exec(query); err != nil { if errRollback := tx.Rollback(); errRollback != nil { err = multierror.Append(err, errRollback) } return &database.Error{OrigErr: err, Query: []byte(query)} } - if err := tx.Commit(); err != nil { + if err = tx.Commit(); err != nil { return &database.Error{OrigErr: err, Err: "transaction commit failed"} } return nil @@ -234,7 +244,7 @@ func (m *Sqlite) SetVersion(version int, dirty bool) error { } query := "DELETE FROM " + m.config.MigrationsTable - if _, err := tx.Exec(query); err != nil { + if _, err = tx.Exec(query); err != nil { return &database.Error{OrigErr: err, Query: []byte(query)} } @@ -242,8 +252,8 @@ func (m *Sqlite) SetVersion(version int, dirty bool) error { // empty schema version for failed down migration on the first migration // See: https://github.com/golang-migrate/migrate/issues/330 if version >= 0 || (version == database.NilVersion && dirty) { - query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable) - if _, err := tx.Exec(query, version, dirty); err != nil { + query = fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable) + if _, err = tx.Exec(query, version, dirty); err != nil { if errRollback := tx.Rollback(); errRollback != nil { err = multierror.Append(err, errRollback) } @@ -251,7 +261,7 @@ func (m *Sqlite) SetVersion(version int, dirty bool) error { } } - if err := tx.Commit(); err != nil { + if err = tx.Commit(); err != nil { return &database.Error{OrigErr: err, Err: "transaction commit failed"} } diff --git a/database/console/migrate_command.go b/database/console/migrate_command.go index a838017d4..859ff3558 100644 --- a/database/console/migrate_command.go +++ b/database/console/migrate_command.go @@ -21,24 +21,24 @@ func NewMigrateCommand(config config.Config) *MigrateCommand { } } -//Signature The name and signature of the console command. +// Signature The name and signature of the console command. func (receiver *MigrateCommand) Signature() string { return "migrate" } -//Description The console command description. +// Description The console command description. func (receiver *MigrateCommand) Description() string { return "Run the database migrations" } -//Extend The console command extend. +// Extend The console command extend. func (receiver *MigrateCommand) Extend() command.Extend { return command.Extend{ Category: "migrate", } } -//Handle Execute the console command. +// Handle Execute the console command. func (receiver *MigrateCommand) Handle(ctx console.Context) error { m, err := getMigrate(receiver.config) if err != nil { @@ -50,7 +50,7 @@ func (receiver *MigrateCommand) Handle(ctx console.Context) error { return nil } - if err := m.Up(); err != nil && err != migrate.ErrNoChange { + if err = m.Up(); err != nil && err != migrate.ErrNoChange { color.Redln("Migration failed:", err.Error()) return nil diff --git a/database/console/migrate_command_test.go b/database/console/migrate_command_test.go index e9d025eed..24955754b 100644 --- a/database/console/migrate_command_test.go +++ b/database/console/migrate_command_test.go @@ -116,6 +116,9 @@ func createMysqlMigrations() { KEY idx_agents_updated_at (updated_at) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO agents (name, created_at, updated_at) VALUES ('goravel', '2023-03-11 16:07:41', '2023-03-11 16:07:45'); +`) + _ = file.Create("database/migrations/20230311160527_create_agents_table.down.sql", + `DROP TABLE agents; `) } @@ -128,6 +131,9 @@ func createPostgresqlMigrations() { updated_at timestamp NOT NULL ); INSERT INTO agents (name, created_at, updated_at) VALUES ('goravel', '2023-03-11 16:07:41', '2023-03-11 16:07:45'); +`) + _ = file.Create("database/migrations/20230311160527_create_agents_table.down.sql", + `DROP TABLE agents; `) } @@ -141,6 +147,9 @@ func createSqlserverMigrations() { PRIMARY KEY (id) ); INSERT INTO agents (name, created_at, updated_at) VALUES ('goravel', '2023-03-11 16:07:41', '2023-03-11 16:07:45'); +`) + _ = file.Create("database/migrations/20230311160527_create_agents_table.down.sql", + `DROP TABLE agents; `) } @@ -153,6 +162,9 @@ func createSqliteMigrations() { updated_at datetime NOT NULL ); INSERT INTO agents (name, created_at, updated_at) VALUES ('goravel', '2023-03-11 16:07:41', '2023-03-11 16:07:45'); +`) + _ = file.Create("database/migrations/20230311160527_create_agents_table.down.sql", + `DROP TABLE agents; `) } diff --git a/database/console/migrate_creator.go b/database/console/migrate_creator.go index 60d3e1513..aefe63d0a 100644 --- a/database/console/migrate_creator.go +++ b/database/console/migrate_creator.go @@ -21,7 +21,7 @@ func NewMigrateCreator(config config.Config) *MigrateCreator { } } -//Create a new migration +// Create a new migration func (receiver MigrateCreator) Create(name string, table string, create bool) error { // First we will get the stub file for the migration, which serves as a type // of template for the migration. Once we have those we will populate the @@ -41,7 +41,7 @@ func (receiver MigrateCreator) Create(name string, table string, create bool) er return nil } -//getStub Get the migration stub file. +// getStub Get the migration stub file. func (receiver MigrateCreator) getStub(table string, create bool) (string, string) { if table == "" { return "", "" @@ -76,7 +76,7 @@ func (receiver MigrateCreator) getStub(table string, create bool) (string, strin } } -//populateStub Populate the place-holders in the migration stub. +// populateStub Populate the place-holders in the migration stub. func (receiver MigrateCreator) populateStub(stub string, table string) string { stub = strings.ReplaceAll(stub, "DummyDatabaseCharset", receiver.config.GetString("database.connections."+receiver.config.GetString("database.default")+".charset")) @@ -87,7 +87,7 @@ func (receiver MigrateCreator) populateStub(stub string, table string) string { return stub } -//getPath Get the full path to the migration. +// getPath Get the full path to the migration. func (receiver MigrateCreator) getPath(name string, category string) string { pwd, _ := os.Getwd() diff --git a/database/console/migrate_fresh_command.go b/database/console/migrate_fresh_command.go new file mode 100644 index 000000000..daefd1243 --- /dev/null +++ b/database/console/migrate_fresh_command.go @@ -0,0 +1,74 @@ +package console + +import ( + _ "github.com/go-sql-driver/mysql" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/gookit/color" + + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" +) + +type MigrateFreshCommand struct { + config config.Config +} + +func NewMigrateFreshCommand(config config.Config) *MigrateFreshCommand { + return &MigrateFreshCommand{ + config: config, + } +} + +// Signature The name and signature of the console command. +func (receiver *MigrateFreshCommand) Signature() string { + return "migrate:fresh" +} + +// Description The console command description. +func (receiver *MigrateFreshCommand) Description() string { + return "Drop all tables and re-run all migrations" +} + +// Extend The console command extend. +func (receiver *MigrateFreshCommand) Extend() command.Extend { + return command.Extend{ + Category: "migrate", + } +} + +// Handle Execute the console command. +func (receiver *MigrateFreshCommand) Handle(ctx console.Context) error { + m, err := getMigrate(receiver.config) + if err != nil { + return err + } + if m == nil { + color.Yellowln("Please fill database config first") + return nil + } + + if err = m.Drop(); err != nil && err != migrate.ErrNoChange { + color.Redln("Migration failed:", err.Error()) + return nil + } + + m2, err2 := getMigrate(receiver.config) + if err2 != nil { + return err2 + } + if m2 == nil { + color.Yellowln("Please fill database config first") + return nil + } + + if err2 = m2.Up(); err2 != nil && err2 != migrate.ErrNoChange { + color.Redln("Migration failed:", err2.Error()) + return nil + } + + color.Greenln("Migration fresh success") + + return nil +} diff --git a/database/console/migrate_fresh_command_test.go b/database/console/migrate_fresh_command_test.go new file mode 100644 index 000000000..9bac06778 --- /dev/null +++ b/database/console/migrate_fresh_command_test.go @@ -0,0 +1,104 @@ +package console + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/assert" + + configmock "github.com/goravel/framework/contracts/config/mocks" + consolemocks "github.com/goravel/framework/contracts/console/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/database/gorm" +) + +func TestMigrateFreshCommand(t *testing.T) { + var ( + mockConfig *configmock.Config + pool *dockertest.Pool + resource *dockertest.Resource + query ormcontract.Query + ) + + beforeEach := func() { + pool = nil + mockConfig = &configmock.Config{} + } + + tests := []struct { + name string + setup func() + }{ + { + name: "mysql", + setup: func() { + var err error + docker := gorm.NewMysqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createMysqlMigrations() + + }, + }, + { + name: "postgresql", + setup: func() { + var err error + docker := gorm.NewPostgresqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createPostgresqlMigrations() + }, + }, + { + name: "sqlserver", + setup: func() { + var err error + docker := gorm.NewSqlserverDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqlserverMigrations() + }, + }, + { + name: "sqlite", + setup: func() { + var err error + docker := gorm.NewSqliteDocker("goravel") + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqliteMigrations() + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + beforeEach() + test.setup() + + mockContext := &consolemocks.Context{} + + migrateCommand := NewMigrateCommand(mockConfig) + assert.Nil(t, migrateCommand.Handle(mockContext)) + + migrateFreshCommand := NewMigrateFreshCommand(mockConfig) + assert.Nil(t, migrateFreshCommand.Handle(mockContext)) + + var agent Agent + err := query.Where("name", "goravel").First(&agent) + assert.Nil(t, err) + assert.True(t, agent.ID > 0) + + if pool != nil && test.name != "sqlite" { + assert.Nil(t, pool.Purge(resource)) + } + + removeMigrations() + }) + } +} diff --git a/database/console/migrate_make_command.go b/database/console/migrate_make_command.go index 38228325b..0c9e68605 100644 --- a/database/console/migrate_make_command.go +++ b/database/console/migrate_make_command.go @@ -16,24 +16,24 @@ func NewMigrateMakeCommand(config config.Config) *MigrateMakeCommand { return &MigrateMakeCommand{config: config} } -//Signature The name and signature of the console command. +// Signature The name and signature of the console command. func (receiver *MigrateMakeCommand) Signature() string { return "make:migration" } -//Description The console command description. +// Description The console command description. func (receiver *MigrateMakeCommand) Description() string { return "Create a new migration file" } -//Extend The console command extend. +// Extend The console command extend. func (receiver *MigrateMakeCommand) Extend() command.Extend { return command.Extend{ Category: "make", } } -//Handle Execute the console command. +// Handle Execute the console command. func (receiver *MigrateMakeCommand) Handle(ctx console.Context) error { // It's possible for the developer to specify the tables to modify in this // schema operation. The developer may also specify if this table needs diff --git a/database/console/migrate_refresh_command.go b/database/console/migrate_refresh_command.go new file mode 100644 index 000000000..c29ad8d92 --- /dev/null +++ b/database/console/migrate_refresh_command.go @@ -0,0 +1,68 @@ +package console + +import ( + "github.com/gookit/color" + _ "github.com/go-sql-driver/mysql" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/source/file" + + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" +) + +type MigrateRefreshCommand struct { + config config.Config +} + +func NewMigrateRefreshCommand(config config.Config) *MigrateRefreshCommand { + return &MigrateRefreshCommand{ + config: config, + } +} + +// Signature The name and signature of the console command. +func (receiver *MigrateRefreshCommand) Signature() string { + return "migrate:refresh" +} + +// Description The console command description. +func (receiver *MigrateRefreshCommand) Description() string { + return "Reset and re-run all migrations" +} + +// Extend The console command extend. +func (receiver *MigrateRefreshCommand) Extend() command.Extend { + return command.Extend{ + Category: "migrate", + } +} + +// Handle Execute the console command. +func (receiver *MigrateRefreshCommand) Handle(ctx console.Context) error { + m, err := getMigrate(receiver.config) + if err != nil { + return err + } + if m == nil { + color.Yellowln("Please fill database config first") + + return nil + } + + if err = m.Down(); err != nil && err != migrate.ErrNoChange { + color.Redln("Migration reset failed:", err.Error()) + + return nil + } + + if err = m.Up(); err != nil && err != migrate.ErrNoChange { + color.Redln("Migration failed:", err.Error()) + + return nil + } + + color.Greenln("Migration refresh success") + + return nil +} diff --git a/database/console/migrate_refresh_command_test.go b/database/console/migrate_refresh_command_test.go new file mode 100644 index 000000000..9a2b9c582 --- /dev/null +++ b/database/console/migrate_refresh_command_test.go @@ -0,0 +1,103 @@ +package console + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/assert" + + configmock "github.com/goravel/framework/contracts/config/mocks" + consolemocks "github.com/goravel/framework/contracts/console/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/database/gorm" +) + +func TestMigrateRefreshCommand(t *testing.T) { + var ( + mockConfig *configmock.Config + pool *dockertest.Pool + resource *dockertest.Resource + query ormcontract.Query + ) + + beforeEach := func() { + pool = nil + mockConfig = &configmock.Config{} + } + + tests := []struct { + name string + setup func() + }{ + { + name: "mysql", + setup: func() { + var err error + docker := gorm.NewMysqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createMysqlMigrations() + }, + }, + { + name: "postgresql", + setup: func() { + var err error + docker := gorm.NewPostgresqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createPostgresqlMigrations() + }, + }, + { + name: "sqlserver", + setup: func() { + var err error + docker := gorm.NewSqlserverDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqlserverMigrations() + }, + }, + { + name: "sqlite", + setup: func() { + var err error + docker := gorm.NewSqliteDocker("goravel") + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqliteMigrations() + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + beforeEach() + test.setup() + + mockContext := &consolemocks.Context{} + + migrateCommand := NewMigrateCommand(mockConfig) + assert.Nil(t, migrateCommand.Handle(mockContext)) + + migrateRefreshCommand := NewMigrateRefreshCommand(mockConfig) + assert.Nil(t, migrateRefreshCommand.Handle(mockContext)) + + var agent Agent + err := query.Where("name", "goravel").First(&agent) + assert.Nil(t, err) + assert.True(t, agent.ID > 0) + + if pool != nil && test.name != "sqlite" { + assert.Nil(t, pool.Purge(resource)) + } + + removeMigrations() + }) + } +} diff --git a/database/console/migrate_reset_command.go b/database/console/migrate_reset_command.go new file mode 100644 index 000000000..45701bd8d --- /dev/null +++ b/database/console/migrate_reset_command.go @@ -0,0 +1,63 @@ +package console + +import ( + _ "github.com/go-sql-driver/mysql" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/gookit/color" + + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" +) + +type MigrateResetCommand struct { + config config.Config +} + +func NewMigrateResetCommand(config config.Config) *MigrateResetCommand { + return &MigrateResetCommand{ + config: config, + } +} + +// Signature The name and signature of the console command. +func (receiver *MigrateResetCommand) Signature() string { + return "migrate:reset" +} + +// Description The console command description. +func (receiver *MigrateResetCommand) Description() string { + return "Rollback all database migrations" +} + +// Extend The console command extend. +func (receiver *MigrateResetCommand) Extend() command.Extend { + return command.Extend{ + Category: "migrate", + } +} + +// Handle Execute the console command. +func (receiver *MigrateResetCommand) Handle(ctx console.Context) error { + m, err := getMigrate(receiver.config) + if err != nil { + return err + } + if m == nil { + color.Yellowln("Please fill database config first") + + return nil + } + + // Rollback all migrations. + if err = m.Down(); err != nil && err != migrate.ErrNoChange { + color.Redln("Migration reset failed:", err.Error()) + + return nil + } + + color.Greenln("Migration reset success") + + return nil +} diff --git a/database/console/migrate_reset_command_test.go b/database/console/migrate_reset_command_test.go new file mode 100644 index 000000000..ecd39798d --- /dev/null +++ b/database/console/migrate_reset_command_test.go @@ -0,0 +1,103 @@ +package console + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/assert" + + configmock "github.com/goravel/framework/contracts/config/mocks" + consolemocks "github.com/goravel/framework/contracts/console/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/database/gorm" +) + +func TestMigrateResetCommand(t *testing.T) { + var ( + mockConfig *configmock.Config + pool *dockertest.Pool + resource *dockertest.Resource + query ormcontract.Query + ) + + beforeEach := func() { + pool = nil + mockConfig = &configmock.Config{} + } + + tests := []struct { + name string + setup func() + }{ + { + name: "mysql", + setup: func() { + var err error + docker := gorm.NewMysqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createMysqlMigrations() + + }, + }, + { + name: "postgresql", + setup: func() { + var err error + docker := gorm.NewPostgresqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createPostgresqlMigrations() + }, + }, + { + name: "sqlserver", + setup: func() { + var err error + docker := gorm.NewSqlserverDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqlserverMigrations() + }, + }, + { + name: "sqlite", + setup: func() { + var err error + docker := gorm.NewSqliteDocker("goravel") + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqliteMigrations() + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + beforeEach() + test.setup() + + mockContext := &consolemocks.Context{} + + migrateCommand := NewMigrateCommand(mockConfig) + assert.Nil(t, migrateCommand.Handle(mockContext)) + + migrateResetCommand := NewMigrateResetCommand(mockConfig) + assert.Nil(t, migrateResetCommand.Handle(mockContext)) + + var agent Agent + err := query.Where("name", "goravel").FirstOrFail(&agent) + assert.Error(t, err) + + if pool != nil && test.name != "sqlite" { + assert.Nil(t, pool.Purge(resource)) + } + + removeMigrations() + }) + } +} diff --git a/database/console/migrate_rollback_command.go b/database/console/migrate_rollback_command.go index da962b366..f95f96bd2 100644 --- a/database/console/migrate_rollback_command.go +++ b/database/console/migrate_rollback_command.go @@ -23,17 +23,17 @@ func NewMigrateRollbackCommand(config config.Config) *MigrateRollbackCommand { } } -//Signature The name and signature of the console command. +// Signature The name and signature of the console command. func (receiver *MigrateRollbackCommand) Signature() string { return "migrate:rollback" } -//Description The console command description. +// Description The console command description. func (receiver *MigrateRollbackCommand) Description() string { return "Rollback the database migrations" } -//Extend The console command extend. +// Extend The console command extend. func (receiver *MigrateRollbackCommand) Extend() command.Extend { return command.Extend{ Category: "migrate", @@ -47,7 +47,7 @@ func (receiver *MigrateRollbackCommand) Extend() command.Extend { } } -//Handle Execute the console command. +// Handle Execute the console command. func (receiver *MigrateRollbackCommand) Handle(ctx console.Context) error { m, err := getMigrate(receiver.config) if err != nil { @@ -67,7 +67,7 @@ func (receiver *MigrateRollbackCommand) Handle(ctx console.Context) error { return nil } - if err := m.Steps(step); err != nil && err != migrate.ErrNoChange && err != migrate.ErrNilVersion { + if err = m.Steps(step); err != nil && err != migrate.ErrNoChange && err != migrate.ErrNilVersion { switch err.(type) { case migrate.ErrShortLimit: default: diff --git a/database/console/migrate_rollback_command_test.go b/database/console/migrate_rollback_command_test.go new file mode 100644 index 000000000..f0bbe2eb6 --- /dev/null +++ b/database/console/migrate_rollback_command_test.go @@ -0,0 +1,104 @@ +package console + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/assert" + + configmock "github.com/goravel/framework/contracts/config/mocks" + consolemocks "github.com/goravel/framework/contracts/console/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/database/gorm" +) + +func TestMigrateRollbackCommand(t *testing.T) { + var ( + mockConfig *configmock.Config + pool *dockertest.Pool + resource *dockertest.Resource + query ormcontract.Query + ) + + beforeEach := func() { + pool = nil + mockConfig = &configmock.Config{} + } + + tests := []struct { + name string + setup func() + }{ + { + name: "mysql", + setup: func() { + var err error + docker := gorm.NewMysqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createMysqlMigrations() + + }, + }, + { + name: "postgresql", + setup: func() { + var err error + docker := gorm.NewPostgresqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createPostgresqlMigrations() + }, + }, + { + name: "sqlserver", + setup: func() { + var err error + docker := gorm.NewSqlserverDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqlserverMigrations() + }, + }, + { + name: "sqlite", + setup: func() { + var err error + docker := gorm.NewSqliteDocker("goravel") + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqliteMigrations() + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + beforeEach() + test.setup() + + mockContext := &consolemocks.Context{} + mockContext.On("Option", "step").Return("1").Once() + + migrateCommand := NewMigrateCommand(mockConfig) + assert.Nil(t, migrateCommand.Handle(mockContext)) + + migrateRollbackCommand := NewMigrateRollbackCommand(mockConfig) + assert.Nil(t, migrateRollbackCommand.Handle(mockContext)) + + var agent Agent + err := query.Where("name", "goravel").FirstOrFail(&agent) + assert.Error(t, err) + + if pool != nil && test.name != "sqlite" { + assert.Nil(t, pool.Purge(resource)) + } + + removeMigrations() + }) + } +} diff --git a/database/console/migrate_status_command.go b/database/console/migrate_status_command.go new file mode 100644 index 000000000..c91b79cf2 --- /dev/null +++ b/database/console/migrate_status_command.go @@ -0,0 +1,69 @@ +package console + +import ( + _ "github.com/go-sql-driver/mysql" + _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/gookit/color" + + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" +) + +type MigrateStatusCommand struct { + config config.Config +} + +func NewMigrateStatusCommand(config config.Config) *MigrateStatusCommand { + return &MigrateStatusCommand{ + config: config, + } +} + +// Signature The name and signature of the console command. +func (receiver *MigrateStatusCommand) Signature() string { + return "migrate:status" +} + +// Description The console command description. +func (receiver *MigrateStatusCommand) Description() string { + return "Show the status of each migration" +} + +// Extend The console command extend. +func (receiver *MigrateStatusCommand) Extend() command.Extend { + return command.Extend{ + Category: "migrate", + } +} + +// Handle Execute the console command. +func (receiver *MigrateStatusCommand) Handle(ctx console.Context) error { + m, err := getMigrate(receiver.config) + if err != nil { + return err + } + if m == nil { + color.Yellowln("Please fill database config first") + return nil + } + + version, dirty, err := m.Version() + if err != nil { + color.Redln("Migration status failed:", err.Error()) + + return nil + } + + if dirty { + color.Yellowln("Migration status: dirty") + color.Greenln("Migration version:", version) + + return nil + } + + color.Greenln("Migration status: clean") + color.Greenln("Migration version:", version) + + return nil +} diff --git a/database/console/migrate_status_command_test.go b/database/console/migrate_status_command_test.go new file mode 100644 index 000000000..3294b8e07 --- /dev/null +++ b/database/console/migrate_status_command_test.go @@ -0,0 +1,105 @@ +package console + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/assert" + + configmock "github.com/goravel/framework/contracts/config/mocks" + consolemocks "github.com/goravel/framework/contracts/console/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/database/gorm" +) + +func TestMigrateStatusCommand(t *testing.T) { + var ( + mockConfig *configmock.Config + pool *dockertest.Pool + resource *dockertest.Resource + query ormcontract.Query + ) + + beforeEach := func() { + pool = nil + mockConfig = &configmock.Config{} + } + + tests := []struct { + name string + setup func() + }{ + { + name: "mysql", + setup: func() { + var err error + docker := gorm.NewMysqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createMysqlMigrations() + + }, + }, + { + name: "postgresql", + setup: func() { + var err error + docker := gorm.NewPostgresqlDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createPostgresqlMigrations() + }, + }, + { + name: "sqlserver", + setup: func() { + var err error + docker := gorm.NewSqlserverDocker() + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqlserverMigrations() + }, + }, + { + name: "sqlite", + setup: func() { + var err error + docker := gorm.NewSqliteDocker("goravel") + pool, resource, query, err = docker.New() + assert.Nil(t, err) + mockConfig = docker.MockConfig + createSqliteMigrations() + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + beforeEach() + test.setup() + + mockContext := &consolemocks.Context{} + + migrateCommand := NewMigrateCommand(mockConfig) + assert.Nil(t, migrateCommand.Handle(mockContext)) + + migrateStatusCommand := NewMigrateStatusCommand(mockConfig) + assert.Nil(t, migrateStatusCommand.Handle(mockContext)) + + res, err := query.Table("migrations").Where("dirty", false).Update("dirty", true) + assert.Nil(t, err) + assert.Equal(t, int64(1), res.RowsAffected) + + assert.Nil(t, migrateStatusCommand.Handle(mockContext)) + + if pool != nil && test.name != "sqlite" { + assert.Nil(t, pool.Purge(resource)) + } + + removeMigrations() + }) + } +} diff --git a/database/console/migrate_stubs.go b/database/console/migrate_stubs.go index 434d30a87..97b9829da 100644 --- a/database/console/migrate_stubs.go +++ b/database/console/migrate_stubs.go @@ -3,7 +3,7 @@ package console type MysqlStubs struct { } -//CreateUp Create up migration content. +// CreateUp Create up migration content. func (receiver MysqlStubs) CreateUp() string { return `CREATE TABLE DummyTable ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, @@ -16,19 +16,19 @@ func (receiver MysqlStubs) CreateUp() string { ` } -//CreateDown Create down migration content. +// CreateDown Create down migration content. func (receiver MysqlStubs) CreateDown() string { return `DROP TABLE IF EXISTS DummyTable; ` } -//UpdateUp Update up migration content. +// UpdateUp Update up migration content. func (receiver MysqlStubs) UpdateUp() string { return `ALTER TABLE DummyTable ADD column varchar(255) COMMENT ''; ` } -//UpdateDown Update down migration content. +// UpdateDown Update down migration content. func (receiver MysqlStubs) UpdateDown() string { return `ALTER TABLE DummyTable DROP COLUMN column; ` @@ -37,7 +37,7 @@ func (receiver MysqlStubs) UpdateDown() string { type PostgresqlStubs struct { } -//CreateUp Create up migration content. +// CreateUp Create up migration content. func (receiver PostgresqlStubs) CreateUp() string { return `CREATE TABLE DummyTable ( id SERIAL PRIMARY KEY NOT NULL, @@ -47,19 +47,19 @@ func (receiver PostgresqlStubs) CreateUp() string { ` } -//CreateDown Create down migration content. +// CreateDown Create down migration content. func (receiver PostgresqlStubs) CreateDown() string { return `DROP TABLE IF EXISTS DummyTable; ` } -//UpdateUp Update up migration content. +// UpdateUp Update up migration content. func (receiver PostgresqlStubs) UpdateUp() string { return `ALTER TABLE DummyTable ADD column varchar(255) NOT NULL; ` } -//UpdateDown Update down migration content. +// UpdateDown Update down migration content. func (receiver PostgresqlStubs) UpdateDown() string { return `ALTER TABLE DummyTable DROP COLUMN column; ` @@ -68,7 +68,7 @@ func (receiver PostgresqlStubs) UpdateDown() string { type SqliteStubs struct { } -//CreateUp Create up migration content. +// CreateUp Create up migration content. func (receiver SqliteStubs) CreateUp() string { return `CREATE TABLE DummyTable ( id integer PRIMARY KEY AUTOINCREMENT NOT NULL, @@ -78,19 +78,19 @@ func (receiver SqliteStubs) CreateUp() string { ` } -//CreateDown Create down migration content. +// CreateDown Create down migration content. func (receiver SqliteStubs) CreateDown() string { return `DROP TABLE IF EXISTS DummyTable; ` } -//UpdateUp Update up migration content. +// UpdateUp Update up migration content. func (receiver SqliteStubs) UpdateUp() string { return `ALTER TABLE DummyTable ADD column text; ` } -//UpdateDown Update down migration content. +// UpdateDown Update down migration content. func (receiver SqliteStubs) UpdateDown() string { return `ALTER TABLE DummyTable DROP COLUMN column; ` @@ -99,7 +99,7 @@ func (receiver SqliteStubs) UpdateDown() string { type SqlserverStubs struct { } -//CreateUp Create up migration content. +// CreateUp Create up migration content. func (receiver SqlserverStubs) CreateUp() string { return `CREATE TABLE DummyTable ( id bigint NOT NULL IDENTITY(1,1), @@ -110,19 +110,19 @@ func (receiver SqlserverStubs) CreateUp() string { ` } -//CreateDown Create down migration content. +// CreateDown Create down migration content. func (receiver SqlserverStubs) CreateDown() string { return `DROP TABLE IF EXISTS DummyTable; ` } -//UpdateUp Update up migration content. +// UpdateUp Update up migration content. func (receiver SqlserverStubs) UpdateUp() string { return `ALTER TABLE DummyTable ADD column varchar(255); ` } -//UpdateDown Update down migration content. +// UpdateDown Update down migration content. func (receiver SqlserverStubs) UpdateDown() string { return `ALTER TABLE DummyTable DROP COLUMN column; ` diff --git a/database/console/table_guesser.go b/database/console/table_guesser.go index e184ce17e..ae6356ab5 100644 --- a/database/console/table_guesser.go +++ b/database/console/table_guesser.go @@ -17,7 +17,7 @@ var ChangePatterns = []string{ type TableGuesser struct { } -//Guess Attempt to guess the table name and "creation" status of the given migration, return table, create. +// Guess Attempt to guess the table name and "creation" status of the given migration, return table, create. func (receiver TableGuesser) Guess(migration string) (string, bool) { for _, createPattern := range CreatePatterns { reg := regexp.MustCompile(createPattern) diff --git a/database/service_provider.go b/database/service_provider.go index 87fa702fa..1b2b003fd 100644 --- a/database/service_provider.go +++ b/database/service_provider.go @@ -38,6 +38,10 @@ func (database *ServiceProvider) registerCommands(app foundation.Application) { console.NewMigrateMakeCommand(config), console.NewMigrateCommand(config), console.NewMigrateRollbackCommand(config), + console.NewMigrateResetCommand(config), + console.NewMigrateRefreshCommand(config), + console.NewMigrateFreshCommand(config), + console.NewMigrateStatusCommand(config), console.NewModelMakeCommand(), console.NewObserverMakeCommand(), })