Upsert not working when using UpdateOne with the MongoDB Golang driver - mongodb

For reference, I have this struct:
type OpenOrderCleaned struct {
OrderID string `json:"orderId" bson:"orderId"`
DateTimeOrderPlaced time.Time `json:"dateTimeOrderPlaced" bson:"dateTimeOrderPlaced"`
OrderItems []struct {
OrderItemID string `json:"orderItemId" bson:"orderItemId"`
Ean string `json:"ean" bson:"ean"`
CancelRequest bool `json:"cancelRequest" bson:"cancelRequest"`
Quantity int `json:"quantity" bson:"quantity"`
} `json:"orderItems" bson:"orderItems"`
}
I get an API response with multiple JSON instances that I want to save in MongoDB, so I use a for loop. I want to check if a document already exists in the database, by using the orderId field, which is unique for every JSON instance. I thought UpdateOne was a good option for this because it has upsert. So if the orderId does not exist, the full document should be generated and stored in the database.
for _, OpenOrderCleaned := range o.Orders {
c := auth.GetClient()
collection := c.Database("goprac").Collection(x)
filter := bson.M{"orderId": bson.M{"$eq": OpenOrderCleaned.OrderID}}
update := bson.M{
"$set": bson.M{
"orderId": OpenOrderCleaned.OrderID,
"dateTimeOrderPlaced": OpenOrderCleaned.DateTimeOrderPlaced,
"orderItems": OpenOrderCleaned.OrderItems,
},
}
ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
result, err := collection.UpdateOne(ctx, filter, update)
if err != nil {
fmt.Println("UpdateOne() result ERROR:", err)
os.Exit(1)
} else {
fmt.Println("UpdateOne() result:", result)
fmt.Println("UpdateOne() result TYPE:", reflect.TypeOf(result))
fmt.Println("UpdateOne() result MatchedCount:", result.MatchedCount)
fmt.Println("UpdateOne() result ModifiedCount:", result.ModifiedCount)
fmt.Println("UpdateOne() result UpsertedCount:", result.UpsertedCount)
fmt.Println("UpdateOne() result UpsertedID:", result.UpsertedID)
}
}
But right now the Upsert is not working. When I put a manual document into MongoDB and running the program, it is updating though. So why are new instances not made in the database? Do I have to state somewhere upsert=True or something? Or is maybe the mapping of "orderItems": OpenOrderCleaned.OrderItems not correct?
Any help is appreciated.

You are calling update, not upsert. You have to pass the correct options to UpdateOne to upsert. This is from the mongo driver examples:
opts := options.Update().SetUpsert(true)
filter := bson.D{{"_id", id}}
update := bson.D{{"$set", bson.D{{"email", "newemail#example.com"}}}}
result, err := coll.UpdateOne(context.TODO(), filter, update, opts)
You are missing the opts.

Related

In Golang Driver for Mongo DB "Collection.Watch" doesn't work with Aggregation Pipeline

I am trying to create a mongodb "Change Stream" on collection and listen to changes on a specific document.
matchID := bson.D{
{"$match", bson.M{"_id": tid}},
}
stream, err := collection.Watch(ctx, mongo.Pipeline{matchID}, options.ChangeStream().SetFullDocument(options.UpdateLookup))
Then listen on the stream with a for loop.
for stream.Next(ctx) {
log.Println("stream.Next")
if err = stream.Decode(&event); err != nil {
log.Printf("error decoding: %s", err)
}
log.Printf("change event: %v", event)
publisher.NotifyEvent(event["fullDocument"])
}
The whole setup works when I remove the pipeline argument, and I start getting data in loop. But when I add the pipeline filter, it stops working.
The filter I used was wrong, since the pipeline filters the event stream rather that the actual document.
I had the change the filter to
matchID := bson.D{
{"$match", bson.M{"fullDocument._id": tid}},
}
instead of
matchID := bson.D{
{"$match", bson.M{"_id": tid}},
}
the additional keyword "fullDocument" is necessary because the event stream holds the updated document under the field "fullDocument"
More details can be found in this article.
https://www.mongodb.com/basics/change-streams

How to find the data in mongodb using golang?

In this code, I am trying to find the data from MongoDB based on the userID. But whenever I run this code, it gives me an error that mongo: no documents in result.
I wrote db.dataStored.findOne({_id: ObjectId("60a60718503219dfd740f9fe")}) in mongo shell also but it gave me a null result. Although the data is present in the MongoDB database. Here is the picture to see.
userID := "60a60718503219dfd740f9fe"
var result Trainer
collection := client.Database("PMS").Collection("dataStored")
err = collection.FindOne(context.TODO(), bson.M{"_id": userID}).Decode(&result)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found a single document: %+v\n", result)
You are searching for a string _id, but _id is an ObjectId.
objectId, err:=primitive.ObjectIDFromHex(userID)
collection.FindOne(context.TODO(), bson.M{"_id": objectId}).Decode(&result)

How to use the element query operator ($exists) for a nested document?

