[GODRIVER-2879] BulkWrite: Performing an update on the path '_id' Created: 26/Jun/23  Updated: 27/Oct/23  Resolved: 29/Jun/23

Status: Closed
Project: Go Driver
Component/s: Bulk API
Affects Version/s: 1.12.0
Fix Version/s: None

Type: Bug Priority: Minor - P4
Reporter: Artem Yadelskyi Assignee: Qingyang Hu
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

In go.mod:

go.mongodb.org/mongo-driver v1.12.0 

Example code:

type Model struct {
    ID   uint64 `bson:"_id"`
    Data string `bson:"data"`
}
 
func UpdateMany(ctx context.Context, coll *mongo.Collection, models []Model) error {
    update := make([]mongo.WriteModel, 0, len(models))
    for _, model := range models {
       update = append(update,
          mongo.NewUpdateOneModel().
             SetFilter(bson.D{{"_id", model.ID}}).
             SetUpdate(bson.D{{"$set", model}}),
       )
    }
    _, err := coll.BulkWrite(ctx, update)
    return err
} 

Error when calling UpdateMany:

bulk write exception: write errors: [Performing an update on the path '_id' would modify the immutable field '_id'] 

If running the same query from mongosh there will be no error, also if ID field will have complex type (for example struct with 2 fields) - also no error



 Comments   
Comment by Qingyang Hu [ 29/Jun/23 ]

Hello mymmrac@gmail.com,

Handling "int" and "long" identically for the "_id" field in the filter is a server behavior. Please don't hesitate to file a server ticket if you suggest a behavior change or a docs ticket if you prefer a doc improvement.

I'm closing this ticket since your Go driver issue has been resolved. Please feel free to reopen it or create a new one if necessary.

Again, I appreciate your support on MongoDB and the Go driver.

Comment by Artem Yadelskyi [ 29/Jun/23 ]

Thanks for deeper explanation, we fixed the issue for us. It's just a bit confusing that conversion is fully silent and doesn't work everywhere, but maybe for simplicity it's okay

Comment by Qingyang Hu [ 29/Jun/23 ]

mymmrac@gmail.com, I appreciate your detailed report!

32-bit integers and 64-bit integers (long) are consistently interchangeable for the "_id" field in the filter for both "find" and "update". (But, for your reference, the driver silently converts the "_id" from an unsigned value to a signed value in your case.)

However, the "model" in the following line:

SetUpdate(bson.D{{"$set", model}}),

includes an "_id" of uint64, which does not match the original "_id" type. Thus, the server interprets it as an update on the "_id" field. Therefore, you got the error message complaining about:

Performing an update on the path '_id' would modify the immutable field '_id'

I hope this answers your question. Please let me know if you need more help.

 

Comment by Artem Yadelskyi [ 28/Jun/23 ]

qingyang.hu@mongodb.com is it expected that integer and long considered interchangeable only when filtering but not when updating documents?

Event thought you can't create 2 documents with ID 1 and Long("1")

Comment by Artem Yadelskyi [ 28/Jun/23 ]

Found the root cause, and inconsistency (in my mind). The actual error was caused by type inconsistency: in DB we had _id as plain number (int), but in model it was long (uint64).

Here is what is inconsistent: you can filter documents by any value (int or uint64), but update only by int, basically for find int and uint64 is identical, but for update it's different types

Comment by Qingyang Hu [ 27/Jun/23 ]

Does your coworker also use a Mongo 6 in Docker on WSL2?

Comment by Artem Yadelskyi [ 27/Jun/23 ]

Hello qingyang.hu@mongodb.com , yep sure, here is full example:

package main
 
import (
    "context"
 
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
)
 
func main() {
    ctx := context.Background()
    conn, err := mongo.Connect(ctx)
    if err != nil {
       panic(err)
    }
 
    coll := conn.Database("test").Collection("coll")
 
    model := Model{
       ID:   1,
       Data: "init",
    }
    
    _, err = coll.InsertOne(ctx, model)
    if err != nil {
       panic(err)
    }
    
    model.Data = "update"
    err = UpdateMany(ctx, coll, []Model{model})
    if err != nil {
       panic(err) // Error here
    }
}
 
type Model struct {
    ID   uint64 `bson:"_id"`
    Data string `bson:"data"`
}
 
func UpdateMany(ctx context.Context, coll *mongo.Collection, models []Model) error {
    update := make([]mongo.WriteModel, 0, len(models))
    for _, model := range models {
       update = append(update,
          mongo.NewUpdateOneModel().
             SetFilter(bson.D{{"_id", model.ID}}).
             SetUpdate(bson.D{{"$set", model}}),
       )
    }
 
    _, err := coll.BulkWrite(ctx, update)
    return err
} 

Important note:

Unfortunately, this doesn't give an error on my machine (Mongo 6 in Docker on Arch Linux), but it reproduces on Atlas server and on my coworker's machine with Docker on WSL2, I am not sure if this is related or not

Comment by Qingyang Hu [ 27/Jun/23 ]

Hello mymmrac@gmail.com,

This error may be caused by a type inconsistency.

For example, if you are using the following code to insert:

coll.InsertOne(ctx, bson.D{{"_id", 1}, {"data", "x"}})

The "_id" constant will be parsed as an int instead of a uint64.

Can you please let us know more context for a better investigation?

Comment by PM Bot [ 26/Jun/23 ]

Hi mymmrac@gmail.com, thank you for reporting this issue! The team will look into it and get back to you soon.

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