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

GODRIVER-1808 Fix BSON unmarshaling into an interface containing a concrete value. #1584

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

qingyang-hu
Copy link
Collaborator

GODRIVER-1808

Summary

This PR fixes BSON's unmarshaling behavior for interfaces by passing reflect.Value instead of reflect.Type, so the actual value type contained in the interface can be used.
Especially for an interface containing a pointer, this PR unmarshals value into the pointed concrete value.

Background & Motivation

This PR provides a more flexible way of unmarshaling and makes the behavior closer to the JSON package.

return nil, err
var vDecoder *ValueDecoder
var tDecoder *typeDecoder
if !(eType.Kind() == reflect.Interface && val.Len() > 0) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delay the decoder retrieval for interface slice/array so each element will be decoded based on its type.

Copy link

mongodb-drivers-pr-bot bot commented Mar 20, 2024

API Change Report

./bson

incompatible changes

##(*Decoder).Reset: changed from func(ValueReader) to func(./bson/bsonrw.ValueReader)
##(*Decoder).SetRegistry: changed from func(Registry) to func(./bson/bsoncodec.Registry)
##(*Encoder).Reset: changed from func(ValueWriter) to func(./bson/bsonrw.ValueWriter)
##(*Encoder).SetRegistry: changed from func(Registry) to func(./bson/bsoncodec.Registry)
##A: changed from A to ./bson/primitive.A
ArrayCodec: removed
ArrayReader: removed
ArrayWriter: removed
Binary: removed
ByteSliceCodec: removed
BytesReader: removed
BytesWriter: removed
CodeWithScope: removed
CodecZeroer: removed
CompareTimestamp: removed
CopyValueToBytes: removed
##D: changed from D to ./bson/primitive.D
DBPointer: removed
DateTime: removed
Decimal128: removed
DecodeContext: removed
DecodeError: removed
##DefaultRegistry: changed from *Registry to *./bson/bsoncodec.Registry
DefaultStructTagParser: removed
DefaultValueDecoders: removed
DefaultValueEncoders: removed
DocumentReader: removed
DocumentWriter: removed
##E: changed from E to ./bson/primitive.E
EmptyInterfaceCodec: removed
EncodeContext: removed
ErrEOA: removed
ErrEOD: removed
ErrInvalidHex: removed
ErrInvalidJSON: removed
ErrNilType: removed
ErrNoDecoder: removed
ErrNoEncoder: removed
ErrNoTypeMapEntry: removed
ErrNotInterface: removed
ErrNotPointer: removed
ErrParseInf: removed
ErrParseNaN: removed
ErrParseNegInf: removed
ExtJSONValueReaderPool: removed
ExtJSONValueWriterPool: removed
IsValidObjectID: removed
JSONFallbackStructTagParser: removed
JavaScript: removed
KeyMarshaler: removed
KeyUnmarshaler: removed
##M: changed from M to ./bson/primitive.M
MapCodec: removed
##MarshalValue: changed from func(interface{}) (Type, []byte, error) to func(interface{}) (./bson/bsontype.Type, []byte, error)
##MarshalValueWithRegistry: changed from func(Registry, interface{}) (Type, []byte, error) to func(./bson/bsoncodec.Registry, interface{}) (./bson/bsontype.Type, []byte, error)
MaxDecimal128Exp: removed
MaxKey: removed
MinDecimal128Exp: removed
MinKey: removed
NewArrayCodec: removed
NewBSONValueReader: removed
NewByteSliceCodec: removed
NewDateTimeFromTime: removed
NewDecimal128: removed
##NewDecoder: changed from func(ValueReader) *Decoder to func(./bson/bsonrw.ValueReader) *Decoder
NewEmptyInterfaceCodec: removed
##NewEncoder: changed from func(ValueWriter) *Encoder to func(./bson/bsonrw.ValueWriter) *Encoder
NewExtJSONValueReader: removed
NewExtJSONValueReaderPool: removed
NewExtJSONValueWriter: removed
NewExtJSONValueWriterPool: removed
NewMapCodec: removed
NewObjectID: removed
NewObjectIDFromTimestamp: removed
NewPointerCodec: removed
##NewRegistry: changed from func() *Registry to func() *./bson/bsoncodec.Registry
##NewRegistryBuilder: changed from func() *RegistryBuilder to func() *./bson/bsoncodec.RegistryBuilder
NewSliceCodec: removed
NewStringCodec: removed
NewStructCodec: removed
NewTimeCodec: removed
NewUIntCodec: removed
NewValueReader: removed
NewValueReaderPool: removed
NewValueWriter: removed
NewValueWriterPool: removed
NilObjectID: removed
Null: removed
ObjectID: removed
ObjectIDFromHex: removed
ParseDecimal128: removed
ParseDecimal128FromBigInt: removed
PointerCodec: removed
##PrimitiveCodecs.RawDecodeValue: changed from func(DecodeContext, ValueReader, reflect.Value) error to func(./bson/bsoncodec.DecodeContext, ./bson/bsonrw.ValueReader, reflect.Value) error
##PrimitiveCodecs.RawEncodeValue: changed from func(EncodeContext, ValueWriter, reflect.Value) error to func(./bson/bsoncodec.EncodeContext, ./bson/bsonrw.ValueWriter, reflect.Value) error
##PrimitiveCodecs.RawValueDecodeValue: changed from func(DecodeContext, ValueReader, reflect.Value) error to func(./bson/bsoncodec.DecodeContext, ./bson/bsonrw.ValueReader, reflect.Value) error
##PrimitiveCodecs.RawValueEncodeValue: changed from func(EncodeContext, ValueWriter, reflect.Value) error to func(./bson/bsoncodec.EncodeContext, ./bson/bsonrw.ValueWriter, reflect.Value) error
##PrimitiveCodecs.RegisterPrimitiveCodecs: changed from func(RegistryBuilder) to func(./bson/bsoncodec.RegistryBuilder)
Proxy: removed
RawArray.DebugString: removed
RawArray.Index: changed from func(uint) RawValue to func(uint) RawElement
RawArray.IndexErr: changed from func(uint) (RawValue, error) to func(uint) (RawElement, error)
RawArray: removed
##RawValue.DBPointer: changed from func() (string, ObjectID) to func() (string, ./bson/primitive.ObjectID)
##RawValue.DBPointerOK: changed from func() (string, ObjectID, bool) to func() (string, ./bson/primitive.ObjectID, bool)
##RawValue.Decimal128: changed from func() Decimal128 to func() ./bson/primitive.Decimal128
##RawValue.Decimal128OK: changed from func() (Decimal128, bool) to func() (./bson/primitive.Decimal128, bool)
##RawValue.ObjectID: changed from func() ObjectID to func() ./bson/primitive.ObjectID
##RawValue.ObjectIDOK: changed from func() (ObjectID, bool) to func() (./bson/primitive.ObjectID, bool)
##RawValue.Type: changed from Type to ./bson/bsontype.Type
##RawValue.UnmarshalWithContext: changed from func(DecodeContext, interface{}) error to func(./bson/bsoncodec.DecodeContext, interface{}) error
##RawValue.UnmarshalWithRegistry: changed from func(Registry, interface{}) error to func(./bson/bsoncodec.Registry, interface{}) error
ReadArray: removed
Regex: removed
Registry: removed
RegistryBuilder: removed
SliceCodec: removed
SliceWriter: removed
StringCodec: removed
StructCodec: removed
StructTagParser: removed
StructTagParserFunc: removed
StructTags: removed
Symbol: removed
TimeCodec: removed
Timestamp: removed
TransitionError: removed
Type: removed
##TypeArray: changed from Type to ./bson/bsontype.Type
##TypeBinary: changed from Type to ./bson/bsontype.Type
##TypeBoolean: changed from Type to ./bson/bsontype.Type
##TypeCodeWithScope: changed from Type to ./bson/bsontype.Type
##TypeDBPointer: changed from Type to ./bson/bsontype.Type
##TypeDateTime: changed from Type to ./bson/bsontype.Type
##TypeDecimal128: changed from Type to ./bson/bsontype.Type
##TypeDouble: changed from Type to ./bson/bsontype.Type
##TypeEmbeddedDocument: changed from Type to ./bson/bsontype.Type
##TypeInt32: changed from Type to ./bson/bsontype.Type
##TypeInt64: changed from Type to ./bson/bsontype.Type
##TypeJavaScript: changed from Type to ./bson/bsontype.Type
##TypeMaxKey: changed from Type to ./bson/bsontype.Type
##TypeMinKey: changed from Type to ./bson/bsontype.Type
##TypeNull: changed from Type to ./bson/bsontype.Type
##TypeObjectID: changed from Type to ./bson/bsontype.Type
##TypeRegex: changed from Type to ./bson/bsontype.Type
##TypeString: changed from Type to ./bson/bsontype.Type
##TypeSymbol: changed from Type to ./bson/bsontype.Type
##TypeTimestamp: changed from Type to ./bson/bsontype.Type
##TypeUndefined: changed from Type to ./bson/bsontype.Type
UIntCodec: removed
Undefined: removed
##UnmarshalExtJSONWithContext: changed from func(DecodeContext, []byte, bool, interface{}) error to func(./bson/bsoncodec.DecodeContext, []byte, bool, interface{}) error
##UnmarshalExtJSONWithRegistry: changed from func(Registry, []byte, bool, interface{}) error to func(./bson/bsoncodec.Registry, []byte, bool, interface{}) error
##UnmarshalValue: changed from func(Type, []byte, interface{}) error to func(./bson/bsontype.Type, []byte, interface{}) error
##UnmarshalValueWithRegistry: changed from func(Registry, Type, []byte, interface{}) error to func(./bson/bsoncodec.Registry, ./bson/bsontype.Type, []byte, interface{}) error
##UnmarshalWithContext: changed from func(DecodeContext, []byte, interface{}) error to func(./bson/bsoncodec.DecodeContext, []byte, interface{}) error
##UnmarshalWithRegistry: changed from func(Registry, []byte, interface{}) error to func(./bson/bsoncodec.Registry, []byte, interface{}) error
ValueCodec: removed
ValueDecoder: removed
ValueDecoderError: removed
ValueDecoderFunc: removed
ValueEncoder: removed
ValueEncoderError: removed
ValueEncoderFunc: removed
##ValueMarshaler.MarshalBSONValue: changed from func() (Type, []byte, error) to func() (./bson/bsontype.Type, []byte, error)
ValueReader: removed
ValueReaderPool: removed
##ValueUnmarshaler.UnmarshalBSONValue: changed from func(Type, []byte) error to func(./bson/bsontype.Type, []byte) error
ValueWriter: removed
ValueWriterFlusher: removed
ValueWriterPool: removed

