Skip to content

Commit fa7fb1d

Browse files
committed
✨ 实现自动迁移支持 GORM 自定义数据类型 JSONMap
1. 完善 Oracle 数据库类型别名,其中 `JSONMap` 自定义类型对应 `OCIBlobLocator`; 2. 修复 `HasIndex` 判断索引是否存在方法索引名被替换的问题; 3. 完善自动迁移单元测试。 Close #29 Signed-off-by: liutianqi <zixizixi@vip.qq.com>
1 parent 3f5f631 commit fa7fb1d

File tree

3 files changed

+215
-9
lines changed

3 files changed

+215
-9
lines changed

datatypes_json_map_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package oracle
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"database/sql/driver"
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"strings"
11+
12+
"gorm.io/gorm"
13+
"gorm.io/gorm/clause"
14+
"gorm.io/gorm/schema"
15+
)
16+
17+
// JSONMap defined JSON data type, need to implement driver.Valuer, sql.Scanner interface
18+
type JSONMap map[string]interface{}
19+
20+
// Value return json value, implement driver.Valuer interface
21+
//
22+
//goland:noinspection GoMixedReceiverTypes
23+
func (m JSONMap) Value() (driver.Value, error) {
24+
if m == nil {
25+
return nil, nil
26+
}
27+
ba, err := m.MarshalJSON()
28+
return string(ba), err
29+
}
30+
31+
// Scan value into Jsonb, implements sql.Scanner interface
32+
//
33+
//goland:noinspection GoMixedReceiverTypes
34+
func (m *JSONMap) Scan(val interface{}) error {
35+
if val == nil {
36+
*m = make(JSONMap)
37+
return nil
38+
}
39+
var ba []byte
40+
switch v := val.(type) {
41+
case []byte:
42+
ba = v
43+
case string:
44+
ba = []byte(v)
45+
default:
46+
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", val))
47+
}
48+
t := map[string]interface{}{}
49+
rd := bytes.NewReader(ba)
50+
decoder := json.NewDecoder(rd)
51+
decoder.UseNumber()
52+
err := decoder.Decode(&t)
53+
*m = t
54+
return err
55+
}
56+
57+
// MarshalJSON to output non base64 encoded []byte
58+
//
59+
//goland:noinspection GoMixedReceiverTypes
60+
func (m JSONMap) MarshalJSON() ([]byte, error) {
61+
if m == nil {
62+
return []byte("null"), nil
63+
}
64+
t := (map[string]interface{})(m)
65+
return json.Marshal(t)
66+
}
67+
68+
// UnmarshalJSON to deserialize []byte
69+
//
70+
//goland:noinspection GoMixedReceiverTypes
71+
func (m *JSONMap) UnmarshalJSON(b []byte) error {
72+
t := map[string]interface{}{}
73+
err := json.Unmarshal(b, &t)
74+
*m = t
75+
return err
76+
}
77+
78+
// GormDataType gorm common data type
79+
//
80+
//goland:noinspection GoMixedReceiverTypes
81+
func (m JSONMap) GormDataType() string {
82+
return "jsonmap"
83+
}
84+
85+
// GormDBDataType gorm db data type
86+
//
87+
//goland:noinspection GoMixedReceiverTypes
88+
func (JSONMap) GormDBDataType(db *gorm.DB, field *schema.Field) string {
89+
switch db.Dialector.Name() {
90+
case "sqlite":
91+
return "JSON"
92+
case "mysql":
93+
return "JSON"
94+
case "postgres":
95+
return "JSONB"
96+
case "sqlserver":
97+
return "NVARCHAR(MAX)"
98+
case "oracle":
99+
return "BLOB"
100+
default:
101+
return getGormTypeFromTag(field)
102+
}
103+
}
104+
105+
func getGormTypeFromTag(field *schema.Field) (dataType string) {
106+
if field != nil {
107+
if val, ok := field.TagSettings["TYPE"]; ok {
108+
dataType = strings.ToLower(val)
109+
}
110+
}
111+
return
112+
}
113+
114+
//goland:noinspection GoMixedReceiverTypes
115+
func (m JSONMap) GormValue(_ context.Context, _ *gorm.DB) clause.Expr {
116+
data, _ := m.MarshalJSON()
117+
return gorm.Expr("?", string(data))
118+
}

