Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

decode with inline pointers #217

Merged
merged 7 commits into from
Oct 3, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
59 changes: 55 additions & 4 deletions bson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
88 changes: 55 additions & 33 deletions bson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
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
7 changes: 6 additions & 1 deletion bson/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down