How to use mongodb/mongo-go-driver to perform efficient paging - mongodb

I read in the following article that it is more efficient to use the natural ordering of _id to perform pagination because skip always starts from the beginning of the collection.
Fast and Efficient Pagination in MongoDB
// Page 1
db.students.find().limit(10)
// Page 2
last_id = ... # logic to get last_id
db.students.find({'_id': {'$gt': last_id}}).limit(10)
But I have no idea how to perform the above using the mongodb/mongo-go-driver.

you can create a new func, dont forget to pass http.writer to read parameter.
func Pagination(r *http.Request, FindOptions *options.FindOptions) (int64, int64) {
if r.URL.Query().Get("page") != "" && r.URL.Query().Get("limit") != "" {
page, _ := strconv.ParseInt(r.URL.Query().Get("page"), 10, 32)
limit, _ := strconv.ParseInt(r.URL.Query().Get("limit"), 10, 32)
if page == 1 {
FindOptions.SetSkip(0)
FindOptions.SetLimit(limit)
return page, limit
}
FindOptions.SetSkip((page - 1) * limit)
FindOptions.SetLimit(limit)
return page, limit
}
FindOptions.SetSkip(0)
FindOptions.SetLimit(0)
return 0, 0
}
just call
Pagination(r, options)
example
options := options.Find()
page, limit := parameter.Pagination(r, options)
// page, limit as response for header payload

The cursor.skip() method requires the server to scan from the beginning of the input results set before beginning to return results. As the offset increases, cursor.skip() will become slower. While range queries can use indexes to avoid scanning unwanted documents, typically yielding better performance as the offset grows compared to using cursor.skip() for pagination. See more information on MongoDB: Pagination Example
Using the current version of mongo-go-driver (v0.0.15).An example to perform pagination showing latest entry first:
func Paginate(collection *mongo.Collection, startValue objectid.ObjectID, nPerPage int64) ([]bson.Document, *bson.Value, error) {
// Query range filter using the default indexed _id field.
filter := bson.VC.DocumentFromElements(
bson.EC.SubDocumentFromElements(
"_id",
bson.EC.ObjectID("$gt", startValue),
),
)
var opts []findopt.Find
opts = append(opts, findopt.Sort(bson.NewDocument(bson.EC.Int32("_id", -1))))
opts = append(opts, findopt.Limit(nPerPage))
cursor, _ := collection.Find(context.Background(), filter, opts...)
var lastValue *bson.Value
var results []bson.Document
for cursor.Next(context.Background()) {
elem := bson.NewDocument()
err := cursor.Decode(elem)
if err != nil {
return results, lastValue, err
}
results = append(results, *elem)
lastValue = elem.Lookup("_id")
}
return results, lastValue, nil
}
An example to call the pagination function above:
database := client.Database("databaseName")
collection := database.Collection("collectionName")
startObjectID, _ := objectid.FromHex("5bbafea2b5e14ee3a298fa4a")
// Paginate only the latest 20 documents
elements, lastID, err := Paginate(collection, startObjectID, 20)
for _, e := range elements {
fmt.Println(&e)
}
// Last seen ObjectID can be used to call next Paginate()
fmt.Println("Last seen ObjectID: ", lastID.ObjectID())
Note that you can also substitute the _id field with another indexed field.

Set page=0 and limit=0 if no pagination is required.
func GetUsers (page, limit int) {
filter := bson.D{{}} // selects all documents
options := new(options.FindOptions)
if limit != 0 {
if page == 0 {
page = 1
}
options.SetSkip(int64((page - 1) * limit))
options.SetLimit(int64(limit))
}
cursor, err := mongoCollection.Find(context.TODO(), filter, options)
...
}

Related

Converting MongoDB $max result to golang data

