Skip to content

Commit f77315b

Browse files
committed
feat: [#280] Manage migrations table
1 parent aeaf75d commit f77315b

14 files changed

Lines changed: 1050 additions & 183 deletions

File tree

contracts/database/migration/grammar.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ type Grammar interface {
99
CompileCreate(blueprint Blueprint, query orm.Query) string
1010
// CompileDropIfExists Compile a drop table (if exists) command.
1111
CompileDropIfExists(blueprint Blueprint) string
12+
// CompileTables Compile the query to determine the tables.
13+
CompileTables(database string) string
1214
// GetAttributeCommands Get the commands for the schema build.
1315
GetAttributeCommands() []string
1416
// GetModifiers Get the column modifiers.
Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
package migration
22

3+
type File struct {
4+
ID uint
5+
Migration string
6+
Batch int
7+
}
8+
39
type Repository interface {
4-
//// CreateRepository Create the migration repository data store.
5-
//CreateRepository()
6-
//// Delete Remove a migration from the log.
7-
//Delete(migration string)
8-
//// DeleteRepository Delete the migration repository data store.
9-
//DeleteRepository()
10-
//// GetLast Get the last migration batch.
11-
//GetLast()
12-
//// GetMigrationBatches Get the completed migrations with their batch numbers.
13-
//GetMigrationBatches()
14-
//// GetMigrations Get the list of migrations.
15-
//GetMigrations(steps int)
16-
//// GetMigrationsByBatch Get the list of the migrations by batch.
17-
//GetMigrationsByBatch(batch int)
18-
//// GetNextBatchNumber Get the next migration batch number.
19-
//GetNextBatchNumber()
20-
//// GetRan Get the completed migrations.
21-
//GetRan()
22-
//// Log that a migration was run.
23-
//Log(file, batch string)
24-
//// RepositoryExists Determine if the migration repository exists.
25-
//RepositoryExists()
10+
// CreateRepository Create the migration repository data store.
11+
CreateRepository() error
12+
// Delete Remove a migration from the log.
13+
Delete(migration string) error
14+
// DeleteRepository Delete the migration repository data store.
15+
DeleteRepository() error
16+
// GetLast Get the last migration batch.
17+
GetLast() ([]File, error)
18+
// GetMigrations Get the list of migrations.
19+
GetMigrations(steps int) ([]File, error)
20+
// GetMigrationsByBatch Get the list of the migrations by batch.
21+
GetMigrationsByBatch(batch int) ([]File, error)
22+
// GetNextBatchNumber Get the next migration batch number.
23+
GetNextBatchNumber() int
24+
// GetRan Get the completed migrations.
25+
GetRan() ([]string, error)
26+
// Log that a migration was run.
27+
Log(file string, batch int) error
28+
// RepositoryExists Determine if the migration repository exists.
29+
RepositoryExists() bool
2630
}

contracts/database/migration/schema.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package migration
22

33
type Schema interface {
44
// Create a new table on the schema.
5-
Create(table string, callback func(table Blueprint))
5+
Create(table string, callback func(table Blueprint)) error
66
// Connection Get the connection for the schema.
77
Connection(name string) Schema
88
// DropIfExists Drop a table from the schema if exists.
9-
DropIfExists(table string)
9+
DropIfExists(table string) error
10+
// GetTables Get the tables that belong to the database.
11+
GetTables() ([]Table, error)
12+
// HasTable Determine if the given table exists.
13+
HasTable(table string) bool
1014
// Register migrations.
1115
Register([]Migration)
1216
// Sql Execute a sql directly.
@@ -40,3 +44,9 @@ type Command struct {
4044
References []string
4145
Value string
4246
}
47+
48+
type Table struct {
49+
Comment string
50+
Name string
51+
Size int
52+
}

database/migration/blueprint.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ type Blueprint struct {
2525
table string
2626
}
2727

28-
func NewBlueprint(prefix, schema string) *Blueprint {
29-
return &Blueprint{
28+
func NewBlueprint(table, prefix string) *Blueprint {
29+
blueprint := &Blueprint{
3030
prefix: prefix,
31-
schema: schema,
31+
table: table,
3232
}
33+
34+
return blueprint
3335
}
3436

3537
func (r *Blueprint) BigIncrements(column string) migration.ColumnDefinition {

database/migration/grammars/postgres.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ func (r *Postgres) CompileDropIfExists(blueprint migration.Blueprint) string {
3737
return fmt.Sprintf("drop table if exists %s", blueprint.GetTableName())
3838
}
3939

40+
func (r *Postgres) CompileTables(database string) string {
41+
return "select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, " +
42+
"obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " +
43+
"where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " +
44+
"order by c.relname"
45+
}
46+
4047
func (r *Postgres) GetAttributeCommands() []string {
4148
return r.attributeCommands
4249
}

database/migration/repository.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package migration
2+
3+
import (
4+
"github.com/goravel/framework/contracts/database/migration"
5+
"github.com/goravel/framework/contracts/database/orm"
6+
)
7+
8+
type Repository struct {
9+
query orm.Query
10+
schema migration.Schema
11+
table string
12+
}
13+
14+
func NewRepository(query orm.Query, schema migration.Schema, table string) *Repository {
15+
return &Repository{
16+
query: query,
17+
schema: schema,
18+
table: table,
19+
}
20+
}
21+
22+
func (r *Repository) CreateRepository() error {
23+
return r.schema.Create(r.table, func(table migration.Blueprint) {
24+
table.ID()
25+
table.String("migration")
26+
table.Integer("batch")
27+
})
28+
}
29+
30+
func (r *Repository) Delete(migration string) error {
31+
_, err := r.query.Table(r.table).Where("migration", migration).Delete()
32+
33+
return err
34+
}
35+
36+
func (r *Repository) DeleteRepository() error {
37+
return r.schema.DropIfExists(r.table)
38+
}
39+
40+
func (r *Repository) GetLast() ([]migration.File, error) {
41+
var files []migration.File
42+
if err := r.query.Table(r.table).Where("batch", r.getLastBatchNumber()).OrderByDesc("migration").Get(&files); err != nil {
43+
return nil, err
44+
}
45+
46+
return files, nil
47+
}
48+
49+
func (r *Repository) GetMigrations(steps int) ([]migration.File, error) {
50+
var files []migration.File
51+
if err := r.query.Table(r.table).Where("batch >= 1").OrderByDesc("batch").OrderByDesc("migration").Limit(steps).Get(&files); err != nil {
52+
return nil, err
53+
}
54+
55+
return files, nil
56+
}
57+
58+
func (r *Repository) GetMigrationsByBatch(batch int) ([]migration.File, error) {
59+
var files []migration.File
60+
if err := r.query.Table(r.table).Where("batch", batch).OrderByDesc("migration").Get(&files); err != nil {
61+
return nil, err
62+
}
63+
64+
return files, nil
65+
}
66+
67+
func (r *Repository) GetNextBatchNumber() int {
68+
return r.getLastBatchNumber() + 1
69+
}
70+
71+
func (r *Repository) GetRan() ([]string, error) {
72+
var migrations []string
73+
if err := r.query.Table(r.table).OrderBy("batch").OrderBy("migration").Pluck("migration", &migrations); err != nil {
74+
return nil, err
75+
}
76+
77+
return migrations, nil
78+
}
79+
80+
func (r *Repository) Log(file string, batch int) error {
81+
return r.query.Table(r.table).Create(map[string]any{
82+
"migration": file,
83+
"batch": batch,
84+
})
85+
}
86+
87+
func (r *Repository) RepositoryExists() bool {
88+
return r.schema.HasTable(r.table)
89+
}
90+
91+
func (r *Repository) getLastBatchNumber() int {
92+
var batch int
93+
if err := r.query.Table(r.table).OrderBy("batch", "desc").Pluck("batch", &batch); err != nil {
94+
return 0
95+
}
96+
97+
return batch
98+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package migration
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
8+
"github.com/goravel/framework/contracts/database"
9+
"github.com/goravel/framework/contracts/database/migration"
10+
"github.com/goravel/framework/contracts/database/orm"
11+
"github.com/goravel/framework/database/gorm"
12+
mocksorm "github.com/goravel/framework/mocks/database/orm"
13+
"github.com/goravel/framework/support/docker"
14+
"github.com/goravel/framework/support/env"
15+
)
16+
17+
type RepositoryTestSuite struct {
18+
suite.Suite
19+
driverToTestQuery map[database.Driver]*gorm.TestQuery
20+
}
21+
22+
func TestRepositoryTestSuite(t *testing.T) {
23+
if env.IsWindows() {
24+
t.Skip("Skipping tests of using docker")
25+
}
26+
27+
suite.Run(t, &RepositoryTestSuite{})
28+
}
29+
30+
func (s *RepositoryTestSuite) SetupTest() {
31+
postgresDocker := docker.Postgres()
32+
postgresQuery := gorm.NewTestQuery(postgresDocker)
33+
s.driverToTestQuery = map[database.Driver]*gorm.TestQuery{
34+
database.DriverPostgres: postgresQuery,
35+
}
36+
}
37+
38+
func (s *RepositoryTestSuite) TestCreate_Delete_Exists() {
39+
for driver, query := range s.driverToTestQuery {
40+
s.Run(driver.String(), func() {
41+
repository, mockOrm := s.initRepository(s.T(), driver, query.Query())
42+
43+
mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
44+
mockOrm.EXPECT().Query().Return(repository.query).Once()
45+
46+
err := repository.CreateRepository()
47+
s.NoError(err)
48+
49+
mockOrm.EXPECT().Query().Return(repository.query).Once()
50+
51+
s.True(repository.RepositoryExists())
52+
53+
mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
54+
mockOrm.EXPECT().Query().Return(repository.query).Once()
55+
56+
err = repository.DeleteRepository()
57+
s.NoError(err)
58+
59+
mockOrm.EXPECT().Query().Return(repository.query).Once()
60+
61+
s.False(repository.RepositoryExists())
62+
})
63+
}
64+
}
65+
66+
func (s *RepositoryTestSuite) TestRecord() {
67+
for driver, query := range s.driverToTestQuery {
68+
s.Run(driver.String(), func() {
69+
repository, mockOrm := s.initRepository(s.T(), driver, query.Query())
70+
71+
mockOrm.EXPECT().Query().Return(repository.query).Once()
72+
73+
if !repository.RepositoryExists() {
74+
mockOrm.EXPECT().Connection(driver.String()).Return(mockOrm).Once()
75+
mockOrm.EXPECT().Query().Return(repository.query).Once()
76+
77+
s.NoError(repository.CreateRepository())
78+
}
79+
80+
err := repository.Log("migration1", 1)
81+
s.NoError(err)
82+
83+
err = repository.Log("migration2", 1)
84+
s.NoError(err)
85+
86+
err = repository.Log("migration3", 2)
87+
s.NoError(err)
88+
89+
lastBatchNumber := repository.getLastBatchNumber()
90+
s.Equal(2, lastBatchNumber)
91+
92+
nextBatchNumber := repository.GetNextBatchNumber()
93+
s.Equal(3, nextBatchNumber)
94+
95+
ranMigrations, err := repository.GetRan()
96+
s.NoError(err)
97+
s.ElementsMatch([]string{"migration1", "migration2", "migration3"}, ranMigrations)
98+
99+
migrations, err := repository.GetMigrations(2)
100+
s.NoError(err)
101+
s.ElementsMatch([]migration.File{
102+
{Migration: "migration3", Batch: 2},
103+
{Migration: "migration2", Batch: 1},
104+
}, migrations)
105+
106+
migrations, err = repository.GetMigrationsByBatch(1)
107+
s.NoError(err)
108+
s.ElementsMatch([]migration.File{
109+
{Migration: "migration2", Batch: 1},
110+
{Migration: "migration1", Batch: 1},
111+
}, migrations)
112+
113+
migrations, err = repository.GetLast()
114+
s.NoError(err)
115+
s.ElementsMatch([]migration.File{
116+
{Migration: "migration3", Batch: 2},
117+
}, migrations)
118+
119+
err = repository.Delete("migration1")
120+
s.NoError(err)
121+
122+
ranMigrations, err = repository.GetRan()
123+
s.NoError(err)
124+
s.ElementsMatch([]string{"migration2", "migration3"}, ranMigrations)
125+
})
126+
}
127+
}
128+
129+
func (s *RepositoryTestSuite) initRepository(t *testing.T, driver database.Driver, query orm.Query) (*Repository, *mocksorm.Orm) {
130+
schema, _, _, mockOrm := initSchema(s.T(), driver)
131+
132+
return NewRepository(query, schema, schema.prefix+"migrations"), mockOrm
133+
}

0 commit comments

Comments
 (0)