Skip to content

Commit

Permalink
Merge pull request #217 from larrycinnabar/development
Browse files Browse the repository at this point in the history
decode with inline pointers
  • Loading branch information
eminano authored Oct 3, 2018
2 parents fdec4b9 + 2d67f02 commit 6d406f4
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
_harness
.vscode
.vscode
.idea
67 changes: 63 additions & 4 deletions bson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,14 @@ 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
}

type fieldInfo struct {
Expand Down Expand Up @@ -720,6 +724,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 {
Expand Down Expand Up @@ -786,6 +791,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
Expand Down Expand Up @@ -827,10 +833,63 @@ 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
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
}

// 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
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
}
129 changes: 92 additions & 37 deletions bson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,38 +274,91 @@ 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 := InlineG1{G1: 4, Final: &Final{G0: 8}}
c.Assert(in.Final, NotNil)

data, err := bson.Marshal(in)
c.Assert(err, IsNil)

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
{
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 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)
}

for _, cs := range cases {
data, err := bson.Marshal(cs.In)
// struct with nil inline pointer
{
in := InlineG1{G1: 42}
c.Assert(in.Final, IsNil)

data, err := bson.Marshal(in)
c.Assert(err, IsNil)

// default behaviour: no respect to nil values
var out InlineG1
err = bson.Unmarshal(data, &out)
c.Assert(err, IsNil)
var dataBSON bson.M
err = bson.Unmarshal(data, &dataBSON)
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)
}

// deeper struct with nil inline pointer
{
in := InlineG2{G2: 108}
c.Assert(in.InlineG1, IsNil)

c.Assert(dataBSON, DeepEquals, cs.Out)
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, IsNil)
c.Assert(outRespectNils, DeepEquals, in)
}
}

Expand Down Expand Up @@ -1210,19 +1263,21 @@ type inlineUnexported struct {
M map[string]interface{} `bson:",inline"`
unexported `bson:",inline"`
}
type MStruct struct {
M int `bson:"m,omitempty"`
type unexported struct {
A int
}
type inlinePtrStruct struct {
A int
*MStruct `bson:",inline"`

// Structures for `inline pointers` feature
type Final struct {
G0 int `bson:"g0,omitempty"`
}
type inlinePtrPtrStruct struct {
B int
*inlinePtrStruct `bson:",inline"`
type InlineG1 struct {
G1 int `bson:"g1,omitempty"`
*Final `bson:",inline"`
}
type unexported struct {
A int
type InlineG2 struct {
G2 int `bson:"g2,omitempty"`
*InlineG1 `bson:",inline"`
}

type getterSetterD bson.D
Expand Down
2 changes: 1 addition & 1 deletion bson/decimal_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// BSON library for Go
// BSON library for Go
//
// Copyright (c) 2010-2012 - Gustavo Niemeyer <[email protected]>
//
Expand Down
36 changes: 34 additions & 2 deletions bson/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,16 @@ func (d *decoder) readDocTo(out reflect.Value) {
panic(err)
}
fieldsMap = sinfo.FieldsMap
out.Set(sinfo.Zero)
if sinfo.InlineStruct {
if useRespectNilValues {
out.Set(sinfo.Zero)
} else {
out.Set(deepZero(sinfo.st))
}
} else {
out.Set(sinfo.Zero)
}

if sinfo.InlineMap != -1 {
inlineMap = out.Field(sinfo.InlineMap)
if !inlineMap.IsNil() && inlineMap.Len() > 0 {
Expand Down Expand Up @@ -281,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() {
Expand Down

0 comments on commit 6d406f4

Please sign in to comment.