From 657744efb16d473360942d45fd7ea86cc23f84a9 Mon Sep 17 00:00:00 2001 From: miraclesu Date: Fri, 8 Apr 2016 21:53:27 +0800 Subject: [PATCH 1/3] orm: add json & jsonb type support --- orm/cmd_utils.go | 14 +++++++ orm/db.go | 14 +++++-- orm/db_postgres.go | 2 + orm/models_fields.go | 88 +++++++++++++++++++++++++++++++++++++++++++- orm/models_info_f.go | 13 +++++-- 5 files changed, 124 insertions(+), 7 deletions(-) diff --git a/orm/cmd_utils.go b/orm/cmd_utils.go index 13f35722..51e9b8d2 100644 --- a/orm/cmd_utils.go +++ b/orm/cmd_utils.go @@ -90,6 +90,18 @@ checkColumn: } else { col = fmt.Sprintf(s, fi.digits, fi.decimals) } + case TypeJsonField: + if al.Driver != DRPostgres { + fieldType = TypeCharField + goto checkColumn + } + col = T["json"] + case TypeJsonbField: + if al.Driver != DRPostgres { + fieldType = TypeCharField + goto checkColumn + } + col = T["jsonb"] case RelForeignKey, RelOneToOne: fieldType = fi.relModelInfo.fields.pk.fieldType fieldSize = fi.relModelInfo.fields.pk.size @@ -278,6 +290,8 @@ func getColumnDefault(fi *fieldInfo) string { case TypeBooleanField: t = " DEFAULT %s " d = "FALSE" + case TypeJsonField, TypeJsonbField: + d = "{}" } if fi.colDefault { diff --git a/orm/db.go b/orm/db.go index 04cdc30a..0ff97cda 100644 --- a/orm/db.go +++ b/orm/db.go @@ -141,7 +141,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val } else { value = field.Bool() } - case TypeCharField, TypeTextField: + case TypeCharField, TypeTextField, TypeJsonField, TypeJsonbField: if ns, ok := field.Interface().(sql.NullString); ok { value = nil if ns.Valid { @@ -247,6 +247,14 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val field.Set(reflect.ValueOf(tnow.In(DefaultTimeLoc))) } } + case TypeJsonField, TypeJsonbField: + if s, ok := value.(string); (ok && len(s) == 0) || value == nil { + if fi.colDefault && fi.initial.Exist() { + value = fi.initial.String() + } else { + value = nil + } + } } } return value, nil @@ -1093,7 +1101,7 @@ setValue: } value = b } - case fieldType == TypeCharField || fieldType == TypeTextField: + case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJsonField || fieldType == TypeJsonbField: if str == nil { value = ToStr(val) } else { @@ -1239,7 +1247,7 @@ setValue: field.SetBool(value.(bool)) } } - case fieldType == TypeCharField || fieldType == TypeTextField: + case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJsonField || fieldType == TypeJsonbField: if isNative { if ns, ok := field.Interface().(sql.NullString); ok { if value == nil { diff --git a/orm/db_postgres.go b/orm/db_postgres.go index 8fbcb88d..e972c4a2 100644 --- a/orm/db_postgres.go +++ b/orm/db_postgres.go @@ -56,6 +56,8 @@ var postgresTypes = map[string]string{ "uint64": `bigint CHECK("%COL%" >= 0)`, "float64": "double precision", "float64-decimal": "numeric(%d, %d)", + "json": "json", + "jsonb": "jsonb", } // postgresql dbBaser. diff --git a/orm/models_fields.go b/orm/models_fields.go index 1bfbfb2f..a49cde4d 100644 --- a/orm/models_fields.go +++ b/orm/models_fields.go @@ -38,6 +38,8 @@ const ( TypePositiveBigIntegerField TypeFloatField TypeDecimalField + TypeJsonField + TypeJsonbField RelForeignKey RelOneToOne RelManyToMany @@ -49,7 +51,7 @@ const ( const ( IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6 IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10 - IsRelField = ^-RelReverseMany >> 15 << 16 + IsRelField = ^-RelReverseMany >> 17 << 18 IsFieldType = ^-RelReverseMany<<1 + 1 ) @@ -681,3 +683,87 @@ func (e *TextField) RawValue() interface{} { // verify TextField implement Fielder var _ Fielder = new(TextField) + +// JsonField postgres json field. +type JsonField string + +// Value return JsonField value +func (j JsonField) Value() string { + return string(j) +} + +// Set the JsonField value +func (j *JsonField) Set(d string) { + *j = JsonField(d) +} + +// String convert JsonField to string +func (j *JsonField) String() string { + return j.Value() +} + +// FieldType return enum type +func (j *JsonField) FieldType() int { + return TypeJsonField +} + +// SetRaw convert interface string to string +func (j *JsonField) SetRaw(value interface{}) error { + switch d := value.(type) { + case string: + j.Set(d) + default: + return fmt.Errorf(" unknown value `%s`", value) + } + return nil +} + +// RawValue return JsonField value +func (j *JsonField) RawValue() interface{} { + return j.Value() +} + +// verify JsonField implement Fielder +var _ Fielder = new(JsonField) + +// JsonbField postgres json field. +type JsonbField string + +// Value return JsonbField value +func (j JsonbField) Value() string { + return string(j) +} + +// Set the JsonbField value +func (j *JsonbField) Set(d string) { + *j = JsonbField(d) +} + +// String convert JsonbField to string +func (j *JsonbField) String() string { + return j.Value() +} + +// FieldType return enum type +func (j *JsonbField) FieldType() int { + return TypeJsonbField +} + +// SetRaw convert interface string to string +func (j *JsonbField) SetRaw(value interface{}) error { + switch d := value.(type) { + case string: + j.Set(d) + default: + return fmt.Errorf(" unknown value `%s`", value) + } + return nil +} + +// RawValue return JsonbField value +func (j *JsonbField) RawValue() interface{} { + return j.Value() +} + +// verify JsonbField implement Fielder +var _ Fielder = new(JsonbField) diff --git a/orm/models_info_f.go b/orm/models_info_f.go index 20a7c9d6..1d4d267e 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -239,8 +239,15 @@ checkType: if err != nil { goto end } - if fieldType == TypeCharField && tags["type"] == "text" { - fieldType = TypeTextField + if fieldType == TypeCharField { + switch tags["type"] { + case "text": + fieldType = TypeTextField + case "json": + fieldType = TypeJsonField + case "jsonb": + fieldType = TypeJsonbField + } } if fieldType == TypeFloatField && (digits != "" || decimals != "") { fieldType = TypeDecimalField @@ -342,7 +349,7 @@ checkType: switch fieldType { case TypeBooleanField: - case TypeCharField: + case TypeCharField, TypeJsonField, TypeJsonbField: if size != "" { v, e := StrTo(size).Int32() if e != nil { From e95bef13315c3f5b4c49fe2f26633c3d66676d35 Mon Sep 17 00:00:00 2001 From: miraclesu Date: Fri, 8 Apr 2016 22:26:28 +0800 Subject: [PATCH 2/3] orm: add test case for json & jsonb type support --- orm/models_test.go | 4 ++++ orm/orm_test.go | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/orm/models_test.go b/orm/models_test.go index cbc51dcc..d1e3e66e 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -112,6 +112,8 @@ type Data struct { Boolean bool Char string `orm:"size(50)"` Text string `orm:"type(text)"` + Json string `orm:"type(json);default({\"name\":\"json\"})"` + Jsonb string `orm:"type(jsonb)"` Time time.Time `orm:"type(time)"` Date time.Time `orm:"type(date)"` DateTime time.Time `orm:"column(datetime)"` @@ -137,6 +139,8 @@ type DataNull struct { Boolean bool `orm:"null"` Char string `orm:"null;size(50)"` Text string `orm:"null;type(text)"` + Json string `orm:"type(json);null"` + Jsonb string `orm:"type(jsonb);null"` Time time.Time `orm:"null;type(time)"` Date time.Time `orm:"null;type(date)"` DateTime time.Time `orm:"null;column(datetime)"` diff --git a/orm/orm_test.go b/orm/orm_test.go index 0c615328..e8909da8 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -241,6 +241,8 @@ var DataValues = map[string]interface{}{ "Boolean": true, "Char": "char", "Text": "text", + "Json": `{"name":"json"}`, + "Jsonb": `{"name": "jsonb"}`, "Time": time.Now(), "Date": time.Now(), "DateTime": time.Now(), @@ -266,6 +268,9 @@ func TestDataTypes(t *testing.T) { ind := reflect.Indirect(reflect.ValueOf(&d)) for name, value := range DataValues { + if name == "Json" { + continue + } e := ind.FieldByName(name) e.Set(reflect.ValueOf(value)) } @@ -310,10 +315,18 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, err) throwFail(t, AssertIs(id, 1)) + data := `{"ok":1,"data":{"arr":[1,2],"msg":"gopher"}}` + d = DataNull{ID: 1, Json: data} + num, err := dORM.Update(&d) + throwFail(t, err) + throwFail(t, AssertIs(num, 1)) + d = DataNull{ID: 1} err = dORM.Read(&d) throwFail(t, err) + throwFail(t, AssertIs(d.Json, data)) + throwFail(t, AssertIs(d.NullBool.Valid, false)) throwFail(t, AssertIs(d.NullString.Valid, false)) throwFail(t, AssertIs(d.NullInt64.Valid, false)) From 99f1e6c8b5683ef789d72d1b0e684ac658890fb0 Mon Sep 17 00:00:00 2001 From: miraclesu Date: Sat, 9 Apr 2016 10:22:40 +0800 Subject: [PATCH 3/3] orm: fix golint --- orm/cmd_utils.go | 4 ++-- orm/db.go | 8 ++++---- orm/models_fields.go | 44 +++++++++++++++++++++++++------------------- orm/models_info_f.go | 4 ++-- orm/models_test.go | 24 ++++++++++++------------ orm/orm_test.go | 28 ++++++++++++++-------------- 6 files changed, 59 insertions(+), 53 deletions(-) diff --git a/orm/cmd_utils.go b/orm/cmd_utils.go index 51e9b8d2..94e6070b 100644 --- a/orm/cmd_utils.go +++ b/orm/cmd_utils.go @@ -90,7 +90,7 @@ checkColumn: } else { col = fmt.Sprintf(s, fi.digits, fi.decimals) } - case TypeJsonField: + case TypeJSONField: if al.Driver != DRPostgres { fieldType = TypeCharField goto checkColumn @@ -290,7 +290,7 @@ func getColumnDefault(fi *fieldInfo) string { case TypeBooleanField: t = " DEFAULT %s " d = "FALSE" - case TypeJsonField, TypeJsonbField: + case TypeJSONField, TypeJsonbField: d = "{}" } diff --git a/orm/db.go b/orm/db.go index 0ff97cda..7796686a 100644 --- a/orm/db.go +++ b/orm/db.go @@ -141,7 +141,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val } else { value = field.Bool() } - case TypeCharField, TypeTextField, TypeJsonField, TypeJsonbField: + case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField: if ns, ok := field.Interface().(sql.NullString); ok { value = nil if ns.Valid { @@ -247,7 +247,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val field.Set(reflect.ValueOf(tnow.In(DefaultTimeLoc))) } } - case TypeJsonField, TypeJsonbField: + case TypeJSONField, TypeJsonbField: if s, ok := value.(string); (ok && len(s) == 0) || value == nil { if fi.colDefault && fi.initial.Exist() { value = fi.initial.String() @@ -1101,7 +1101,7 @@ setValue: } value = b } - case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJsonField || fieldType == TypeJsonbField: + case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField: if str == nil { value = ToStr(val) } else { @@ -1247,7 +1247,7 @@ setValue: field.SetBool(value.(bool)) } } - case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJsonField || fieldType == TypeJsonbField: + case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField: if isNative { if ns, ok := field.Interface().(sql.NullString); ok { if value == nil { diff --git a/orm/models_fields.go b/orm/models_fields.go index a49cde4d..57820600 100644 --- a/orm/models_fields.go +++ b/orm/models_fields.go @@ -38,7 +38,7 @@ const ( TypePositiveBigIntegerField TypeFloatField TypeDecimalField - TypeJsonField + TypeJSONField TypeJsonbField RelForeignKey RelOneToOne @@ -148,7 +148,7 @@ func (e *CharField) RawValue() interface{} { // verify CharField implement Fielder var _ Fielder = new(CharField) -// A time, represented in go by a time.Time instance. +// TimeField A time, represented in go by a time.Time instance. // only time values like 10:00:00 // Has a few extra, optional attr tag: // @@ -163,22 +163,27 @@ var _ Fielder = new(CharField) // eg: `orm:"auto_now"` or `orm:"auto_now_add"` type TimeField time.Time +// Value return the time.Time func (e TimeField) Value() time.Time { return time.Time(e) } +// Set set the TimeField's value func (e *TimeField) Set(d time.Time) { *e = TimeField(d) } +// String convert time to string func (e *TimeField) String() string { return e.Value().String() } +// FieldType return enum type Date func (e *TimeField) FieldType() int { return TypeDateField } +// SetRaw convert the interface to time.Time. Allow string and time.Time func (e *TimeField) SetRaw(value interface{}) error { switch d := value.(type) { case time.Time: @@ -195,6 +200,7 @@ func (e *TimeField) SetRaw(value interface{}) error { return nil } +// RawValue return time value func (e *TimeField) RawValue() interface{} { return e.Value() } @@ -684,47 +690,47 @@ func (e *TextField) RawValue() interface{} { // verify TextField implement Fielder var _ Fielder = new(TextField) -// JsonField postgres json field. -type JsonField string +// JSONField postgres json field. +type JSONField string -// Value return JsonField value -func (j JsonField) Value() string { +// Value return JSONField value +func (j JSONField) Value() string { return string(j) } -// Set the JsonField value -func (j *JsonField) Set(d string) { - *j = JsonField(d) +// Set the JSONField value +func (j *JSONField) Set(d string) { + *j = JSONField(d) } -// String convert JsonField to string -func (j *JsonField) String() string { +// String convert JSONField to string +func (j *JSONField) String() string { return j.Value() } // FieldType return enum type -func (j *JsonField) FieldType() int { - return TypeJsonField +func (j *JSONField) FieldType() int { + return TypeJSONField } // SetRaw convert interface string to string -func (j *JsonField) SetRaw(value interface{}) error { +func (j *JSONField) SetRaw(value interface{}) error { switch d := value.(type) { case string: j.Set(d) default: - return fmt.Errorf(" unknown value `%s`", value) + return fmt.Errorf(" unknown value `%s`", value) } return nil } -// RawValue return JsonField value -func (j *JsonField) RawValue() interface{} { +// RawValue return JSONField value +func (j *JSONField) RawValue() interface{} { return j.Value() } -// verify JsonField implement Fielder -var _ Fielder = new(JsonField) +// verify JSONField implement Fielder +var _ Fielder = new(JSONField) // JsonbField postgres json field. type JsonbField string diff --git a/orm/models_info_f.go b/orm/models_info_f.go index 1d4d267e..366473ce 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -244,7 +244,7 @@ checkType: case "text": fieldType = TypeTextField case "json": - fieldType = TypeJsonField + fieldType = TypeJSONField case "jsonb": fieldType = TypeJsonbField } @@ -349,7 +349,7 @@ checkType: switch fieldType { case TypeBooleanField: - case TypeCharField, TypeJsonField, TypeJsonbField: + case TypeCharField, TypeJSONField, TypeJsonbField: if size != "" { v, e := StrTo(size).Int32() if e != nil { diff --git a/orm/models_test.go b/orm/models_test.go index d1e3e66e..c68c7339 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -78,41 +78,41 @@ func (e *SliceStringField) RawValue() interface{} { var _ Fielder = new(SliceStringField) // A json field. -type JSONField struct { +type JSONFieldTest struct { Name string Data string } -func (e *JSONField) String() string { +func (e *JSONFieldTest) String() string { data, _ := json.Marshal(e) return string(data) } -func (e *JSONField) FieldType() int { +func (e *JSONFieldTest) FieldType() int { return TypeTextField } -func (e *JSONField) SetRaw(value interface{}) error { +func (e *JSONFieldTest) SetRaw(value interface{}) error { switch d := value.(type) { case string: return json.Unmarshal([]byte(d), e) default: - return fmt.Errorf(" unknown value `%v`", value) + return fmt.Errorf(" unknown value `%v`", value) } } -func (e *JSONField) RawValue() interface{} { +func (e *JSONFieldTest) RawValue() interface{} { return e.String() } -var _ Fielder = new(JSONField) +var _ Fielder = new(JSONFieldTest) type Data struct { ID int `orm:"column(id)"` Boolean bool Char string `orm:"size(50)"` Text string `orm:"type(text)"` - Json string `orm:"type(json);default({\"name\":\"json\"})"` + JSON string `orm:"type(json);default({\"name\":\"json\"})"` Jsonb string `orm:"type(jsonb)"` Time time.Time `orm:"type(time)"` Date time.Time `orm:"type(date)"` @@ -139,7 +139,7 @@ type DataNull struct { Boolean bool `orm:"null"` Char string `orm:"null;size(50)"` Text string `orm:"null;type(text)"` - Json string `orm:"type(json);null"` + JSON string `orm:"type(json);null"` Jsonb string `orm:"type(jsonb);null"` Time time.Time `orm:"null;type(time)"` Date time.Time `orm:"null;type(date)"` @@ -243,7 +243,7 @@ type User struct { ShouldSkip string `orm:"-"` Nums int Langs SliceStringField `orm:"size(100)"` - Extra JSONField `orm:"type(text)"` + Extra JSONFieldTest `orm:"type(text)"` unexport bool `orm:"-"` unexportBool bool } @@ -394,12 +394,12 @@ func NewInLineOneToOne() *InLineOneToOne { } type IntegerPk struct { - Id int64 `orm:"pk"` + ID int64 `orm:"pk"` Value string } type UintPk struct { - Id uint32 `orm:"pk"` + ID uint32 `orm:"pk"` Name string } diff --git a/orm/orm_test.go b/orm/orm_test.go index e8909da8..fa1147f6 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -241,7 +241,7 @@ var DataValues = map[string]interface{}{ "Boolean": true, "Char": "char", "Text": "text", - "Json": `{"name":"json"}`, + "JSON": `{"name":"json"}`, "Jsonb": `{"name": "jsonb"}`, "Time": time.Now(), "Date": time.Now(), @@ -268,7 +268,7 @@ func TestDataTypes(t *testing.T) { ind := reflect.Indirect(reflect.ValueOf(&d)) for name, value := range DataValues { - if name == "Json" { + if name == "JSON" { continue } e := ind.FieldByName(name) @@ -316,7 +316,7 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, AssertIs(id, 1)) data := `{"ok":1,"data":{"arr":[1,2],"msg":"gopher"}}` - d = DataNull{ID: 1, Json: data} + d = DataNull{ID: 1, JSON: data} num, err := dORM.Update(&d) throwFail(t, err) throwFail(t, AssertIs(num, 1)) @@ -325,7 +325,7 @@ func TestNullDataTypes(t *testing.T) { err = dORM.Read(&d) throwFail(t, err) - throwFail(t, AssertIs(d.Json, data)) + throwFail(t, AssertIs(d.JSON, data)) throwFail(t, AssertIs(d.NullBool.Valid, false)) throwFail(t, AssertIs(d.NullString.Valid, false)) @@ -2027,9 +2027,9 @@ func TestInLineOneToOne(t *testing.T) { func TestIntegerPk(t *testing.T) { its := []IntegerPk{ - {Id: math.MinInt64, Value: "-"}, - {Id: 0, Value: "0"}, - {Id: math.MaxInt64, Value: "+"}, + {ID: math.MinInt64, Value: "-"}, + {ID: 0, Value: "0"}, + {ID: math.MaxInt64, Value: "+"}, } num, err := dORM.InsertMulti(len(its), its) @@ -2037,7 +2037,7 @@ func TestIntegerPk(t *testing.T) { throwFail(t, AssertIs(num, len(its))) for _, intPk := range its { - out := IntegerPk{Id: intPk.Id} + out := IntegerPk{ID: intPk.ID} err = dORM.Read(&out) throwFail(t, err) throwFail(t, AssertIs(out.Value, intPk.Value)) @@ -2085,21 +2085,21 @@ func TestInsertAuto(t *testing.T) { func TestUintPk(t *testing.T) { name := "go" u := &UintPk{ - Id: 8, + ID: 8, Name: name, } - created, pk, err := dORM.ReadOrCreate(u, "Id") + created, pk, err := dORM.ReadOrCreate(u, "ID") throwFail(t, err) throwFail(t, AssertIs(created, true)) throwFail(t, AssertIs(u.Name, name)) - nu := &UintPk{Id: 8} - created, pk, err = dORM.ReadOrCreate(nu, "Id") + nu := &UintPk{ID: 8} + created, pk, err = dORM.ReadOrCreate(nu, "ID") throwFail(t, err) throwFail(t, AssertIs(created, false)) - throwFail(t, AssertIs(nu.Id, u.Id)) - throwFail(t, AssertIs(pk, u.Id)) + throwFail(t, AssertIs(nu.ID, u.ID)) + throwFail(t, AssertIs(pk, u.ID)) throwFail(t, AssertIs(nu.Name, name)) dORM.Delete(u)