filter query mongoldb Golang - mongodb

I am trying to get a list of data that match specific queries but I am getting this error
"(AtlasError) merchant is not allowed or the syntax is incorrect, see
the Atlas documentation for more information"
func ...
var result []*model.Package
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
tokenData, err := middleware.CtxValue(ctx)
if err != nil {
return nil, err
}
orQuery := []bson.M{}
merchant := "abc"
completedQuery := bson.M{"status": "completed"}
cancelledQuery := bson.M{"status": "cancelled"}
orQuery = append(
orQuery,
cancelledQuery,
completedQuery)
limit64 := int64(limit)
page64 := int64(page)
match := bson.M{"$match": bson.M{"$nor": orQuery}}
var filterQuery primitive.M
if tokenData.Role == "admin" && merchant != nil {
filterQuery = bson.M{"merchant": bson.M{"id": merchant}}
} else {
filterQuery = bson.M{"user": bson.M{"id": tokenData.Id}}
}
paginatedData, err1 := paginate.New(r.Collection).Context(ctx).Limit(limit64).Page(page64).Aggregate(match, filterQuery)
if err1 != nil {
return nil, err1
}
...

filterQuery, which seems to contain { "merchant" : { "id" : "abc" } }, is being passed sepearately to .Aggregate(). But the aggregation framework is expecting to receive something that represents a sequence of pipeline stages. Each of these stages, outlined here in the documentation, are expected to begin with a $ character such as the $match stage.
Currently the database is attempting to process merchant as an options for the pipeline (see here and here). But such an option doesn't exist, hence the error message.
To resolve this, you should incorporate the filterQuery logic into the existing match variable/stage that you are building and passing. Alternatively you can wrap filterQuery in a different $match and then pass both of them (as a single argument) to .Aggregate().
This example in the documentation shows they build multiple stages and then submit them together to .Aggregate() via mongo.Pipeline{...}:
// create the stages
matchStage := bson.D{{"$match", bson.D{{"toppings", "milk foam"}}}}
unsetStage := bson.D{{"$unset", bson.A{"_id", "category"}}}
sortStage := bson.D{{"$sort", bson.D{
{"price", 1},
{"toppings", 1}},
}}
limitStage := bson.D{{"$limit", 2}}
// pass the stage into a pipeline
// pass the pipeline as the second paramter in the Aggregate() method
cursor, err := coll.Aggregate(context.TODO(), mongo.Pipeline{matchStage, unsetStage, sortStage, limitStage})

Related

Why my Golang nullify all fileds in document after MongoDB unwind stage?

I have this method:
func GetPostBySlug(slug string) (PostWithCategory, error) {
post := PostWithCategory{}
filter := bson.M{
"slug": slug,
}
database, error := Database.GetMongoDatabase()
if error != nil {
println(error)
}
match := bson.D{{"$match", bson.D{{"slug", slug}}}}
lookup := bson.D{{"$lookup", bson.D{{"from", categories.Model_name}, {"localField", "category"}, {"foreignField", "_id"}, {"as", "category"}}}}
unwind := bson.D{{"$unwind", bson.D{{"path", "$category"}, {"preserveNullAndEmptyArrays", false}}}}
collection := database.Collection(model_name)
cursor, err := collection.Aggregate(context.TODO(), mongo.Pipeline{match, lookup, unwind})
if err != nil {
return post, err
}
var results []PostWithCategory
if err = cursor.All(context.TODO(), &results); err != nil {
panic(err)
}
fmt.Println(results)
if len(results) > 0 {
post = results[0]
}
return post, err
}
If I remove "unwind" stage from Aggregate(), I will get my document but with empty array instead of category. If I keep it, all the fields become "null" in the document.
Why is this so hard 😭 Maybe there is some other way around? The goal is simple. I have an ID of the category in the post and in above method I want to return the post with category object (which is stored in other collection). In node.js with mongoose there is a very simple method called "populate", so I need to perform similar operation here :(

MongoDB | Go using a pipeline to listen to updates on a document by id

I'm trying to make a function that watches the database for a certain document with a certain id to update but it does not work. It just stays alive while updating the document while the function should return. I've tried multiple things and the rest of the code works fine. When i remove the id part and listen for all document updates in that collection the function does as it should
func iterateChangeStream(routineCtx context.Context,stream *mongo.ChangeStream, chn chan string) {
defer stream.Close(routineCtx)
for stream.Next(routineCtx) {
var data bson.M
if err := stream.Decode(&data); err != nil {
fmt.Println(err)
}
chn <- "updated"
err := stream.Close(routineCtx)
if err != nil {
return
}
return
}
return
}
func (s Storage) ListenForScannerUpdateById(id primitive.ObjectID) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
chn := make(chan string)
coll := s.db.Collection("scanners")
scan, err := s.GetScannerById(id)
fmt.Println(scan)
matchPipeline := bson.D{
{
"$match", bson.D{
{"operationType", "update"},
{"fullDocument._id", bson.D{
{"$eq", id},
}},
},
},
}
scannerStream, err := coll.Watch(ctx, mongo.Pipeline{matchPipeline})
if err != nil {
err := scannerStream.Close(ctx)
if err != nil {
panic( err)
}
fmt.Printf("err: %v", err)
}
routineCtx, _ := context.WithCancel(context.Background())
go iterateChangeStream(routineCtx, scannerStream, chn)
msg, _ := <- chn
defer close(chn)
fmt.Println(msg)
return
}
Ok, so after reading the documentation for a seccond time i found this:
For update operations, this field only appears if you configured the change stream with fullDocument set to updateLookup. This field then represents the most current majority-committed version of the document modified by the update operation. This document may differ from the changes described in updateDescription if other majority-committed operations modified the document between the original update operation and the full document lookup.
so after setting the fullDocument option to updateLookup like this it works perfect:
scannerStream, err := coll.Watch(ctx, mongo.Pipeline{matchPipeline}, options.ChangeStream().SetFullDocument(options.UpdateLookup))

