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: [#280] Optimize migration command #680

Merged
merged 12 commits into from
Oct 18, 2024
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement the paths when removing the sql driver.

}
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))
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
// 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
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
// 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 @@

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

"github.com/golang-migrate/migrate/v4"
Expand All @@ -14,12 +13,13 @@
"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 @@
configBuilder := databasedb.NewConfigBuilder(config, connection)
writeConfigs := configBuilder.Writes()
if len(writeConfigs) == 0 {
return nil, errors.New("not found database configuration")
return nil, errors.OrmDatabaseConfigNotFound

Check warning on line 31 in database/console/migration/migrate.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate.go#L31

Added line #L31 was not covered by tests
}

switch database.Driver(driver) {
switch dbDriver {
case database.DriverMysql:
mysqlDsn := databasedb.Dsn(writeConfigs[0])
if mysqlDsn == "" {
Expand Down Expand Up @@ -81,7 +81,7 @@
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 @@

return migrate.NewWithDatabaseInstance(dir, "sqlserver", instance)
default:
return nil, errors.New("database driver only support mysql, postgres, sqlite and sqlserver")
return nil, errors.OrmDriverNotSupported

Check warning on line 113 in database/console/migration/migrate.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate.go#L113

Added line #L113 was not covered by tests
}
}
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
}

Check warning on line 20 in database/console/migration/migrate_command.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate_command.go#L18-L20

Added lines #L18 - L20 were not covered by tests

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 {

Check warning on line 28 in database/console/migration/migrate_command.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate_command.go#L28

Added line #L28 was not covered by tests
return "migrate"
}

// Description The console command description.
func (receiver *MigrateCommand) Description() string {
func (r *MigrateCommand) Description() string {

Check warning on line 33 in database/console/migration/migrate_command.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate_command.go#L33

Added line #L33 was not covered by tests
return "Run the database migrations"
}

// Extend The console command extend.
func (receiver *MigrateCommand) Extend() command.Extend {
func (r *MigrateCommand) Extend() command.Extend {

Check warning on line 38 in database/console/migration/migrate_command.go

View check run for this annotation

Codecov / codecov/patch

database/console/migration/migrate_command.go#L38

Added line #L38 was not covered by tests
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
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
}

hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
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
Loading