Deserialize cursor into array with mongo-go-driver and interface - mongodb

I create an api using golang, i would like to create some functionnal test, for that i create an interface to abstract my database. But for that i need to be able to convert the cursor to an array without knowing the type.
func (self *KeyController) GetKey(c echo.Context) (err error) {
var res []dto.Key
err = db.Keys.Find(bson.M{}, 10, 0, &res)
if err != nil {
fmt.Println(err)
return c.String(http.StatusInternalServerError, "internal error")
}
c.JSON(http.StatusOK, res)
return
}
//THE FIND FUNCTION ON THE DB PACKAGE
func (s MongoCollection) Find(filter bson.M, limit int, offset int, res interface{}) (err error) {
ctx := context.Background()
var cursor *mongo.Cursor
l := int64(limit)
o := int64(offset)
objectType := reflect.TypeOf(res).Elem()
cursor, err = s.c.Find(ctx, filter, &options.FindOptions{
Limit: &l,
Skip: &o,
})
if err != nil {
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
result := reflect.New(objectType).Interface()
err := cursor.Decode(&result)
if err != nil {
panic(err)
}
res = append(res.([]interface{}), result)
}
return
}
Does someone have an idea?

You can call directly the "All" method:
ctx := context.Background()
err = cursor.All(ctx, res)
if err != nil {
fmt.Println(err.Error())
}
For reference:
https://godoc.org/go.mongodb.org/mongo-driver/mongo#Cursor.All

i think you want to encapsulate the Find method for mongo query.
Using the reflect package i have improved your code by adding an additional parameter that serves as a template to instantiate new instances of slice items.
func (m *MongoDbModel) FindAll(database string, colname string, obj interface{}, parameter map[string]interface{}) ([]interface{}, error) {
var list = make([]interface{}, 0)
collection, err := m.Client.Database(database).Collection(colname).Clone()
objectType := reflect.TypeOf(obj).Elem()
fmt.Println("objectype", objectType)
if err != nil {
log.Println(err)
return nil, err
}
filter := bson.M{}
filter["$and"] = []bson.M{}
for key, value := range parameter {
filter["$and"] = append(filter["$and"].([]bson.M), bson.M{key: value})
}
cur, err := collection.Find(context.Background(), filter)
if err != nil {
log.Fatal(err)
}
defer cur.Close(context.Background())
for cur.Next(context.Background()) {
result := reflect.New(objectType).Interface()
err := cur.Decode(result)
if err != nil {
log.Println(err)
return nil, err
}
list = append(list, result)
}
if err := cur.Err(); err != nil {
return nil, err
}
return list, nil
}
The difference is that FindAll method returns []interface{}, where err := cur.Decode(result) directly consumes a pointer like the result variable.

Related

IndexStats Query in DocumentDB returning 0 ops

I'm trying to build a Documentdb Prometheus exporter. I'm trying to get the IndexStats to identify the unused Indices. But I'm getting ops value as 0 even though the ops value is not 0. Can you please help me identify the error?
This the Go code for the same.
databases, err := client.ListDatabaseNames(ctx, bson.D{})
if err != nil{
return make(map[string]map[string]interface{}), err
}
var indexMap map[string]map[string]interface{} = make(map[string]map[string]interface{})
for _, database := range databases {
collections, err := client.Database(database).ListCollectionNames(ctx, bson.D{})
if err != nil {
return make(map[string]map[string]interface{}), err
}
for _, collectionName := range collections {
collection := client.Database(database).Collection(collectionName)
indexStats := bson.D{{Key: "$indexStats", Value: bson.M{}}}
cursor, err := collection.Aggregate(ctx, mongo.Pipeline{indexStats})
if err != nil {
return make(map[string]map[string]interface{}), err
}
var indices []bson.M
err = cursor.All(ctx, &indices)
if err != nil {
return make(map[string]map[string]interface{}), err
}
err = cursor.Close(ctx)
if err != nil {
return make(map[string]map[string]interface{}), err
}
fmt.Print(database + " " + collectionName + " ")
fmt.Println(indices)
indicesBytes, err := json.Marshal(indices)
if err != nil {
return make(map[string]map[string]interface{}), err
}
var indicesJson []interface{}
err = json.Unmarshal(indicesBytes, &indicesJson)
if err != nil {
return make(map[string]map[string]interface{}), err
}
if _, ok := indexMap[database]; !ok {
indexMap[database] = make(map[string]interface{})
}
indexMap[database][collectionName] = indicesJson
}
}
May I know the reason and the fix for this issue?
Thanks in Advance :)

session was not created by this client - MongoDB, Golang

