Skip to content

Commit

Permalink
feat: [#280] Optimize migration command (#680)
Browse files Browse the repository at this point in the history
* feat: [#280] Implement the Run method of migration default driver

* feat: [#280] Optimize migrate command

* fix tests

* fix nilaway

* add test

* fix nilaway

* update test cases

* update test cases

* update test cases

* Update database/migration/sql_driver_test.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
hwbrzzl and coderabbitai[bot] authored Oct 18, 2024
1 parent 3902196 commit 62d45f5
Show file tree
Hide file tree
Showing 49 changed files with 1,451 additions and 559 deletions.
2 changes: 1 addition & 1 deletion contracts/database/migration/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ type Driver interface {
// Create a new migration file.
Create(name string) error
// Run the migrations according to paths.
Run(paths []string) error
Run() error
}
4 changes: 2 additions & 2 deletions contracts/database/migration/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ type File struct {

type Repository interface {
// CreateRepository Create the migration repository data store.
CreateRepository() error
CreateRepository()
// Delete Remove a migration from the log.
Delete(migration string) error
// DeleteRepository Delete the migration repository data store.
DeleteRepository() error
DeleteRepository()
// GetLast Get the last migration batch.
GetLast() ([]File, error)
// GetMigrations Get the list of migrations.
Expand Down
20 changes: 16 additions & 4 deletions contracts/database/migration/schema.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
package migration

import (
"github.com/goravel/framework/contracts/database/orm"
)

type Schema interface {
// Create a new table on the schema.
Create(table string, callback func(table Blueprint)) error
// Connection Get the connection for the schema.
Connection(name string) Schema
// Create a new table on the schema.
Create(table string, callback func(table Blueprint))
// DropIfExists Drop a table from the schema if exists.
DropIfExists(table string) error
DropIfExists(table string)
// GetConnection Get the connection of the schema.
GetConnection() string
// GetTables Get the tables that belong to the database.
GetTables() ([]Table, error)
// HasTable Determine if the given table exists.
HasTable(table string) bool
// Migrations Get the migrations.
Migrations() []Migration
// Orm Get the orm instance.
Orm() orm.Orm
// Register migrations.
Register([]Migration)
// SetConnection Set the connection of the schema.
SetConnection(name string)
// Sql Execute a sql directly.
Sql(sql string)
// Table Modify a table on the schema.
Table(table string, callback func(table Blueprint)) error
Table(table string, callback func(table Blueprint))
}

type Migration interface {
Expand Down
6 changes: 4 additions & 2 deletions contracts/database/orm/orm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ type Orm interface {
Connection(name string) Orm
// DB gets the underlying database connection.
DB() (*sql.DB, error)
// Query gets a new query builder instance.
Query() Query
// Factory gets a new factory instance for the given model name.
Factory() Factory
// Name gets the current connection name.
Name() string
// Observe registers an observer with the Orm.
Observe(model any, observer Observer)
// Query gets a new query builder instance.
Query() Query
// Refresh resets the Orm instance.
Refresh()
// Transaction runs a callback wrapped in a database transaction.
Expand Down
8 changes: 3 additions & 5 deletions database/console/driver/sqlite.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sqlite
package driver

import (
"database/sql"
Expand All @@ -19,11 +19,9 @@ func init() {
database.Register("sqlite", &Sqlite{})
}

var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
DefaultMigrationsTable = "schema_migrations"
ErrNilConfig = fmt.Errorf("no config")
)

type Config struct {
Expand Down
12 changes: 6 additions & 6 deletions database/console/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package migration

import (
"database/sql"
"errors"
"fmt"

"github.com/golang-migrate/migrate/v4"
Expand All @@ -14,12 +13,13 @@ import (
"github.com/goravel/framework/contracts/database"
"github.com/goravel/framework/database/console/driver"
databasedb "github.com/goravel/framework/database/db"
"github.com/goravel/framework/errors"
"github.com/goravel/framework/support"
)

func getMigrate(config config.Config) (*migrate.Migrate, error) {
connection := config.GetString("database.default")
driver := config.GetString("database.connections." + connection + ".driver")
dbDriver := database.Driver(config.GetString("database.connections." + connection + ".driver"))
dir := "file://./database/migrations"
if support.RelativePath != "" {
dir = fmt.Sprintf("file://%s/database/migrations", support.RelativePath)
Expand All @@ -28,10 +28,10 @@ func getMigrate(config config.Config) (*migrate.Migrate, error) {
configBuilder := databasedb.NewConfigBuilder(config, connection)
writeConfigs := configBuilder.Writes()
if len(writeConfigs) == 0 {
return nil, errors.New("not found database configuration")
return nil, errors.OrmDatabaseConfigNotFound
}

switch database.Driver(driver) {
switch dbDriver {
case database.DriverMysql:
mysqlDsn := databasedb.Dsn(writeConfigs[0])
if mysqlDsn == "" {
Expand Down Expand Up @@ -81,7 +81,7 @@ func getMigrate(config config.Config) (*migrate.Migrate, error) {
return nil, err
}

instance, err := sqlite.WithInstance(db, &sqlite.Config{
instance, err := driver.WithInstance(db, &driver.Config{
MigrationsTable: config.GetString("database.migrations.table"),
})
if err != nil {
Expand Down Expand Up @@ -110,6 +110,6 @@ func getMigrate(config config.Config) (*migrate.Migrate, error) {

return migrate.NewWithDatabaseInstance(dir, "sqlserver", instance)
default:
return nil, errors.New("database driver only support mysql, postgres, sqlite and sqlserver")
return nil, errors.OrmDriverNotSupported
}
}
38 changes: 15 additions & 23 deletions database/console/migration/migrate_command.go
Original file line number Diff line number Diff line change
@@ -1,58 +1,50 @@
package migration

import (
"errors"

"github.com/golang-migrate/migrate/v4"

"github.com/goravel/framework/contracts/config"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/contracts/database/migration"
"github.com/goravel/framework/support/color"
)

type MigrateCommand struct {
config config.Config
driver migration.Driver
}

func NewMigrateCommand(config config.Config) *MigrateCommand {
func NewMigrateCommand(config config.Config, schema migration.Schema) *MigrateCommand {
driver, err := GetDriver(config, schema)
if err != nil {
color.Red().Println(err.Error())
return nil
}

return &MigrateCommand{
config: config,
driver: driver,
}
}

// Signature The name and signature of the console command.
func (receiver *MigrateCommand) Signature() string {
func (r *MigrateCommand) Signature() string {
return "migrate"
}

// Description The console command description.
func (receiver *MigrateCommand) Description() string {
func (r *MigrateCommand) Description() string {
return "Run the database migrations"
}

// Extend The console command extend.
func (receiver *MigrateCommand) Extend() command.Extend {
func (r *MigrateCommand) Extend() command.Extend {
return command.Extend{
Category: "migrate",
}
}

// Handle Execute the console command.
func (receiver *MigrateCommand) Handle(ctx console.Context) error {
m, err := getMigrate(receiver.config)
if err != nil {
return err
}
if m == nil {
color.Yellow().Println("Please fill database config first")

return nil
}

if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
func (r *MigrateCommand) Handle(ctx console.Context) error {
if err := r.driver.Run(); err != nil {
color.Red().Println("Migration failed:", err.Error())

return nil
}

Expand Down
41 changes: 0 additions & 41 deletions database/console/migration/migrate_command_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion database/console/migration/migrate_fresh_command.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package migration

import (
"errors"
"strings"

"github.com/golang-migrate/migrate/v4"

"github.com/goravel/framework/contracts/config"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/errors"
"github.com/goravel/framework/support/color"
)

Expand Down
51 changes: 35 additions & 16 deletions database/console/migration/migrate_fresh_command_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package migration

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

contractsmigration "github.com/goravel/framework/contracts/database/migration"
"github.com/goravel/framework/database/gorm"
"github.com/goravel/framework/database/migration"
mocksconsole "github.com/goravel/framework/mocks/console"
mocksmigration "github.com/goravel/framework/mocks/database/migration"
"github.com/goravel/framework/support/env"
"github.com/goravel/framework/support/file"
)
Expand All @@ -20,45 +25,59 @@ func TestMigrateFreshCommand(t *testing.T) {
for driver, testQuery := range testQueries {
query := testQuery.Query()
mockConfig := testQuery.MockConfig()
createMigrations(driver)
mockConfig.EXPECT().GetString("database.migrations.table").Return("migrations").Once()
mockConfig.EXPECT().GetString("database.migrations.driver").Return(contractsmigration.DriverSql).Once()
mockConfig.EXPECT().GetString(fmt.Sprintf("database.connections.%s.charset", testQuery.Docker().Driver().String())).Return("utf8bm4").Once()

mockSchema := mocksmigration.NewSchema(t)
migration.CreateTestMigrations(driver)

mockContext := mocksconsole.NewContext(t)
mockArtisan := mocksconsole.NewArtisan(t)
migrateCommand := NewMigrateCommand(mockConfig)

migrateCommand := NewMigrateCommand(mockConfig, mockSchema)
require.NotNil(t, migrateCommand)
assert.Nil(t, migrateCommand.Handle(mockContext))
mockContext.On("OptionBool", "seed").Return(false).Once()

mockContext.EXPECT().OptionBool("seed").Return(false).Once()

migrateFreshCommand := NewMigrateFreshCommand(mockConfig, mockArtisan)
assert.Nil(t, migrateFreshCommand.Handle(mockContext))

var agent Agent
var agent migration.Agent
err := query.Where("name", "goravel").First(&agent)
assert.Nil(t, err)
assert.True(t, agent.ID > 0)

// Test MigrateFreshCommand with --seed flag and seeders specified
mockContext = &mocksconsole.Context{}
mockArtisan = &mocksconsole.Artisan{}
mockContext.On("OptionBool", "seed").Return(true).Once()
mockContext.On("OptionSlice", "seeder").Return([]string{"MockSeeder"}).Once()
mockArtisan.On("Call", "db:seed --seeder MockSeeder").Return(nil).Once()
mockContext = mocksconsole.NewContext(t)
mockConfig.EXPECT().GetString("database.migrations.table").Return("migrations").Once()
mockConfig.EXPECT().GetString("database.migrations.driver").Return(contractsmigration.DriverSql).Once()

mockArtisan = mocksconsole.NewArtisan(t)
mockContext.EXPECT().OptionBool("seed").Return(true).Once()
mockContext.EXPECT().OptionSlice("seeder").Return([]string{"MockSeeder"}).Once()
mockArtisan.EXPECT().Call("db:seed --seeder MockSeeder").Once()

migrateFreshCommand = NewMigrateFreshCommand(mockConfig, mockArtisan)
assert.Nil(t, migrateFreshCommand.Handle(mockContext))

var agent1 Agent
var agent1 migration.Agent
err = query.Where("name", "goravel").First(&agent1)
assert.Nil(t, err)
assert.True(t, agent1.ID > 0)

// Test MigrateFreshCommand with --seed flag and no seeders specified
mockContext = &mocksconsole.Context{}
mockArtisan = &mocksconsole.Artisan{}
mockContext.On("OptionBool", "seed").Return(true).Once()
mockContext.On("OptionSlice", "seeder").Return([]string{}).Once()
mockArtisan.On("Call", "db:seed").Return(nil).Once()
mockContext = mocksconsole.NewContext(t)
mockArtisan = mocksconsole.NewArtisan(t)
mockContext.EXPECT().OptionBool("seed").Return(true).Once()
mockContext.EXPECT().OptionSlice("seeder").Return([]string{}).Once()
mockArtisan.EXPECT().Call("db:seed").Once()

migrateFreshCommand = NewMigrateFreshCommand(mockConfig, mockArtisan)
assert.Nil(t, migrateFreshCommand.Handle(mockContext))

var agent2 Agent
var agent2 migration.Agent
err = query.Where("name", "goravel").First(&agent2)
assert.Nil(t, err)
assert.True(t, agent2.ID > 0)
Expand Down
Loading

0 comments on commit 62d45f5

Please sign in to comment.