I need to remove and get all elements of an array in mongodb. I found $pull and $pullAll but they require query condition, how to remove all elements without condition?
The code not work, elements still exist after the $pull:
var UserId = 123
type Event struct {
UserId uint64 `gorm:"uniqueIndex"`
Array [][]byte
}
func main() {
var DB_NAME = "test"
var ctx = context.Background()
client, _ := mongo.Connect(
ctx, options.Client().ApplyURI("mongodb://localhost"))
col := client.Database("test").Collection(`Test`)
{ // pull items
r := col.FindOneAndUpdate(ctx, bson.M{
`UserId`: UserId,
}, bson.M{
`$pull`: bson.M{
`Array`: nil,
},
}, options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.Before))
if r.Err() != nil {
panic(r.Err())
}
}
}
Edit:
I found $set': bson.M{"Array": [][]byte{}} does the job, is $pull capable of doing this? Which performance is better?
The $pull operator is a "top level" operator in update statements, so you simply have this the wrong way around:
r := col.FindOneAndUpdate(ctx, bson.M{bson.M{"$pull": bson.M{"UserId": bson.ObjectIdHex(UserId)}}
The order of update operators is always operator first, action second.
If there is no operator at the "top-level" keys, MongoDB interprets this as just a "plain object" to update and "replace" the matched document. Hence the error about the $ in the key name.
Related
Need some help to figure out why nested bson.M doesn’t work occasionally in FindOne().
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!
bson.M is a map, thus, the order of its elements are not guaranteed. Since you are comparing parents.0.chilren.0.name, which is a structure, the search criteria has to match exactly, with the order of fields in the criteria matching the order fields in the database. That's why when you use bson.M, it sometimes matches and sometimes not. When the search criteria matches the database field order, you get results, otherwise you don't.
That's the reason why the criteria with bson.D always matches, because it is based on a slice, so the order of fields are preserved.
The last search criteria compares individual fields, not a structure, so the order of fields are immeterial.
I'm trying to update a nested document in MongoDB based on certain conditions. The problem is that using arrayFilters on the same top-level field name is not allowed. Any solutions for solving this problem?
Error: Found multiple array filters with the same top-level field name x
filter := bson.D{primitive.E{Key: "_id", Value: e.EventId}}
arrayFilters := options.ArrayFilters{
Filters: []interface{}{
bson.M{"x._id": tt.TypeId},
bson.M{"x.typeAmountUsed": bson.M{"$lt": "$x.typeAmount"}}, // <-- multiple filters
},
}
upsert := true
opts := options.UpdateOptions{
ArrayFilters: &arrayFilters,
Upsert: &upsert,
}
update := bson.M{
"$inc": bson.D{{"eventTicketTypes.$[x].typeAmountUsed", 1}},
}
if _, err = events.UpdateOne(sessCtx, filter, update, &opts); err != nil {
return nil, err
}
To use multiple criteria in the same filter, combine them into a single filter.
See https://www.mongodb.com/docs/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations
However, arrayFilters do not support directly comparing 2 fields within the array element. You might be able to accomplish that with $map.
I'm trying to append fields to an object in my mongodb collection. So far this is what my document in MongoDB looks like.
My users can have multiple devices so I'm trying to append more fields to the devices object. I have tried to use $push for an array instead of an object but I didn't like how I would have to access the data later on when I retrieve it from the database.
So I started to use $set. $set works great because it gives me the format in which I want my data to save in the db but it will continually override my one key value pair in the devices object every time and I don't want that to happen.
db.go
func AddDeviceToProfile(uid string, deviceId int, deviceName string) {
client := ConnectClient()
col := client.Database(uid).Collection("User")
idString := strconv.Itoa(deviceId)
filter := bson.M{"uid": uid}
update := bson.M{
"$set": bson.M{"devices": bson.M{idString: deviceName}}, <------ Need to fix this
}
option := options.FindOneAndUpdate()
_ = col.FindOneAndUpdate(context.TODO(), filter, update, option)
log.Print("Device Added")
_ = client.Disconnect(context.TODO())
}
I have looked into using $addFields but I don't know if I was doing it correctly I just replaced $set above and added $addFields and I also tried it this way
update := bson.M{
"devices": bson.M{"$addFields": bson.M{idString: deviceName}},
}
What I want my document to look like
Instead of using $push or $addFields what you need is $set directive.
To specify a field in an embedded document, use dot notation.
For the document matching the criteria _id equal to 100, the following operation updates the make field in the devices document:
db.products.update(
{ _id: 100 },
{ $set: { "devices.make": "zzz" } }
)
Converting them to Go syntax is easy as well. What you are doing is correct. The following should work or might require a little bit of tweaking.
func AddDeviceToProfile(uid string, deviceId int, deviceName string) {
client := ConnectClient()
col := client.Database(uid).Collection("User")
idString := strconv.Itoa(deviceId)
filter := bson.M{"uid": uid}
update := bson.M{"$set": bson.M{"devices." + idString: deviceName}}
option := options.FindOneAndUpdate()
_ = col.FindOneAndUpdate(context.TODO(), filter, update, option)
log.Print("Device Added")
_ = client.Disconnect(context.TODO())
}
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.)
I have a created a basic nested structure:
type Komplex struct {
count int `bson:"count"`
text string `bson:"text"`
}
type Parent struct {
Count int `bson:"count"`
SubCount []Komplex `bson:"subcount"`
}
And i would like to use mongo's safe update feature to extend a collection:
session.DB("test").C("ints").Upsert(bson.M{"count": toWrite.Count},
bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": toWrite.SubCount}}})
This works when i replace the Komplex struct with a slice of just int. However when i try to follow a complex set addition with my Komplex struct like described at https://docs.mongodb.org/manual/reference/operator/update/push/#up._S_push nothing gets inserted.
How do i properly marshal my slice with structs to bson.M in this situation?
Maybe work only for this code :
session.DB("test").C("ints").Update(bson.M{"count": toWrite.Count},
bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": toWrite.SubCount}}}
)
Your Golang code should be like:
data := model.Komplex {
count: 12345,
text: "yourText",
}
selector := bson.M{"count": toWrite.Count}
changes := bson.M{"$addToSet": bson.M{"subcount": bson.M{"$each": []model.Komplex{data}}}}
err = c.Update(selector, changes)
You have shared the reference link which deals with a $push but you are using $addToSet in your query.
$push - appends the array even if the data is duplicate
$addToSet- keeps only distinct values in the array
Also, I'd like to suggest you to include an _id field in the Parent struct.