-
Notifications
You must be signed in to change notification settings - Fork 916
GODRIVER-3470 Correct BSON unmarshaling logic for null values #1924
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
Changes from 5 commits
dc02142
f1e8a1d
7d2be3c
32190fe
75ee151
cd82ab3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1521,7 +1521,13 @@ func (dvd DefaultValueDecoders) ValueUnmarshalerDecodeValue(_ DecodeContext, vr | |
return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val} | ||
} | ||
|
||
if vr.Type() == bsontype.Null { | ||
// If BSON value is null and the go value is a pointer, then don't call | ||
// UnmarshalBSONValue. Even if the Go pointer is already initialized (i.e., | ||
// non-nil), encountering null in BSON will result in the pointer being | ||
// directly set to nil here. Since the pointer is being replaced with nil, | ||
// there is no opportunity (or reason) for the custom UnmarshalBSONValue logic | ||
// to be called. | ||
if vr.Type() == bsontype.Null && val.Kind() == reflect.Ptr { | ||
val.Set(reflect.Zero(val.Type())) | ||
|
||
return vr.ReadNull() | ||
|
@@ -1563,6 +1569,18 @@ func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(_ DecodeContext, vr bsonr | |
return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val} | ||
} | ||
|
||
// If BSON value is null and the go value is a pointer, then don't call | ||
// UnmarshalBSON. Even if the Go pointer is already initialized (i.e., | ||
// non-nil), encountering null in BSON will result in the pointer being | ||
// directly set to nil here. Since the pointer is being replaced with nil, | ||
// there is no opportunity (or reason) for the custom UnmarshalBSON logic to | ||
// be called. | ||
if val.Kind() == reflect.Ptr && vr.Type() == bsontype.Null { | ||
val.Set(reflect.Zero(val.Type())) | ||
|
||
return vr.ReadNull() | ||
} | ||
|
||
if val.Kind() == reflect.Ptr && val.IsNil() { | ||
if !val.CanSet() { | ||
return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val} | ||
|
@@ -1575,18 +1593,6 @@ func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(_ DecodeContext, vr bsonr | |
return err | ||
} | ||
|
||
// If the target Go value is a pointer and the BSON field value is empty, set the value to the | ||
// zero value of the pointer (nil) and don't call UnmarshalBSON. UnmarshalBSON has no way to | ||
// change the pointer value from within the function (only the value at the pointer address), | ||
// so it can't set the pointer to "nil" itself. Since the most common Go value for an empty BSON | ||
// field value is "nil", we set "nil" here and don't call UnmarshalBSON. This behavior matches | ||
// the behavior of the Go "encoding/json" unmarshaler when the target Go value is a pointer and | ||
// the JSON field value is "null". | ||
if val.Kind() == reflect.Ptr && len(src) == 0 { | ||
val.Set(reflect.Zero(val.Type())) | ||
return nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing this will result in Here are two test cases that can be added to {
name: "nil pointer and non-pointer type with BSON minkey",
sType: reflect.TypeOf(unmarshalBehaviorTestCase{}),
want: &unmarshalBehaviorTestCase{
BSONValueTracker: unmarshalBSONValueCallTracker{
called: true,
},
BSONValuePtrTracker: &unmarshalBSONValueCallTracker{
called: true,
},
BSONTracker: unmarshalBSONCallTracker{
called: true,
},
BSONPtrTracker: nil,
},
data: docToBytes(D{
{Key: "bv_tracker", Value: primitive.MinKey{}},
{Key: "bv_ptr_tracker", Value: primitive.MinKey{}},
{Key: "b_tracker", Value: primitive.MinKey{}},
{Key: "b_ptr_tracker", Value: primitive.MinKey{}},
}),
},
{
name: "nil pointer and non-pointer type with BSON maxkey",
sType: reflect.TypeOf(unmarshalBehaviorTestCase{}),
want: &unmarshalBehaviorTestCase{
BSONValueTracker: unmarshalBSONValueCallTracker{
called: true,
},
BSONValuePtrTracker: &unmarshalBSONValueCallTracker{
called: true,
},
BSONTracker: unmarshalBSONCallTracker{
called: true,
},
BSONPtrTracker: nil,
},
data: docToBytes(D{
{Key: "bv_tracker", Value: primitive.MaxKey{}},
{Key: "bv_ptr_tracker", Value: primitive.MaxKey{}},
{Key: "b_tracker", Value: primitive.MaxKey{}},
{Key: "b_ptr_tracker", Value: primitive.MaxKey{}},
}),
}, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch, updated! |
||
|
||
if !val.Type().Implements(tUnmarshaler) { | ||
if !val.CanAddr() { | ||
return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a similar block in
UnmarshalerDecodeValue
after the value is read into a[]byte
:Can we use the same condition in both methods? Or are they distinct scenarios?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
len(src)
check was implemented here: https://github.com/mongodb/mongo-go-driver/pull/833/filesSince the bytes represent BSON null are copied, the type is converted from null to invalid:
Which means that this check doesn’t work:
Checking after copying is weaker since validity should be checked on the first block:
So I think BSON null is the only case where you would get an invalid type after copying. I suggest we mirror
ValueUnmarshalerDecodeValue
.