Skip to content

Commit 42ecefd

Browse files
committed
feat : initial migrator implementation
Signed-off-by: DavidDexter <dmwangi@kineticengines.co.ke>
1 parent 21fae79 commit 42ecefd

File tree

11 files changed

+265
-41
lines changed

11 files changed

+265
-41
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ gormgx.go -v make-migrations
4545
## Gotchas
4646

4747
- Models specifications should be in one (1) go file. Preferably `models.go`
48+
- Override Foreign Key must be of the form `ModelNameRefer`.
49+
- Foreign key must be `Interface{}`. Gormgx will extract the extact model from the tags

example/app/main.go

+6-22
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,11 @@ package main
33
import (
44
"time"
55

6-
"github.com/lib/pq"
7-
6+
"github.com/kineticengines/gorm-migrations/example/models"
87
"gorm.io/driver/postgres"
98
"gorm.io/gorm"
109
)
1110

12-
// Accounts ...
13-
type Accounts struct {
14-
gorm.Model
15-
GUID *string `gorm:"not null;unique;column:guid"`
16-
FirstName *string `gorm:"not null;column:first_name"`
17-
LastName string `gorm:"not null;column:last_name"`
18-
IsAccountBillable *bool `gorm:"default:true;column:is_account_billable"`
19-
20-
Active *bool `gorm:"default:true"`
21-
HasAcceptedTerms bool `gorm:"default:false"`
22-
AmountPaid float32 `gorm:"null;type:numeric"`
23-
AmountDeducted *float32 `gorm:"type:numeric;default:0"`
24-
Date time.Time `gorm:"not null"`
25-
Grouped pq.StringArray `gorm:"type:varchar(64)[];"`
26-
}
27-
2811
func main() {
2912

3013
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable TimeZone=Africa/Nairobi"
@@ -35,21 +18,22 @@ func main() {
3518
}
3619

3720
// Migrate the schema
38-
_ = db.AutoMigrate(&Accounts{})
21+
_ = db.AutoMigrate(&models.Accounts{}, &models.Company{}, &models.User{}, &models.Organisations{}, &models.Credentials{}, &models.Company{})
3922

4023
// Create
4124
guid := "0afca2aa-7de1-11eb-8398-434ca8dedd68"
4225
fname := "dx"
43-
db.Create(&Accounts{FirstName: &fname, LastName: "ter", GUID: &guid})
26+
lname := "ter"
27+
db.Create(&models.Accounts{FirstName: &fname, LastName: &lname, GUID: &guid})
4428

4529
// Read
46-
var account Accounts
30+
var account models.Accounts
4731
db.First(&account, 1)
4832
db.First(&account, "guid = ?", guid)
4933

5034
db.Model(&account).Update("last_name", "new-last-name")
5135

52-
db.Model(&account).Updates(Accounts{HasAcceptedTerms: true, Date: time.Now()})
36+
db.Model(&account).Updates(models.Accounts{HasAcceptedTerms: true, Date: time.Now()})
5337

5438
// Delete - delete product
5539
db.Delete(&account, 1)

example/models/models.go

+35-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,24 @@ type Product struct {
1313
Price uint
1414
}
1515

16+
// // IsModel ..
17+
// func (m *Product) IsModel() bool {
18+
// return true
19+
// }
20+
1621
// User ...
1722
type User struct {
18-
Name string
19-
Age uint
20-
Product
23+
gorm.Model
24+
Name string
25+
Age uint
26+
CompanyRefer int
27+
Company Company `gorm:"foreignKey:CompanyRefer"`
2128
}
2229

23-
// IsModel ..
24-
func (m *User) IsModel() bool {
25-
return true
26-
}
30+
// // IsModel ..
31+
// func (m *User) IsModel() bool {
32+
// return true
33+
// }
2734

2835
// Organisations is the parent of each individual account
2936
type Organisations struct {
@@ -32,10 +39,10 @@ type Organisations struct {
3239
OrganisationCode *int32 `gorm:"not null;unique;column:organisation_code"`
3340
}
3441

35-
// IsModel ..
36-
func (m *Organisations) IsModel() bool {
37-
return true
38-
}
42+
// // IsModel ..
43+
// func (m *Organisations) IsModel() bool {
44+
// return true
45+
// }
3946

4047
// Accounts is the child/children of an organisation. An organisation must have at least one
4148
// acoount
@@ -54,6 +61,7 @@ type Accounts struct {
5461
AmountDeducted *float32 `gorm:"type:numeric;default:0"`
5562
Date time.Time `gorm:"not null"`
5663
Grouped pq.StringArray `gorm:"type:varchar(64)[];"`
64+
Company interface{} `gorm:"foreignKey:CompanyRefer"`
5765
}
5866

5967
// IsModel ..
@@ -68,3 +76,19 @@ type Credentials struct {
6876
AccountID string `gorm:"not null;unique;column:account_id"`
6977
Password string `gorm:"type:varchar(255)"`
7078
}
79+
80+
// // IsModel ..
81+
// func (m *Credentials) IsModel() bool {
82+
// return true
83+
// }
84+
85+
// Company ....
86+
type Company struct {
87+
ID int
88+
Name string
89+
}
90+
91+
// IsModel ..
92+
func (m *Company) IsModel() bool {
93+
return true
94+
}

pkg/commands/intialize.go

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ models:
3333
# Optional: set to add "gorm.Model" to your models
3434
add_gorm_model: true
3535
36+
# Optional : set the time zone for time.Time fields. Defaults to "Africa/Nairobi"
37+
time_zone: Africa/Nairobi
38+
3639
`))
3740

3841
type initializor struct {

pkg/commands/make_migrations.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var MakeMigrationCmd = &cli.Command{
2525
dir, _ := os.Getwd()
2626
configPath = filepath.Join(dir, definitions.GormgxYamlFileName)
2727
verbose := c.Bool("v")
28-
return NewMgxMaker(configPath, engine.NewRunner(), verbose).Migrate()
28+
return NewMgxMaker(configPath, verbose).Migrate()
2929
},
3030
}
3131

@@ -53,9 +53,14 @@ type MgxMaker struct {
5353
}
5454

5555
// NewMgxMaker ...
56-
func NewMgxMaker(path string, runner definitions.Worker, verbose bool) MakeMigration {
57-
return &MgxMaker{modelsPath: path, verbose: verbose, errorsCache: &sync.Map{}, runner: runner,
58-
modelsPkgs: &[]*types.Package{}, tables: map[string]*definitions.TableTree{}}
56+
func NewMgxMaker(path string, verbose bool) MakeMigration {
57+
return &MgxMaker{
58+
modelsPath: path,
59+
verbose: verbose,
60+
errorsCache: &sync.Map{},
61+
runner: engine.NewRunner(),
62+
modelsPkgs: &[]*types.Package{},
63+
tables: map[string]*definitions.TableTree{}}
5964
}
6065

6166
// Migrate ...
@@ -163,7 +168,7 @@ func (m *MgxMaker) createMigrationFiles() (MakeMigration, error) {
163168
wg.Add(1)
164169
go func(w *sync.WaitGroup, tn string, tt *definitions.TableTree, mgx *MgxMaker) {
165170
defer wg.Done()
166-
if err := migrator.NewMigratorWorker(tn, tt, m.verbose).RunIntialIntent(); err != nil {
171+
if err := migrator.NewMigratorWorker(tn, tt, mgx.verbose, mgx.runner).RunIntialIntent(); err != nil {
167172
m.errorsCache.Store(errorKey, err)
168173
}
169174

pkg/definitions/basic_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ const (
2323
String BasicType = "string"
2424
Compound BasicType = "compound"
2525
Time BasicType = "time"
26+
NullTime BasicType = "nulltime"
27+
Bytes BasicType = "bytes"
2628
Nil BasicType = "nil"
2729
)

pkg/definitions/models.go

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type Config struct {
99
Migrations string `yaml:"migrations"`
1010
Models []string `yaml:"models"`
1111
AddGormModel bool `yaml:"add_gorm_model"`
12+
TimeZone string `yaml:"time_zone"`
1213
}
1314

1415
// Model is a same model as defines in `gorm.Model`

pkg/definitions/ops.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package definitions
2+
3+
// Op identifier of operations that can be invoked
4+
type Op int
5+
6+
const (
7+
8+
// CreateModelOp creates a new model and the corresponding database table
9+
CreateModelOp Op = iota
10+
11+
// DeleteModelOp deletes a model and drops its database table
12+
DeleteModelOp
13+
14+
// RenameModelOp renames a model and renames its database table
15+
RenameModelOp
16+
17+
// AlterModelTableOp renames the database table for a model
18+
AlterModelTableOp
19+
20+
// AlterUniqueTogetherOp changes the unique constraints of a model
21+
AlterUniqueTogetherOp
22+
23+
// AlterIndexTogetherOp changes the indexes of a model
24+
AlterIndexTogetherOp
25+
26+
// AlterOrderWithRespectToOp creates or deletes the _order column for a model
27+
AlterOrderWithRespectToOp
28+
29+
// AlterModelOptionsOp changes various model options without affecting the database
30+
AlterModelOptionsOp
31+
32+
// AddFieldOp adds a field to a model and the corresponding column in the database
33+
AddFieldOp
34+
35+
// RemoveFieldOp removes a field from a model and drops the corresponding column from the database
36+
RemoveFieldOp
37+
38+
// AlterFieldOp changes a field’s definition and alters its database column if necessary
39+
AlterFieldOp
40+
41+
// RenameFieldOp renames a field and, if necessary, also its database column
42+
RenameFieldOp
43+
44+
// AddIndexOp creates an index in the database table for the model
45+
AddIndexOp
46+
47+
// RemoveIndexOp removes an index in the database table for the model
48+
RemoveIndexOp
49+
)
50+
51+
// FieldConstraint ...
52+
type FieldConstraint int
53+
54+
const (
55+
56+
// OnDeleteCascadeConstraint ...
57+
OnDeleteCascadeConstraint FieldConstraint = 1
58+
59+
// OnUpdateCascaseConstraint ...
60+
OnUpdateCascaseConstraint
61+
)
62+
63+
// OpColumn ...
64+
type OpColumn struct {
65+
66+
// this is the column name. If name is provided by the tags, gormgx will use that.
67+
// Otherwise, lowercase, underscore separated name from struct will be used
68+
CanonicalName string
69+
70+
FieldType BasicType
71+
72+
// the type of the field that corresponds to what the database expects
73+
DatabaseType string
74+
75+
// whether this column can take nulls
76+
IsNull bool
77+
78+
// the maximum number of characters. this is for `VARCHAR`, INT and Floats types
79+
Size int
80+
81+
// the default of the the column, It's an interface since we don't know the `DatabaseType` for hand
82+
Default interface{}
83+
84+
// the timezone to use for date fields. All date/time fields in gormgx are timezone by default
85+
TimeZone string
86+
87+
IsNullable bool
88+
89+
IsPrimaryKey bool
90+
91+
IsAutoIncrement bool
92+
93+
// whether this column should considered as an index
94+
IsIndex bool
95+
96+
IsUnique bool
97+
98+
IsForeignKey bool
99+
100+
// the table that this column is foreignKey to. the value will be like `Model.Field`
101+
ForeignKeyTo string
102+
103+
ForeignKeyConstraints FieldConstraint
104+
105+
// applied for floats data types
106+
Precision int
107+
108+
Scale int
109+
}
110+
111+
// Operation describes a single operation with all it's relevant metadata
112+
type Operation struct {
113+
Op Op
114+
TableName string
115+
Columns []*OpColumn
116+
}
117+
118+
// MigrationDump ...
119+
type MigrationDump struct {
120+
IsInitial bool `yaml:"is_initial"`
121+
Dependencies []string `yaml:"dependencies"`
122+
Operations []Operation `yaml:"operations"`
123+
}

pkg/definitions/schema_data_types.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package definitions
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// SchemaDataType ...
8+
type SchemaDataType interface {
9+
DataTypeOf() string
10+
}
11+
12+
// PostgresSchemaDataType ....
13+
type PostgresSchemaDataType struct{}
14+
15+
// DataTypeOf ...
16+
func (p *PostgresSchemaDataType) DataTypeOf(field OpColumn) string {
17+
switch field.FieldType {
18+
case Bool:
19+
return "boolean"
20+
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64:
21+
size := field.Size
22+
if field.FieldType == Uint {
23+
size++
24+
}
25+
if field.IsAutoIncrement {
26+
switch {
27+
case size <= 16:
28+
return "smallserial"
29+
case size <= 32:
30+
return "serial"
31+
default:
32+
return "bigserial"
33+
}
34+
} else {
35+
switch {
36+
case size <= 16:
37+
return "smallint"
38+
case size <= 32:
39+
return "integer"
40+
default:
41+
return "bigint"
42+
}
43+
}
44+
case Float32, Float64:
45+
if field.Precision > 0 {
46+
if field.Scale > 0 {
47+
return fmt.Sprintf("numeric(%d, %d)", field.Precision, field.Scale)
48+
}
49+
return fmt.Sprintf("numeric(%d)", field.Precision)
50+
}
51+
return "decimal"
52+
case String:
53+
if field.Size > 0 {
54+
return fmt.Sprintf("varchar(%d)", field.Size)
55+
}
56+
return "text"
57+
case Time:
58+
return "timestamptz"
59+
case Bytes:
60+
return "bytea"
61+
}
62+
63+
return string(field.FieldType)
64+
}

0 commit comments

Comments
 (0)