Bson interface has some problems - mongodb

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

Related

What is the difference between the following two codes when using mongo driver in Golang?

Currently I'm working on a project, and I'm looking for a better way to source code.
I wonder What is Different,
func (db *Database) FindData(ctx context.Context, filter *Data) (*Data, error) {
col := db.client.Database(DefaultDatabase).Collection(COLLECTION_DATA)
var data Data
err := col.FindOne(ctx, filter).Decode(&data)
if err != nil {
return nil, err
}
return &data, nil
}
and
func (db *Database) FindData(ctx context.Context, filter *Data) (*Data, error) {
col := db.client.Database(DefaultDatabase).Collection(COLLECTION_DATA)
res := col.FindOne(ctx, filter)
if err:= res.Err(); err != nil {
return nil, err
}
var data Data
err := res.Decode(&reason)
return &data, err
}
What are the possible differences, and which code is better?
I verified that both sources were the same, and realized that I didn't have to use the long version.
There is no need to implement ERROR processing because ERROR processing of the corresponding result is carried out in Decode().
Thanks for Comment :) Burak Serdar

ObjectIdFromHex invalid byte error on identical strings

I'm trying to implement a FindOne method in my Golang REST API. The trouble comes where i have to search by ID. I have to convert the ID into something readable by the database, so i use primitive.ObjectIDFromHex(id)
The problem is that this method throws an error :
2021/06/19 06:56:15 encoding/hex: invalid byte: U+000A
ONLY when i call it with the id that comes from my URL GET params.
I did two versions : one with hard-coded ID, and one with GET ID. See code below.
func Admin(id string) (bson.M, error) {
coll, err := db.ConnectToCollection("admin")
if err != nil {
log.Fatal(err)
}
var admin bson.M
HardCoded := "60cb275c074ab46a1aeda45e"
fmt.Println(HardCoded) // Just to be sure : the two strings seem identical
fmt.Println(id)
objetId, err := primitive.ObjectIDFromHex(id) // throws encoding error
// objetId, err := primitive.ObjectIDFromHex(HardCoded) // Doesnt throw encoding err
if err != nil {
log.Fatal(err)
}
var ctx = context.TODO()
if err := coll.FindOne(ctx, bson.M{"_id": objetId}).Decode(&admin); err != nil {
log.Fatal(err)
}
return admin, nil
}
Of course, you'll want to know where the param id comes from.
Here you go :
func GetAdmin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
admin, err := Admin(params["id"]) // Calling the Admin function above
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusUnauthorized)
} else {
JSON, err := json.Marshal(admin)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Write(JSON)
}
}
Trim the line feed from the end of id:
id = strings.TrimSpace(id)
Use the %q format verb when debugging issues like this. The line feed is clearly visible in this output:
fmt.Printf("%q\n", HardCoded) // prints "60cb275c074ab46a1aeda45e"
fmt.Printf("%q\n", id) // prints "60cb275c074ab46a1aeda45e\n"

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.

Handling MongoDB Errors depending on it's type

I am getting the data of an account using the ID of it. Currently, when I make this query the mongo-go-driver gives an error and I want to handle this error differently, depending on its type. For example if the document doesn't exist I want to return a 404 but lets suppose that the instance holding my mongodb falls, in this case I want to return a 500. How can I handle the error type:
func (dao MongoDAO) Get(ctx *gin.Context, filter bson.M, entity interface{}) error {
context, _ := context.WithTimeout(context.Background(), 5*time.Second)
if err := dao.Collection.FindOne(context, filter).Decode(entity); err != nil {
return err
}
return nil
}
You could use the Error variables defined in the mongo-go-driver docs (https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo?tab=doc#pkg-variables). With these you might be able to do something like this:
func (dao MongoDAO) Get(ctx *gin.Context, filter bson.M, entity interface{}) error {
context, _ := context.WithTimeout(context.Background(), 5*time.Second)
if err := dao.Collection.FindOne(context, filter).Decode(entity); err != nil {
if err == mongo.ErrNoDocuments {
// Return the 404
}
return err
}
return nil
}

Multiple find results handling

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
}