Optional Find in .Find() MongoDB query - mongodb

My listing may receive a filter parameter, but this parameter is mandatory.
status := r.FormValue("status")
var bet []*Bet
if err := db.C(collectionName).Find(bson.M{"status": status}).Sort("-data-criacao").All(&bet); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
If the parameter was not informed, the query returns no result.
To return all the results, I used to do the following
var bet []*Bet
if err := db.C(collectionName).Find(nil).Sort("-data-criacao").All(&bet); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
How can I meet both alternatives?

Simply use an if statement to construct your query based on whether the parameter is supplied.
Something like this:
status := r.FormValue("status")
var bet []*Bet
var filter bson.M
if status != "" {
filter = bson.M{"status": status}
}
err := db.C(collectionName).Find(filter).Sort("-data-criacao").All(&bet)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

Related

Receiving error(*errors.errorString) *{s: "pq: unexpected DataRow in simple query execution"}

The error
(*errors.errorString) *{s: "pq: unexpected DataRow in simple query execution"}
appears after the line with the commentary. Didn't find any solution online. Since stackoverflow asks for more details, this is an update query that is supposed to update a todo and a list of subtasks in the database. The exact error is in the question topic. I post the complete code for the function that returns the error.
func (t *TodoTable) UpdateTodo(ctx context.Context, todo *Todo, t_id int) error {
tx, err := t.sqlxdb.BeginTxx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
rollback_err := func(err error) error {
if err2 := tx.Rollback(); err2 != nil {
return fmt.Errorf("%v; %v", err, err2)
}
return err
}
row := tx.QueryRowxContext(ctx, "UPDATE todos SET todo_name=$1, deadline=$2, updated_at=$3 WHERE todo_id=$4 returning todo_id", todo.TodoName, todo.Deadline, todo.UpdatedAt, t_id)
if row.Err() != nil {
return rollback_err(err)
}
var subs_ids []int
// Getting subs ids from database
query := fmt.Sprintf("SELECT sub_id FROM subs WHERE todo_id=%d", t_id)
// THE ERROR COMES AFTER EXECUTING THE LINE BELOW
rows, err := tx.Query(query)
if err != nil {
rollback_err(err)
}
if rows != nil {
for rows.Next() {
var sub_id int
err = rows.Scan(&sub_id)
if err != nil {
rollback_err(err)
}
subs_ids = append(subs_ids, sub_id)
}
if err := tx.Commit(); err != nil {
return rollback_err(err)
}
}
// Updating subs
for i, sub := range todo.Subs {
_, err = tx.ExecContext(ctx, fmt.Sprintf("UPDATE subs SET sub_name='%s' WHERE sub_id=%d", sub.Sub_name, subs_ids[i]))
if err != nil {
return rollback_err(err)
}
}
return nil
}

MongoDB driver pagination

