How can I convert updateMany with ArrayFilters options mongodb query into Golang? - mongodb

I am facing issue to write golang code for mongodb query. I had tried to convert following mongodb query:
db.batches.updateMany(
{"batchedOrders.newOrderObject.orderDetails.trackingId" : "ORDER_JEET_2023_02_05_01"},
{ $set: { "batchedOrders.$[elem].newOrderObject.pickupDetails.note" : "Welcome" } },
{ arrayFilters: [ { "elem.newOrderObject.orderDetails.trackingId": "ORDER_JEET_2023_02_05_01" } ] }
)
Golang code:
Here b is interface{} and carrying following data:
{
"batchedOrders.$[elem].newOrderObject.dropoffDetails.recipientDetails.email": "das#rara.delivery",
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.height": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.length": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.unit": "cm",
"batchedOrders.$[elem].newOrderObject.orderDetails.dimensions.width": 0,
"batchedOrders.$[elem].newOrderObject.orderDetails.orderDeliveryDetails.sla.dropoff": 1675712794,
"batchedOrders.$[elem].newOrderObject.orderDetails.parcelSize": "Medium",
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.billableWeight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.volWeight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightDetails.weight": 5,
"batchedOrders.$[elem].newOrderObject.orderDetails.weightIndex": 1,
"batchedOrders.$[elem].newOrderObject.pickupDetails.pickupIncharge.name": "Mukesh"
}
Following is the golang code:
data, _ := bson.Marshal(b)
err = bson.Unmarshal([]byte(data), &doc)
arrayFilters := options.ArrayFilters{Filters: []interface{}{bson.D{
{Key: "elem.newOrderObject.orderDetails.trackingId", Value: trackingId},
}}}
opts := options.UpdateOptions{
ArrayFilters: &arrayFilters,
}
res, err := db.Collection(BATCH_COLLECTION_NAME).UpdateMany(ctx, bson.D{{Key: "batchedOrders.newOrderObject.orderDetails.trackingId", Value: trackingId}}, bson.M{"$set": doc}, &opts)
if err != nil {
log.Println(lg.Info(err))
return false, err
}
I am getting following error:
WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
I am trying to update multiple data in mongodb database. But getting error.

