Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions contracts/database/migration/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package migration

const (
DriverDefault = "default"
DriverSql = "sql"
)

type Driver interface {
Create(name string) error
}
4 changes: 4 additions & 0 deletions contracts/database/schema/blueprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package schema

type Blueprint interface {
}
27 changes: 27 additions & 0 deletions contracts/database/schema/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package schema

type Schema interface {
// Create a new table on the schema.
//Create(table string, callback func(table Blueprint))
// Connection Get the connection for the schema.
Connection() Schema
// DropIfExists Drop a table from the schema if exists.
//DropIfExists(table string)
// Register migrations.
Register([]Migration)
// Sql Execute a sql directly.
Sql(callback func(table Blueprint))
// Table Modify a table on the schema.
//Table(table string, callback func(table Blueprint))
}

type Migration interface {
// Signature Get the migration signature.
Signature() string
// Connection Get the connection for the migration.
Connection() string
// Up Run the migrations.
Up()
// Down Reverse the migrations.
Down()
}
3 changes: 3 additions & 0 deletions contracts/foundation/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/crypt"
"github.com/goravel/framework/contracts/database/orm"
"github.com/goravel/framework/contracts/database/schema"
"github.com/goravel/framework/contracts/database/seeder"
"github.com/goravel/framework/contracts/event"
"github.com/goravel/framework/contracts/filesystem"
Expand Down Expand Up @@ -70,6 +71,8 @@ type Container interface {
MakeRoute() route.Route
// MakeSchedule resolves the schedule instance.
MakeSchedule() schedule.Schedule
// MakeSchema resolves the schema instance.
MakeSchema() schema.Schema
// MakeSession resolves the session instance.
MakeSession() session.Manager
// MakeStorage resolves the storage instance.
Expand Down
25 changes: 13 additions & 12 deletions database/console/migrate_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/goravel/framework/contracts/config"
"github.com/goravel/framework/contracts/database/orm"
"github.com/goravel/framework/database/migration"
"github.com/goravel/framework/support/carbon"
"github.com/goravel/framework/support/file"
)
Expand All @@ -22,7 +23,7 @@ func NewMigrateCreator(config config.Config) *MigrateCreator {
}

// Create a new migration
func (receiver MigrateCreator) Create(name string, table string, create bool) error {
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
// various place-holders, save the file, and run the post create event.
Expand All @@ -42,7 +43,7 @@ func (receiver MigrateCreator) Create(name string, table string, create bool) er
}