I am trying to creat transaction in MongoDB with Golang and Iris. Problem is that transaction did not accept iris context and Con, I don't know why this thing happened. Can you tell me what I am doing wrong here?
Main.go Using Iris
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
app.Use(recover.New())
app.Use(logger.New())
// Resource: http://localhost:8080
app.Get("/", func(ctx iris.Context) {
ctx.JSON(iris.Map{"message": "Welcome to Woft Bank"})
})
// API endpoints
router.SetRoute(app)
app.Listen(PORT)}
Router
func SetRoute(app *iris.Application) {
userRoute := app.Party("/user")
{
userRoute.Post("/register", middleware.UserValidator, controller.CreateUser)
userRoute.Get("/detail", middleware.UserValidator, controller.GetUserBalanceWithUserID)
userRoute.Patch("/transfer", middleware.TransferValidator, controller.Transfer)
}}
Transacion function (session was not created by this client)
func Transfer(ctx iris.Context) {
senderID := ctx.URLParam("from")
receiverID := ctx.URLParam("to")
amount, _ := strconv.ParseInt(ctx.URLParam("amount"), 10, 64)
session, err := Config.DB().StartSession()
if err != nil {
handleErr(ctx, err)
return
}
defer session.EndSession(ctx)
callback := func(sessCtx mongo.SessionContext) (interface{}, error) {
upsert := false
after := options.After
opt := options.FindOneAndUpdateOptions{
ReturnDocument: &after,
Upsert: &upsert,
}
sender := Models.User{}
filter := bson.M{"username": senderID}
update := bson.M{"$inc": bson.M{"balance": -amount}}
//FindOneAndUpdate did not accept sessCtx
err := UserCollection.FindOneAndUpdate(sessCtx, filter, update, &opt).Decode(&sender)
if err != nil {
return nil, err
}
if sender.Balance < 0 {
return nil, errors.New("sender's balance is not enough")
}
filter = bson.M{"username": receiverID}
update = bson.M{"$inc": bson.M{"balance": +amount}}
_, err = UserCollection.UpdateOne(sessCtx, filter, update)
if err != nil {
return nil, err
}
return sender, nil
}
result, err := session.WithTransaction(ctx, callback)
if err != nil {
handleErr(ctx, err)
return
}
response(result, "success", ctx)}

How do I convert a string type url param into an acceptable type for my mongodb query?

