Niestety mgo.v2
sterownik nie zapewnia wywołań API w celu określenia cursor.min()
.
Ale jest rozwiązanie. mgo.Database
typ dostarcza Database.Run()
metoda do uruchamiania dowolnych poleceń MongoDB. Dostępne polecenia i ich dokumentację można znaleźć tutaj:Polecenia bazy danych
Począwszy od MongoDB 3.2, nowe find
dostępne jest polecenie, które może być używane do wykonywania zapytań i obsługuje określanie min
argument, który oznacza pierwszy wpis indeksu, od którego rozpocznie się wyświetlanie wyników.
Dobry. To, co musimy zrobić, to po każdej partii (dokumenty strony) wygenerować min
dokument z ostatniego dokumentu wyniku zapytania, który musi zawierać wartości wpisu indeksu, który został użyty do wykonania zapytania, a następnie można pobrać następną partię (dokumenty następnej strony) ustawiając ten minimalny wpis indeksu przed do wykonania zapytania.
Ten wpis indeksu – nazwijmy go kursorem od teraz – może być zakodowany jako string
i wysyłane do klienta wraz z wynikami, a gdy klient chce mieć następną stronę, odsyła kursor mówiąc, że chce, aby wyniki zaczynały się po tym kursorze.
Zrób to ręcznie ("trudny")
Polecenie do wykonania może mieć różne formy, ale nazwa polecenia (find
) musi być pierwszy w uporządkowanym wyniku, więc użyjemy bson.D
(co zachowuje porządek w przeciwieństwie do 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},
)
}
Wynik wykonania MongoDB find
polecenie z Database.Run()
można przechwycić za pomocą następującego typu:
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)
}
Mamy teraz wyniki, ale w wycinku typu []bson.Raw
. Ale chcemy go w kawałku typu []*User
. To tutaj Collection.NewIter()
przydaje się. Może przekształcać (odblokowywać) wartość typu []bson.Raw
do dowolnego typu, który zwykle przekazujemy do Query.All()
lub Iter.All()
. Dobry. Zobaczmy:
firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
Mamy teraz użytkowników następnej strony. Pozostało tylko jedno:wygenerowanie kursora, który zostanie użyty do pobrania kolejnej strony, jeśli kiedykolwiek będziemy jej potrzebować:
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
}
Wszystko jest w porządku, ale jak przekonwertować cursorData
? do string
i wzajemnie? Możemy użyć bson.Marshal()
i bson.Unmarshal()
w połączeniu z kodowaniem base64; użycie base64.RawURLEncoding
da nam bezpieczny w sieci ciąg kursora, który można dodać do zapytań URL bez uciekania.
Oto przykładowa implementacja:
// 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
}
I wreszcie mamy wydajną, ale nie tak krótką MongoDB mgo
funkcjonalność stronicowania. Czytaj dalej...
Korzystanie z github.com/icza/minquery
("łatwy" sposób)
Sposób ręczny jest dość długi; można to uczynić ogólnym i automatycznie . To tutaj github.com/icza/minquery
pojawia się na zdjęciu (ujawnienie:jestem autorem ). Zapewnia opakowanie do konfiguracji i wykonania MongoDB find
polecenie, które pozwala określić kursor, a po wykonaniu zapytania zwraca nowy kursor, który będzie używany do zapytania następnej partii wyników. Opakowanie to MinQuery
typ bardzo podobny do mgo.Query
ale obsługuje określanie min
MongoDB za pomocą MinQuery.Cursor()
metoda.
Powyższe rozwiązanie przy użyciu minquery
wygląda tak:
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")
I to wszystko. newCursor
jest kursorem używanym do pobrania następnej partii.
Uwaga nr 1: Podczas wywoływania MinQuery.All()
, musisz podać nazwy pól kursora, zostaną one użyte do zbudowania danych kursora (i ostatecznie ciągu kursora).
Uwaga #2: Jeśli pobierasz częściowe wyniki (za pomocą MinQuery.Select()
), musisz uwzględnić wszystkie pola, które są częścią kursora (wpis indeksu), nawet jeśli nie zamierzasz ich używać bezpośrednio, w przeciwnym razie MinQuery.All()
nie będzie miał wszystkich wartości pól kursora, więc nie będzie w stanie utworzyć prawidłowej wartości kursora.
Sprawdź dokumentację pakietu minquery
tutaj:https://godoc.org/github.com/icza/minquery, jest raczej krótki i mam nadzieję, że czysty.