Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add methods for migrate #174

Merged
merged 23 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions database/console/driver/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)}
}
Expand Down Expand Up @@ -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
Expand All @@ -234,24 +244,24 @@ 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)}
}

// Also re-write the schema version for nil dirty versions to prevent
// 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)
}
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"}
}

Expand Down
10 changes: 5 additions & 5 deletions database/console/migrate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions database/console/migrate_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`)
}

Expand All @@ -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;
`)
}

Expand All @@ -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;
`)
}

Expand All @@ -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;
`)
}

Expand Down
8 changes: 4 additions & 4 deletions database/console/migrate_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 "", ""
Expand Down Expand Up @@ -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"))

Expand All @@ -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()

Expand Down
74 changes: 74 additions & 0 deletions database/console/migrate_fresh_command.go
Original file line number Diff line number Diff line change
@@ -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 err
devhaozi marked this conversation as resolved.
Show resolved Hide resolved
}

m2, err2 := getMigrate(receiver.config)
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
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 err2
devhaozi marked this conversation as resolved.
Show resolved Hide resolved
}

color.Greenln("Migration fresh success")

return nil
}
104 changes: 104 additions & 0 deletions database/console/migrate_fresh_command_test.go
Original file line number Diff line number Diff line change
@@ -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()
})
}
}
Loading