compatible changes

Raw.Elements: added
Raw.Lookup: added
Raw.LookupErr: added

./bson/bsoncodec

compatible changes

package added

./bson/bsonrw

compatible changes

package added

./bson/bsonrw/bsonrwtest

compatible changes

package added

./bson/bsontype

compatible changes

package added

./bson/mgocompat

incompatible changes

##GetterEncodeValue: changed from func(./bson.EncodeContext, ./bson.ValueWriter, reflect.Value) error to func(./bson/bsoncodec.EncodeContext, ./bson/bsonrw.ValueWriter, reflect.Value) error
##NewRegistryBuilder: changed from func() *./bson.RegistryBuilder to func() *./bson/bsoncodec.RegistryBuilder
##NewRespectNilValuesRegistryBuilder: changed from func() *./bson.RegistryBuilder to func() *./bson/bsoncodec.RegistryBuilder
##Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry
RespectNilValuesRegistry: removed
##SetterDecodeValue: changed from func(./bson.DecodeContext, ./bson.ValueReader, reflect.Value) error to func(./bson/bsoncodec.DecodeContext, ./bson/bsonrw.ValueReader, reflect.Value) error

compatible changes

RegistryRespectNilValues: added

./bson/primitive

compatible changes

package added

./event

incompatible changes

