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 '$').
Related
I have this kind of query to run. Running this query manually return OK with upsertedCount = 1 when the key not exist
db.test.update({Key: 'random-id'}, {$inc: {Version: 1}},{upsert: true})
I try to convert it to mongodb golang version below
client, _ := mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://localhost:27017/"))
coll := client.Database("test").Collection("test")
filter := bson.D{bson.E{"Key", "random-id"}}
docs := bson.D{bson.E{"$inc", bson.E{"Version", 1}}}
upsert := true
result, err := coll.UpdateOne(
context.TODO(),
filter, docs,
&options.UpdateOptions{Upsert: &upsert})
if err != nil {
panic(err)
}
fmt.Print(result)
Unfortunately, this query returns error
multiple write errors: [{write errors: [{Cannot increment with non-numeric argument: {key: "Version"}}]}, {<nil>}]
Why can't it works? It seems that the driver trying to increment it without sending it to mongo
Edit:
change the schema case to Upper, to follow the go code
Use simpler version of code
The problem is with your docs value. It's supposed to be a valid document. bson.D is a valid document if all its elements are valid. It has an element with $inc key, which requires its value to be a valid document too. bson.E is not a document, it's an element of a document.
Change your docs to this:
docs := bson.D{bson.E{"$inc", bson.D{bson.E{"Version", 1}}}}
And it will work.
If order is not important (it isn't in your case), alternatively you may use bson.M to model your filter and docs like this:
filter := bson.M{"Key": "random-id"}
docs := bson.M{
"$inc": bson.M{"Version": 1},
}
This is much simpler, clearer and more intuitive.
Also note that there are builders for the options. Obtain your options.UpdateOptions value safely, idiomatically and clearly like this:
options.Update().SetUpsert(true)
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.
How to query find using golang mongodb driver?
I try this one :
db.Collection("products").Find(nil, bson.M{}, &options.FindOptions{Sort: "-price"})
But I got this error :
cannot transform type string to a BSON Document: WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
I don't know what to pass to Sort variable becuase it is an interface{}.
try the below code
findOptions := options.Find()
// Sort by `price` field descending
findOptions.SetSort(bson.D{{"price", -1}})
db.Collection("products").Find(nil, bson.D{}, findOptions)
I couldn't pass bson.D to options(It caused error).
but this code worked for me:
queryOptions := options.FindOneOptions{}
queryOptions.SetSort(bson.D{{"priority", -1}, {"last_error_time", 1}})
sResult := collection.FindOne(context.TODO(), queryFilter, &queryOptions)
A few notes I've come across trying to solve a related problem:
If trying to sort by multiple fields be sure to use bson.D rather
than bson.M because bson.M doesn't preserve order.
If trying to programmatically build up multiple sort fields, try
appending bson.E to a bson.D
As dassum did, pass bson.M{} for an empty filter as recommended by
the mongo documentation
Applied:
sort := bson.D{}
for _, example := examples {
sort = append(sort, bson.E{example, 1})
}
findOptions.SetSort(sort)
db.Collection("products").Find(nil, bson.D{}, findOptions)
I'm Using Mongo Go Adapter: github.com/mongodb/mongo-go-driver/
I'm trying different patterns but none of them working for me.
//ref struct
type userbase struct {
Name string `bosn:"Name"`
Coins int `bson:"Coins"`
}
//ref code, it's updating _id, but not updating a value
filter := bson.M{"name": "Dinamis"}
update := bson.D{{"$inc", bson.M{"Coins": 1}}}
db := Client.Database("Nothing").Collection("dataUser")
db.UpdateOne(context.Background(), filter, update)
//update filters that i also used
update := bson.D{{"$inc", bson.D{{"Coins", 1},}},}
//simple ways was tryed also
update := &userbase{name, amount} //should i try *userbase{} ?
//Also i'm tryed
ReplaceOne()
FindOneAndReplace()
FindOneAndUpdate()
it's hard to dig deeper b-cuz of luck of actual documentation: https://docs.mongodb.com/ecosystem/drivers/go/
Thanks #Wan Bachtiar for answering this in official MongoDB-go-adapter group.
By default queries in MongoDB is case sensitive on the field name. In
your struct you defined the field to be Name, but in your filter to
specify name. This would result in no documents matching the query
predicates for the the update operation. For example, if you have a
document as below:
{ "_id": ObjectId("..."), "Name": "Dinamis", "Coins": 1 }
You can perform an update to increment the number of Coins using below
snippet:
collection := client.Database("Nothing").Collection("dataUser")
filter := bson.M{"Name": "Dinamis"}
update := bson.D{{"$inc", bson.M{"Coins": 1}}}
result, err := collection.UpdateOne(context.TODO(), filter, update)
Also, note that you have a typo on the bson tag in your struct. It’s
supposed to be bson:"Name" not bosn:"Name". You may find Query
Documents as a useful reference (Select the Go tab to show examples in
Go)
Regards, Wan.
I'm trying to execute a query with "sort" and "limit". With mgo you could do Find(nil).Sort(“-when”).Limit(10) but the new, official mongo driver has no such methods. How can I sort and "limit" with the new driver?
In the current version mongo-go-driver v1.0.3, the options are simplified. For example to perform find, sort and limit:
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
options := options.Find()
// Sort by `_id` field descending
options.SetSort(bson.D{{"_id", -1}})
// Limit by 10 documents only
options.SetLimit(10)
cursor, err := collection.Find(context.Background(), bson.D{}, options)
See more available options on godoc.org/go.mongodb.org/mongo-driver/mongo/options. Especially FindOptions for all possible options for Find().
The official driver is not straightforward as mgo. You can do sort and limit using the findopt.Limit and findopt.Sort.
You can see examples from the official repository.
https://github.com/mongodb/mongo-go-driver/blob/5fea1444e52844a15513c0d9490327b2bd89ed7c/mongo/crud_spec_test.go#L364
You can use
findOptions := options.Find()
findOptions.SetLimit(2)
findOptions.SetSkip(2)
...
cursor, err := collection.Find(context.Background(), bson.M{}, findOptions)
resource at https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial
you need import "github.com/mongodb/mongo-go-driver/options" package to build a findOptions.
import github.com/mongodb/mongo-go-driver/options
findOptions := options.Find() // build a `findOptions`
findOptions.SetSort(map[string]int{"when": -1}) // reverse order by `when`
findOptions.SetSkip(0) // skip whatever you want, like `offset` clause in mysql
findOptions.SetLimit(10) // like `limit` clause in mysql
// apply findOptions
cur, err := collection.Find(context.TODO(), bson.D{}, findOptions)
// resolve err
for cur.Next(context.TODO()) {
// call cur.Decode()
}
The sort-option apparently requires you to add a map[string]interface{} where you can specify a field as key and a sortOrder as value (where 1 means ascending and -1 means descending) as following:
sortMap := make(map[string]interface{})
sortMap["version"] = 1
opt := findopt.Sort(sortMap)
As far as I can see this means that you are only able to properly sort results by one sortField because keys in a go map are stored in a random order.
ONE LINE OPTION
I know there is already a lot of answers but you can do it as one line (if you need it for any case of yours)
// From the Doc
// func (f *FindOptions) SetSort(sort interface{}) *FindOptions
cursor, err := collection.Find(context.Background(), bson.M{}, options.Find().SetSort(map[string]int{"when": -1}).SetLimit(10))
SetSort() and the others currently returns the parent pointer itself