I am trying to find whether a field exist in a nested document using Go.
Currently, the document looks like this.
I am trying to see if the item id field- 5f15d53f205c36fa8b022089 exist in the shoppingCart for this user. Using the Mongo Compass, I am able to successfully query the right document using this filter command.
{"_id": ObjectId('5f19a8950268ef67ce0c5124'), "shoppingCart.items.5f15d53f205c36fa8b022089": {$exists: true}}
I tried to do use the same syntax in Go, but I still get nothing back from the results.
cursor, err := customersCollection.Find(
ctx,
bson.M{"_id": customerID, "shoppingCart.items.5f15d53f205c36fa8b022089": bson.M{"$exists": true}},
options.Find().SetProjection(bson.M{"_id": 1}),
)
// This is how I am reading the results from the cursor. When
// printing the results, I get an empty array.
var results []bson.M
if err = cursor.All(ctx, &results); err != nil {
customerLog.Errorf(logger.LogError(logger.LogInfo()), err)
}
fmt.Printf("Products Result: %v\n", results)
I am unable to find any documentation for the proper way to include element query operators in the filter parameter.
This is the Mongo driver I am using, https://godoc.org/go.mongodb.org/mongo-driver/mongo
Edit 1.0
Things that I have tried:
Used bson.D instead of bson.M. Updated code segment.
cursor, err := customersCollection.Find(
ctx,
bson.D{{"_id", customerID}, {"shoppingCart.items.5f15d53f205c36fa8b022089", bson.D{{"$exists", true}}}},
options.Find().SetProjection(bson.M{"_id": 1}),
)
if you use go.mongodb.org/mongo-driver/bson package, you can do the following:
query := bson.M{}
query["_id"] = bson.M{
"$exists": true,
}
If you want, you can also do it cleaner, by using a wrapper:
type FilterQuery bson.M
func NewFilterQuery() FilterQuery {
return FilterQuery(bson.M{})
}
func (q FilterQuery) SetIdExists(exist bool) FilterQuery {
q["_id"] = bson.M{
"$exists": exist,
}
return q
}
And then from your code, you can do something like
query := NewFilterQuery()
query.SetIdExist(true)
..
cursor, err := customersCollection.Find(
ctx,
query,
)
...
Try the following:
bson.D{
{"$exists", true},
}
(I searched the driver's source for $exist.)

What is return type of findOneAndUpdate(updateQuery, updateSet, returnFields), and how to get the values that are returned?

I want to update some fields in DB and also want to it to return some fields, can you suggest how to retrieve the return fields?
so i am using here,
returnFields := map[string]interface{}{"order_id":1}
data := FindAndUpdateVerticalsOffers(updateQuery, updateFields, returnFields)
How to get order_id from "data":
func FindAndUpdateVerticalsOffers(updateQuery map[string]interface{}, updateFields interface{}, returnFields map[string]interface{}) map[string]interface{} {
session := db.GetSession()
defer session.Close()
collection := session.DB("").C(VerticalsOffersName)
updateSet := bson.M{"$set": updateFields}
return collection.FindOneAndUpdate(updateQuery, updateSet, returnFields)
}
I want to update some fields in DB and also want to it to return some fields,
If you're using mongo-go-driver (currently v1.1), you can utilise FindOneAndUpdate() which finds a single document and updates it, returning either the original or the updated.
The method accepts argument for FindOneAndUpdateOptions, which supports projection. For example:
collection := client.Database("dbName").Collection("collName")
// Sets projection (or return fields)
findUpdateOptions := options.FindOneAndUpdateOptions{}
findUpdateOptions.SetProjection(bson.M{"order_id": 1})
result := collection.FindOneAndUpdate(context.TODO(),
bson.M{"foo":1},
bson.M{"$set": bson.M{"bar":1}},
&findUpdateOptions)
doc := bson.M{}
err = result.Decode(&doc)
The above query will match a document where field foo is 1, update field bar to 1, and return only order_id as the result. Note that by default the _id field is also returned. You can suppress the _id field from being projected by setting it to 0.
Please note that the return type of FindOneAndUpdate is a SingleResult object, which represents a single document returned from an operation. If the operation returned an error, the Err method of SingleResult will return that error.

Update/Replace mongodb document using struct & mongodb/mongo-go-driver

I am trying to update/replace a mongodb document using a struct but i keep on getting err: update document must contain key beginning with '$'
collection := r.client.Database(database).Collection(greetingCollection)
payment.MongoID = objectid.New()
filter := bson.NewDocument(bson.EC.String("id", payment.ID))
_, err := collection.UpdateOne(ctx, filter, payment)
return err
I believe the accepted answer did not work for me because I am using the go.mongodb.org/mongo-driver package. With this package, the syntax is even simpler:
update := bson.M{
"$set": yourDocument,
}
collection.UpdateOne(ctx, filter, update)
You should provide an update statement instead of a document as third parameter to the Collection.UpdateOne method. For example:
update := bson.NewDocument(
bson.EC.SubDocumentFromElements(
"$set",
bson.EC.Double("pi", 3.14159),
),
)
collection.UpdateOne(ctx, filter, update)
See more on the available update operators in the MongoDB docs (the keys begin with '$').