##CommandFailedEvent.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##CommandFinishedEvent.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##CommandStartedEvent.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##CommandSucceededEvent.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
PoolEvent.Duration: removed
##PoolEvent.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##ServerClosedEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##ServerDescriptionChangedEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##ServerOpeningEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##TopologyClosedEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##TopologyDescriptionChangedEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##TopologyOpeningEvent.TopologyID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID

./mongo

incompatible changes

(*ChangeStream).RemainingBatchLength: removed
##(Client).StartSession: changed from func(..../mongo/options.SessionOptions) (Session, error) to func(..../mongo/options.SessionOptions) (Session, error)
(*Client).UseSession: changed from func(context.Context, func(context.Context) error) error to func(context.Context, func(SessionContext) error) error
##(*Client).UseSessionWithOptions: changed from func(context.Context, *./mongo/options.SessionOptions, func(context.Context) error) error to func(context.Context, *./mongo/options.SessionOptions, func(SessionContext) error) error
##(*ClientEncryption).AddKeyAltName: changed from func(context.Context, ./bson.Binary, string) *SingleResult to func(context.Context, ./bson/primitive.Binary, string) SingleResult
##(ClientEncryption).CreateDataKey: changed from func(context.Context, string, ..../mongo/options.DataKeyOptions) (./bson.Binary, error) to func(context.Context, string, ...
./mongo/options.DataKeyOptions) (./bson/primitive.Binary, error)
##(*ClientEncryption).CreateEncryptedCollection: changed from func(context.Context, *Database, string, *./mongo/options.CreateCollectionOptions, string, interface{}) (*Collection, ./bson.M, error) to func(context.Context, *Database, string, *./mongo/options.CreateCollectionOptions, string, interface{}) (*Collection, ./bson/primitive.M, error)
##(*ClientEncryption).Decrypt: changed from func(context.Context, ./bson.Binary) (./bson.RawValue, error) to func(context.Context, ./bson/primitive.Binary) (./bson.RawValue, error)
##(*ClientEncryption).DeleteKey: changed from func(context.Context, ./bson.Binary) (*DeleteResult, error) to func(context.Context, ./bson/primitive.Binary) (DeleteResult, error)
##(ClientEncryption).Encrypt: changed from func(context.Context, ./bson.RawValue, ..../mongo/options.EncryptOptions) (./bson.Binary, error) to func(context.Context, ./bson.RawValue, ...
./mongo/options.EncryptOptions) (./bson/primitive.Binary, error)
##(*ClientEncryption).GetKey: changed from func(context.Context, ./bson.Binary) *SingleResult to func(context.Context, ./bson/primitive.Binary) *SingleResult
##(*ClientEncryption).RemoveKeyAltName: changed from func(context.Context, ./bson.Binary, string) *SingleResult to func(context.Context, ./bson/primitive.Binary, string) SingleResult
##(Collection).Distinct: changed from func(context.Context, string, interface{}, ..../mongo/options.DistinctOptions) DistinctResult to func(context.Context, string, interface{}, ..../mongo/options.DistinctOptions) ([]interface{}, error)
##(GridFSBucket).UploadFromStream: changed from func(context.Context, string, io.Reader, ..../mongo/options.UploadOptions) (./bson.ObjectID, error) to func(context.Context, string, io.Reader, ...
./mongo/options.UploadOptions) (./bson/primitive.ObjectID, error)
##CollectionSpecification.UUID: changed from *./bson.Binary to *./bson/primitive.Binary
DistinctResult: removed
##NewCursorFromDocuments: changed from func([]interface{}, error, *./bson.Registry) (*Cursor, error) to func([]interface{}, error, *./bson/bsoncodec.Registry) (*Cursor, error)
NewSessionContext: changed from func(context.Context, *Session) context.Context to func(context.Context, Session) SessionContext
##NewSingleResultFromDocument: changed from func(interface{}, error, *./bson.Registry) *SingleResult to func(interface{}, error, *./bson/bsoncodec.Registry) *SingleResult
##Pipeline: changed from []./bson.D to []./bson/primitive.D
##Session: changed from struct{clientSession *./x/mongo/driver/session.Client; client Client; deployment ./x/mongo/driver.Deployment; didCommitAfterStart bool} to interface{AbortTransaction(context.Context) error; AdvanceClusterTime(./bson.Raw) error; AdvanceOperationTime(./bson/primitive.Timestamp) error; Client() Client; ClusterTime() ./bson.Raw; CommitTransaction(context.Context) error; EndSession(context.Context); ID() ./bson.Raw; OperationTime() ./bson/primitive.Timestamp; StartTransaction(..../mongo/options.TransactionOptions) error; WithTransaction(ctx context.Context, fn func(ctx SessionContext) (interface{}, error), opts ..../mongo/options.TransactionOptions) (interface{}, error); session()}
SessionFromContext: changed from func(context.Context) *Session to func(context.Context) Session
WithSession: changed from func(context.Context, *Session, func(context.Context) error) error to func(context.Context, Session, func(SessionContext) error) error

