Querying mongodb from golang using the _id stored in an array - mongodb

So here is my question. I have an array which are stored the _ids of mongodbs objects. Whats the right way to retrieve them all in one query using the mgo and bson package?
So if the array is like that: ids:=["543d171c5b2c12420dd016","543d171c5b2dd016"]
How we make the query ? I tried that but I know its wrong.
query := bson.M{"_id": bson.M{"$in": ids}}
c.Find(query).All()
Thanks in advance

If the documents are stored with string ids, then the code looks correct.
The ids look like hex encoded object ids. If the object identifiers are object ids, then you need to the convert the hex strings to object ids:
oids := make([]bson.ObjectId, len(ids))
for i := range ids {
oids[i] = bson.ObjectIdHex(ids[i])
}
query := bson.M{"_id": bson.M{"$in": oids}}

MongoDB syntax for go.mongodb.org/mongo-driver has been updated, this should work using the official driver.
oids := make([]primitive.ObjectID, len(ids))
for i := range ids {
objID, err := primitive.ObjectIDFromHex(ids[i])
if err == nil {
oids = append(oids, objID)
}
}

This is to convert back to a struct that can be used through out the app
type MongoUser struct {
ID *primitive.ObjectID `json:"id" bson:"_id"`
FirstName string `json:"first_name" bson:"firstName"`
LastName string `json:"last_name" bson:"lastName"`
Email string `json:"email" bson:"email"`
}
This is a helper method that takes your slice of ids and turns it into the object id type.
func formatObjectIdMultiple(hex []string) ([]primitive.ObjectID, error) {
var list []primitive.ObjectID
oids := make([]primitive.ObjectID, len(hex))
for _, i := range hex {
objectId, err := primitive.ObjectIDFromHex(i)
if err != nil {
return nil, err
}
oids = append(oids, objectId)
}
return list, nil
}
Here is my method for the db. Its important you use bson.M for some reason bson.D does not work with this. Also dont forget to close your cursor the defer function will close it at the end of your GetMultipleUser function.
func (mongo *Mongo) GetMultipleUser(ids []string) ([]*MongoUser, error) {
objectIDs, err := formatObjectIdMultiple(ids)
if err != nil {
return nil, err
}
query := bson.M{"_id": bson.M{"$in": objectIDs}}
coll := mongo.Con.Database("dbName").Collection("users")
cursor, err := coll.Find(context.Background(), query)
if err != nil {
return nil, err
}
defer func() {
cursor.Close(context.Background())
}()
var output []*MongoUser
for cursor.Next(context.Background()) {
var temp *MongoUser
cursor.Decode(&temp)
output = append(output, temp)
}
return output, nil
}

Related

MongoDB returns object data as array of key-value pair in Go

I have written following query which returns me records where updated_at date is greater than synced_at date from all records using mongodb library in Golang.
pipeline := []bson.M{}
filter := []string{"$updated_at", "$synced_at"}
pipeline = append(pipeline, bson.M{"$match": bson.M{"$expr": bson.M{"$gte": filter}}})
opts := options.Aggregate().SetMaxTime(2 * time.Second)
cursor, err := collection.Aggregate(ctx, pipeline, opts)
for cursor.Next(context.Background()) {
records := model.DData{}
err = cursor.Decode(&records)
}
The structure of Data is:
type DData struct {
Name string `json:"name" bson:"name"`
Data interface{} `json:"data" bson:"data"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
SyncedAt time.Time `json:"synced_at" bson:"synced_at"`
}
Data in collection is of the form:
{
"name":"Vallabh",
"data":{
"field1":"value1",
"field2":"value2",
"field3":"value3",
},
"updated_at":2021-08-17T09:43:27Z,
"synced_at":2021-08-07T09:43:27Z
}
But with above query I am getting data in the form:
{
"name":"Vallabh",
"data":[
{
"key":"field1",
"value":"value1"
},
{
"key":"field2",
"value":"value2"
},
{
"key":"field3",
"value":"value3"
}
}],
"updated_at":"2021-08-17T09:43:27Z",
"synced_at":"2021-08-07T09:43:27Z"
}
What am I doing wrong? Its happening only when field type is an interface in struct.
Try with this
type DData struct {
Name string `json:"name" bson:"name"`
Data map[string]string `json:"data" bson:"data"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
SyncedAt time.Time `json:"synced_at" bson:"synced_at"`
}
Always use the type if you know it beforehand, with interface{} the burden is on the library to find out what the type is. If you expect the varied values in the map you can use map[string]interface{}
Found a very weird solution to this problem where I decoded it to bson.M{}, then marshalled it and unmarshalled it to my struct. I am sure there are better ways but this one worked for me.
for cursor.Next(context.Background()) {
tempResult := bson.M{}
err = decode(cursor, &tempResult)
if err != nil {
logger.LogErrorf(err, "error while decoding")
continue
}
obj, err := marshal(tempResult)
if err != nil {
logger.LogErrorf(err, "error while marshalling")
continue
}
var data model.DData
err = json.Unmarshal(obj, &data)
if err != nil {
tenant.LogError(err, "error while marshalling")
continue
}
}
#VallabhLakade I had similar concern like this and tried the below way that helped .
So basically what's the problem is that the mongo-driver defaults to unmarshalling as bson.D for structs of type interface{} where as mgo mgo-driver defaults to bson.M .
So we will have to add the below code while trying to establish connection with mongo-db , SetRegistry() options as clientOpts To map the old mgo behavior, so that mongo-driver defaults to bson.M while unmarshalling structs of type interface{} , and this should not display the values back as key-value pair
tM := reflect.TypeOf(bson.M{})
reg := bson.NewRegistryBuilder().RegisterTypeMapEntry(bsontype.EmbeddedDocument, tM).Build()
clientOpts := options.Client().ApplyURI(SOMEURI).SetAuth(authVal).SetRegistry(reg)
client, err := mongo.Connect(ctx, clientOpts)
Reference -> MongoDB document returns array of key value pair in go mongo-driver
#Vallabh you shall use mgocompat too something like below this should mimic the old mgo behavior
registry := mgocompat.NewRegistryBuilder().Build()
connectOpts := options.Client().ApplyURI(SOMEURI).SetAuth(info).SetRegistry(registry)

