1
0
mirror of https://github.com/astaxie/beego.git synced 2024-12-23 15:30:50 +00:00

Merge pull request #1875 from miraclesu/feature/orm_json

orm: add json & jsonb type support
This commit is contained in:
astaxie 2016-04-12 14:25:54 +08:00
commit f6ad2cf848
7 changed files with 168 additions and 28 deletions

View File

@ -90,6 +90,18 @@ checkColumn:
} else { } else {
col = fmt.Sprintf(s, fi.digits, fi.decimals) 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: case RelForeignKey, RelOneToOne:
fieldType = fi.relModelInfo.fields.pk.fieldType fieldType = fi.relModelInfo.fields.pk.fieldType
fieldSize = fi.relModelInfo.fields.pk.size fieldSize = fi.relModelInfo.fields.pk.size
@ -278,6 +290,8 @@ func getColumnDefault(fi *fieldInfo) string {
case TypeBooleanField: case TypeBooleanField:
t = " DEFAULT %s " t = " DEFAULT %s "
d = "FALSE" d = "FALSE"
case TypeJSONField, TypeJsonbField:
d = "{}"
} }
if fi.colDefault { if fi.colDefault {

View File

@ -141,7 +141,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
} else { } else {
value = field.Bool() value = field.Bool()
} }
case TypeCharField, TypeTextField: case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
if ns, ok := field.Interface().(sql.NullString); ok { if ns, ok := field.Interface().(sql.NullString); ok {
value = nil value = nil
if ns.Valid { 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))) 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 return value, nil
@ -1093,7 +1101,7 @@ setValue:
} }
value = b value = b
} }
case fieldType == TypeCharField || fieldType == TypeTextField: case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if str == nil { if str == nil {
value = ToStr(val) value = ToStr(val)
} else { } else {
@ -1239,7 +1247,7 @@ setValue:
field.SetBool(value.(bool)) field.SetBool(value.(bool))
} }
} }
case fieldType == TypeCharField || fieldType == TypeTextField: case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if isNative { if isNative {
if ns, ok := field.Interface().(sql.NullString); ok { if ns, ok := field.Interface().(sql.NullString); ok {
if value == nil { if value == nil {

View File

@ -56,6 +56,8 @@ var postgresTypes = map[string]string{
"uint64": `bigint CHECK("%COL%" >= 0)`, "uint64": `bigint CHECK("%COL%" >= 0)`,
"float64": "double precision", "float64": "double precision",
"float64-decimal": "numeric(%d, %d)", "float64-decimal": "numeric(%d, %d)",
"json": "json",
"jsonb": "jsonb",
} }
// postgresql dbBaser. // postgresql dbBaser.

View File

@ -38,6 +38,8 @@ const (
TypePositiveBigIntegerField TypePositiveBigIntegerField
TypeFloatField TypeFloatField
TypeDecimalField TypeDecimalField
TypeJSONField
TypeJsonbField
RelForeignKey RelForeignKey
RelOneToOne RelOneToOne
RelManyToMany RelManyToMany
@ -49,7 +51,7 @@ const (
const ( const (
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6 IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10 IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10
IsRelField = ^-RelReverseMany >> 15 << 16 IsRelField = ^-RelReverseMany >> 17 << 18
IsFieldType = ^-RelReverseMany<<1 + 1 IsFieldType = ^-RelReverseMany<<1 + 1
) )
@ -146,7 +148,7 @@ func (e *CharField) RawValue() interface{} {
// verify CharField implement Fielder // verify CharField implement Fielder
var _ Fielder = new(CharField) 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 // only time values like 10:00:00
// Has a few extra, optional attr tag: // Has a few extra, optional attr tag:
// //
@ -161,22 +163,27 @@ var _ Fielder = new(CharField)
// eg: `orm:"auto_now"` or `orm:"auto_now_add"` // eg: `orm:"auto_now"` or `orm:"auto_now_add"`
type TimeField time.Time type TimeField time.Time
// Value return the time.Time
func (e TimeField) Value() time.Time { func (e TimeField) Value() time.Time {
return time.Time(e) return time.Time(e)
} }
// Set set the TimeField's value
func (e *TimeField) Set(d time.Time) { func (e *TimeField) Set(d time.Time) {
*e = TimeField(d) *e = TimeField(d)
} }
// String convert time to string
func (e *TimeField) String() string { func (e *TimeField) String() string {
return e.Value().String() return e.Value().String()
} }
// FieldType return enum type Date
func (e *TimeField) FieldType() int { func (e *TimeField) FieldType() int {
return TypeDateField return TypeDateField
} }
// SetRaw convert the interface to time.Time. Allow string and time.Time
func (e *TimeField) SetRaw(value interface{}) error { func (e *TimeField) SetRaw(value interface{}) error {
switch d := value.(type) { switch d := value.(type) {
case time.Time: case time.Time:
@ -193,6 +200,7 @@ func (e *TimeField) SetRaw(value interface{}) error {
return nil return nil
} }
// RawValue return time value
func (e *TimeField) RawValue() interface{} { func (e *TimeField) RawValue() interface{} {
return e.Value() return e.Value()
} }
@ -681,3 +689,87 @@ func (e *TextField) RawValue() interface{} {
// verify TextField implement Fielder // verify TextField implement Fielder
var _ Fielder = new(TextField) 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("<JSONField.SetRaw> 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("<JsonbField.SetRaw> 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)

View File

@ -239,8 +239,15 @@ checkType:
if err != nil { if err != nil {
goto end goto end
} }
if fieldType == TypeCharField && tags["type"] == "text" { if fieldType == TypeCharField {
fieldType = TypeTextField switch tags["type"] {
case "text":
fieldType = TypeTextField
case "json":
fieldType = TypeJSONField
case "jsonb":
fieldType = TypeJsonbField
}
} }
if fieldType == TypeFloatField && (digits != "" || decimals != "") { if fieldType == TypeFloatField && (digits != "" || decimals != "") {
fieldType = TypeDecimalField fieldType = TypeDecimalField
@ -342,7 +349,7 @@ checkType:
switch fieldType { switch fieldType {
case TypeBooleanField: case TypeBooleanField:
case TypeCharField: case TypeCharField, TypeJSONField, TypeJsonbField:
if size != "" { if size != "" {
v, e := StrTo(size).Int32() v, e := StrTo(size).Int32()
if e != nil { if e != nil {

View File

@ -78,40 +78,42 @@ func (e *SliceStringField) RawValue() interface{} {
var _ Fielder = new(SliceStringField) var _ Fielder = new(SliceStringField)
// A json field. // A json field.
type JSONField struct { type JSONFieldTest struct {
Name string Name string
Data string Data string
} }
func (e *JSONField) String() string { func (e *JSONFieldTest) String() string {
data, _ := json.Marshal(e) data, _ := json.Marshal(e)
return string(data) return string(data)
} }
func (e *JSONField) FieldType() int { func (e *JSONFieldTest) FieldType() int {
return TypeTextField return TypeTextField
} }
func (e *JSONField) SetRaw(value interface{}) error { func (e *JSONFieldTest) SetRaw(value interface{}) error {
switch d := value.(type) { switch d := value.(type) {
case string: case string:
return json.Unmarshal([]byte(d), e) return json.Unmarshal([]byte(d), e)
default: default:
return fmt.Errorf("<JsonField.SetRaw> unknown value `%v`", value) return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
} }
} }
func (e *JSONField) RawValue() interface{} { func (e *JSONFieldTest) RawValue() interface{} {
return e.String() return e.String()
} }
var _ Fielder = new(JSONField) var _ Fielder = new(JSONFieldTest)
type Data struct { type Data struct {
ID int `orm:"column(id)"` ID int `orm:"column(id)"`
Boolean bool Boolean bool
Char string `orm:"size(50)"` Char string `orm:"size(50)"`
Text string `orm:"type(text)"` Text string `orm:"type(text)"`
JSON string `orm:"type(json);default({\"name\":\"json\"})"`
Jsonb string `orm:"type(jsonb)"`
Time time.Time `orm:"type(time)"` Time time.Time `orm:"type(time)"`
Date time.Time `orm:"type(date)"` Date time.Time `orm:"type(date)"`
DateTime time.Time `orm:"column(datetime)"` DateTime time.Time `orm:"column(datetime)"`
@ -137,6 +139,8 @@ type DataNull struct {
Boolean bool `orm:"null"` Boolean bool `orm:"null"`
Char string `orm:"null;size(50)"` Char string `orm:"null;size(50)"`
Text string `orm:"null;type(text)"` 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)"` Time time.Time `orm:"null;type(time)"`
Date time.Time `orm:"null;type(date)"` Date time.Time `orm:"null;type(date)"`
DateTime time.Time `orm:"null;column(datetime)"` DateTime time.Time `orm:"null;column(datetime)"`
@ -239,7 +243,7 @@ type User struct {
ShouldSkip string `orm:"-"` ShouldSkip string `orm:"-"`
Nums int Nums int
Langs SliceStringField `orm:"size(100)"` Langs SliceStringField `orm:"size(100)"`
Extra JSONField `orm:"type(text)"` Extra JSONFieldTest `orm:"type(text)"`
unexport bool `orm:"-"` unexport bool `orm:"-"`
unexportBool bool unexportBool bool
} }
@ -390,12 +394,12 @@ func NewInLineOneToOne() *InLineOneToOne {
} }
type IntegerPk struct { type IntegerPk struct {
Id int64 `orm:"pk"` ID int64 `orm:"pk"`
Value string Value string
} }
type UintPk struct { type UintPk struct {
Id uint32 `orm:"pk"` ID uint32 `orm:"pk"`
Name string Name string
} }

View File

@ -241,6 +241,8 @@ var DataValues = map[string]interface{}{
"Boolean": true, "Boolean": true,
"Char": "char", "Char": "char",
"Text": "text", "Text": "text",
"JSON": `{"name":"json"}`,
"Jsonb": `{"name": "jsonb"}`,
"Time": time.Now(), "Time": time.Now(),
"Date": time.Now(), "Date": time.Now(),
"DateTime": time.Now(), "DateTime": time.Now(),
@ -266,6 +268,9 @@ func TestDataTypes(t *testing.T) {
ind := reflect.Indirect(reflect.ValueOf(&d)) ind := reflect.Indirect(reflect.ValueOf(&d))
for name, value := range DataValues { for name, value := range DataValues {
if name == "JSON" {
continue
}
e := ind.FieldByName(name) e := ind.FieldByName(name)
e.Set(reflect.ValueOf(value)) e.Set(reflect.ValueOf(value))
} }
@ -310,10 +315,18 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(id, 1)) 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} d = DataNull{ID: 1}
err = dORM.Read(&d) err = dORM.Read(&d)
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(d.JSON, data))
throwFail(t, AssertIs(d.NullBool.Valid, false)) throwFail(t, AssertIs(d.NullBool.Valid, false))
throwFail(t, AssertIs(d.NullString.Valid, false)) throwFail(t, AssertIs(d.NullString.Valid, false))
throwFail(t, AssertIs(d.NullInt64.Valid, false)) throwFail(t, AssertIs(d.NullInt64.Valid, false))
@ -2014,9 +2027,9 @@ func TestInLineOneToOne(t *testing.T) {
func TestIntegerPk(t *testing.T) { func TestIntegerPk(t *testing.T) {
its := []IntegerPk{ its := []IntegerPk{
{Id: math.MinInt64, Value: "-"}, {ID: math.MinInt64, Value: "-"},
{Id: 0, Value: "0"}, {ID: 0, Value: "0"},
{Id: math.MaxInt64, Value: "+"}, {ID: math.MaxInt64, Value: "+"},
} }
num, err := dORM.InsertMulti(len(its), its) num, err := dORM.InsertMulti(len(its), its)
@ -2024,7 +2037,7 @@ func TestIntegerPk(t *testing.T) {
throwFail(t, AssertIs(num, len(its))) throwFail(t, AssertIs(num, len(its)))
for _, intPk := range its { for _, intPk := range its {
out := IntegerPk{Id: intPk.Id} out := IntegerPk{ID: intPk.ID}
err = dORM.Read(&out) err = dORM.Read(&out)
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(out.Value, intPk.Value)) throwFail(t, AssertIs(out.Value, intPk.Value))
@ -2072,21 +2085,21 @@ func TestInsertAuto(t *testing.T) {
func TestUintPk(t *testing.T) { func TestUintPk(t *testing.T) {
name := "go" name := "go"
u := &UintPk{ u := &UintPk{
Id: 8, ID: 8,
Name: name, Name: name,
} }
created, pk, err := dORM.ReadOrCreate(u, "Id") created, pk, err := dORM.ReadOrCreate(u, "ID")
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(created, true)) throwFail(t, AssertIs(created, true))
throwFail(t, AssertIs(u.Name, name)) throwFail(t, AssertIs(u.Name, name))
nu := &UintPk{Id: 8} nu := &UintPk{ID: 8}
created, pk, err = dORM.ReadOrCreate(nu, "Id") created, pk, err = dORM.ReadOrCreate(nu, "ID")
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(created, false)) throwFail(t, AssertIs(created, false))
throwFail(t, AssertIs(nu.Id, u.Id)) throwFail(t, AssertIs(nu.ID, u.ID))
throwFail(t, AssertIs(pk, u.Id)) throwFail(t, AssertIs(pk, u.ID))
throwFail(t, AssertIs(nu.Name, name)) throwFail(t, AssertIs(nu.Name, name))
dORM.Delete(u) dORM.Delete(u)