Skip to content

Commit

Permalink
✨ 实现自动迁移支持 GORM 自定义数据类型 JSONMap
Browse files Browse the repository at this point in the history
1. 完善 Oracle 数据库类型别名,其中 `JSONMap` 自定义类型对应 `OCIBlobLocator`;
2. 修复 `HasIndex` 判断索引是否存在方法索引名被替换的问题;
3. 完善自动迁移单元测试。

Close #29

Signed-off-by: liutianqi <zixizixi@vip.qq.com>
  • Loading branch information
iTanken committed Apr 28, 2024
1 parent 3f5f631 commit fa7fb1d
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 9 deletions.
118 changes: 118 additions & 0 deletions datatypes_json_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package oracle

import (
"bytes"
"context"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"strings"

"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
)

// JSONMap defined JSON data type, need to implement driver.Valuer, sql.Scanner interface
type JSONMap map[string]interface{}

// Value return json value, implement driver.Valuer interface
//
//goland:noinspection GoMixedReceiverTypes
func (m JSONMap) Value() (driver.Value, error) {
if m == nil {
return nil, nil
}
ba, err := m.MarshalJSON()
return string(ba), err
}

// Scan value into Jsonb, implements sql.Scanner interface
//
//goland:noinspection GoMixedReceiverTypes
func (m *JSONMap) Scan(val interface{}) error {
if val == nil {
*m = make(JSONMap)
return nil
}
var ba []byte
switch v := val.(type) {
case []byte:
ba = v
case string:
ba = []byte(v)
default:
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", val))
}
t := map[string]interface{}{}
rd := bytes.NewReader(ba)
decoder := json.NewDecoder(rd)
decoder.UseNumber()
err := decoder.Decode(&t)
*m = t
return err
}

// MarshalJSON to output non base64 encoded []byte
//
//goland:noinspection GoMixedReceiverTypes
func (m JSONMap) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
t := (map[string]interface{})(m)
return json.Marshal(t)
}

// UnmarshalJSON to deserialize []byte
//
//goland:noinspection GoMixedReceiverTypes
func (m *JSONMap) UnmarshalJSON(b []byte) error {
t := map[string]interface{}{}
err := json.Unmarshal(b, &t)
*m = t
return err
}

// GormDataType gorm common data type
//
//goland:noinspection GoMixedReceiverTypes
func (m JSONMap) GormDataType() string {
return "jsonmap"
}

// GormDBDataType gorm db data type
//
//goland:noinspection GoMixedReceiverTypes
func (JSONMap) GormDBDataType(db *gorm.DB, field *schema.Field) string {
switch db.Dialector.Name() {
case "sqlite":
return "JSON"
case "mysql":
return "JSON"
case "postgres":
return "JSONB"
case "sqlserver":
return "NVARCHAR(MAX)"
case "oracle":
return "BLOB"
default:
return getGormTypeFromTag(field)
}
}

func getGormTypeFromTag(field *schema.Field) (dataType string) {
if field != nil {
if val, ok := field.TagSettings["TYPE"]; ok {
dataType = strings.ToLower(val)
}
}
return
}

