Go Mongo Update NonZero values only - mongodb

How to update the document with non zero values only. As example I didn't received any value for status and Struct has only two values to be updated. So it should only update those 2 values and skip zero/null values. But as given below it's updating it to zero/null/""
type Product struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Status int `json:"status" bson:"status"`
DisplayName string `json:"displayName" bson:"display_name"`
Text string `json:"text" bson:"text"`
}
I have tried the following up it's overriding the status value to 0 if no value is passed for it.
opts := options.Update().SetUpsert(false)
filter := bson.D{primitive.E{Key: "_id", Value: product.ID}}
update := bson.D{{"$set", bson.D{{"status", product.Status}, bson.D{{"text",product.Text}, {"display_name", product.DisplayName}}}}
_, err := db.Collection("product").UpdateOne(context.TODO(), filter, update, opts)
How to achieve this cleanly without ifs. For any struct in Service.

First, your update document should not contain an embedded document if Product is the Go struct that models your documents. It should be:
update := bson.D{
{"$set", bson.D{
{"status", product.Status},
{"text", product.Text},
{"display_name", product.DisplayName},
}},
}
Now on to your issue. You explicitly tell in the update document to set them to their zero value, so that's what MongoDB does.
If you don't want to set zero values, don't add them to the update document. Build your update document like this:
setDoc := bson.D{}
if product.Status != 0 {
setDoc = append(setDoc, bson.E{"status", product.Status})
}
if product.Text != "" {
setDoc = append(setDoc, bson.E{"text", product.Text})
}
if product.DisplayName != "" {
setDoc = append(setDoc, bson.E{"display_name", product.DisplayName})
}
update := bson.D{{"$set", setDoc}}
Note that you can achieve the same if you use the ,omitempty BSON tag option and use / pass a Product struct value for the setDoc:
type Product struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Status int `json:"status" bson:"status,omitempty"`
DisplayName string `json:"displayName" bson:"display_name,omitempty"`
Text string `json:"text" bson:"text,omitempty"`
}
And then simply:
update := bson.D{{"$set", product}}

Related

Create nested struct based on url query parameters

My goal is to get some filtered records from database. Filtration is based on a struct which depends on another struct:
type Group struct {
ID primitive.ObjectID
Name string
}
type Role struct {
ID primitive.ObjectID
Name string
Description string
Groups []*group.Group
}
I create an object of Role struct from URL query parameters:
var roleWP Role
if r.URL.Query().Has("name") {
name := r.URL.Query().Get("name")
roleWP.Name = name
}
if r.URL.Query().Has("description") {
description := r.URL.Query().Get("description")
roleWP.Description = description
}
if r.URL.Query().Has("groups") {
//How would look groups parameter?
}
Filling name and description fields of Role struct is pretty simple. The whole url would be: myhost/roles?name=rolename&description=roledescription
But how would look url if I want to pass data for Group struct? Is it possible to pass data as a json object in query parameter? Also, I want to mention that groups field in Role is an array. My ideal dummy url would look like: myhost/roles?name=rolename&description=roledescription&groups={name:groupname1}&groups={name:groupname2}
Loop through the groups, split on :, create group and append to slice:
roleWP := Role{
Name: r.FormValue("name"),
Description: r.FormValue("description"),
}
for _, g := range r.Form["groups"] {
g = strings.TrimPrefix(g, "{")
g = strings.TrimSuffix(g, "}")
i := strings.Index(g, ":")
if i < 0 {
// handle error
}
roleWP.Groups = append(roleWP.Groups, &Group{g[:i], g[i+1:]})
}
Here's how to use JSON instead of OP's ideal format:
roleWP := Role{
Name: r.FormValue("name"),
Description: r.FormValue("description"),
}
for _, s := range r.Form["groups"] {
var g Group
err := json.Unmarshal([]byte(s), &v)
if err != nil {
// handle error
}
roleWP.Groups = append(roleWP.Groups, &g)
}