compatible changes

SessionContext: added
XSession: added

./mongo/description

incompatible changes

##SelectedServer.ElectionID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##SelectedServer.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##Server.ElectionID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID
##Server.ServiceID: changed from *./bson.ObjectID to *./bson/primitive.ObjectID
##TopologyVersion.ProcessID: changed from ./bson.ObjectID to ./bson/primitive.ObjectID

./mongo/options

incompatible changes

##(*AggregateOptions).SetCustom: changed from func(./bson.M) *AggregateOptions to func(./bson/primitive.M) *AggregateOptions
##(*ChangeStreamOptions).SetCustom: changed from func(./bson.M) *ChangeStreamOptions to func(./bson/primitive.M) *ChangeStreamOptions
##(*ChangeStreamOptions).SetCustomPipeline: changed from func(./bson.M) *ChangeStreamOptions to func(./bson/primitive.M) *ChangeStreamOptions
##(ChangeStreamOptions).SetStartAtOperationTime: changed from func(./bson.Timestamp) ChangeStreamOptions to func(./bson/primitive.Timestamp) *ChangeStreamOptions
##(ClientOptions).SetRegistry: changed from func(./bson.Registry) ClientOptions to func(./bson/bsoncodec.Registry) *ClientOptions
##(CollectionOptions).SetRegistry: changed from func(./bson.Registry) CollectionOptions to func(./bson/bsoncodec.Registry) *CollectionOptions
##(DatabaseOptions).SetRegistry: changed from func(./bson.Registry) DatabaseOptions to func(./bson/bsoncodec.Registry) *DatabaseOptions
##(*EncryptOptions).SetKeyID: changed from func(./bson.Binary) *EncryptOptions to func(./bson/primitive.Binary) *EncryptOptions
##AggregateOptions.Custom: changed from ./bson.M to ./bson/primitive.M
##ArrayFilters.Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry
##ChangeStreamOptions.Custom: changed from ./bson.M to ./bson/primitive.M
##ChangeStreamOptions.CustomPipeline: changed from ./bson.M to ./bson/primitive.M
##ChangeStreamOptions.StartAtOperationTime: changed from *./bson.Timestamp to *./bson/primitive.Timestamp
##ClientOptions.Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry
##CollectionOptions.Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry
##DatabaseOptions.Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry
##EncryptOptions.KeyID: changed from *./bson.Binary to *./bson/primitive.Binary
##UploadOptions.Registry: changed from *./bson.Registry to *./bson/bsoncodec.Registry