In order to successfully use the UpdateMany method, you should do something similar to this:
package main
import (
"context"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
ctx context.Context
cancel context.CancelFunc
)
type Message struct {
ID string `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
}
func main() {
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// set MongoDB connection
clientOptions := options.Client().ApplyURI("mongodb://root:root#localhost:27017")
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
panic(err)
}
defer mongoClient.Disconnect(ctx)
// select collection
collection := mongoClient.Database("demodb").Collection("myCollection")
defer collection.Drop(ctx)
fmt.Println("original documents")
// insert some random msg
if _, err := collection.InsertMany(ctx, []interface{}{
Message{"1", "John Doe"},
Message{"2", "Suzy Lee"},
Message{"3", "John Doe"},
}); err != nil {
panic(err)
}
// get records
var messagesTmp []bson.M
cursorTmp, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursorTmp.All(ctx, &messagesTmp); err != nil {
panic(err)
}
for _, v := range messagesTmp {
fmt.Println(v)
}
fmt.Println(strings.Repeat("#", 100))
// update
var updateRes *mongo.UpdateResult
if updateRes, err = collection.UpdateMany(ctx,
bson.M{"name": "John Doe"},
bson.D{
bson.E{
Key: "$set",
Value: bson.D{
bson.E{
Key: "name",
Value: "John Doe - Edited",
},
},
},
},
); err != nil {
panic(err)
}
fmt.Println("num docs updated:", updateRes.ModifiedCount)
// list all
fmt.Println(strings.Repeat("#", 100))
fmt.Println("new documents")
var messages []Message
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
if err := cursor.All(ctx, &messages); err != nil {
panic(err)
}
for _, v := range messages {
fmt.Println(v)
}
}
Here, the relevant part is the // update section. First, we build the filter criteria we're going to use to fetch the documents to update. Then, we specify to $set a field name to a particular value John Doe - Edited.
The rest of the code is used to build a trivial program to demonstrate that the actual change is working. Feel free to adapt my example code to your specs and let me know if works for you too!

Related

How to handle null value in golang?

User model
type UserExample struct {
Id primitive.ObjectID `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Location string `json:"location,omitempty"`
Title string `json:"title,omitempty"`
}
Update User
func UpdateUserExample() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.UserExample
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//Validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponseExample{
Status: http.StatusBadRequest,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error()},
})
}
update := bson.M{
"name": user.Name,
"location": user.Location,
"title": user.Title,
}
result, err := userCollectionExample.UpdateOne(ctx, bson.M{
"id": objId,
}, bson.M{
"$set": update,
})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponseExample{
Status: http.StatusInternalServerError,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error(),
}})
return
}
//Get Update UserExample Detail
var updateUser models.UserExample
if result.MatchedCount == 1 {
err := userCollectionExample.FindOne(ctx, bson.M{
"id": objId,
}).Decode(&updateUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponseExample{
Status: http.StatusInternalServerError,
Message: "Error",
Data: map[string]interface{}{
"data": err.Error(),
}})
return
}
}
c.JSON(http.StatusOK, responses.UserResponseExample{
Status: http.StatusOK,
Message: "Success",
Data: map[string]interface{}{
"data": updateUser,
},
})
}
}
i have try update data via postman, but if value == null will be delete from collection
In this case, i want Update Title of the User, before update all data already exist
Postman
{
"title": "User One"
}
its working to change title in collection. but, for other data (name and location)has gone
"data": {
"id": "63d2ac86aeb9d78d3d5daf21",
"title": "User One",
}
so, how to handle null value from request body?
i just want change title for this case
Usually, such partial updates are handled using a structure that looks like this:
type UserUpdateRequest struct {
Id primitive.ObjectId `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Location *string `json:"location,omitempty"`
Title *string `json:"title,omitempty"`
}
Note the pointers. This way, the API caller can send non-nil values for the field it wants to update. It can also use an empty string to set the field values to empty.
Then on the database side, you have to create an update statement:
updateFields:=bson.M{}
if request.Name!=nil {
updateFields["name"]=*request.Name
}
if request.Location!=nil {
updateFields["location"]=*request.Location
}
// etc.
update:=bson.M{"$set":updateFields}
Then use the update to update the database record.

query with aggregate in mongodb and go (match, lookup)

I need that when passing a search parameter I can obtain the subcategories with the categories, also obtain the total data of all the subcategories. I don't know if it can be done with bson.M. I leave my code.
This function is the one I use for categories, I used it for subcategories but adding $lookup to bson.M throws an error.
func SubcategoriesGet(c *fiber.Ctx) error {
categoryCollection := config.MI.DB.Collection("subcategories")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var categories []models.Category
filter := bson.M{}
findOptions := options.Find()
if s := c.Query("s"); s != "" {
filter = bson.M{
"$or": []bson.M{
{
"name": bson.M{
"$regex": primitive.Regex{
Pattern: s,
Options: "i",
},
},
},
// {
// "visible": bson.M{
// "$regex": primitive.Regex{
// Pattern: s,
// Options: "i",
// },
// },
// },
},
}
}
if visible := c.Query("visible"); visible == "1" {
filter = bson.M{"visible": true}
}
if visible := c.Query("visible"); visible == "0" {
filter = bson.M{"visible": false}
}
page, _ := strconv.Atoi(c.Query("page", "1"))
limitVal, _ := strconv.Atoi(c.Query("limit", "10"))
var limit int64 = int64(limitVal)
total, _ := categoryCollection.CountDocuments(ctx, filter)
findOptions.SetSort(bson.M{"_id": -1})
findOptions.SetSkip((int64(page) - 1) * limit)
findOptions.SetLimit(limit)
// findOptions.
cursor, err := categoryCollection.Find(ctx, filter, findOptions)
defer cursor.Close(ctx)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Users not found",
"error": err,
})
}
for cursor.Next(ctx) {
var category models.Category
cursor.Decode(&category)
categories = append(categories, category)
}
last := math.Ceil(float64(total / limit))
if last < 1 && total > 0 {
last = 1
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": categories,
"total": total,
"page": page,
"last_page": last,
"limit": limit,
})
}
and this is the code that advances, I only need to obtain the total number of documents and be able to do a search by subcategory name.
func SubcategoriesGet(c *fiber.Ctx) error {
subcategoryCollection := config.MI.DB.Collection("subcategories")
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
var subcategory []models.Subategory
// querys
page, _ := strconv.Atoi(c.Query("page", "1")) // page
limitVal, _ := strconv.Atoi(c.Query("limit", "10")) // limit
var limit int64 = int64(limitVal)
// matchState := bson.D{}
if s := c.Query("s"); s != "" {
// bson.D{{"$regexMatch", bson.D{{"input", "$name"}, {"regex", "/g/"}}}}
// matchState = bson.M{"$match": bson.M{"wordname": bson.M{"$in": []bson.RegEx{{"^how$", "i"}}}}}
}
// pipeline
lookupState := bson.D{{"$lookup", bson.D{{"from", "categories"}, {"localField", "idcategory"}, {"foreignField", "_id"}, {"as", "category"}}}}
unwindState := bson.D{{"$unwind", bson.D{{"path", "$category"}, {"preserveNullAndEmptyArrays", false}}}}
limitState := bson.D{{"$limit", limit}}
sortState := bson.D{{"$sort", bson.D{{"_id", -1}}}}
skipState := bson.D{{"$skip", (int64(page) - 1) * limit}}
// groupState := bson.D{{"$project", bson.D{{"_id", 1}, {"name", 1}, {"category.name", 1}}}}
// orState := bson.D{{"$or", bson.D{{"name", bson.D{{"$regex", primitive.Regex{Pattern: "gec", Options: "i"}}}}}}}
// groupState := bson.D{{"$group", bson.D{{"_id", "$_id"}}}}
// countState := bson.D{{"$count", "total"}}
cursor, err := subcategoryCollection.Aggregate(ctx, mongo.Pipeline{skipState, lookupState, unwindState, limitState, sortState})
if err != nil {
panic(err)
}
// cursorTotal, err := subcategoryCollection.Aggregate(ctx, mongo.Pipeline{lookupState, unwindState, limitState, sortState})
// if err != nil {
// panic(err)
// }
// var showData []bson.M
// if err = cursor.Decode(); err != nil {
// panic(err)
// }
for cursor.Next(ctx) {
var subc models.Subategory
cursor.Decode(&subc)
subcategory = append(subcategory, subc)
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"data": subcategory,
// "total": ,
"page": page,
// "last_page": last,
"limit": limit,
})
}
I did tests with aggregate ($lookup, $skip, $limit, $count) etc. I still need to do a LIKE in SQL search and get the total number of documents

MongoDB controlled transaction abort

I worked with mongo driver for golang, but possibly that question is actual for other implementations.
Do mongo driver always abort transaction in case errors? Can I prevent implicit abort for transactions?
For example, for such code, I always get err2 = (NoSuchTransaction) Transaction 7 has been aborted. if err1!= nil.
client := ir.Source.Client()
session, err := client.StartSession()
if err != nil {
return err
}
if err := session.StartTransaction(); err != nil {
return err
}
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
_, err1 := ir.Source.Collection(collectionName).UpdateOne(sc,
bson.D{{Key: "_id", Value: bid}},
bson.M{
"$set": bson.D{{Key: "name", Value: "Name"}},
},
)
if err1 != nil {
log.Println(err1) // I don`t want abort here
}
_, err2 = ir.Source.Collection("collectionName").UpdateOne(sc,
bson.D{{Key: "_id", Value: bid}},
bson.M{
"$set": bson.D{{Key: "name", Value: "Name"}},
},
)
if err2 != nil {
log.Println(err2)
sc.AbortTransaction(sc) // i want abort only here
}
return sc.CommitTransaction(sc)
}); err!=nil {
return err
}
Can I use some options for transactions or rewrite code, so I will control transactions abort by myself?