bson.M {} deepequal does not seem to hande int32

I have a function for comparing two structs and making a bson document as input to mongodb updateOne()
Example struct format
type event struct {
...
Name string
StartTime int32
...
}
Diff function, please ignore that I have not checked for no difference yet.
func diffEvent(e event, u event) (bson.M, error) {
newValues := bson.M{}
if e.Name != u.Name {
newValues["name"] = u.Name
}
if e.StartTime != u.StartTime {
newValues["starttime"] = u.StartTime
}
...
return bson.M{"$set": newValues}, nil
}
Then I generated a test function like so:
func Test_diffEvent(t *testing.T) {
type args struct {
e event
u event
}
tests := []struct {
name string
args args
want bson.M
wantErr bool
}{
{
name: "update startime",
args: args{
e: event{StartTime: 1},
u: event{StartTime: 2},
},
want: bson.M{"$set": bson.M{"starttime": 2}},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := diffEvent(tt.args.e, tt.args.u)
if (err != nil) != tt.wantErr {
t.Errorf("diffEvent() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("diffEvent() = %v, want %v", got, tt.want)
}
})
}
}
This fails with a
--- FAIL: Test_diffEvent/update_startime (0.00s)
models_test.go:582: diffEvent() = map[$set:map[starttime:2]], want map[$set:map[starttime:2]]
For me this seem to be the same. I have played around with this and bool fields, string fields, enum fields, and fields as struct or fields as arrays of structs seems to work fine with deepequal, but it gives an error for int32 fields.
As a go beginner; what am I missing here? I would assume that if bool/string works then int32 would too.
This:
bson.M{"starttime": 2}
Sets the "starttime" key to the value of the literal 2. 2 is an untyped integer constant, and since no type is provided, its default type will be used which is int.
And 2 values stored in interface values are only equal if the dynamic value stored in them have identical type and value. So a value 2 with int type cannot be equal to a value 2 of type int32.
Use explicit type to tell you want to specify a value of int32 type:
bson.M{"starttime": int32(2)}

Nested field update using golang struct in mongoDB

I am facing a issue with update document using golang mongo driver.
Scenario: I want to update a field that is nested in a struct. For ex: StructOuter -> structInner -> field1, field2, field3. Now if I want to update the field3 and I have the corresponding value as another struct, how can i go ahead by just updating this field alone. I tried with code below but it updates the whole structInner leaving only field3:
conv, _ := bson.Marshal(prod)
bson.Unmarshal(conv, &updateFields)
update := bson.M{
"$set": updateFields,
}
model.SetUpdate(update).
Sample JSON:
{
"field_one": "value",
"data": {
"field_two": [
"data1",
"data2"
],
"field_three": "check",
"field_four": "abc",
"field_five": "work",
}
}
I want to avoid hard coded field query for updating.
Just want to know if this is supported, if yes can you help me with it and also point to some deep dive links on this.
If you have control over the code, you could try creating methods on the struct. These methods can help you construct the fields path to perform partial update. For example, if you have the following structs:
type Outer struct {
Data Inner `bson:"data"`
}
type Inner struct {
FieldThree string `bson:"field_three"`
FieldFour string `bson:"field_four"`
}
You can try adding methods as below to construct update statements. These are returned in the dot-notation format.
func (o *Outer) SetFieldThree(value string) bson.E {
return bson.E{"data.field_three", value}
}
func (o *Outer) SetFieldFour(value string) bson.E {
return bson.E{"data.field_four", value}
}
To update, you can construct the statements like below:
x := Outer{}
var updateFields bson.D
updateFields = append(updateFields, x.SetFieldThree("updated"))
updateFields = append(updateFields, x.SetFieldFour("updated"))
statement := bson.D{{"$set", updateFields}}
result, err := collection.UpdateOne(ctx, bson.M{}, statement)

mongodb get items inserted in last 30 min ago