./mongo/readconcern

incompatible changes

##(*ReadConcern).MarshalBSONValue: changed from func() (./bson.Type, []byte, error) to func() (./bson/bsontype.Type, []byte, error)

./mongo/writeconcern

incompatible changes

##(*WriteConcern).MarshalBSONValue: changed from func() (./bson.Type, []byte, error) to func() (./bson/bsontype.Type, []byte, error)

./x/bsonx/bsoncore

incompatible changes

##(*ArrayBuilder).AppendDBPointer: changed from func(string, [12]byte) *ArrayBuilder to func(string, ./bson/primitive.ObjectID) *ArrayBuilder
##(*ArrayBuilder).AppendDecimal128: changed from func(uint64, uint64) *ArrayBuilder to func(./bson/primitive.Decimal128) *ArrayBuilder
##(*ArrayBuilder).AppendObjectID: changed from func([12]byte) *ArrayBuilder to func(./bson/primitive.ObjectID) *ArrayBuilder
##(*DocumentBuilder).AppendDBPointer: changed from func(string, string, [12]byte) *DocumentBuilder to func(string, string, ./bson/primitive.ObjectID) *DocumentBuilder
##(*DocumentBuilder).AppendDecimal128: changed from func(string, uint64, uint64) *DocumentBuilder to func(string, ./bson/primitive.Decimal128) *DocumentBuilder
##(*DocumentBuilder).AppendObjectID: changed from func(string, [12]byte) *DocumentBuilder to func(string, ./bson/primitive.ObjectID) *DocumentBuilder
##AppendDBPointer: changed from func([]byte, string, [12]byte) []byte to func([]byte, string, ./bson/primitive.ObjectID) []byte
##AppendDBPointerElement: changed from func([]byte, string, string, [12]byte) []byte to func([]byte, string, string, ./bson/primitive.ObjectID) []byte
##AppendDecimal128: changed from func([]byte, uint64, uint64) []byte to func([]byte, ./bson/primitive.Decimal128) []byte
##AppendDecimal128Element: changed from func([]byte, string, uint64, uint64) []byte to func([]byte, string, ./bson/primitive.Decimal128) []byte
##AppendHeader: changed from func([]byte, Type, string) []byte to func([]byte, ./bson/bsontype.Type, string) []byte
##AppendObjectID: changed from func([]byte, [12]byte) []byte to func([]byte, ./bson/primitive.ObjectID) []byte
##AppendObjectIDElement: changed from func([]byte, string, [12]byte) []byte to func([]byte, string, ./bson/primitive.ObjectID) []byte
##AppendType: changed from func([]byte, Type) []byte to func([]byte, ./bson/bsontype.Type) []byte
##ElementTypeError.Type: changed from Type to ./bson/bsontype.Type
##EqualValue: changed from func(Type, Type, []byte, []byte) bool to func(./bson/bsontype.Type, ./bson/bsontype.Type, []byte, []byte) bool
##InvalidDepthTraversalError.Type: changed from Type to ./bson/bsontype.Type
##ReadDBPointer: changed from func([]byte) (string, [12]byte, []byte, bool) to func([]byte) (string, ./bson/primitive.ObjectID, []byte, bool)
##ReadDecimal128: changed from func([]byte) (uint64, uint64, []byte, bool) to func([]byte) (./bson/primitive.Decimal128, []byte, bool)
##ReadHeader: changed from func([]byte) (Type, string, []byte, bool) to func([]byte) (./bson/bsontype.Type, string, []byte, bool)
##ReadObjectID: changed from func([]byte) ([12]byte, []byte, bool) to func([]byte) (./bson/primitive.ObjectID, []byte, bool)
##ReadType: changed from func([]byte) (Type, []byte, bool) to func([]byte) (./bson/bsontype.Type, []byte, bool)
##ReadValue: changed from func([]byte, Type) (Value, []byte, bool) to func([]byte, ./bson/bsontype.Type) (Value, []byte, bool)
Type: removed
TypeArray: removed
TypeBinary: removed
TypeBoolean: removed
TypeCodeWithScope: removed
TypeDBPointer: removed
TypeDateTime: removed
TypeDecimal128: removed
TypeDouble: removed
TypeEmbeddedDocument: removed
TypeInt32: removed
TypeInt64: removed
TypeJavaScript: removed
TypeMaxKey: removed
TypeMinKey: removed
TypeNull: removed
TypeObjectID: removed
TypeRegex: removed
TypeString: removed
TypeSymbol: removed
TypeTimestamp: removed
TypeUndefined: removed
##Value.DBPointer: changed from func() (string, [12]byte) to func() (string, ./bson/primitive.ObjectID)
##Value.DBPointerOK: changed from func() (string, [12]byte, bool) to func() (string, ./bson/primitive.ObjectID, bool)
##Value.Decimal128: changed from func() (uint64, uint64) to func() ./bson/primitive.Decimal128
##Value.Decimal128OK: changed from func() (uint64, uint64, bool) to func() (./bson/primitive.Decimal128, bool)
##Value.ObjectID: changed from func() [12]byte to func() ./bson/primitive.ObjectID
##Value.ObjectIDOK: changed from func() ([12]byte, bool) to func() (./bson/primitive.ObjectID, bool)
##Value.Type: changed from Type to ./bson/bsontype.Type

