Sfortunatamente il mgo.v2
driver non fornisce chiamate API per specificare cursor.min()
.
Ma c'è una soluzione. Il mgo.Database
type fornisce un Database.Run()
metodo per eseguire qualsiasi comando MongoDB. I comandi disponibili e la relativa documentazione possono essere trovati qui:Comandi del database
A partire da MongoDB 3.2, un nuovo find
è disponibile un comando che può essere utilizzato per eseguire query e supporta la specifica del min
argomento che denota la prima voce dell'indice da cui iniziare a elencare i risultati.
Bene. Quello che dobbiamo fare è dopo che ogni batch (documenti di una pagina) genera il min
documento dall'ultimo documento del risultato della query, che deve contenere i valori della voce di indice che è stata utilizzata per eseguire la query, quindi è possibile acquisire il batch successivo (i documenti della pagina successiva) impostando questa voce di indice minima prima per eseguire la query.
Questa voce di indice, chiamiamola cursore d'ora in poi– può essere codificato in una string
e inviato al cliente insieme ai risultati, e quando il cliente desidera la pagina successiva, rinvia il cursore dicendo che vuole che i risultati inizino dopo questo cursore.
Fallo manualmente (il modo "difficile")
Il comando da eseguire può essere in diverse forme, ma il nome del comando (find
) deve essere il primo nel risultato sottoposto a marshalling, quindi useremo bson.D
(che mantiene l'ordine in contrasto con 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},
)
}
Il risultato dell'esecuzione di un MongoDB find
comando con Database.Run()
può essere catturato con il seguente tipo:
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)
}
Ora abbiamo i risultati, ma in una slice di tipo []bson.Raw
. Ma lo vogliamo in una sezione di tipo []*User
. Qui è dove Collection.NewIter()
viene a portata di mano. Può trasformare (unmarshal) un valore di tipo []bson.Raw
in qualsiasi tipo che di solito passiamo a Query.All()
o Iter.All()
. Bene. Vediamolo:
firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
Ora abbiamo gli utenti della pagina successiva. Rimane solo una cosa:generare il cursore da utilizzare per ottenere la pagina successiva in caso di necessità:
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
}
Va tutto bene, ma come si converte un cursorData
a string
e viceversa? Possiamo usare bson.Marshal()
e bson.Unmarshal()
combinato con la codifica base64; l'uso di base64.RawURLEncoding
ci fornirà una stringa del cursore sicura per il Web, che può essere aggiunta alle query URL senza caratteri di escape.
Ecco un esempio di implementazione:
// 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
}
E finalmente abbiamo il nostro efficiente, ma non così breve MongoDB mgo
funzionalità di paging. Continua a leggere...
Utilizzo di github.com/icza/minquery
(il modo "facile")
Il modo manuale è piuttosto lungo; può essere reso generale e automatizzato . Qui è dove github.com/icza/minquery
entra in scena (rilevamento:io sono l'autore ). Fornisce un wrapper per configurare ed eseguire un MongoDB find
comando, che consente di specificare un cursore e, dopo aver eseguito la query, restituisce il nuovo cursore da utilizzare per interrogare il successivo batch di risultati. Il wrapper è il MinQuery
tipo che è molto simile a mgo.Query
ma supporta la specifica di min
di MongoDB tramite il MinQuery.Cursor()
metodo.
La soluzione sopra utilizzando minquery
assomiglia a questo:
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")
E questo è tutto. newCursor
è il cursore da utilizzare per recuperare il batch successivo.
Nota n. 1: Quando si chiama MinQuery.All()
, devi fornire i nomi dei campi del cursore, questo verrà utilizzato per costruire i dati del cursore (e infine la stringa del cursore).
Nota n. 2: Se stai recuperando risultati parziali (usando MinQuery.Select()
), devi includere tutti i campi che fanno parte del cursore (la voce dell'indice) anche se non intendi utilizzarli direttamente, altrimenti MinQuery.All()
non avrà tutti i valori dei campi del cursore, quindi non sarà in grado di creare il valore corretto del cursore.
Controlla il pacchetto doc di minquery
qui:https://godoc.org/github.com/icza/minquery, è piuttosto breve e, si spera, pulito.