i looking to check if exist item added in last 30 min in golang with mongodb.
this is my type models:
type PayCoin struct {
ID bson.ObjectId `json:"id" bson:"_id"`
OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"`
PublicKey string `json:"public_key" bson:"public_key"`
PrivateKey string `json:"-" bson:"private_key"`
QrCode string `json:"qrcode" bson:"-"`
ExchangeRate uint64 `json:"exchange_rate" bson:"exchange_rate"`
DepositAmount float32 `json:"deposit_amount" bson:"deposit_amount"`
Received uint64 `json:"received" bson:"received"`
Completed bool `json:"-" bson:"completed"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
this is my current function :
func (s *Storage) CoinPayExistOperation(ownerID bson.ObjectId) (*models.PayCoin, error) {
collection := s.getCoinPay()
var lt models.PayCoin
timeFormat := "2006-01-02 15:04:05"
now := time.Now()
after := now.Add(-30*time.Minute)
nowFormated := after.Format(timeFormat)
err := collection.Find(bson.M{"owner_id": ownerID, "created_at": nowFormated}).One(&lt)
return &lt, err
}
i want to check if exist items in database added in last 30 min, my current code not return any item, and in database exist. How i can do this ?
You have two small things to fix here.
If you want to fetch various records you should change the word one by all
you are doing a filter where your data time is Greater than, for this, you have to use a comparison query operator $gt
here an example how your query should looks like
collection.Find(bson.M{"owner_id": ownerID, "created_at": bson.M{"$gt": nowFormated}}).All(&lt)
Note: as this will return multiple records, remember change the lt by an slice.

MGO - empty results returned from Mongo that has results

I have a GOLANG struct as follows:
type OrgWhoAmI struct {
FriendlyName string `json:"friendlyName"`
RedemptionCode string `json:"redemptionCode"`
StartUrls []StartUrl `json:"startUrls"`
Status string `json:"status"`
Children []OrgChildren `json:"childrenReemptionCodes"`
}
type StartUrl struct {
DisplayName string `json:"displayName"`
URL string `json:"url"`
}
type OrgChildren struct {
FriendlyName string `json:"childFriendlyName"`
RedemptionCode string `json:"childRedemptionCode"`
}
I've created and successfully inserted records into a MongoDB collection (as I can see the results by querying Mongo with the CLI mongo program) - but when I query with MGO as follows, I get nothing:
func main() {
session, sessionErr := mgo.Dial("localhost")
defer session.Close()
// Query All
collection := session.DB("OrgData").C("orgWhoAmI")
var results []OrgWhoAmI
err = collection.Find(bson.M{}).All(&results)
if err != nil {
panic(err)
}
for _, res := range results {
fmt.Printf("Result: %s|%s\n", res.FriendlyName, res.RedemptionCode)
}
}
The results printed are:
Result: |
Result: |
Result: |
Result: |
If I ask for the count for records, I get the correct number, but all values for all fields are blank. Not sure what I'm missing here.
If you aren't creating them in go, it's probably not serializing the key names for you properly. The default for bson is to lowercase the keys, so you need to specify it if you want something else. Also note that you have a typo in OrgWhoAmI for json:"childrenReemptionCodes" (should be Redemption, I'm guessing). You can specify both bson and json separately if you want them to be different.
type OrgWhoAmI struct {
FriendlyName string `bson:"friendlyName" json:"friendlyName"`
RedemptionCode string `bson:"redemptionCode" json:"redemptionCode"`
StartUrls []StartUrl `bson:"startUrls" json:"startUrls"`
Status string `bson:"status" json:"status"`
Children []OrgChildren `bson:"childrenRedemptionCodes" json:"childrenRedemptionCodes"`
}
type StartUrl struct {
DisplayName string `bson:"displayName" json:"displayName"`
URL string `bson:"url" json:"url"`
}
type OrgChildren struct {
FriendlyName string `bson:"childFriendlyName" json:"childFriendlyName"`
RedemptionCode string `bson:"childRedemptionCode" json:"childRedemptionCode"`
}