[Golang] FindOne() with two Bson.M failed for 10% attempts, Bson.M+Bson.D or a single Bson.M always work, why?

Hi,

Need some help to figure out why nested bson.M doesn’t work occasionally.

For the following Golang structs stored in a MongoDb collection for type A:

type A struct {
 Id       primitive.ObjectID 
 Random1  string
 Parents  []B
 Random2  int
}

type B struct {
 Id       primitive.ObjectID 
 Random3  string
 Children []C
 Random4  int
}

type C struct {
 Random5  string
 Name     Name
 Random6  int
}

type Name struct {
  FirstName string
  LastName string
}

The following filter for FindOne(), which uses two bson.M, worked in most situations but failed to find a match in about 10% runs

filter1 := bson.M{
		"parents.0.chilren.0.name": bson.M{
			"first_name":  "Mike",
			"last_name": "Anderson",
		},
}

The following two filters alway work, where filter 2 uses bson.D inside bson.M, and filter 3 just uses one bson.M

filter2 := bson.M{
		"parents.0.chilren.0.name": bson.D{
			{Key: "first_name",  Value: "Mike"},
			{Key: "last_name",  Value: "Anderson"},
		},
}

filter3 := bson.M{
		"parents.0.chilren.0.name.first_name":  "Mike",
		"parents.0.chilren.0.name.last_name":  "Anderson",
}

I found a similar question in https://jira.mongodb.org/browse/GODRIVER-877 but still don’t understand the differences or root cause. Thanks for the help!

Bump the thread. Hope to find an answer in the new year, thanks!

Hi @Tianjun_Fu,

Welcome to the MongoDB Community forums :sparkles:

In Go, maps are intentionally non-deterministic. This is mentioned in the article Go maps in action specifically in the section Iteration Order where it is stated:

When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next.

So, while querying in MongoDB, in most cases the order of keys does not matter, so we take advantage of the concise syntax of Go maps.

In filter1 you are matching on a BSON document where field order matters. Instead, you should use the approach from filter3 which worked well for you. Also, it is a shorter and clearer filter declaration:

Whereas when you use bson.D instead of bson.M, you will see deterministic behavior as you noted in filter2.

Also as per the comment in the ticket - GODRIVER-877:

The only two cases where the order is significant are for generic commands(where the first key has to be the command name) and index specifications(where the order determines the structure of the index), and in those cases, it’s recommended to use bson.D instead of bson.M.

I hope it helps!

Best,
Kushagra

Thank you for the reply, Kushagra. Can you clarify why field order matters in filter1 only but not filter3?

Hi :wave: @Tianjun_Fu,

In filter1, the query looks like the following:

Here if you note, the query is using nested bson.M. Specifically, in the "parents.0.chilren.0.name" field, the matching order is crucial and needs to be definite whereas in the filter3 it doesn’t matter which comes first because it directly points out to the specific keys which are "parents.0.chilren.0.name.first_name" and "parents.0.chilren.0.name.last_name"

To explain it further, consider the following example:

replset [direct: primary] test> db.test.find()
[
  { _id: 0, name: { first: 'aaa', last: 'bbb' } },
  { _id: 1, name: { last: 'bbb', first: 'aaa' } }
]

replset [direct: primary] test> db.test.find({name:{first:'aaa',last:'bbb'}})
[ { _id: 0, name: { first: 'aaa', last: 'bbb' } } ]

replset [direct: primary] test> db.test.find({name:{last:'bbb',first:'aaa'}})
[ { _id: 1, name: { last: 'bbb', first: 'aaa' } } ]

It’s worth noting that when you want to match the sub-document, field order does matter.

I hope it answers your questions.

Feel free to reach out if you have any further questions.

Best,
Kushagra

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.