Appending Data to an Interface Pointing to a Defined Struct using 'reflect'

I am trying to create a function where it gets all the documents from a Mongo Collection and queries them to declared structs. To achieve this I set the parameters for the function of type interface so it can work with two structs. Here is my code:
In package entities:
type Project struct {
Title string
Position string
....
}
type Projects struct {
Projects []Project
}
In the current package:
var docs entities.Projects
var doc entities.Project
//doc represents a document from Mongo Collection
//docs represents an array of documents, each element is a document
//collection has type *mongo.Collection and points to the desired collection on MongoDB.
createQuery(&doc, &docs, collection)
func createQuery(doc interface{}, docs interface{}, c *mongo.Collection) {
documents := reflect.ValueOf(docs).Elem()
document := reflect.ValueOf(doc)
cur, err := c.Find(context.Background(), bson.D{{}})
if err != nil {
log.Fatal(err)
}
for cur.Next(context.Background()) {
err = cur.Decode(document.Interface())
if err != nil {
log.Fatal(err)
}
//Error is thrown here
documents.Set(reflect.Append(documents, document))
fmt.Println(doc)
}
if err := cur.Err(); err != nil {
log.Fatal(err)
}
if err != nil {
fmt.Printf("oh shit this is the error %s \n", err)
}
cur.Close(context.Background())
fmt.Printf("documents: %+v\n", documents.Interface())
fmt.Printf("document: %+v\n", document.CanSet())
}
---ERROR OUTPUT---
panic: reflect: call of reflect.Append on struct Value
I was able to set data to doc using the document variable, although when doing document.CanSet() is false (so it may not even work). Where the program breaks is when I try to append the document to the documents interface.
The code in the question passes the struct docs to a function that expects a slice. Pass the address of the slice field in docs instead of docs itself.
The createQuery function can determine the slice element type from the slice itself. There's no need to pass in an example value.
var docs entities.Projects
createQuery(&docs.Projects, collection)
for _, doc := range docs.Projects {
fmt.Println(doc.Title)
}
The call to cur.Decode requires a pointer to an uninitialized value. Use reflect.New to create that value.
func createQuery(docs interface{}, c *mongo.Collection) {
docsv := reflect.ValueOf(docs).Elem()
doct := docsv.Type().Elem()
cur, err := c.Find(context.Background(), bson.D{{}})
if err != nil {
log.Fatal(err)
}
for cur.Next(context.Background()) {
docpv := reflect.New(doct)
err = cur.Decode(docpv.Interface())
if err != nil {
log.Fatal(err)
}
docsv.Set(reflect.Append(docsv, docpv.Elem()))
}
if err := cur.Err(); err != nil {
log.Fatal(err)
}
cur.Close(context.Background())
}
As an aside, the entities.Projects struct type is not needed if that struct type will only ever have one field. Use []Project instead:
var docs []entities.Project
createQuery(&docs, collection)
for _, doc := range docs {
fmt.Println(doc.Title)
}

How to find the data from collection according to url in golang?