Golang GraphQL MongoDB Struggling to get date and id out of the Database

I am new to Golang and Graphql so I probably messed a lot of the configuration up but I am struggling to get values returned from my database using the GraphQL API I created. Whenever I query my database using the GraphQL API I created in Golang It throws the error cannot decode UTC datetime into a string type and struggles to get the id out.
Here is my GrapqhQL schema:
type User {
_id: ID!
username: String!
passwordHash: String!
email: String!
userInfo: userStats
profileStats: profileInfo
}
type userStats {
firstName: String
lastName: String
birthday: String
dateCreated: String!
nativeLanguage: String
currentlyLearning: String
location: Location
}
type Location {
city: String
state: String
zipcode: Int
country: String
}
type profileInfo {
level: Int
xp: Int
xpTillNextLevel: Int
posts: Int
}
input NewUser {
id: ID!
username: String!
passwordHash: String!
email: String!
userStats: String
profileInfo: String
}
type Mutation {
createUser(input: NewUser!): User!
}
type Query {
users: [User!]!
user(id: ID!): User!
}
Here is my code that executes when a query is provided:
func (u *UserRepo) GetUsers() ([]*model.User, error) {
var users []*model.User
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
usersCollection := u.DB.Collection(u.KEYS["collection"].(string))
cursor, err := usersCollection.Find(ctx, bson.M{})
if err != nil {
fmt.Println(err)
return nil, err
}
if err = cursor.All(ctx, &users); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Println(users[0])
return users, nil
}
func (u *UserRepo) GetUserById(id string) (*model.User, error) {
var user model.User
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
usersCollection := u.DB.Collection(u.KEYS["collection"].(string))
userID, err := primitive.ObjectIDFromHex(id)
if err != nil {
fmt.Println("Invalid ObjectID")
}
err = usersCollection.FindOne(ctx, bson.M{"_id": userID}).Decode(&user)
if err != nil {
fmt.Println("error retrieving user userid : " + id)
fmt.Printf("error: %d", err)
//return nil, err
}
fmt.Println(err)
fmt.Println(user)
return &user, nil
}
If I uncomment the return nil,err on the bottom query for selecting one user by the id, it will just return the error of the date and no information so I am leaving it commented out for testing purposes.
my query and result
query:
query getUsers {
user(id: "5ea75c4c67f9266c89dfb659") {
_id
username
email
passwordHash
userInfo{
lastName
dateCreated
location{
state
}
}
profileStats{
level
}
}
}
result:
{
"data": {
"user": {
"_id": "",
"username": "Aerith",
"email": "Aerith#LanguageLearning.com",
"passwordHash": "testingpassword",
"userInfo": {
"lastName": "Gainsborough",
"dateCreated": "",
"location": null
},
"profileStats": null
}
}
}
and here is example dataset that I made for testing in my MongoDB database
db.users.findOne({_id: ObjectId("5ea75c4c67f9266c89dfb659")})
{
"_id" : ObjectId("5ea75c4c67f9266c89dfb659"),
"username" : "Aerith",
"passwordHash" : "testingpassword",
"email" : "Aerith#LanguageLearning.com",
"userInfo" : {
"firstName" : "Aerith",
"lastName" : "Gainsborough",
"birthday" : ISODate("1985-02-07T00:00:00Z"),
"dateCreated" : ISODate("2020-04-27T22:27:24.650Z"),
"nativeLanguage" : "English",
"currentlyLearning" : "Japanese",
"location" : {
"city" : "Sector 5",
"state" : "Midgar",
"zipcode" : 77777,
"country" : "FF7"
}
},
"profileStats" : {
"level" : 1,
"xp" : 0,
"xpTillNextLevel" : 1000,
"comments" : 0,
"posts" : 0
}
}
Also the location and profile stats are just coming back empty and null and I have no clue why.
Sorry for the long amount of code but I am trying to provide the most information possible to assist with finding the answer. Hopefully, this helps and I can get some assurance on how to fix this issue. Thank you for all your help in advance.
edit: after some testing in the userStats type I can get the firstName and lastName but it fails and the cursor crashes because of the data error when it hits birthday. This is why everything is null under birthday. So the issues is how do I decode the mongo date so I can put in the userStates. I am tempted to just pull everything as bson and convert it to correct model structs but that seems like to much extra work and I really do not want to resort to this.
Some BSON types doesn't have direct mapping with Go primitive types, so you need types with custom unmarshalling, either your own made or already done on bson/primitive package
Try defining your user stats struct that way:
import "go.mongodb.org/mongo-driver/mongo/primitive"
type UserStats {
...
BirthDay primitive.DateTime `bson:"birthday"`
//OR BirthDay primitive.Timestamp `bson:"birthday"`
...
}
https://pkg.go.dev/go.mongodb.org/mongo-driver/bson#v1.3.3?tab=doc#hdr-Native_Go_Types
https://pkg.go.dev/go.mongodb.org/mongo-driver/bson/primitive
https://pkg.go.dev/go.mongodb.org/mongo-driver/bson/primitive?tab=doc#DateTime
https://pkg.go.dev/go.mongodb.org/mongo-driver/bson/primitive?tab=doc#Timestamp

