Desværre er mgo.v2
driveren leverer ikke API-kald for at specificere cursor.min()
.
Men der er en løsning. mgo.Database
type giver en Database.Run()
metode til at køre MongoDB-kommandoer. De tilgængelige kommandoer og deres dokumentation kan findes her:Databasekommandoer
Startende med MongoDB 3.2, en ny find
kommandoen er tilgængelig, som kan bruges til at udføre forespørgsler, og den understøtter specificering af min
argument, der angiver den første indeksindgang, man starter med at vise resultater fra.
Godt. Det, vi skal gøre, er efter hver batch (dokumenter på en side) at generere min
dokument fra det sidste dokument i forespørgselsresultatet, som skal indeholde værdierne for den indeksindgang, der blev brugt til at udføre forespørgslen, og derefter kan den næste batch (dokumenterne på næste side) hentes ved at indstille denne min. indeksindgang før til at udføre forespørgslen.
Denne indeksindgang – lad os kalde det markør fra nu af – kan være kodet til en string
og sendes til klienten sammen med resultaterne, og når klienten vil have den næste side, sender han markøren tilbage siger, at han vil have resultater, der starter efter denne markør.
Gør det manuelt (den "hårde" måde)
Kommandoen, der skal udføres, kan være i forskellige former, men kommandonavnet (find
) skal være først i det marshalerede resultat, så vi bruger bson.D
(som bevarer orden i modsætning til bson.M
):
limit := 10
cmd := bson.D{
{Name: "find", Value: "users"},
{Name: "filter", Value: bson.M{"country": "USA"}},
{Name: "sort", Value: []bson.D{
{Name: "name", Value: 1},
{Name: "_id", Value: 1},
},
{Name: "limit", Value: limit},
{Name: "batchSize", Value: limit},
{Name: "singleBatch", Value: true},
}
if min != nil {
// min is inclusive, must skip first (which is the previous last)
cmd = append(cmd,
bson.DocElem{Name: "skip", Value: 1},
bson.DocElem{Name: "min", Value: min},
)
}
Resultatet af at udføre en MongoDB find
kommando med Database.Run()
kan optages med følgende type:
var res struct {
OK int `bson:"ok"`
WaitedMS int `bson:"waitedMS"`
Cursor struct {
ID interface{} `bson:"id"`
NS string `bson:"ns"`
FirstBatch []bson.Raw `bson:"firstBatch"`
} `bson:"cursor"`
}
db := session.DB("")
if err := db.Run(cmd, &res); err != nil {
// Handle error (abort)
}
Vi har nu resultaterne, men i et udsnit af typen []bson.Raw
. Men vi vil have det i et udsnit af typen []*User
. Det er her Collection.NewIter()
kommer praktisk. Det kan transformere (unmarshal) en værdi af typen []bson.Raw
til enhver type, vi normalt sender til Query.All()
eller Iter.All()
. Godt. Lad os se det:
firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
Vi har nu brugerne af den næste side. Kun én ting tilbage:generering af markøren, der skal bruges til at få den efterfølgende side, hvis vi nogensinde får brug for den:
if len(users) > 0 {
lastUser := users[len(users)-1]
cursorData := []bson.D{
{Name: "country", Value: lastUser.Country},
{Name: "name", Value: lastUser.Name},
{Name: "_id", Value: lastUser.ID},
}
} else {
// No more users found, use the last cursor
}
Det er alt sammen godt, men hvordan konverterer vi en cursorData
til string
og omvendt? Vi kan bruge bson.Marshal()
og bson.Unmarshal()
kombineret med base64-kodning; brugen af base64.RawURLEncoding
vil give os en websikker markørstreng, som kan føjes til URL-forespørgsler uden at undslippe.
Her er et eksempel på implementering:
// CreateCursor returns a web-safe cursor string from the specified fields.
// The returned cursor string is safe to include in URL queries without escaping.
func CreateCursor(cursorData bson.D) (string, error) {
// bson.Marshal() never returns error, so I skip a check and early return
// (but I do return the error if it would ever happen)
data, err := bson.Marshal(cursorData)
return base64.RawURLEncoding.EncodeToString(data), err
}
// ParseCursor parses the cursor string and returns the cursor data.
func ParseCursor(c string) (cursorData bson.D, err error) {
var data []byte
if data, err = base64.RawURLEncoding.DecodeString(c); err != nil {
return
}
err = bson.Unmarshal(data, &cursorData)
return
}
Og vi har endelig vores effektive, men ikke så korte MongoDB mgo
personsøgning funktionalitet. Læs videre...
Brug af github.com/icza/minquery
(den "nemme" måde)
Den manuelle måde er ret lang; det kan gøres generelt og automatiseret . Det er her github.com/icza/minquery
kommer ind i billedet (afsløring:Jeg er forfatteren ). Det giver en indpakning til at konfigurere og udføre en MongoDB find
kommando, som giver dig mulighed for at angive en markør, og efter at have udført forespørgslen, giver den dig den nye markør tilbage, som skal bruges til at forespørge på den næste batch af resultater. Indpakningen er MinQuery
type, som minder meget om mgo.Query
men det understøtter at specificere MongoDB's min
via MinQuery.Cursor()
metode.
Ovenstående løsning ved hjælp af minquery
ser sådan ud:
q := minquery.New(session.DB(""), "users", bson.M{"country" : "USA"}).
Sort("name", "_id").Limit(10)
// If this is not the first page, set cursor:
// getLastCursor() represents your logic how you acquire the last cursor.
if cursor := getLastCursor(); cursor != "" {
q = q.Cursor(cursor)
}
var users []*User
newCursor, err := q.All(&users, "country", "name", "_id")
Og det er alt. newCursor
er den markør, der skal bruges til at hente den næste batch.
Bemærk #1: Når du kalder MinQuery.All()
, skal du angive navnene på markørfelterne, dette vil blive brugt til at bygge markørdataene (og i sidste ende markørstrengen) fra.
Bemærk #2: Hvis du henter delvise resultater (ved at bruge MinQuery.Select()
), skal du inkludere alle de felter, der er en del af markøren (indeksindgangen), selvom du ikke har til hensigt at bruge dem direkte, ellers MinQuery.All()
vil ikke have alle værdierne for markørfelterne, og det vil derfor ikke være i stand til at oprette den korrekte markørværdi.
Tjek pakkedokumentet til minquery
her:https://godoc.org/github.com/icza/minquery, den er ret kort og forhåbentlig ren.