I'm trying to find and retrieve a related number of fields from my mongodb via a url param sent from a get request.
func main() {
r := mux.NewRouter()
r.HandleFunc("/user", createUser).Methods("POST")
r.HandleFunc("/suggest", searchCity).Methods("GET") // The route for the function
fmt.Println("Server running at port 8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
func searchCity(w http.ResponseWriter, r *http.Request) {
//ctx := context.Background()
DB := setup() // setup() returns a mongo.Database type
values := r.URL.Query()
city := values.Get("city_name") // so the route would ultimately be `/suggest?city_name=<cityname>` (I think?)
cityCollection := DB.Collection("city")
cursor, err := cityCollection.Find(r.Context(), city) // options.Find().SetProjection(projection))
if err != nil {
log.Fatal(err)
}
var cityList []bson.M
if err = cursor.All(r.Context(), &cityList); err != nil {
log.Fatal(err)
}
for _, cityList := range cityList {
fmt.Println(cityList["all_names"])
fmt.Println(cityList["country_name"])
}
}
I am specifically trying to return the all_names and country_name fields from the collection and tried to use SetProjection() but I may have been using it incorrectly so not sure about that one.
However, I keep recieveing this error:
cannot transform type string to a BSON Document: WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel
Is there a way to convert this into a format that the mongodb drivers would accept?
As documented and indicated by the error, you need to use the bson package to use a bson object to query mongodb.
collection.Find(ctx, bson.D{{"name", city}})
Below is a working example, which I have tested based on your comment.
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"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 cityHandler(collection *mongo.Collection) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
city := r.URL.Query().Get("city_name")
var q bson.D
if city != "" {
q = bson.D{{"name", city}}
} else {
q = bson.D{{}}
}
cur, currErr := collection.Find(ctx, q)
if currErr != nil {
http.Error(w, currErr.Error(), http.StatusInternalServerError)
}
defer cur.Close(ctx)
var result []primitive.M
if err := cur.All(ctx, &result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(result)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func main() {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://root:example#localhost:27017"))
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
docs := []interface{}{
bson.D{{"name", "Wuhu"}},
bson.D{{"name", "Shexian"}},
}
collection := client.Database("china").Collection("cities")
_, err = collection.InsertMany(ctx, docs)
if err != nil {
panic(err)
}
http.HandleFunc("/", cityHandler(collection))
log.Fatal(http.ListenAndServe(":8080", nil))
}

How to have mongodb decode struct passed into function

The Decode step of the following code does not populate the original document object correctly. It overwrites it with a bson object.
func main() {
c := Call{}
dbGetObject("collection", &c)
}
func dbGetObject(collectionName string, document interface{}) (err error) {
uri, creds, auth := dbGetAuth()
clientOpts := options.Client().ApplyURI(uri).SetAuth(creds)
client, err := mongo.Connect(context.TODO(), clientOpts)
if err != nil {
log.Fatal(err)
return err
}
defer client.Disconnect(context.TODO())
collection := client.Database(auth.Database).Collection(collectionName)
err = collection.FindOne(context.TODO(), bson.M{"number": "12345"}).Decode(&document)
if err != nil {
log.Fatal(err)
return err
}
return nil
}
Yet the following code does work properly:
func dbGetObject(collectionName string) (err error) {
uri, creds, auth := dbGetAuth()
clientOpts := options.Client().ApplyURI(uri).SetAuth(creds)
client, err := mongo.Connect(context.TODO(), clientOpts)
if err != nil {
log.Fatal(err)
return err
}
defer client.Disconnect(context.TODO())
collection := client.Database(auth.Database).Collection(collectionName)
c := Call{}
err = collection.FindOne(context.TODO(), bson.M{"number": "12345"}).Decode(&c)
if err != nil {
log.Fatal(err)
return err
}
return nil
}
The only difference being that the instance of the struct is passed into the function vs instantiated in the dbGetObject function. What am I doing wrong?
In the first example, the type of document is interface.
if you fix the argument type as below, it will work correctly.
func dbGetObject(collectionName string, document *Call)
I actually realized what is going on. In the call to the function and then to Decode I am passing a pointer. So, it's actually passing a pointer to a pointer into the Decode call. So the fix is to change the decode call from:
err = collection.FindOne(context.TODO(), bson.M{"number": "12345"}).Decode(&document)
to
err = collection.FindOne(context.TODO(), bson.M{"number": "12345"}).Decode(document)

How to find by id in golang and mongodb

I need get values using ObjectIdHex and do update and also view the result. I'm using mongodb and golang.But following code doesn't work as expected
package main
import (
"fmt"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Id bson.ObjectId `json:"id" bson:"_id,omitempty"`
Name string
Phone string
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
const (
DB_NAME = "gotest"
DB_COLLECTION = "pepole_new1"
)
func main() {
session, err := mgo.Dial("localhost")
checkError(err)
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB(DB_NAME).C(DB_COLLECTION)
err = c.DropCollection()
checkError(err)
ale := Person{Name:"Ale", Phone:"555-5555"}
cla := Person{Name:"Cla", Phone:"555-1234-2222"}
kasaun := Person{Name:"kasaun", Phone:"533-12554-2222"}
chamila := Person{Name:"chamila", Phone:"533-545-6784"}
fmt.Println("Inserting")
err = c.Insert(&ale, &cla, &kasaun, &chamila)
checkError(err)
fmt.Println("findbyID")
var resultsID []Person
//err = c.FindId(bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")).One(&resultsID)
err = c.FindId(bson.M{"Id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")}).One(&resultsID)
checkError(err)
if err != nil {
panic(err)
}
fmt.Println("Phone:", resultsID)
fmt.Println("Queryingall")
var results []Person
err = c.Find(nil).All(&results)
if err != nil {
panic(err)
}
fmt.Println("Results All: ", results)
}
FindId(bson.M{"Id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")}).One(&resultsID) didn't work for me and giving me following output
Inserting
Queryingall
Results All: [{ObjectIdHex("56bddee2cfa93bfe3d3504a1") Ale 555-5555} {ObjectIdHex("56bddee2cfa93bfe3d3504a2") Cla 555-1234-2222} {ObjectIdHex("56bddee2cfa93bfe3d3504a3") kasaun 533-12554-2222} {ObjectIdHex("56bddee2cfa93bfe3d3504a4") chamila 533-545-6784}]
findbyID
panic: not found
goroutine 1 [running]:
main.checkError(0x7f33d524b000, 0xc8200689b0)
How can i fix this problem? i need get value using oid and do update also how can i do that
Use can do the same with Golang official driver as follows:
// convert id string to ObjectId
objectId, err := primitive.ObjectIDFromHex("5b9223c86486b341ea76910c")
if err != nil{
log.Println("Invalid id")
}
// find
result:= client.Database(database).Collection("user").FindOne(context.Background(), bson.M{"_id": objectId})
user := model.User{}
result.Decode(user)
It should be _id not Id:
c.FindId(bson.M{"_id": bson.ObjectIdHex("56bdd27ecfa93bfe3d35047d")})
Some sample code that i use.
func (model *SomeModel) FindId(id string) error {
db, ctx, client := Drivers.MongoCollection("collection")
defer client.Disconnect(ctx)
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": bson.M{"$eq": objID}}
if err := db.FindOne(ctx, filter).Decode(&model); err != nil {
//fmt.Println(err)
return err
}
fmt.Println(model)
return nil
}