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
Related
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!
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.
A have booking system with apoinments and need to get all booking objects with apoinments filtering with special time.
{
"id": "6295001bef176110cb52076c",
"companyID": "6294deeed16a4491020a6f3a",
"createdAt": "2022-05-30T17:34:19.458Z",
"updatedAt": "2022-05-31T12:23:58.805Z",
"apoinments": [
{
"id": "62a349a1fe907103f8d33111",
"startTime": "2022-06-10T12:00:00Z",
"endTime": "2022-06-10T14:00:00Z",
"createdAt": "2022-06-10T13:39:45.057Z",
"companyID": "6294deeed16a4491020a6f3a",
"bookingItemID": "6295001bef176110cb52076c",
},
{
"id": "62a349c0fe907103f8d33112",
"startTime": "2022-06-10T14:00:00Z",
"endTime": "2022-06-10T16:00:00Z",
"createdAt": "2022-06-10T13:40:16.927Z",
"companyID": "6294deeed16a4491020a6f3a",
"bookingItemID": "6295001bef176110cb52076c",
}
],
"title": "One",
}
I need to get all booking objects with their apoinments with startTime and endTime with special range so i do:
func (companyRepository *companyRepositoryImpl) GetCompanyBookingItems(companyID string) (*model.CompanyBookingItems, error) {
var existingCompany model.Company
objectId, _ := primitive.ObjectIDFromHex(companyID)
filter := bson.M{"_id": objectId}
err := companyRepository.Connection.Collection("companies").FindOne(cntx, filter).Decode(&existingCompany)
if err != nil {
return nil, exception.ResourceNotFoundException("Company", "id", companyID)
}
switch hour := time.Now().Hour(); { // missing expression means "true"
// dev
case hour >= 8 && hour <= 23:
// prod
// case hour >= 3 && hour <= 18:
// log.Println("hour", hour)
startDate := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 11, 0, 0, 0, time.UTC)
endDate := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()+1, 05, 0, 0, 0, time.UTC)
bookingItems := *existingCompany.BookingItems
for i := range bookingItems {
bookingItem := bookingItems[i]
var newApoinments []model.Apoinment
for _, apoinment := range *bookingItem.Apoinments {
if apoinment.StartTime.Local().After(startDate.Local()) && apoinment.EndTime.Local().Before(endDate.Local()) {
newApoinments = append(newApoinments, apoinment)
// log.Println("10 - 23", apoinment.ClientName)
}
}
*bookingItems[i].Apoinments = newApoinments
}
// dev
case hour >= 0 && hour <= 7:
// prod
// case hour >= 19 && hour <= 2:
startDate := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 11, 0, 0, 0, time.UTC)
endDate := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 05, 0, 0, 0, time.UTC)
// log.Println("start", startDate)
// log.Println("end", endDate)
bookingItems := *existingCompany.BookingItems
for i := range bookingItems {
bookingItem := bookingItems[i]
var newApoinments []model.Apoinment
for _, apoinment := range *bookingItem.Apoinments {
if apoinment.StartTime.Local().After(startDate.Local()) && apoinment.EndTime.Local().Before(endDate.Local()) {
newApoinments = append(newApoinments, apoinment)
// log.Println("10 - 23", apoinment.ClientName)
}
}
*bookingItems[i].Apoinments = newApoinments
}
}
return &model.CompanyBookingItems{
Data: *existingCompany.BookingItems,
}, nil
}
but get error runtime error: invalid memory address or nil pointer dereference
How i can filter and mongodb return me only document with special ranged time apoinments of booking object?
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?
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