From 02067f6ff80333a2daae3f46112c5518b141a628 Mon Sep 17 00:00:00 2001 From: Larry Cinnabar Date: Fri, 13 Jul 2018 10:04:35 +0300 Subject: [PATCH 1/4] gitignore idea --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9a3120f6b..ebec85a62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ _harness -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file From e0fb935e4343acde802b5003b915c8c6931711b2 Mon Sep 17 00:00:00 2001 From: Larry Cinnabar Date: Mon, 16 Jul 2018 10:31:47 +0300 Subject: [PATCH 2/4] fixes --- bson/bson.go | 59 +++++++++++++++++++++++++++-- bson/bson_test.go | 88 +++++++++++++++++++++++++++----------------- bson/decimal_test.go | 2 +- bson/decode.go | 7 +++- 4 files changed, 117 insertions(+), 39 deletions(-) diff --git a/bson/bson.go b/bson/bson.go index a7083cc91..041ed21c6 100644 --- a/bson/bson.go +++ b/bson/bson.go @@ -686,10 +686,17 @@ func (e *TypeError) Error() string { // Maintain a mapping of keys to structure field indexes type structInfo struct { - FieldsMap map[string]fieldInfo - FieldsList []fieldInfo - InlineMap int - Zero reflect.Value + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + InlineMap int + InlineStruct bool + + Zero reflect.Value + st reflect.Type +} + +func (s *structInfo) DeepZero() reflect.Value { + return deepZero(s.st) } type fieldInfo struct { @@ -720,6 +727,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { fieldsMap := make(map[string]fieldInfo) fieldsList := make([]fieldInfo, 0, n) inlineMap := -1 + var inlineStruct bool for i := 0; i != n; i++ { field := st.Field(i) if field.PkgPath != "" && !field.Anonymous { @@ -786,6 +794,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { field.Type = field.Type.Elem() fallthrough case reflect.Struct: + inlineStruct = true sinfo, err := getStructInfo(field.Type) if err != nil { return nil, err @@ -827,10 +836,52 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { fieldsMap, fieldsList, inlineMap, + inlineStruct, reflect.New(st).Elem(), + st, } + structMapMutex.Lock() structMap[st] = sinfo structMapMutex.Unlock() return sinfo, nil } + +// DeepZero returns recursive zero object +// @todo tests +func deepZero(st reflect.Type) (result reflect.Value) { + result = reflect.Indirect(reflect.New(st)) + + if result.Kind() == reflect.Struct { + for i := 0; i < result.NumField(); i++ { + if f := result.Field(i); f.Kind() == reflect.Ptr { + if f.CanInterface() { + if ft := reflect.TypeOf(f.Interface()); ft.Elem().Kind() == reflect.Struct { + result.Field(i).Set(recursivePointerTo(deepZero(ft.Elem()))) + } + } + } + } + } + + return +} + +// recursivePointerTo calls reflect.New(v.Type) but recursively for its fields inside +// +// @todo tests +func recursivePointerTo(v reflect.Value) reflect.Value { + v = reflect.Indirect(v) + result := reflect.New(v.Type()) + if v.Kind() == reflect.Struct { + for i := 0; i < v.NumField(); i++ { + if f := v.Field(i); f.Kind() == reflect.Ptr { + if f.Elem().Kind() == reflect.Struct { + result.Elem().Field(i).Set(recursivePointerTo(f)) + } + } + } + } + + return result +} diff --git a/bson/bson_test.go b/bson/bson_test.go index 60dcde1ff..f42056948 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -274,38 +274,58 @@ func (s *S) TestMarshalBuffer(c *C) { } func (s *S) TestPtrInline(c *C) { - cases := []struct { - In interface{} - Out bson.M - }{ - { - In: inlinePtrStruct{A: 1, MStruct: &MStruct{M: 3}}, - Out: bson.M{"a": 1, "m": 3}, - }, - { // go deeper - In: inlinePtrPtrStruct{B: 10, inlinePtrStruct: &inlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}}, - Out: bson.M{"b": 10, "a": 20, "m": 30}, - }, - { - // nil embed struct - In: &inlinePtrStruct{A: 3}, - Out: bson.M{"a": 3}, - }, - { - // nil embed struct - In: &inlinePtrPtrStruct{B: 5}, - Out: bson.M{"b": 5}, - }, + + // struct with inline pointer + { + in := InlinePtrStruct{A: 1, MStruct: &MStruct{M: 8}} + data, err := bson.Marshal(in) + c.Assert(err, IsNil) + + var out InlinePtrStruct + err = bson.Unmarshal(data, &out) + c.Assert(err, IsNil) + c.Assert(out, DeepEquals, in) + } + + // Deeper struct with inline pointer + { + in := InlinePtrPtrStruct{B: 10, InlinePtrStruct: &InlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}} + data, err := bson.Marshal(in) + c.Assert(err, IsNil) + + var out InlinePtrPtrStruct + err = bson.Unmarshal(data, &out) + c.Assert(err, IsNil) + c.Assert(out, DeepEquals, in) } - for _, cs := range cases { - data, err := bson.Marshal(cs.In) + // Nil embed struct + { + in := InlinePtrStruct{A: 4} + data, err := bson.Marshal(in) + c.Assert(err, IsNil) + + out := InlinePtrStruct{} + err = bson.Unmarshal(data, &out) + c.Assert(err, IsNil) + + c.Assert(out.A, Equals, 4) + c.Assert(out.M, Equals, 0) + } + + // Nil deeper embed struct + { + in := InlinePtrPtrStruct{B: 5} + data, err := bson.Marshal(in) c.Assert(err, IsNil) - var dataBSON bson.M - err = bson.Unmarshal(data, &dataBSON) + + var out InlinePtrPtrStruct + err = bson.Unmarshal(data, &out) c.Assert(err, IsNil) - c.Assert(dataBSON, DeepEquals, cs.Out) + c.Assert(out.B, Equals, 5) + c.Assert(out.A, Equals, 0) + c.Assert(out.M, Equals, 0) } } @@ -1210,19 +1230,21 @@ type inlineUnexported struct { M map[string]interface{} `bson:",inline"` unexported `bson:",inline"` } +type unexported struct { + A int +} + +// Structs for `inline pointers` feature type MStruct struct { M int `bson:"m,omitempty"` } -type inlinePtrStruct struct { +type InlinePtrStruct struct { A int *MStruct `bson:",inline"` } -type inlinePtrPtrStruct struct { +type InlinePtrPtrStruct struct { B int - *inlinePtrStruct `bson:",inline"` -} -type unexported struct { - A int + *InlinePtrStruct `bson:",inline"` } type getterSetterD bson.D diff --git a/bson/decimal_test.go b/bson/decimal_test.go index 142adc4a4..09e695a40 100644 --- a/bson/decimal_test.go +++ b/bson/decimal_test.go @@ -1,4 +1,4 @@ -// BSON library for Go +// BSON library for Go // // Copyright (c) 2010-2012 - Gustavo Niemeyer // diff --git a/bson/decode.go b/bson/decode.go index 658856add..dfff36cc7 100644 --- a/bson/decode.go +++ b/bson/decode.go @@ -194,7 +194,12 @@ func (d *decoder) readDocTo(out reflect.Value) { panic(err) } fieldsMap = sinfo.FieldsMap - out.Set(sinfo.Zero) + if sinfo.InlineStruct { + out.Set(sinfo.DeepZero()) + } else { + out.Set(sinfo.Zero) + } + if sinfo.InlineMap != -1 { inlineMap = out.Field(sinfo.InlineMap) if !inlineMap.IsNil() && inlineMap.Len() > 0 { From 33206508fca20e1e0e9fefb21180d4445c2e311b Mon Sep 17 00:00:00 2001 From: Larry Cinnabar Date: Mon, 6 Aug 2018 09:35:11 +0300 Subject: [PATCH 3/4] fix inline-pointer unmarshalling --- bson/bson.go | 22 ++++++--- bson/bson_test.go | 118 ++++++++++++++++++++++++++++++++++++---------- bson/decode.go | 31 +++++++++++- 3 files changed, 136 insertions(+), 35 deletions(-) diff --git a/bson/bson.go b/bson/bson.go index 041ed21c6..07fa95769 100644 --- a/bson/bson.go +++ b/bson/bson.go @@ -692,11 +692,8 @@ type structInfo struct { InlineStruct bool Zero reflect.Value - st reflect.Type -} -func (s *structInfo) DeepZero() reflect.Value { - return deepZero(s.st) + st reflect.Type } type fieldInfo struct { @@ -848,7 +845,6 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { } // DeepZero returns recursive zero object -// @todo tests func deepZero(st reflect.Type) (result reflect.Value) { result = reflect.Indirect(reflect.New(st)) @@ -867,9 +863,21 @@ func deepZero(st reflect.Type) (result reflect.Value) { return } +// getZeroField returns recursive zero object +func getZeroField(v reflect.Value, index []int) reflect.Value { + for _, ind := range index { + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + v = v.Elem() + } + + v = v.Field(ind) + } + + dz := deepZero(v.Type().Elem()) + return reflect.New(dz.Type()) +} + // recursivePointerTo calls reflect.New(v.Type) but recursively for its fields inside -// -// @todo tests func recursivePointerTo(v reflect.Value) reflect.Value { v = reflect.Indirect(v) result := reflect.New(v.Type()) diff --git a/bson/bson_test.go b/bson/bson_test.go index f42056948..5500af3b8 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -277,56 +277,122 @@ func (s *S) TestPtrInline(c *C) { // struct with inline pointer { - in := InlinePtrStruct{A: 1, MStruct: &MStruct{M: 8}} + in := InlineG1{G1: 4, Final: &Final{G0: 8}} + c.Assert(in.Final, NotNil) + data, err := bson.Marshal(in) c.Assert(err, IsNil) - var out InlinePtrStruct + var out InlineG1 err = bson.Unmarshal(data, &out) c.Assert(err, IsNil) + c.Assert(out.Final, NotNil) c.Assert(out, DeepEquals, in) } - // Deeper struct with inline pointer + // deeper struct with inline pointer { - in := InlinePtrPtrStruct{B: 10, InlinePtrStruct: &InlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}} + in := InlineG2{G2: 15, InlineG1: &InlineG1{G1:16, Final: &Final{G0: 23}}} + c.Assert(in.InlineG1, NotNil) + c.Assert(in.Final, NotNil) + data, err := bson.Marshal(in) c.Assert(err, IsNil) - var out InlinePtrPtrStruct + var out InlineG2 err = bson.Unmarshal(data, &out) c.Assert(err, IsNil) + c.Assert(out.InlineG1, NotNil) + c.Assert(out.Final, NotNil) c.Assert(out, DeepEquals, in) } - // Nil embed struct + // struct with nil inline pointer { - in := InlinePtrStruct{A: 4} + in := InlineG1{G1: 42} + c.Assert(in.Final, IsNil) + data, err := bson.Marshal(in) c.Assert(err, IsNil) - out := InlinePtrStruct{} + // default behaviour: no respect to nil values + var out InlineG1 err = bson.Unmarshal(data, &out) c.Assert(err, IsNil) - - c.Assert(out.A, Equals, 4) - c.Assert(out.M, Equals, 0) + c.Assert(out.Final, NotNil) + c.Assert(out.G1, Equals, in.G1) + c.Assert(out.G0, Equals, 0) + + // respect to nil value + var outRespectNils InlineG1 + bson.SetRespectNilValues(true) + err = bson.Unmarshal(data, &outRespectNils) + bson.SetRespectNilValues(false) + c.Assert(err, IsNil) + c.Assert(outRespectNils.Final, IsNil) + c.Assert(outRespectNils, DeepEquals, in) } - // Nil deeper embed struct + // deeper struct with nil inline pointer { - in := InlinePtrPtrStruct{B: 5} + in := InlineG2{G2: 108} + c.Assert(in.InlineG1, IsNil) + data, err := bson.Marshal(in) c.Assert(err, IsNil) - var out InlinePtrPtrStruct + // default behaviour: no respect to nil values + var out InlineG2 err = bson.Unmarshal(data, &out) c.Assert(err, IsNil) + c.Assert(out.InlineG1, NotNil) + c.Assert(out.Final, NotNil) + c.Assert(out.G2, Equals, in.G2) + c.Assert(out.G1, Equals, 0) + c.Assert(out.G0, Equals, 0) + + // respect to nil value + var outRespectNils InlineG2 + bson.SetRespectNilValues(true) + err = bson.Unmarshal(data, &outRespectNils) + bson.SetRespectNilValues(false) + c.Assert(err, IsNil) + c.Assert(outRespectNils.InlineG1, IsNil) + c.Assert(outRespectNils, DeepEquals, in) + } + + // mixing empty & nil + // @TODO + /* + { + in := InlineG2{G2: 108, InlineG1: &InlineG1{}} + c.Assert(in.InlineG1, NotNil) + c.Assert(in.Final, IsNil) + + data, err := bson.Marshal(in) + c.Assert(err, IsNil) - c.Assert(out.B, Equals, 5) - c.Assert(out.A, Equals, 0) - c.Assert(out.M, Equals, 0) + // default behaviour: no respect to nil values + var out InlineG2 + err = bson.Unmarshal(data, &out) + c.Assert(err, IsNil) + c.Assert(out.InlineG1, NotNil) + c.Assert(out.Final, NotNil) + c.Assert(out.G2, Equals, in.G2) + c.Assert(out.G1, Equals, 0) + c.Assert(out.G0, Equals, 0) + + // respect to nil value + var outRespectNils InlineG2 + bson.SetRespectNilValues(true) + err = bson.Unmarshal(data, &outRespectNils) + bson.SetRespectNilValues(false) + c.Assert(err, IsNil) + c.Assert(outRespectNils.InlineG1, NotNil) + c.Assert(outRespectNils.Final, IsNil) + c.Assert(outRespectNils, DeepEquals, in) } + */ } // -------------------------------------------------------------------------- @@ -1234,17 +1300,17 @@ type unexported struct { A int } -// Structs for `inline pointers` feature -type MStruct struct { - M int `bson:"m,omitempty"` +// Structures for `inline pointers` feature +type Final struct { + G0 int `bson:"g0,omitempty"` } -type InlinePtrStruct struct { - A int - *MStruct `bson:",inline"` +type InlineG1 struct { + G1 int `bson:"g1,omitempty"` + *Final `bson:",inline"` } -type InlinePtrPtrStruct struct { - B int - *InlinePtrStruct `bson:",inline"` +type InlineG2 struct { + G2 int `bson:"g2,omitempty"` + *InlineG1 `bson:",inline"` } type getterSetterD bson.D diff --git a/bson/decode.go b/bson/decode.go index dfff36cc7..46320c365 100644 --- a/bson/decode.go +++ b/bson/decode.go @@ -195,7 +195,11 @@ func (d *decoder) readDocTo(out reflect.Value) { } fieldsMap = sinfo.FieldsMap if sinfo.InlineStruct { - out.Set(sinfo.DeepZero()) + if useRespectNilValues { + out.Set(sinfo.Zero) + } else { + out.Set(deepZero(sinfo.st)) + } } else { out.Set(sinfo.Zero) } @@ -286,7 +290,30 @@ func (d *decoder) readDocTo(out reflect.Value) { if info.Inline == nil { d.readElemTo(out.Field(info.Num), kind) } else { - d.readElemTo(out.FieldByIndex(info.Inline), kind) + f, err := safeFieldByIndex(out, info.Inline) + if err != nil { + inlineParent := info.Inline[:len(info.Inline)-1] + + // fix parent + var fParent reflect.Value + if fParent, err = safeFieldByIndex(out, inlineParent); err != nil { + d.dropElem(kind) + continue + } + + fParent.Set(getZeroField(out, inlineParent)) + + // retry now + f, err = safeFieldByIndex(out, info.Inline) + if err == nil { + d.readElemTo(f, kind) + } else { + d.dropElem(kind) + } + + } else { + d.readElemTo(f, kind) + } } } else if inlineMap.IsValid() { if inlineMap.IsNil() { From fb17392b393473216954a6c5aff74cf3213e446e Mon Sep 17 00:00:00 2001 From: Dom Date: Wed, 12 Sep 2018 12:57:21 +0100 Subject: [PATCH 4/4] bson: remove unused test case --- bson/bson_test.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/bson/bson_test.go b/bson/bson_test.go index 5500af3b8..8eae0d079 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -360,39 +360,6 @@ func (s *S) TestPtrInline(c *C) { c.Assert(outRespectNils.InlineG1, IsNil) c.Assert(outRespectNils, DeepEquals, in) } - - // mixing empty & nil - // @TODO - /* - { - in := InlineG2{G2: 108, InlineG1: &InlineG1{}} - c.Assert(in.InlineG1, NotNil) - c.Assert(in.Final, IsNil) - - data, err := bson.Marshal(in) - c.Assert(err, IsNil) - - // default behaviour: no respect to nil values - var out InlineG2 - err = bson.Unmarshal(data, &out) - c.Assert(err, IsNil) - c.Assert(out.InlineG1, NotNil) - c.Assert(out.Final, NotNil) - c.Assert(out.G2, Equals, in.G2) - c.Assert(out.G1, Equals, 0) - c.Assert(out.G0, Equals, 0) - - // respect to nil value - var outRespectNils InlineG2 - bson.SetRespectNilValues(true) - err = bson.Unmarshal(data, &outRespectNils) - bson.SetRespectNilValues(false) - c.Assert(err, IsNil) - c.Assert(outRespectNils.InlineG1, NotNil) - c.Assert(outRespectNils.Final, IsNil) - c.Assert(outRespectNils, DeepEquals, in) - } - */ } // --------------------------------------------------------------------------