[GODRIVER-877] FindOne() nondeterministically returns empty singleResult Created: 13/Mar/19  Updated: 27/Oct/23  Resolved: 14/Mar/19

Status: Closed
Project: Go Driver
Component/s: BSON, Core API, Error Handling
Affects Version/s: 0.3.0
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Matthew Nolf Assignee: Jeffrey Yemin
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

running mongo:3.6-stretch in docker
go version go1.11.5 darwin/amd64
mongo driver github.com/mongodb/mongo-go-driver v0.3.0


Issue Links:
Duplicate
is duplicated by GODRIVER-876 findOne can fail to return Closed

 Description   

When expecting a single result, and calling FindOne(), an empty SingleResult is sometimes* returned instead - which Decode() handles by returning ErrNoDocuments. 

*sometimes seems to be approx. 10% of the time

Find below an example, with steps to reproduce:

Setup collection & insertOne

db.mycollection.insertOne({"name":"some-name", "address": {"first_line":"expected_first_line","second_line":"expected_second_line"}, "age": 10 } )

Structs in code:

 

type Address struct {
   FirstLine  string `bson:"first_line"`
   SecondLine string `bson:"second_line"`
}
 
type User struct {
   ID      primitive.ObjectID `bson:"_id,omitempty"`
   Name    string             `bson:"name"`
   Address Address            `bson:"address"`
   Age     int                `bson:"age"`
}

 

 

Call findOne(), and Decode() in code:

 

func main() {
   mongoClient, err := mongo.NewClient(mongoURI)
   if err != nil {
      return
   }
 
   err = mongoClient.Connect(context.Background())
   if err != nil {
      return
   }
 
 
   _, err = GetUser(mongoClient)
   if err != nil {
      if err == mongo.ErrNoDocuments {
         fmt.Println(err)
      }
   }
}
 
func GetUser(mongoClient *mongo.Client) (*User, error) {
   var actualUser User
 
   singleResult := mongoClient.
      Database(mongoDB).
      Collection(mongoCollection).
      FindOne(
         context.Background(),
         bson.M{
            "address": bson.M{
               "first_line":  expectedFirstLine,
               "second_line": expectedSecondLine},
         })
 
   if singleResult.Err() != nil {
      return nil, singleResult.Err()
   }
 
   err := singleResult.Decode(&actualUser)
   return &actualUser, err
}

 

As stated previously, in most cases, this will successfully decode the document into the actualUser struct; however sometimes the decode will instead return an ErrNoDocument.

 

 



 Comments   
Comment by Jeffrey Yemin [ 15/Mar/19 ]

Hi mjb

It's because in most cases in MongoDB the order of keys does not matter, so we want to take advantage of the concise syntax of Go maps where we can. For example, in the above use case it doesn't matter the order of "address.first_line" and "address.second_line". Barring your usage above, which is uncommon, the only two other cases I can think of where order is significant are

  • generic commands: where the first key has to be the command name
  • indexes specifications: where the order determines the structure of the index

In those case, applications should use bson.D

Comment by Matthew Boyle [ 15/Mar/19 ]

Hi Jeff,

Also interested in this. Could you give a concrete use case for bson.M? I cannot think of one and am wondering why non-deterministic code is included in the API.

 

Thanks,

Matt

Comment by Jeffrey Yemin [ 14/Mar/19 ]

It's because Go maps are intentionally non-deterministic. See https://blog.golang.org/go-maps-in-action, the last section on Iteration Order.

If you were to use bson.D instead of bson.M, you'd see deterministic behavior.

Comment by Matthew Nolf [ 14/Mar/19 ]

Hi Jeff,

Modifying the filter as suggested has fixed the issue I was having, and now consistently finds and decodes a document.

Could you clarify why in the previous example does this lead to inconsistent behaviour - sometimes returning the document, sometimes ErrNoDocuments?

Thanks very much!

Comment by Jeffrey Yemin [ 14/Mar/19 ]

Hi matthewnolf,

I think your query is incorrect. When you match on a BSON document like you're doing, field order matters, and extra fields matter. Instead, you should be doing something like:

   bson.M{
      "address.first_line": expectedFirstLine,
      "address.second_line: expectedSecondLine}
   }

Generated at Thu Feb 08 08:35:09 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.