Why does my mongodb query return 0 results?

This is my database collection:
With this go code, I try to get all the users who are either involved in the story or created the story with the given id.
func main() {
for stf.DB == nil {
}
collection := stf.DB.Collection("user")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
id, _ := primitive.ObjectIDFromHex("5cb4dd7e29d3dca573a73d4c")
fter := bson.M{"_id": id}
involvedFilter := bson.M{"stories_involved": fter}
createdFilter := bson.M{"stories_created": fter}
filter := bson.M{"$or": []bson.M{involvedFilter, createdFilter}}
cur, err := collection.Find(ctx, filter)
if err != nil {
log.Fatal(err.Error())
}
defer cur.Close(ctx)
for cur.Next(ctx) {
var result bson.M
err := cur.Decode(&result)
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(result)
}
if err := cur.Err(); err != nil {
log.Fatal(err.Error())
}
}
The code doesn't output any errors, but it also doesn't output any objects...
Thanks for your help in advance!
Your query translates into:
{"$or":[
{"stories_involved":{
"_id": ObjectId("5cb4dd7e29d3dca573a73d4c")}},
{"stories_created":{
"_id":ObjectId("5cb4dd7e29d3dca573a73d4c")}}
]}
Which means that it's searching for either a document with a nested document
i.e:
{stories_involved: {_id: <value>}} OR {stories_created: {_id: <value>}}.
However, the documents in the collection contains nested document array i.e:
{stories_involved: [{_id:<value>}]} OR {stories_created: [{_id:<value>}]}
This is the reason your query is not returning any value (and no error because the query syntax is correct).
There are two ways of Querying a document nested in an array using dot notation. If you know the index of the array for the document, you can just specify the position:
id, _ := primitive.ObjectIDFromHex("5cb4dd7e29d3dca573a73d4c")
involvedFilter := bson.M{"stories_involved.0._id": id}
createdFilter := bson.M{"stories_created.0._id": id}
filter := bson.M{"$or": []bson.M{involvedFilter, createdFilter}}
cur, err := collection.Find(ctx, filter)
If you do not know the index position of the document nested in the array, concatenate the name of the array field, with a dot (.) and the name of the field in the nested document:
id, _ := primitive.ObjectIDFromHex("5cb4dd7e29d3dca573a73d4c")
involvedFilter := bson.M{"stories_involved._id": id}
createdFilter := bson.M{"stories_created._id": id}
filter := bson.M{"$or": []bson.M{involvedFilter, createdFilter}}
cur, err := collection.Find(ctx, filter)
See also MongoDB: Query Documents

Find entries via substring regex query in mongodb-go-driver

I can't get the official go mongo driver to successfully return objects that are queried via a regex query.
I already know how to do it via the mongo shell and get my expected results.
With this example i get all entries that contain "he" in their 'text' field:
db.getCollection('test').find({"text": /he/})
same with this one:
db.getCollection('test').find({"text": {$regex: /he/, $options: ''}})
This is my current code that doesn't work:
package main
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5*time.Second))
defer cancel()
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
fmt.Println(err)
return
}
err = client.Connect(ctx)
if err != nil {
fmt.Println(err)
return
}
db := client.Database("test")
coll := db.Collection("test")
filter := bson.D{{"text", primitive.Regex{Pattern: "/he/", Options: ""}}}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cur, err := coll.Find(ctx, filter)
if err != nil {
fmt.Println(err)
return
}
i := 0
for cur.Next(ctx) {
i = i + 1
}
fmt.Println("Found", i, "elements")
}
Per example in the official mongo-go-driver repository, this should work.
My current entries in the collection just contain 2 fields, the id field and an extra text field. I currently have 3 entries. that look like this:
{
"_id" : ObjectId("5c9cc7e9950198ceeefecbdd"),
"text" : "hello world"
},
{
"_id" : ObjectId("5c9cc7f6950198ceeefecbec"),
"text" : "hello"
},
{
"_id" : ObjectId("5c9cc804950198ceeefecbfa"),
"text" : "test world"
}
My expected results with the code from above, should be the first 2 entries. Instead i get an empty cursor back.
Does anybode know, what i am doing wrong?
Thanks for your help.
primitive.Regex struct accepts Pattern value without slashes, so it must be:
filter := bson.D{{"text", primitive.Regex{Pattern: "he", Options: ""}}}
Try this one
. -> matches any character
*-> matches zero of more occurrence of previous character
So below filter will give you all results which has "he" as substring
filter = bson.M{"text": primitive.Regex{Pattern: ".*" + "he" + ".*", Options: "i"}}

Sequential queries with golang & mongodb

Wondering what is best way to make sequential queries from Golang for a mongodb.
Example lets say you have :
result *bson.M
ids:=["543d171c5b2c12420dd016","543d171c5b2dd016"]
oids := make([]bson.ObjectId, len(ids))
for i := range ids {
oids[i] = bson.ObjectIdHex(ids[i])
}
query := bson.M{"_id": bson.M{"$in": oids}}
error:= c.Find(query).All(&result)
And you want to take the output of the _ids and use it as a query for another table.
So is this correct?
query = bson.M{"_id": bson.M{"$in": result}}
Here's how to construct a query using the ids of documents returned from some other query.
var docs []bson.M
if err := c.Find(query).All(&docs); err != nil {
// handle error
}
docIDs := make([]interface{}, len(docs))
for i := range docs {
docIds[i] = docs[i]["_id"]
}
query = bson.M{"_id": bson.M{"$in": docIDs}}