//goland:noinspection GoMixedReceiverTypes
func (m JSONMap) GormValue(_ context.Context, _ *gorm.DB) clause.Expr {
data, _ := m.MarshalJSON()
return gorm.Expr("?", string(data))
}
22 changes: 13 additions & 9 deletions migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,22 @@ func (m Migrator) CurrentDatabase() (name string) {
// GetTypeAliases return database type aliases
func (m Migrator) GetTypeAliases(databaseTypeName string) (types []string) {
switch databaseTypeName {
case "blob", "longraw":
types = append(types, "blob", "longraw")
case "clob", "longvarchar", "ocicloblocator":
types = append(types, "clob", "longvarchar", "ocicloblocator")
case "nchar", "varchar", "varchar2":
types = append(types, "nchar", "varchar", "varchar2")
case "blob", "raw", "longraw", "ocibloblocator", "ocifilelocator":
types = append(types, "blob", "raw", "longraw", "ocibloblocator", "ocifilelocator")
case "clob", "nclob", "longvarchar", "ocicloblocator":
types = append(types, "clob", "nclob", "longvarchar", "ocicloblocator")
case "char", "nchar", "varchar", "varchar2", "nvarchar2":
types = append(types, "char", "nchar", "varchar", "varchar2", "nvarchar2")
case "number", "integer", "smallint":
types = append(types, "number", "integer", "smallint")
case "timestampdty", "timestamp":
types = append(types, "timestampdty", "timestamp")
case "decimal", "numeric", "ibfloat", "ibdouble":
types = append(types, "decimal", "numeric", "ibfloat", "ibdouble")
case "timestampdty", "timestamp", "date":
types = append(types, "timestampdty", "timestamp", "date")
case "timestamptz_dty", "timestamp with time zone":
types = append(types, "timestamptz_dty", "timestamp with time zone")
case "timestampltz_dty", "timestampeltz", "timestamp with local time zone":
types = append(types, "timestampltz_dty", "timestampeltz", "timestamp with local time zone")
default:
return
}
Expand Down Expand Up @@ -438,7 +442,7 @@ func (m Migrator) HasIndex(value interface{}, name string) bool {
return m.DB.Raw(
"SELECT COUNT(*) FROM USER_INDEXES WHERE TABLE_NAME = ? AND INDEX_NAME = ?",
m.Migrator.DB.NamingStrategy.TableName(stmt.Table),
m.Migrator.DB.NamingStrategy.IndexName(stmt.Table, name),
name,
).Row().Scan(&count)
})

Expand Down
84 changes: 84 additions & 0 deletions migrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,87 @@ func TestMigrator_FieldNameIsReservedWord(t *testing.T) {
})
}
}

func TestMigrator_DatatypesJsonMapNamingCase(t *testing.T) {
if err := dbErrors[0]; err != nil {
t.Fatal(err)
}
if dbNamingCase == nil {
t.Log("dbNamingCase is nil!")
return
}

type testJsonMapNamingCase struct {
gorm.Model

Extras JSONMap `gorm:"check:\"extras\" IS JSON"`
}
testModel := new(testJsonMapNamingCase)
_ = dbNamingCase.Migrator().DropTable(testModel)

type args struct {
db *gorm.DB
model interface{}
drop bool
}
tests := []struct {
name string
args args
}{
{name: "createDatatypesJsonMapNamingCase", args: args{db: dbNamingCase, model: testModel}},
{name: "alterDatatypesJsonMapNamingCase", args: args{db: dbNamingCase, model: testModel, drop: true}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := tt.args.db
if err := db.AutoMigrate(tt.args.model); err != nil {
t.Errorf("AutoMigrate failed:%v", err)
}
if tt.args.drop {
_ = db.Migrator().DropTable(tt.args.model)
}
})
}
}

func TestMigrator_DatatypesJsonMapIgnoreCase(t *testing.T) {
if err := dbErrors[1]; err != nil {
t.Fatal(err)
}
if dbIgnoreCase == nil {
t.Log("dbNamingCase is nil!")
return
}

type tesJsonMapIgnoreCase struct {
gorm.Model

Extras JSONMap `gorm:"check:extras IS JSON"`
}
testModel := new(tesJsonMapIgnoreCase)
_ = dbIgnoreCase.Migrator().DropTable(testModel)

type args struct {
db *gorm.DB
model interface{}
drop bool
}
tests := []struct {
name string
args args
}{
{name: "createDatatypesJsonMapIgnoreCase", args: args{db: dbIgnoreCase, model: testModel}},
{name: "alterDatatypesJsonMapIgnoreCase", args: args{db: dbIgnoreCase, model: testModel, drop: true}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := tt.args.db
if err := db.AutoMigrate(tt.args.model); err != nil {
t.Errorf("AutoMigrate failed:%v", err)
}
if tt.args.drop {
_ = db.Migrator().DropTable(tt.args.model)
}
})
}
}

0 comments on commit fa7fb1d

Please sign in to comment.