./x/mongo/driver

incompatible changes

##CursorResponse.Connection: changed from *./x/mongo/driver/mnet.Connection to PinnedConnection
##ErrorProcessor.ProcessError: changed from func(error, ./x/mongo/driver/mnet.Describer) ProcessErrorResult to func(error, Connection) ProcessErrorResult
##Handshaker.FinishHandshake: changed from func(context.Context, *./x/mongo/driver/mnet.Connection) error to func(context.Context, Connection) error
##Handshaker.GetHandshakeInformation: changed from func(context.Context, ./mongo/address.Address, *./x/mongo/driver/mnet.Connection) (HandshakeInformation, error) to func(context.Context, ./mongo/address.Address, Connection) (HandshakeInformation, error)
##Operation.ExecuteExhaust: changed from func(context.Context, *./x/mongo/driver/mnet.Connection) error to func(context.Context, StreamerConnection) error
##ResponseInfo.Connection: changed from ./x/mongo/driver/mnet.Connection to Connection
##Server.Connection: changed from func(context.Context) (
./x/mongo/driver/mnet.Connection, error) to func(context.Context) (Connection, error)
##SingleConnectionDeployment.C: changed from ./x/mongo/driver/mnet.Connection to Connection
##SingleConnectionDeployment.Connection: changed from func(context.Context) (
./x/mongo/driver/mnet.Connection, error) to func(context.Context) (Connection, error)

compatible changes

Compressor: added
Connection: added
PinnedConnection: added
StreamerConnection: added

./x/mongo/driver/auth

incompatible changes

##Config.Connection: changed from *./x/mongo/driver/mnet.Connection to ./x/mongo/driver.Connection

compatible changes

Config.Description: added

./x/mongo/driver/drivertest

incompatible changes

(*ChannelConn).Read: removed
(*ChannelConn).Write: removed

compatible changes

(*ChannelConn).ReadWireMessage: added
(*ChannelConn).WriteWireMessage: added

./x/mongo/driver/mnet

incompatible changes

package removed

./x/mongo/driver/mongocrypt/options

incompatible changes

##(*ExplicitEncryptionOptions).SetKeyID: changed from func(./bson.Binary) *ExplicitEncryptionOptions to func(./bson/primitive.Binary) *ExplicitEncryptionOptions
##ExplicitEncryptionOptions.KeyID: changed from *./bson.Binary to *./bson/primitive.Binary

./x/mongo/driver/operation

incompatible changes

##(*Hello).FinishHandshake: changed from func(context.Context, *./x/mongo/driver/mnet.Connection) error to func(context.Context, ./x/mongo/driver.Connection) error
##(*Hello).GetHandshakeInformation: changed from func(context.Context, ./mongo/address.Address, *./x/mongo/driver/mnet.Connection) (./x/mongo/driver.HandshakeInformation, error) to func(context.Context, ./mongo/address.Address, ./x/mongo/driver.Connection) (./x/mongo/driver.HandshakeInformation, error)
##(*Hello).StreamResponse: changed from func(context.Context, *./x/mongo/driver/mnet.Connection) error to func(context.Context, ./x/mongo/driver.StreamerConnection) error

compatible changes

(*CreateSearchIndexes).WriteConcern: added
(*DropSearchIndex).WriteConcern: added
(*UpdateSearchIndex).WriteConcern: added

./x/mongo/driver/session

incompatible changes

##(Client).AdvanceOperationTime: changed from func(./bson.Timestamp) error to func(*./bson/primitive.Timestamp) error
##Client.OperationTime: changed from *./bson.Timestamp to *./bson/primitive.Timestamp
##Client.SnapshotTime: changed from *./bson.Timestamp to *./bson/primitive.Timestamp
LoadBalancedTransactionConnection.ReadWireMessage: added
LoadBalancedTransactionConnection.WriteWireMessage: added
##./x/mongo/driver/mnet.ReadWriteCloser.Read, method set of LoadBalancedTransactionConnection: removed
##./x/mongo/driver/mnet.ReadWriteCloser.Write, method set of LoadBalancedTransactionConnection: removed