// getStub Get the migration stub file.
func (receiver MigrateCreator) getStub(table string, create bool) (string, string) {
func (receiver *MigrateCreator) getStub(table string, create bool) (string, string) {
if table == "" {
return "", ""
}
Expand All @@ -51,33 +52,33 @@ func (receiver MigrateCreator) getStub(table string, create bool) (string, strin
switch orm.Driver(driver) {
case orm.DriverPostgresql:
if create {
return PostgresqlStubs{}.CreateUp(), PostgresqlStubs{}.CreateDown()
return migration.PostgresqlStubs{}.CreateUp(), migration.PostgresqlStubs{}.CreateDown()
}

return PostgresqlStubs{}.UpdateUp(), PostgresqlStubs{}.UpdateDown()
return migration.PostgresqlStubs{}.UpdateUp(), migration.PostgresqlStubs{}.UpdateDown()
case orm.DriverSqlite:
if create {
return SqliteStubs{}.CreateUp(), SqliteStubs{}.CreateDown()
return migration.SqliteStubs{}.CreateUp(), migration.SqliteStubs{}.CreateDown()
}

return SqliteStubs{}.UpdateUp(), SqliteStubs{}.UpdateDown()
return migration.SqliteStubs{}.UpdateUp(), migration.SqliteStubs{}.UpdateDown()
case orm.DriverSqlserver:
if create {
return SqlserverStubs{}.CreateUp(), SqlserverStubs{}.CreateDown()
return migration.SqlserverStubs{}.CreateUp(), migration.SqlserverStubs{}.CreateDown()
}

return SqlserverStubs{}.UpdateUp(), SqlserverStubs{}.UpdateDown()
return migration.SqlserverStubs{}.UpdateUp(), migration.SqlserverStubs{}.UpdateDown()
default:
if create {
return MysqlStubs{}.CreateUp(), MysqlStubs{}.CreateDown()
return migration.MysqlStubs{}.CreateUp(), migration.MysqlStubs{}.CreateDown()
}

return MysqlStubs{}.UpdateUp(), MysqlStubs{}.UpdateDown()
return migration.MysqlStubs{}.UpdateUp(), migration.MysqlStubs{}.UpdateDown()
}
}

// populateStub Populate the place-holders in the migration stub.
func (receiver MigrateCreator) populateStub(stub string, table string) string {
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"))

if table != "" {
Expand All @@ -88,7 +89,7 @@ func (receiver MigrateCreator) populateStub(stub string, table string) string {
}

// getPath Get the full path to the migration.
func (receiver MigrateCreator) getPath(name string, category string) string {
func (receiver *MigrateCreator) getPath(name string, category string) string {
pwd, _ := os.Getwd()

return fmt.Sprintf("%s/database/migrations/%s_%s.%s.sql", pwd, carbon.Now().ToShortDateTimeString(), name, category)
Expand Down
21 changes: 15 additions & 6 deletions database/console/migrate_make_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package console

import (
"errors"
"fmt"

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

Expand Down Expand Up @@ -56,14 +59,20 @@ func (receiver *MigrateMakeCommand) Handle(ctx console.Context) error {
}
}

// We will attempt to guess the table name if this the migration has
// "create" in the name. This will allow us to provide a convenient way
// of creating migrations that create new tables for the application.
table, create := TableGuesser{}.Guess(name)
var migrationDriver contractsmigration.Driver
driver := receiver.config.GetString("database.migration.driver")

switch driver {
case contractsmigration.DriverDefault:
migrationDriver = migration.NewDefaultDriver()
case contractsmigration.DriverSql:
migrationDriver = migration.NewSqlDriver(receiver.config)
default:
return fmt.Errorf("unsupported migration driver: %s", driver)
}

// Write the migration file to disk.
migrateCreator := NewMigrateCreator(receiver.config)
if err := migrateCreator.Create(name, table, create); err != nil {
if err := migrationDriver.Create(name); err != nil {
return err
}

Expand Down
100 changes: 77 additions & 23 deletions database/console/migrate_make_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,83 @@ import (
)

func TestMigrateMakeCommand(t *testing.T) {
var (
mockConfig *configmock.Config
mockContext *consolemocks.Context
)

now := carbon.Now()
up := fmt.Sprintf("database/migrations/%s_%s.%s.sql", now.ToShortDateTimeString(), "create_users_table", "up")
down := fmt.Sprintf("database/migrations/%s_%s.%s.sql", now.ToShortDateTimeString(), "create_users_table", "down")

mockConfig := &configmock.Config{}
mockConfig.On("GetString", "database.default").Return("mysql").Times(3)
mockConfig.On("GetString", "database.connections.mysql.driver").Return("mysql").Once()
mockConfig.On("GetString", "database.connections.mysql.charset").Return("utf8mb4").Twice()

migrateMakeCommand := NewMigrateMakeCommand(mockConfig)
mockContext := &consolemocks.Context{}
mockContext.On("Argument", 0).Return("").Once()
mockContext.On("Ask", "Enter the migration name", mock.Anything).Return("", errors.New("the migration name cannot be empty")).Once()
err := migrateMakeCommand.Handle(mockContext)
assert.EqualError(t, err, "the migration name cannot be empty")
assert.False(t, file.Exists(up))
assert.False(t, file.Exists(down))

mockContext.On("Argument", 0).Return("create_users_table").Once()
assert.Nil(t, migrateMakeCommand.Handle(mockContext))
assert.True(t, file.Exists(up))
assert.True(t, file.Exists(down))
assert.Nil(t, file.Remove("database"))
carbon.SetTestNow(now)

beforeEach := func() {
mockConfig = &configmock.Config{}
mockContext = &consolemocks.Context{}
}

afterEach := func() {
mockConfig.AssertExpectations(t)
mockContext.AssertExpectations(t)
}

tests := []struct {
name string
setup func()
assert func()
expectErr error
}{
{
name: "the migration name is empty",
setup: func() {
mockContext.On("Argument", 0).Return("").Once()
mockContext.On("Ask", "Enter the migration name", mock.Anything).Return("", errors.New("the migration name cannot be empty")).Once()
},
assert: func() {},
expectErr: errors.New("the migration name cannot be empty"),
},
{
name: "default driver",
setup: func() {
mockConfig.On("GetString", "database.migration.driver").Return("default").Once()
mockContext.On("Argument", 0).Return("create_users_table").Once()
},
assert: func() {
migration := fmt.Sprintf("database/migrations/%s_%s.go", now.ToShortDateTimeString(), "create_users_table")

assert.True(t, file.Exists(migration))
},
},
{
name: "sql driver",
setup: func() {
mockConfig.On("GetString", "database.migration.driver").Return("sql").Once()
mockConfig.On("GetString", "database.default").Return("mysql").Times(3)
mockConfig.On("GetString", "database.connections.mysql.driver").Return("mysql").Once()
mockConfig.On("GetString", "database.connections.mysql.charset").Return("utf8mb4").Twice()
mockContext.On("Argument", 0).Return("create_users_table").Once()
},
assert: func() {
up := fmt.Sprintf("database/migrations/%s_%s.%s.sql", now.ToShortDateTimeString(), "create_users_table", "up")
down := fmt.Sprintf("database/migrations/%s_%s.%s.sql", now.ToShortDateTimeString(), "create_users_table", "down")

mockConfig.AssertExpectations(t)
assert.True(t, file.Exists(up))
assert.True(t, file.Exists(down))
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
beforeEach()
test.setup()

migrateMakeCommand := NewMigrateMakeCommand(mockConfig)
err := migrateMakeCommand.Handle(mockContext)
assert.Equal(t, test.expectErr, err)

test.assert()
afterEach()
})
}

assert.Nil(t, file.Remove("database"))
}
73 changes: 73 additions & 0 deletions database/migration/default_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package migration

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/goravel/framework/support/carbon"
"github.com/goravel/framework/support/file"
"github.com/goravel/framework/support/str"
)

type DefaultDriver struct {
}

func NewDefaultDriver() *DefaultDriver {
return &DefaultDriver{}
}

func (r *DefaultDriver) Create(name string) error {
// We will attempt to guess the table name if this the migration has
// "create" in the name. This will allow us to provide a convenient way
// of creating migrations that create new tables for the application.
table, create := TableGuesser{}.Guess(name)

// 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
// various place-holders, save the file, and run the post create event.
stub := r.getStub(table, create)

// Prepend timestamp to the file name.
fileName := r.getFileName(name)

// Create the up.sql file.
if err := file.Create(r.getPath(fileName), r.populateStub(stub, fileName)); err != nil {
return err
}

return nil
}

// getStub Get the migration stub file.
func (r *DefaultDriver) getStub(table string, create bool) string {
if table == "" {
return Stubs{}.Empty()
}

if create {
return Stubs{}.Create()
}

return Stubs{}.Update()
}

// populateStub Populate the place-holders in the migration stub.
func (r *DefaultDriver) populateStub(stub, fileName string) string {
stub = strings.ReplaceAll(stub, "DummyMigration", str.Of(fileName).Prepend("m_").Studly().String())
stub = strings.ReplaceAll(stub, "DummyName", fileName)

return stub
}

// getPath Get the full path to the migration.
func (r *DefaultDriver) getPath(name string) string {
pwd, _ := os.Getwd()

return filepath.Join(pwd, "database", "migrations", name+".go")
}

func (r *DefaultDriver) getFileName(name string) string {
return fmt.Sprintf("%s_%s", carbon.Now().ToShortDateTimeString(), name)
}
Loading