migrator.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,22 @@ func (m Migrator) CurrentDatabase() (name string) {
8787
// GetTypeAliases return database type aliases
8888
func (m Migrator) GetTypeAliases(databaseTypeName string) (types []string) {
8989
switch databaseTypeName {
90-
case "blob", "longraw":
91-
types = append(types, "blob", "longraw")
92-
case "clob", "longvarchar", "ocicloblocator":
93-
types = append(types, "clob", "longvarchar", "ocicloblocator")
94-
case "nchar", "varchar", "varchar2":
95-
types = append(types, "nchar", "varchar", "varchar2")
90+
case "blob", "raw", "longraw", "ocibloblocator", "ocifilelocator":
91+
types = append(types, "blob", "raw", "longraw", "ocibloblocator", "ocifilelocator")
92+
case "clob", "nclob", "longvarchar", "ocicloblocator":
93+
types = append(types, "clob", "nclob", "longvarchar", "ocicloblocator")
94+
case "char", "nchar", "varchar", "varchar2", "nvarchar2":
95+
types = append(types, "char", "nchar", "varchar", "varchar2", "nvarchar2")
9696
case "number", "integer", "smallint":
9797
types = append(types, "number", "integer", "smallint")
98-
case "timestampdty", "timestamp":
99-
types = append(types, "timestampdty", "timestamp")
98+
case "decimal", "numeric", "ibfloat", "ibdouble":
99+
types = append(types, "decimal", "numeric", "ibfloat", "ibdouble")
100+
case "timestampdty", "timestamp", "date":
101+
types = append(types, "timestampdty", "timestamp", "date")
100102
case "timestamptz_dty", "timestamp with time zone":
101103
types = append(types, "timestamptz_dty", "timestamp with time zone")
104+
case "timestampltz_dty", "timestampeltz", "timestamp with local time zone":
105+
types = append(types, "timestampltz_dty", "timestampeltz", "timestamp with local time zone")
102106
default:
103107
return
104108
}
@@ -438,7 +442,7 @@ func (m Migrator) HasIndex(value interface{}, name string) bool {
438442
return m.DB.Raw(
439443
"SELECT COUNT(*) FROM USER_INDEXES WHERE TABLE_NAME = ? AND INDEX_NAME = ?",
440444
m.Migrator.DB.NamingStrategy.TableName(stmt.Table),
441-
m.Migrator.DB.NamingStrategy.IndexName(stmt.Table, name),
445+
name,
442446
).Row().Scan(&count)
443447
})
444448

migrator_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,87 @@ func TestMigrator_FieldNameIsReservedWord(t *testing.T) {
287287
})
288288
}
289289
}
290+
291+
func TestMigrator_DatatypesJsonMapNamingCase(t *testing.T) {
292+
if err := dbErrors[0]; err != nil {
293+
t.Fatal(err)
294+
}
295+
if dbNamingCase == nil {
296+
t.Log("dbNamingCase is nil!")
297+
return
298+
}
299+
300+
type testJsonMapNamingCase struct {
301+
gorm.Model
302+
303+
Extras JSONMap `gorm:"check:\"extras\" IS JSON"`
304+
}
305+
testModel := new(testJsonMapNamingCase)
306+
_ = dbNamingCase.Migrator().DropTable(testModel)
307+
308+
type args struct {
309+
db *gorm.DB
310+
model interface{}
311+
drop bool
312+
}
313+
tests := []struct {
314+
name string
315+
args args
316+
}{
317+
{name: "createDatatypesJsonMapNamingCase", args: args{db: dbNamingCase, model: testModel}},
318+
{name: "alterDatatypesJsonMapNamingCase", args: args{db: dbNamingCase, model: testModel, drop: true}},
319+
}
320+
for _, tt := range tests {
321+
t.Run(tt.name, func(t *testing.T) {
322+
db := tt.args.db
323+
if err := db.AutoMigrate(tt.args.model); err != nil {
324+
t.Errorf("AutoMigrate failed:%v", err)
325+
}
326+
if tt.args.drop {
327+
_ = db.Migrator().DropTable(tt.args.model)
328+
}
329+
})
330+
}
331+
}
332+
333+
func TestMigrator_DatatypesJsonMapIgnoreCase(t *testing.T) {
334+
if err := dbErrors[1]; err != nil {
335+
t.Fatal(err)
336+
}
337+
if dbIgnoreCase == nil {
338+
t.Log("dbNamingCase is nil!")
339+
return
340+
}
341+
342+
type tesJsonMapIgnoreCase struct {
343+
gorm.Model
344+
345+
Extras JSONMap `gorm:"check:extras IS JSON"`
346+
}
347+
testModel := new(tesJsonMapIgnoreCase)
348+
_ = dbIgnoreCase.Migrator().DropTable(testModel)
349+
350+
type args struct {
351+
db *gorm.DB
352+
model interface{}
353+
drop bool
354+
}
355+
tests := []struct {
356+
name string
357+
args args
358+
}{
359+
{name: "createDatatypesJsonMapIgnoreCase", args: args{db: dbIgnoreCase, model: testModel}},
360+
{name: "alterDatatypesJsonMapIgnoreCase", args: args{db: dbIgnoreCase, model: testModel, drop: true}},
361+
}
362+
for _, tt := range tests {
363+
t.Run(tt.name, func(t *testing.T) {
364+
db := tt.args.db
365+
if err := db.AutoMigrate(tt.args.model); err != nil {
366+
t.Errorf("AutoMigrate failed:%v", err)
367+
}
368+
if tt.args.drop {
369+
_ = db.Migrator().DropTable(tt.args.model)
370+
}
371+
})
372+
}
373+
}

0 commit comments

Comments
 (0)