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
2 changes: 2 additions & 0 deletions contracts/database/migration/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type Grammar interface {
CompileCreate(blueprint Blueprint, query orm.Query) string
// CompileDropIfExists Compile a drop table (if exists) command.
CompileDropIfExists(blueprint Blueprint) string
// CompileTables Compile the query to determine the tables.
CompileTables(database string) string
// GetAttributeCommands Get the commands for the schema build.
GetAttributeCommands() []string
// GetModifiers Get the column modifiers.
Expand Down
48 changes: 26 additions & 22 deletions contracts/database/migration/repository.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
package migration

type File struct {
ID uint
Migration string
Batch int
}

type Repository interface {
//// CreateRepository Create the migration repository data store.
//CreateRepository()
//// Delete Remove a migration from the log.
//Delete(migration string)
//// DeleteRepository Delete the migration repository data store.
//DeleteRepository()
//// GetLast Get the last migration batch.
//GetLast()
//// GetMigrationBatches Get the completed migrations with their batch numbers.
//GetMigrationBatches()
//// GetMigrations Get the list of migrations.
//GetMigrations(steps int)
//// GetMigrationsByBatch Get the list of the migrations by batch.
//GetMigrationsByBatch(batch int)
//// GetNextBatchNumber Get the next migration batch number.
//GetNextBatchNumber()
//// GetRan Get the completed migrations.
//GetRan()
//// Log that a migration was run.
//Log(file, batch string)
//// RepositoryExists Determine if the migration repository exists.
//RepositoryExists()
// CreateRepository Create the migration repository data store.
CreateRepository() error
// Delete Remove a migration from the log.
Delete(migration string) error
// DeleteRepository Delete the migration repository data store.
DeleteRepository() error
// GetLast Get the last migration batch.
GetLast() ([]File, error)
// GetMigrations Get the list of migrations.
GetMigrations(steps int) ([]File, error)
// GetMigrationsByBatch Get the list of the migrations by batch.
GetMigrationsByBatch(batch int) ([]File, error)
// GetNextBatchNumber Get the next migration batch number.
GetNextBatchNumber() (int, error)
// GetRan Get the completed migrations.
GetRan() ([]string, error)
// Log that a migration was run.
Log(file string, batch int) error
// RepositoryExists Determine if the migration repository exists.
RepositoryExists() bool
}
21 changes: 17 additions & 4 deletions contracts/database/migration/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package migration

type Schema interface {
// Create a new table on the schema.
Create(table string, callback func(table Blueprint))
Create(table string, callback func(table Blueprint)) error
// Connection Get the connection for the schema.
Connection(name string) Schema
// DropIfExists Drop a table from the schema if exists.
DropIfExists(table string)
DropIfExists(table string) error
// GetTables Get the tables that belong to the database.
GetTables() ([]Table, error)
// HasTable Determine if the given table exists.
HasTable(table string) bool
// Register migrations.
Register([]Migration)
// Sql Execute a sql directly.
Expand All @@ -18,14 +22,17 @@ type Schema interface {
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()
}

type Connection interface {
// Connection Get the connection for the migration.
Connection() string
}

type Command struct {
Algorithm string
Column ColumnDefinition
Expand All @@ -40,3 +47,9 @@ type Command struct {
References []string
Value string
}

type Table struct {
Comment string
Name string
Size int
}
5 changes: 2 additions & 3 deletions database/migration/blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ type Blueprint struct {
columns []*ColumnDefinition
commands []*migration.Command
prefix string
schema string
table string
}

func NewBlueprint(prefix, schema string) *Blueprint {
func NewBlueprint(prefix, table string) *Blueprint {
return &Blueprint{
prefix: prefix,
schema: schema,
table: table,
}
}

Expand Down
7 changes: 7 additions & 0 deletions database/migration/grammars/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ func (r *Postgres) CompileDropIfExists(blueprint migration.Blueprint) string {
return fmt.Sprintf("drop table if exists %s", blueprint.GetTableName())
}

func (r *Postgres) CompileTables(database string) string {
return "select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, " +
"obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " +
"where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " +
"order by c.relname"
}

func (r *Postgres) GetAttributeCommands() []string {
return r.attributeCommands
}
Expand Down
108 changes: 108 additions & 0 deletions database/migration/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package migration

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

type Repository struct {
query orm.Query
schema migration.Schema
table string
}

func NewRepository(query orm.Query, schema migration.Schema, table string) *Repository {
return &Repository{
query: query,
schema: schema,
table: table,
}
}

func (r *Repository) CreateRepository() error {
return r.schema.Create(r.table, func(table migration.Blueprint) {
table.ID()
table.String("migration")
table.Integer("batch")
})
}

func (r *Repository) Delete(migration string) error {
_, err := r.query.Table(r.table).Where("migration", migration).Delete()

return err
}

func (r *Repository) DeleteRepository() error {
return r.schema.DropIfExists(r.table)
}

func (r *Repository) GetLast() ([]migration.File, error) {
var files []migration.File
lastBatchNumber, err := r.getLastBatchNumber()
if err != nil {
return nil, err
}

if err := r.query.Table(r.table).Where("batch", lastBatchNumber).OrderByDesc("migration").Get(&files); err != nil {
return nil, err
}

return files, nil
}

func (r *Repository) GetMigrations(steps int) ([]migration.File, error) {
var files []migration.File
if err := r.query.Table(r.table).Where("batch >= 1").OrderByDesc("batch").OrderByDesc("migration").Limit(steps).Get(&files); err != nil {
return nil, err
}

return files, nil
}

func (r *Repository) GetMigrationsByBatch(batch int) ([]migration.File, error) {
var files []migration.File
if err := r.query.Table(r.table).Where("batch", batch).OrderByDesc("migration").Get(&files); err != nil {
return nil, err
}

return files, nil
}

func (r *Repository) GetNextBatchNumber() (int, error) {
lastBatchNumber, err := r.getLastBatchNumber()
if err != nil {
return 0, err
}

return lastBatchNumber + 1, nil
}

func (r *Repository) GetRan() ([]string, error) {
var migrations []string
if err := r.query.Table(r.table).OrderBy("batch").OrderBy("migration").Pluck("migration", &migrations); err != nil {
return nil, err
}

return migrations, nil
}

func (r *Repository) Log(file string, batch int) error {
return r.query.Table(r.table).Create(map[string]any{
"migration": file,
"batch": batch,
})
}

func (r *Repository) RepositoryExists() bool {
return r.schema.HasTable(r.table)
}

func (r *Repository) getLastBatchNumber() (int, error) {
var batch int
if err := r.query.Table(r.table).OrderByDesc("batch").Limit(1).Pluck("batch", &batch); err != nil {
return 0, err
}

return batch, nil
}
135 changes: 135 additions & 0 deletions database/migration/repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package migration

import (
"testing"

"github.com/stretchr/testify/suite"

"github.com/goravel/framework/contracts/database"
"github.com/goravel/framework/database/gorm"
mocksorm "github.com/goravel/framework/mocks/database/orm"
"github.com/goravel/framework/support/docker"
"github.com/goravel/framework/support/env"
)

type RepositoryTestSuite struct {
suite.Suite
driverToTestQuery map[database.Driver]*gorm.TestQuery
}

func TestRepositoryTestSuite(t *testing.T) {
if env.IsWindows() {
t.Skip("Skipping tests of using docker")
}

suite.Run(t, &RepositoryTestSuite{})
}

func (s *RepositoryTestSuite) SetupTest() {
postgresDocker := docker.Postgres()
postgresQuery := gorm.NewTestQuery(postgresDocker, true)
s.driverToTestQuery = map[database.Driver]*gorm.TestQuery{
database.DriverPostgres: postgresQuery,
}
}

func (s *RepositoryTestSuite) TestCreate_Delete_Exists() {
for driver, testQuery := range s.driverToTestQuery {
s.Run(driver.String(), func() {
repository, mockOrm := s.initRepository(testQuery)

mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
mockOrm.EXPECT().Query().Return(repository.query).Once()

err := repository.CreateRepository()
s.NoError(err)

mockOrm.EXPECT().Query().Return(repository.query).Once()

s.True(repository.RepositoryExists())

mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
mockOrm.EXPECT().Query().Return(repository.query).Once()

err = repository.DeleteRepository()
s.NoError(err)

mockOrm.EXPECT().Query().Return(repository.query).Once()

s.False(repository.RepositoryExists())
})
}
}

func (s *RepositoryTestSuite) TestRecord() {
for driver, testQuery := range s.driverToTestQuery {
s.Run(driver.String(), func() {
repository, mockOrm := s.initRepository(testQuery)

mockOrm.EXPECT().Query().Return(repository.query).Once()

if !repository.RepositoryExists() {
mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
mockOrm.EXPECT().Query().Return(repository.query).Once()

s.NoError(repository.CreateRepository())
}

err := repository.Log("migration1", 1)
s.NoError(err)

err = repository.Log("migration2", 1)
s.NoError(err)

err = repository.Log("migration3", 2)
s.NoError(err)

lastBatchNumber, err := repository.getLastBatchNumber()
s.NoError(err)
s.Equal(2, lastBatchNumber)

nextBatchNumber, err := repository.GetNextBatchNumber()
s.NoError(err)
s.Equal(3, nextBatchNumber)

ranMigrations, err := repository.GetRan()
s.NoError(err)
s.ElementsMatch([]string{"migration1", "migration2", "migration3"}, ranMigrations)

migrations, err := repository.GetMigrations(2)
s.NoError(err)
s.Len(migrations, 2)
s.Equal("migration3", migrations[0].Migration)
s.Equal(2, migrations[0].Batch)
s.Equal("migration2", migrations[1].Migration)
s.Equal(1, migrations[1].Batch)

migrations, err = repository.GetMigrationsByBatch(1)
s.NoError(err)
s.Len(migrations, 2)
s.Equal("migration2", migrations[0].Migration)
s.Equal(1, migrations[0].Batch)
s.Equal("migration1", migrations[1].Migration)
s.Equal(1, migrations[1].Batch)

migrations, err = repository.GetLast()
s.NoError(err)
s.Len(migrations, 1)
s.Equal("migration3", migrations[0].Migration)
s.Equal(2, migrations[0].Batch)

err = repository.Delete("migration1")
s.NoError(err)

ranMigrations, err = repository.GetRan()
s.NoError(err)
s.ElementsMatch([]string{"migration2", "migration3"}, ranMigrations)
})
}
}

func (s *RepositoryTestSuite) initRepository(testQuery *gorm.TestQuery) (*Repository, *mocksorm.Orm) {
schema, mockOrm := initSchema(s.T(), testQuery)

return NewRepository(testQuery.Query(), schema, "migrations"), mockOrm
}
Loading