./x/mongo/driver/topology

incompatible changes

(*Connection).Read: removed
(*Connection).Write: removed
##(Server).Connection: changed from func(context.Context) (./x/mongo/driver/mnet.Connection, error) to func(context.Context) (./x/mongo/driver.Connection, error)
##(*Server).ProcessError: changed from func(error, ./x/mongo/driver/mnet.Describer) ./x/mongo/driver.ProcessErrorResult to func(error, ./x/mongo/driver.Connection) ./x/mongo/driver.ProcessErrorResult
##(*Server).ProcessHandshakeError: changed from func(error, uint64, *./bson.ObjectID) to func(error, uint64, *./bson/primitive.ObjectID)
##ConnectServer: changed from func(./mongo/address.Address, updateTopologyCallback, ./bson.ObjectID, ...ServerOption) (*Server, error) to func(./mongo/address.Address, updateTopologyCallback, ./bson/primitive.ObjectID, ...ServerOption) (*Server, error)
##NewServer: changed from func(./mongo/address.Address, ./bson.ObjectID, ...ServerOption) *Server to func(./mongo/address.Address, ./bson/primitive.ObjectID, ...ServerOption) Server
##WithRegistry: changed from func(func(
./bson.Registry) ./bson.Registry) ServerOption to func(func(./bson/bsoncodec.Registry) *./bson/bsoncodec.Registry) ServerOption

compatible changes

(*Connection).ReadWireMessage: added
(*Connection).WriteWireMessage: added

@mongodb-drivers-pr-bot mongodb-drivers-pr-bot bot added the priority-3-low Low Priority PR for Review label Mar 20, 2024
@qingyang-hu qingyang-hu marked this pull request as ready for review March 20, 2024 22:16
@qingyang-hu qingyang-hu changed the title GODRIVER-1808 GODRIVER-1808 Fix BSON unmarshaling into an interface containing a concrete value. Mar 21, 2024
Copy link
Collaborator

@matthewdale matthewdale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update the implementation to remove the added calls to reflect.New, which seems to increase memory allocs in some cases. Is there a way to pass a reflect.Type when we only have type information, and pass a reflect.Value when we have value and type information?

I noticed BenchmarkUnmarshal reports about 30% more allocations with this PR. Keep in mind that BenchmarkUnmarshal actually only benchmarks unmarhshaling into a map. BenchmarkCodeUnmarshal, which unmarshals into a struct, shows no difference, so it seems to be isolated to map unmarshaling.

❯ benchstat old.txt new.txt                                                                                                       
goos: darwin
goarch: arm64
pkg: go.mongodb.org/mongo-driver/bson
                                    │   old.txt   │               new.txt                │
                                    │   sec/op    │    sec/op     vs base                │
Unmarshal/simple_struct/BSON-10       3.423µ ± 0%   3.961µ ± 19%  +15.72% (p=0.000 n=10)
Unmarshal/nested_struct/BSON-10       8.605µ ± 1%   9.258µ ±  0%   +7.59% (p=0.000 n=10)
Unmarshal/deep_bson.json.gz/BSON-10   41.98µ ± 0%   46.45µ ±  1%  +10.66% (p=0.000 n=10)
Unmarshal/flat_bson.json.gz/BSON-10   41.88µ ± 1%   47.73µ ±  0%  +13.97% (p=0.000 n=10)
Unmarshal/full_bson.json.gz/BSON-10   49.61µ ± 0%   55.90µ ±  0%  +12.67% (p=0.000 n=10)
geomean                               19.14µ        21.45µ        +12.09%

                                    │   old.txt    │               new.txt                │
                                    │     B/op     │     B/op      vs base                │
Unmarshal/simple_struct/BSON-10       1.062Ki ± 0%   1.375Ki ± 0%  +29.41% (p=0.000 n=10)
Unmarshal/nested_struct/BSON-10       7.927Ki ± 0%   8.411Ki ± 0%   +6.11% (p=0.000 n=10)
Unmarshal/deep_bson.json.gz/BSON-10   30.15Ki ± 0%   33.12Ki ± 0%   +9.85% (p=0.000 n=10)
Unmarshal/flat_bson.json.gz/BSON-10   13.11Ki ± 0%   16.79Ki ± 0%  +28.09% (p=0.000 n=10)
Unmarshal/full_bson.json.gz/BSON-10   19.93Ki ± 0%   23.57Ki ± 0%  +18.27% (p=0.000 n=10)
geomean                               9.212Ki        10.87Ki       +17.97%

                                    │  old.txt   │               new.txt               │
                                    │ allocs/op  │  allocs/op   vs base                │
