diff --git a/orm/cmd_utils.go b/orm/cmd_utils.go index 13f35722..94e6070b 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..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: + 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..57820600 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 ) @@ -146,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: // @@ -161,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: @@ -193,6 +200,7 @@ func (e *TimeField) SetRaw(value interface{}) error { return nil } +// RawValue return time value func (e *TimeField) RawValue() interface{} { return e.Value() } @@ -681,3 +689,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..366473ce 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 { diff --git a/orm/models_test.go b/orm/models_test.go index cbc51dcc..c68c7339 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -78,40 +78,42 @@ 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\"})"` + 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)"` @@ -239,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 } @@ -390,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 0c615328..fa1147f6 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)) @@ -2014,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) @@ -2024,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)) @@ -2072,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)