Golang find a value from a map of nested json data from MongoDB

I am trying to receive data from my MongoDB using MGO in a map of type []map[string]interface{}
My JSON looks like this -
{
"_id":"string",
"brandId":123,
"category":{
"television":[
{
"cat":"T1",
"subCategory":[
{
"subCat":"T1A TV",
"warrantyPeriod":6
}
],
"warrantyPeriod":12
},
{
"cat":"T2",
"subCategory":[
{
"subCat":"T2A",
"warrantyPeriod":18
},
{
"subCat":"T2B",
"warrantyPeriod":9
}
],
"warrantyPeriod":15
},
{
"cat":"T3",
"subCategory":[
{
"subCat":"T3A",
"warrantyPeriod":3
},
{
"subCat":"T3B",
"warrantyPeriod":5
},
{
"subCat":"T3C",
"warrantyPeriod":7
},
{
"subCat":"T3D",
"warrantyPeriod":11
}
],
"warrantyPeriod":4
}
],
"television_warrantyPeriod":24
},
"title":"BrandName"
}
I would ideally pass in the category name i.e. 'television' and cat and subCat values which could be optional.
For e.g. something like this -
{
"categorySlug": "television",
"brandId": "123",
"model": "T2"
}
In which case I would expect to find '15' which is the warrantyPeriod value for T2 if there are no T2A or T2B specified.
My query functions look like this -
var data map[string]string
err := json.NewDecoder(r.Body).Decode(&data)
log.Println(err)
var buffer bytes.Buffer
buffer.WriteString("category.")
buffer.WriteString(data["categorySlug"])
brandId, _ := strconv.Atoi(data["brandId"])
concernedbrandandcategory := database.GetMappedFields("Brands", bson.M{"brandId": brandId, buffer.String(): bson.M{"$exists": true}}, bson.M{buffer.String(): 1})
categorymap := concernedbrandandcategory[0]
log.Println(categorymap["category"]["television"], reflect.TypeOf(categorymap))
My GetMappedFields function looks like this -
func GetMappedFields(collectionName string, query interface{}, selector interface{}) (result []map[string]interface{}) {
MgoSession.DB(Dbname).C(collectionName).Find(query).Select(selector).All(&result)
return
}
I'm just not able to wrap my head around this nested structure which sometimes returns a map and sometimes an interface!
Any help would be highly appreciated!
you can do something like this
majorCat := body["categorySlug"]
category := body["category"]
subCategory := body["subCategory"]
brandId, err := strconv.Atoi(body["brandId"])
if err != nil {
log.Println(err)
}
result := database.GetMappedFields("Brands", bson.M{"brandId": brandId}, bson.M{"category": 1, "_id": 0})
internalObj := result[0]["category"].(map[string]interface{})
finalValue := internalObj["television_warrantyPeriod"]
if category != "" {
for _, v := range internalObj[majorCat].([]interface{}) {
subObj := v.(map[string]interface{})
if subObj["cat"] == category {
finalValue = subObj["warrantyPeriod"]
if subCategory != "" {
minorObj := subObj["subCategory"].([]interface{})
for _, iter := range minorObj {
kevVal := iter.(map[string]interface{})
if kevVal["subCat"] == subCategory {
finalValue = kevVal["warrantyPeriod"]
}
}
}
}
}
}
Hopefully this will do dynamically or you can create a struct so that it can directly be decoded into that cheers