Unmarshal/simple_struct/BSON-10       62.00 ± 0%    86.00 ± 0%  +38.71% (p=0.000 n=10)
Unmarshal/nested_struct/BSON-10       143.0 ± 0%    178.0 ± 0%  +24.48% (p=0.000 n=10)
Unmarshal/deep_bson.json.gz/BSON-10   822.0 ± 0%   1012.0 ± 0%  +23.11% (p=0.000 n=10)
Unmarshal/flat_bson.json.gz/BSON-10   740.0 ± 0%   1030.0 ± 0%  +39.19% (p=0.000 n=10)
Unmarshal/full_bson.json.gz/BSON-10   797.0 ± 0%   1086.0 ± 0%  +36.26% (p=0.000 n=10)
geomean                               336.2         444.4       +32.16%

Here's a graph view of a memory profile of running BenchmarkUnmarshal on the previous commit, filtered to only reflect calls:
Screenshot 2024-04-19 at 7 06 21 PM
And here's the same profile of the PR commit, filtered to only reflect calls:
Screenshot 2024-04-19 at 7 06 30 PM

The allocs from reflect.New grew from 0.28GB to 1GB, which seems to be the biggest change between the two profiles.

@qingyang-hu qingyang-hu marked this pull request as draft April 22, 2024 16:29
Copy link
Collaborator

@matthewdale matthewdale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigating this further, there are a few quick changes we can make to keep the allocations identical to the base branch.

}

func decodeTypeOrValueWithInfo(vd ValueDecoder, td typeDecoder, dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type, convert bool) (reflect.Value, error) {
func decodeTypeOrValueWithInfo(vd ValueDecoder, td typeDecoder, dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value, convert bool) (reflect.Value, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change the reflect.Value parameter back to reflect.Type because all we do with that reflect.Value is extract the type. Instead, we should pass the reflect.Type to avoid the unnecessary reflect.New allocations.

val.Index(idx).Set(v)
e = v
}
elem, err = decodeTypeOrValueWithInfo(valueDecoder, typeDecoder, dc, vr, e, true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call e.Type() here instead of passing the reflect.Value.

Suggested change
elem, err = decodeTypeOrValueWithInfo(valueDecoder, typeDecoder, dc, vr, e, true)
elem, err = decodeTypeOrValueWithInfo(valueDecoder, typeDecoder, dc, vr, e.Type(), true)

elem = reflect.Zero(val.Index(idx).Type())
}
} else {
elem, err = decodeTypeOrValueWithInfo(vDecoder, tDecoder, dc, vr, reflect.New(eType).Elem(), true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass the type here instead of allocating a new reflect.Value.

Suggested change
elem, err = decodeTypeOrValueWithInfo(vDecoder, tDecoder, dc, vr, reflect.New(eType).Elem(), true)
elem, err = decodeTypeOrValueWithInfo(vDecoder, tDecoder, dc, vr, eType, true)

@@ -213,7 +213,7 @@ func (mc *MapCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val ref
return err
}

elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, eType, true)
elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, reflect.New(eType).Elem(), true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass the type here instead of allocating a new reflect.Value.

Suggested change
elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, reflect.New(eType).Elem(), true)
elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, eType, true)

Comment on lines 350 to 351
val := reflect.New(t).Elem()
return decodeTypeOrValueWithInfo(decoder, td, dc, vr, val, true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass the type here instead of allocating a new reflect.Value.

Suggested change
val := reflect.New(t).Elem()
return decodeTypeOrValueWithInfo(decoder, td, dc, vr, val, true)
return decodeTypeOrValueWithInfo(decoder, td, dc, vr, t, true)

@@ -185,7 +185,7 @@ func (dvd DefaultValueDecoders) DDecodeValue(dc DecodeContext, vr bsonrw.ValueRe
}

// Pass false for convert because we don't need to call reflect.Value.Convert for tEmpty.
elem, err := decodeTypeOrValueWithInfo(decoder, tEmptyTypeDecoder, dc, elemVr, tEmpty, false)
elem, err := decodeTypeOrValueWithInfo(decoder, tEmptyTypeDecoder, dc, elemVr, reflect.New(tEmpty).Elem(), false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass the type here instead of allocating a new reflect.Value.

Suggested change
elem, err := decodeTypeOrValueWithInfo(decoder, tEmptyTypeDecoder, dc, elemVr, reflect.New(tEmpty).Elem(), false)
elem, err := decodeTypeOrValueWithInfo(decoder, tEmptyTypeDecoder, dc, elemVr, tEmpty, false)

prestonvasquez
prestonvasquez previously approved these changes May 10, 2024
Copy link
Collaborator

@prestonvasquez prestonvasquez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with updates proposed by Matt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority-3-low Low Priority PR for Review
Projects
None yet
3 participants