Multiple find results handling - mongodb

I am storing users and treating them as the center of the universe in my application, i am now trying to introduce the concept of an Org whereby users can be a member of many Orgs and then certain settings etc will belong to the Org. The function I am trying to create is to search for all Orgs where the users ID can be found and either the Owner or one of the Members and return a list of Orgs to then render the details client-side.
The issue I am having relates to the handling and conversion of results from the Mongo Find and then how to handle that and convert to a format I can return safely at the end.
Currently im unable to return the data with the error
cannot use &org (value of type *[]*model.Org) as *model.Org value in
return statement
Org Model
package model
// Org is the structure of a org
type Org struct {
ID string `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Owner string `json:"owner" bson:"owner"`
Members []string `json:"members" bson:"members"`
Token string `json:"token" bson:"-"`
VerifyToken string `json:"verifyToken" bson:"verifyToken"`
}
Function
// GetOrgByUserID returns a user by his id
func (db *DB) GetOrgByUserID(id string) (*model.Org, error) {
findOptions := options.Find()
var org []*model.Org
cur, err := db.collections.orgs.Find(context.TODO(), bson.D{{"owner", id}}, findOptions)
if err != nil {
return nil, err
}
// Iterate through the cursor
for cur.Next(context.TODO()) {
var elem model.Org
err := cur.Decode(&elem)
if err != nil {
return nil, err
}
org = append(org, &elem)
}
if err := cur.Err(); err != nil {
return nil, err
}
// Close the cursor once finished
cur.Close(context.TODO())
return &org, nil
}

Fix by declaring the return value as a slice. Also, simplify the code by using the cursor All method:
func (db *DB) GetOrgByUserID(id string) ([]*model.Org, error) {
findOptions := options.Find()
cur, err := db.collections.orgs.Find(context.TODO(), bson.D{{"owner", id}}, findOptions)
if err != nil {
return nil, err
}
defer cur.Close(ctx.TODO())
var org []*model.Org
err = cur.All(ctx.TODO(), &org)
return org, nil
}

Related

Convert Bson.M to a map[string]interface

I'm trying to convert the structure I'm decoding with my query to map[string]interface.
Here is my code:
var m map[string]interface
var result []Result
type Result struct {
Id ResultId `bson:"_id"`
Filename string `bson:"filename"`
}
type ResultId struct {
Host string `bson:"host"`
}
group := bson.D{{"$group", bson.D{{"_id", bson.D{{"host","$host"}}}, {"filename", bson.D{{"$last","$filename"}}}}}}
collection := client.Database("mongodb").Collection("Meta")
cursor, err := collection.Aggregate(ctx, mongo.Pipeline{group})
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &results); err != nil {
fmt.Printf("cursor.All() error:", err)
return c.JSON(http.StatusInternalServerError, err)
}
for _, value := range results {
m = append(m,&bson.M{value.Id.Host:value.Filename})
}
But it does not return a map[string]interface and for information I use the go.mongodb.org package.
append only works on slices, (refer to effective go's section for more on append)
The way to add elements to a map, is simply:
m["key"] = value
Also keep in mind maps need to be initialised which I don't see in your code. Either with make or by giving an initial value (can be an empty value)

Decode value from mongodb cursor which is of interface type

I am writing an abstraction over the official mongo driver which consists of a struct containing a pointer to the collection needed and CRUD methods on it. In order to be able to work with multiple types(all of which have bson adnotations) I use an interface called Storable, but I don't see a way in which I could decode the field without knowing the type exactly. Code snippet:
func (c *Collection) GetAll() ([]models.Storable, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := c.coll.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
var result []models.Storable
for cursor.Next(ctx) {
var doc models.Storable
err = cursor.Decode(&doc)
if err != nil {
return nil, err
}
result = append(result, doc)
}
return result, nil
}
type example:
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
FirstName string `json:"firstName" bson:"firstName"`
LastName string `json:"lastName" bson:"lastName"`
Email string `json:"email" bson:"email"`
Password string `json:"password" bson:"password"`
}
You need to pass in the correct type to decode to somehow. To be able to pass in different types you could use the empty interface{}. However if you pass in the entire slice (e.g. []User) into interface{} you cannot append to it any more without complex usage of reflection.
Using a function to create a new row
As #mkopriva mentioned in the comments, we could pass a function creating a new row to it (or pass it to the collection on initialisation):
func (c *Collection) GetAll(newRow func() models.Storable) ([]models.Storable, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := c.coll.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
var result []models.Storable
for cursor.Next(ctx) {
nRow := newRow()
err = cursor.Decode(nRow)
if err != nil {
return nil, err
}
result = append(result, nRow.(models.Storable))
}
return result, nil
}
You would then call with a function returning the correct type (must be a pointer):
rows, err := c.GetAll(func() models.Storable {
return new(User)
})
Here my reference implementation using json decoding to test this: Playground
Using reflection to create a new row
You could pass in the variable type for a single row:
func (c *Collection) GetAll(row models.Storable) ([]models.Storable, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := c.coll.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
var result []models.Storable
for cursor.Next(ctx) {
nRow := reflect.New(reflect.TypeOf(p)).Interface()
err = cursor.Decode(nRow)
if err != nil {
return nil, err
}
result = append(result, nRow.(models.Storable))
}
return result, nil
}
You would then call this with the correct type:
var user User
rows, err := c.GetAll(user)
Note that I don't use a pointer type here! The pointer is taken via reflection in the function.
Here my reference implementation using json decoding to test this: Playground
The downside of the "one function for all" is that you now have a slice of models.Storable with different data inside and you have to use a type switch or type assertion to work with the data.
For this reason I don't use generic functions for database calls but create a new function for every call I need: e.g. GetAllUsers, GetUserByID, GetAllContacts, etc.
Note: this is one of the things I will rethink when generics get added to Go.

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)
}

What is the bson syntax for $set in UpdateOne for the official mongo-go-driver

I am trying to get some familiarity with the official mongo-go-driver and the right syntax for UpdateOne.
My simplest full example follows:
(NOTE: in order to use this code you will need to substitute in your own user and server names as well as export the login password to the environment as MONGO_PW):
package main
import (
"context"
"fmt"
"os"
"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"
)
type DB struct {
User string
Server string
Database string
Collection string
Client *mongo.Client
Ctx context.Context
}
var db = DB{
User: <username>,
Server: <server_IP>,
Database: "test",
Collection: "movies",
Ctx: context.TODO(),
}
type Movie struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Name string `bson:"name" json:"name"`
Description string `bson:"description" json:"description"`
}
func main() {
if err := db.Connect(); err != nil {
fmt.Println("error: unable to connect")
os.Exit(1)
}
fmt.Println("connected")
// The code assumes the original entry for dunkirk is the following
// {"Name":"dunkirk", "Description":"a world war 2 movie"}
updatedMovie := Movie{
Name: "dunkirk",
Description: "movie about the british evacuation in WWII",
}
res, err := db.UpdateByName(updatedMovie)
if err != nil {
fmt.Println("error updating movie:", err)
os.Exit(1)
}
if res.MatchedCount < 1 {
fmt.Println("error: update did not match any documents")
os.Exit(1)
}
}
// UpdateByName changes the description for a movie identified by its name
func (db *DB) UpdateByName(movie Movie) (*mongo.UpdateResult, error) {
filter := bson.D{{"name", movie.Name}}
res, err := db.Client.Database(db.Database).Collection(db.Collection).UpdateOne(
db.Ctx,
filter,
movie,
)
if err != nil {
return nil, err
}
return res, nil
}
// Connect assumes that the database password is stored in the
// environment variable MONGO_PW
func (db *DB) Connect() error {
pw, ok := os.LookupEnv("MONGO_PW")
if !ok {
fmt.Println("error: unable to find MONGO_PW in the environment")
os.Exit(1)
}
mongoURI := fmt.Sprintf("mongodb+srv://%s:%s#%s", db.User, pw, db.Server)
// Set client options and verify connection
clientOptions := options.Client().ApplyURI(mongoURI)
client, err := mongo.Connect(db.Ctx, clientOptions)
if err != nil {
return err
}
err = client.Ping(db.Ctx, nil)
if err != nil {
return err
}
db.Client = client
return nil
}
The function signature for UpdateOne from the package docs is:
func (coll *Collection) UpdateOne(ctx context.Context, filter interface{},
update interface{}, opts ...*options.UpdateOptions) (*UpdateResult, error)
So I am clearly making some sort of mistake in creating the update interface{} argument to the function because I am presented with this error
error updating movie: update document must contain key beginning with '$'
The most popular answer here shows that I need to use a document sort of like this
{ $set: {"Name" : "The Matrix", "Decription" "Neo and Trinity kick butt" } }
but taken verbatim this will not compile in the mongo-go-driver.
I think I need some form of a bson document to comply with the Go syntax. What is the best and/or most efficient syntax to create this bson document for the update?
After playing around with this for a little while longer I was able to solve the problem after A LOT OF TRIAL AND ERROR using the mongodb bson package by changing the UpdateByName function in my code above as follows:
// UpdateByName changes the description for a movie identified by its name
func (db *DB) UpdateByName(movie Movie) (*mongo.UpdateResult, error) {
filter := bson.D{{"name", movie.Name}}
update := bson.D{{"$set",
bson.D{
{"description", movie.Description},
},
}}
res, err := db.Client.Database(db.Database).Collection(db.Collection).UpdateOne(
db.Ctx,
filter,
update,
)
if err != nil {
return nil, err
}
return res, nil
}
Note the use of bson.D{{$"set", .... It is unfortunate the way MongoDB has implemented the bson package this syntax still does not pass the go-vet. If anyone has a comment to fix the lint conflict below it would be appreciated.
go.mongodb.org/mongo-driver/bson/primitive.E composite literal uses unkeyed fields
In many cases you can replace construction
filter := bson.D{{"name", movie.Name}}
with
filter := bson.M{"name": movie.Name}
if arguments order dowsn't matter

Bson interface has some problems

I uesd MongoDB v3.6.4 with mgo(gopkg.in/mgo.v2) package
Bson
var id interface{}
id = 249678041972736
bson.M{"_id": id}
var id int64
id = 249678041972736
bson.M{"_id": id}
Tow bsons are not same?
eg:
func GetUser(id interface{}) (*User, error) {
session := MongoDB()
defer session.Close()
var m *User
err := session.DB.C("user").Find(&bson.M{"_id": id}).One(&m)
// !!!err: not found
if err != nil {
return nil, err
} else {
return m, nil
}
}
but:
func GetUser(id int64) (*User, error) {
session := MongoDB()
defer session.Close()
var m *User
err := session.DB.C("user").Find(&bson.M{"_id": id}).One(&m)
// !!! err == nil
if err != nil {
return nil, err
} else {
return m, nil
}
}
GetUser(id interface{}) can get err (not found)
GetUser(id int64) can get nil err
Pay attention to error
I used function GetUser and import same value 249678041972736
but different parameter type get different result
Why?
You are putting an unnecessary & in front of the bson.M{…
err := session.DB.C("user").Find(bson.M{"_id": id}).One(&m)
The use of bson.M in the find is also unnecessary, mgo has a call of FindId specifically for the search you are doing.
err := session.DB.C("user").FindId(id).One(&m)
gopkg.in/mgo.v2 is now marked as unmaintained. github.com/globalsign/mgo and github.com/globalsign/mgo/bson are the two maintained forked libraries. I have found no problems using them instead pf gopkg.in