I try to get max values from MongoDB collection from my Go code.
What type should I use to decode result?
When I use bson.D{} as val2 type the result looks like [{_id <nil>} {max 66} {cnt 14}].
Here's the code:
filter := []bson.M{{
"$group": bson.M{
"_id": nil,
"max": bson.M{"$max": "$hellid"},
}},
}
cursor, err := collection.Aggregate(ctx, filter)
for cursor.Next(ctx) {
val2 := ???
err := cursor.Decode(&val2)
fmt.Printf("cursor: %v, value: %v\n", cursor.Current, val2)
}
}
Using bson.D already works as you presented. The problem may be you can't "easily" get out the max and cnt values.
Model your result document with a struct like this:
type result struct {
Max int `bson:"max"`
Count int `bson:"cnt"
}
Although cnt is not produced by the example code you provided.
And then:
var res result
err := cursor.Decode(&res)

Convert a string slice to a BSON array

I am trying to insert an array into a MongoDB instance using Go. I have the [] string slice in Go and want to convert it into a BSON array to pass it to the DB using the github.com/mongodb/mongo-go-driver driver.
var result bson.Array
for _, data := range myData {
value := bson.VC.String(data)
result.Append(value)
}
This loops over each element of my input data and tries to append it to the BSON array. However the line with the Append() fails with panic: document is nil. How should I do this conversion?
Edit: The code in the question and this answer is no longer relevant because the bson.Array type was deleted from the package. At the time of this edit, the bson.A and basic slice operations should be used to construct arrays.
Use the factory function NewArray to create the array:
result := bson.NewArray()
for _, data := range myData {
value := bson.VC.String(data)
result.Append(value)
}
As mentioned by #Cerise bson.Array has since been deleted. I do this with multiple utility functions as follows:
func BSONStringA(sa []string) (result bson.A) {
result = bson.A{}
for_, e := range sa {
result = append(result, e)
}
return
}
func BSONIntA(ia []string) (result bson.A) {
// ...
}
Converting a slice of string (ids) to BSON array
var objIds bson.A
for _, val := range ids {
objIds = append(objIds, val)
}
log.Println(objIds)

How will we entered the data according to the number of result and also according the count field?

By using go api I'm retrieving the an array object. like given below:-
[
{0 1 Sunday 1 21600 25200 1}
{0 1 Sunday 2 28800 32400 2}
{0 1 Sunday 3 36000 39600 1}
]
This data will be arranged using struct:-
type ProviderSpot struct {
Id int `json:"_id" bson:"_id"`
PId int `json:"pid" bson:"pid"`
Day string `json:"day" bson:"day"`
TimeSlug int `json:"time_slug" bson:"time_slug"`
StartTime int64 `json:"start_time" bson:"start_time"`
EndTime int64 `json:"end_time" bson:"end_time"`
Count int `json:"count" bson:"count"`
}
type ProviderSpots []ProviderSpot
See in the array object I have an count values in each object 1,2,1 then I have to store this record like that those record having count they will store in the available_spot only one time means that the upper record will save in the collection only one time having there count value 1 after that the left record will remains there count value 0,1,0 then
those record having there count field value more than 0 they will save that number of times having count value in the addition_spot. The code of golang I'm using is this:-
func SaveProviderSpot(c *gin.Context) {
response := ResponseController{}
values := c.PostForm("array")
var err error
byt := []byte(values)
var result models.ProviderSpots
if err = json.Unmarshal(byt, &result); err != nil{
fmt.Println(err)
}
fmt.Println(result)
for i := 0; i < len(result); i++ {
lastValue :=result[i].Count-1
if lastValue != -1 {
providerspot.PId = result[i].PId
providerspot.Day = result[i].Day
providerspot.TimeSlug = result[i].TimeSlug
providerspot.StartTime = result[i].StartTime
providerspot.EndTime = result[i].EndTime
providerspot.Count = result[i].Count - lastValue
id, _ := models.GetAutoIncrementCounter(config.ProvidersSpotsCounterId, config.ProvidersSpotsCollection)
providerspot.Id = id
fmt.Println("Here We go now :- ", &providerspot)
err = models.AddProviderSpot(&providerspot)
}
}
}
Give some example of this which will solve this. Thanks for your valueable time spending on this question.
I solved that question answer but Can anyone tell me that will right for my code or not:-
func SaveProviderSpot(c *gin.Context) {
response := ResponseController{}
values := c.PostForm("array")
var err error
byt := []byte(values)
var result models.ProviderSpots
if err = json.Unmarshal(byt, &result); err != nil{
fmt.Println(err)
}
fmt.Println(result)
for i := 0; i < len(result); i++ {
for j := 1; j <= result[i].Count; j++ {
// lastValue := result[i].Count-1
// if lastValue != -1 {
if j == 1{
providerspot.PId = result[i].PId
providerspot.Day = result[i].Day
providerspot.TimeSlug = result[i].TimeSlug
providerspot.StartTime = result[i].StartTime
providerspot.EndTime = result[i].EndTime
providerspot.Count = 1//result[i].Count - lastValue
id, _ := models.GetAutoIncrementCounter(config.ProvidersSpotsCounterId, config.ProvidersSpotsCollection)
providerspot.Id = id
fmt.Println("Here We go now :- ", &providerspot)
err = models.AddProviderSpot(&providerspot)
}else{
providerspot.PId = result[i].PId
providerspot.Day = result[i].Day
providerspot.TimeSlug = result[i].TimeSlug
providerspot.StartTime = result[i].StartTime
providerspot.EndTime = result[i].EndTime
providerspot.Count = 1//result[i].Count - lastValue
id, _ := models.GetAutoIncrementCounter(config.AdditionalProviderCounterSpot, config.AdditionalProviderSpot)
providerspot.Id = id
err = models.AddAdditionalProviderSpot(&providerspot)
}
}
}
}
This will do want I want but I'm confused that it is right for me or not.

Golang Postgresql Array

If I have a table that returns something like:
id: 1
names: {Jim, Bob, Sam}
names is a varchar array.
How do I scan that back into a []string in Go?
I'm using lib/pg
Right now I have something like
rows, err := models.Db.Query("SELECT pKey, names FROM foo")
for rows.Next() {
var pKey int
var names []string
err = rows.Scan(&pKey, &names)
}
I keep getting:
panic: sql: Scan error on column index 1: unsupported Scan, storing driver.Value type []uint8 into type *[]string
It looks like I need to use StringArray
https://godoc.org/github.com/lib/pq#StringArray
But, I think I'm too new to Go to understand exactly how to use:
func (a *StringArray) Scan(src interface{})
You are right, you can use StringArray but you don't need to call the
func (a *StringArray) Scan(src interface{})
method yourself, this will be called automatically by rows.Scan when you pass it anything that implements the Scanner interface.
So what you need to do is to convert your []string to *StringArray and pass that to rows.Scan, like so:
rows, err := models.Db.Query("SELECT pKey, names FROM foo")
for rows.Next() {
var pKey int
var names []string
err = rows.Scan(&pKey, (*pq.StringArray)(&names))
}
Long Story Short, use like this to convert pgSQL array to GO array, here 5th column is coming as a array :
var _temp3 []string
for rows.Next() {
// ScanRows scan a row into temp_tbl
err := rows.Scan(&_temp, &_temp0, &_temp1, &_temp2, pq.Array(&_temp3))
In detail :
To insert a row that contains an array value, use the pq.Array function like this:
// "ins" is the SQL insert statement
ins := "INSERT INTO posts (title, tags) VALUES ($1, $2)"
// "tags" is the list of tags, as a string slice
tags := []string{"go", "goroutines", "queues"}
// the pq.Array function is the secret sauce
_, err = db.Exec(ins, "Job Queues in Go", pq.Array(tags))
To read a Postgres array value into a Go slice, use:
func getTags(db *sql.DB, title string) (tags []string) {
// the select query, returning 1 column of array type
sel := "SELECT tags FROM posts WHERE title=$1"
// wrap the output parameter in pq.Array for receiving into it
if err := db.QueryRow(sel, title).Scan(pq.Array(&tags)); err != nil {
log.Fatal(err)
}
return
}
Note: that in lib/pq, only slices of certain Go types may be passed to pq.Array().
Another example in which varchar array in pgSQL in generated at runtime in 5th column, like :
--> predefined_allow false admin iam.create {secrets,configMap}
I converted this as,
Q := "SELECT ar.policy_name, ar.allow, ar.role_name, pro.operation_name, ARRAY_AGG(pro.resource_id) as resources FROM iam.authorization_rules ar LEFT JOIN iam.policy_rules_by_operation pro ON pro.id = ar.operation_id GROUP BY ar.policy_name, ar.allow, ar.role_name, pro.operation_name;"
tx := g.db.Raw(Q)
rows, _ := tx.Rows()
defer rows.Close()
var _temp string
var _temp0 bool
var _temp1 string
var _temp2 string
var _temp3 []string
for rows.Next() {
// ScanRows scan a row into temp_tbl
err := rows.Scan(&_temp, &_temp0, &_temp1, &_temp2, pq.Array(&_temp3))
if err != nil {
return nil, err
}
fmt.Println("Query Executed...........\n", _temp, _temp0, _temp1, _temp2, _temp3)
}
Output :
Query Executed...........
predefined_allow false admin iam.create [secrets configMap]

How do I convert from a slice of interface{} to a slice of my struct type in Go? [duplicate]

This question already has answers here:
Type converting slices of interfaces
(9 answers)
Closed 3 years ago.
func GetFromDB(tableName string, m *bson.M) interface{} {
var (
__session *mgo.Session = getSession()
)
//if the query arg is nil. give it the null query
if m == nil {
m = &bson.M{}
}
__result := []interface{}{}
__cs_Group := __session.DB(T_dbName).C(tableName)
__cs_Group.Find(m).All(&__result)
return __result
}
call
GetFromDB(T_cs_GroupName, &bson.M{"Name": "Alex"}).([]CS_Group)
runtime will give me panic:
panic: interface conversion: interface is []interface {}, not []mydbs.CS_Group
how convert the return value to my struct?
You can't automatically convert between a slice of two different types – that includes []interface{} to []CS_Group. In every case, you need to convert each element individually:
s := GetFromDB(T_cs_GroupName, &bson.M{"Name": "Alex"}).([]interface{})
g := make([]CS_Group, 0, len(s))
for _, i := range s {
g = append(g, i.(CS_Group))
}
You need to convert the entire hierarchy of objects:
rawResult := GetFromDB(T_cs_GroupName, &bson.M{"Name": "Alex"}).([]interface{})
var result []CS_Group
for _, m := range rawResult {
result = append(result,
CS_Group{
SomeField: m["somefield"].(typeOfSomeField),
AnotherField: m["anotherfield"].(typeOfAnotherField),
})
}
This code is for the simple case where the type returned from mgo matches the type of your struct fields. You may need to sprinkle in some type conversions and type switches on the bson.M value types.
An alternate approach is to take advantage of mgo's decoder by passing the output slice as an argument:
func GetFromDB(tableName string, m *bson.M, result interface{}) error {
var (
__session *mgo.Session = getSession()
)
//if the query arg is nil. give it the null query
if m == nil {
m = &bson.M{}
}
__result := []interface{}{}
__cs_Group := __session.DB(T_dbName).C(tableName)
return __cs_Group.Find(m).All(result)
}
With this change, you can fetch directly to your type:
var result []CS_Group
err := GetFromDB(T_cs_GroupName, bson.M{"Name": "Alex"}, &result)
See also: FAQ: Can I convert a []T to an []interface{}?