I'm retrieving the data from the database when the user hit the url like http://localhost:8080/api/v1/customer?keyword=dhiman then it search for the data in the collection if there is any field matches then it will retrieve that data. if the user entered the short url like http://localhost:8080/api/v1/customer?keyword=dhi then it also retrieve the data which matches like that how I'll solve this problem. I tried the code for this like following:-
Struct of the customer
type Customer struct {
Id int `json:"id" bson:"_id"`
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
PhoneNumber string `json:"phone_number" bson:"phone_number"`
}
type Customers []Customer
Functions
func GetCustomers(c *gin.Context) {
value := c.Query("keyword")
fmt.Println(value)
response := ResponseControllerList{}
conditions := bson.M{"last_name":value}
data, err := models.GetCustomerListing(conditions)
if err != nil {
response = ResponseControllerList{
config.FailureCode,
config.FailureFlag,
config.FailureMsg,
nil,
nil,
}
} else {
response = ResponseControllerList{
config.SuccessFlag,
config.SuccessFlag,
config.SuccessMsg,
data,
// dataCount,
nil,
}
}
GetResponseList(c, response)
}
GetCustomerListing function in models page:-
func GetCustomerListing(customerQuery interface{}) (result Customers, err error) {
mongoSession := config.ConnectDb()
sessionCopy := mongoSession.Copy()
defer sessionCopy.Close()
getCollection := mongoSession.DB(config.Database).C(config.CustomerCollection)
err = getCollection.Find(customerQuery).Select(bson.M{"password": 0}).All(&result) //.Skip(skip).Limit(limit)
if err != nil {
return result, err
}
return result, nil
}
collection images
I got the answer it is done by using the $or in mongodb.
In the monogdb there is a operator called or $or it checks the value with all the fields and produce result.
There is a bson.RegExis used. Because it will matches or checks the data similar to it receives from the user.
There is change in condition. The condition is:-
conditions := bson.M{"$or": []bson.M{
bson.M{"first_name": bson.RegEx{value,""}},
bson.M{"last_name": bson.RegEx{value,""}},
bson.M{"email": bson.RegEx{value,""}},
bson.M{"phone_number": bson.RegEx{value,""}},
}}
there is change in the query

Is there a way to get slice as result of Find()?

Now I'm doing:
sess := mongodb.DB("mybase").C("mycollection")
var users []struct {
Username string `bson:"username"`
}
err = sess.Find(nil).Select(bson.M{"username": 1, "_id": 0}).All(&users)
if err != nil {
fmt.Println(err)
}
var myUsers []string
for _, user := range users{
myUsers = append(myUsers, user.Username)
}
Is there a more effective way to get slice with usernames from Find (or another search function) directly, without struct and range loop?
The result of a MongoDB find() is always a list of documents. So if you want a list of values, you have to convert it manually just as you did.
Using a custom type (derived from string)
Also note that if you would create your own type (derived from string), you could override its unmarshaling logic, and "extract" just the username from the document.
This is how it could look like:
type Username string
func (u *Username) SetBSON(raw bson.Raw) (err error) {
doc := bson.M{}
if err = raw.Unmarshal(&doc); err != nil {
return
}
*u = Username(doc["username"].(string))
return
}
And then querying the usernames into a slice:
c := mongodb.DB("mybase").C("mycollection") // Obtain collection
var uns []Username
err = c.Find(nil).Select(bson.M{"username": 1, "_id": 0}).All(&uns)
if err != nil {
fmt.Println(err)
}
fmt.Println(uns)
Note that []Username is not the same as []string, so this may or may not be sufficient to you. Should you need a user name as a value of string instead of Username when processing the result, you can simply convert a Username to string.
Using Query.Iter()
Another way to avoid the slice copying would be to call Query.Iter(), iterate over the results and extract and store the username manually, similarly how the above custom unmarshaling logic does.
This is how it could look like:
var uns []string
it := c.Find(nil).Select(bson.M{"username": 1, "_id": 0}).Iter()
defer it.Close()
for doc := (bson.M{}); it.Next(&doc); {
uns = append(uns, doc["username"].(string))
}
if err := it.Err(); err != nil {
fmt.Println(err)
}
fmt.Println(uns)
I don't see what could be more effective than a simple range loop with appends. Without all the Mongo stuff your code basically is this and that's exactly how I would do this.
package main
import (
"fmt"
)
type User struct {
Username string
}
func main() {
var users []User
users = append(users, User{"John"}, User{"Jane"}, User{"Jim"}, User{"Jean"})
fmt.Println(users)
// Interesting part starts here.
var myUsers []string
for _, user := range users {
myUsers = append(myUsers, user.Username)
}
// Interesting part ends here.
fmt.Println(myUsers)
}
https://play.golang.com/p/qCwENmemn-R

Select column from Mongodb in golang using mgo

As I know, we can use
> db['twitter-3'].find({}, {"text": 1})
to select all texts in collection.
How can we use mgo to find specific field in golang?
I tried
var result []string
err = conn.Find(bson.M{}, bson.M{"text", 1}).All(&result)
But it is not correct.
Use the query Select method to specify the fields to return:
var result []struct{ Text string `bson:"text"` }
err := c.Find(nil).Select(bson.M{"text": 1}).All(&result)
if err != nil {
// handle error
}
for _, v := range result {
fmt.Println(v.Text)
}
In this example, I declared an anonymous type with the one selected field. It's OK to use a type with all document fields.
to select multiple fields:
var result []struct{
Text string `bson:"text"`
Otherfield string `bson:"otherfield"`
}
err := c.Find(nil).Select(bson.M{"text": 1, "otherfield": 1}).All(&result)
if err != nil {
// handle error
}
for _, v := range result {
fmt.Println(v.Text)
}
var result interface{}
err = c.Find(nil).Select(bson.M{"text": 1}).All(&result)