Currently I am able to return all my products from the collection.
I however want to be able to return products that come after a specific product ID (which would be the last one on the client side so they could load more)
Current way (return all)
query := bson.M{}
var product ReturnedProdcut
var products []ReturnedProduct
cur, err := mg.Db.Collection("products").Find(c.Request().Context(), query)
if err != nil {
fmt.Println(err)
}
for cur.Next(c.Request().Context()) {
err := cur.Decode(&product)
if err != nil {
fmt.Println(err)
}
products = append(products, product)
}
// return products list in JSON format
return c.JSON(http.StatusOK, products)
New Way Attempt(return based on page)
afterID := c.QueryParam("afterID")
if afterID == "" {
// get from start of collection based on latest date
}
// get 10 products after this ID, if no ID then get from start
query := bson.M{}
var product ReturnedProduct
var products []Returnedproduct
//.find(afterId).limit(10) - something like this?
cur, err := mg.Db.Collection("products").Find(c.Request().Context(), query)
if err != nil {
fmt.Println(err)
}
for cur.Next(c.Request().Context()) {
err := cur.Decode(&product)
if err != nil {
fmt.Println(err)
}
products = append(products, product)
}
// return products list in JSON format
return c.JSON(http.StatusOK, products)
The official MongoDB Go driver also has a *FindOptions optional parameter you could also explore.
pageOptions := options.Find()
pageOptions.SetSkip(int64(page)) //0-i
pageOptions.SetLimit(int64(limit)) // number of records to return
cur, err := userCollection.Find(c.Request().Context(), bson.D{{}}, pageOptions)
if err != nil {
// handle error
}
defer cur.Close(ctx)
var products []Returnedproduct
for cur.Next(c.Request().Context()) {
var product Returnedproduct
if err := cur.Decode(&product); err != nil {
// handle error
}
products = append(products, &product)
}
if err := cur.Err(); err != nil {
// handle error
}
You may construct a query where _id is greater than afterID, in which case you should also specify sorting by _id. For sorting and for setting a limit, you may use options.FindOptions.
You also should use Cursor.All() to decode all results and not one-by-one.
This is how it could look like:
query := bson.M{"_id": bson.M{"$gt": afterID}}
opts := options.Find().
SetSort(bson.M{"_id": 1}).
SetLimit(10)
ctx := c.Request().Context()
curs, err := mg.Db.Collection("products").Find(ctx, query, opts)
if err != nil {
// Handle error
}
var products []Returnedproduct
if err = curs.All(ctx, &products); err != nil {
// Handle error
}
return c.JSON(http.StatusOK, products)

How to get DecodeBytes() output without canonical extended JSON additions

I use DecodeBytes() function to get the data from mongoDB (as the struct of the data can varies) with mongo-driver for Go.
My problem is when one of the values is int/double (and not string).
In that case it add adds some stuff of canonical extended JSON, for example 3 to "$numberDouble": "3.0".
How can I remove those additions of the canonical extended JSON?
func (m *Mongoclient) Find(collection string, filter interface{}) string {
findResult := m.Db.Collection(collection).FindOne(m.Ctx, filter)
if findResult.Err() != nil {
fmt.Println(findResult.Err().Error())
return ""
}
db, err := findResult.DecodeBytes()
if err != nil {
fmt.Println(err.Error())
return ""
}
return db.String()
}
The solution was to use Decode function to bson.M and than json.Marshal it:
var document bson.M
err := findResult.Decode(&document)
if err != nil {
fmt.Println(err.Error())
return ""
}
resBytes, err := json.Marshal(document)
if err != nil {
fmt.Println(err)
return ""
}
return string(resBytes)

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

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.

Official mongo-go-driver using sessions

Is there any example of using sessions with official mongodb driver for golang? I am trying to use sessions to take advantage from transactions and it seems that just reading tests on github I can’t find the way to do it.
To be more specific I am struggling now with this:
session, err := pool.StartSession()
if err != nil {
log.Println("Could not create db session", err)
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: http.StatusInternalServerError,
}, err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
defer session.EndSession(ctx)
var db *mongo.Database
Everything fine with code above but when I do:
db = session.Database("testrest")
It gives the error:
session.Database undefined (type mongo.Session has no field or method
Database)
Which seems to work with mgo package… How do I select the database and run queries with session?
The solution can be found here: https://github.com/simagix/mongo-go-examples/blob/master/examples/transaction_test.go#L68
if session, err = client.StartSession(); err != nil {
t.Fatal(err)
}
if err = session.StartTransaction(); err != nil {
t.Fatal(err)
}
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
if result, err = collection.UpdateOne(sc, bson.M{"_id": id}, update); err != nil {
t.Fatal(err)
}
if result.MatchedCount != 1 || result.ModifiedCount != 1 {
t.Fatal("replace failed, expected 1 but got", result.MatchedCount)
}
if err = session.AbortTransaction(sc); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
session.EndSession(ctx)