Sayangnya mgo.v2
driver tidak menyediakan panggilan API untuk menentukan cursor.min()
.
Tetapi ada sebuah solusi. mgo.Database
type menyediakan Database.Run()
metode untuk menjalankan perintah MongoDB apa pun. Perintah yang tersedia dan dokumentasinya dapat ditemukan di sini:Perintah database
Dimulai dengan MongoDB 3.2, find
baru tersedia perintah yang dapat digunakan untuk mengeksekusi kueri, dan mendukung penetapan min
argumen yang menunjukkan entri indeks pertama untuk memulai hasil daftar.
Bagus. Yang perlu kita lakukan adalah setelah setiap batch (dokumen halaman) menghasilkan min
dokumen dari dokumen terakhir hasil kueri, yang harus berisi nilai entri indeks yang digunakan untuk menjalankan kueri, dan kemudian kumpulan berikutnya (dokumen halaman berikutnya) dapat diperoleh dengan menyetel entri indeks min ini sebelumnya untuk mengeksekusi kueri.
Entri indeks ini – sebut saja kursor mulai sekarang– dapat dikodekan ke string
dan dikirim ke klien bersama dengan hasilnya, dan ketika klien menginginkan halaman berikutnya, dia mengirim kembali kursor mengatakan dia ingin hasil dimulai setelah kursor ini.
Melakukannya secara manual (cara "sulit")
Perintah yang akan dieksekusi bisa dalam bentuk yang berbeda, tetapi nama perintahnya (find
) harus menjadi yang pertama di hasil marshaled, jadi kita akan menggunakan bson.D
(yang menjaga ketertiban berbeda dengan 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},
)
}
Hasil eksekusi find
Mon MongoDB perintah dengan Database.Run()
dapat ditangkap dengan tipe berikut:
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)
}
Kami sekarang memiliki hasilnya, tetapi dalam sepotong jenis []bson.Raw
. Tapi kami menginginkannya dalam potongan tipe []*User
. Di sinilah Collection.NewIter()
berguna. Itu dapat mengubah (menghapus) nilai tipe []bson.Raw
ke dalam jenis apa pun yang biasanya kami berikan ke Query.All()
atau Iter.All()
. Bagus. Mari kita lihat:
firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
Kami sekarang memiliki pengguna halaman berikutnya. Hanya satu hal yang tersisa:membuat kursor yang akan digunakan untuk mendapatkan halaman berikutnya jika kita membutuhkannya:
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
}
Ini semua bagus, tetapi bagaimana kita mengonversi cursorData
ke string
dan sebaliknya? Kami dapat menggunakan bson.Marshal()
dan bson.Unmarshal()
dikombinasikan dengan pengkodean base64; penggunaan base64.RawURLEncoding
akan memberi kita string kursor aman-web, yang dapat ditambahkan ke kueri URL tanpa keluar.
Berikut contoh implementasinya:
// 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
}
Dan kami akhirnya memiliki mgo
MongoDB kami yang efisien, tetapi tidak terlalu pendek fungsionalitas halaman. Baca terus...
Menggunakan github.com/icza/minquery
(cara "mudah")
Cara manual cukup panjang; itu bisa dibuat umum dan otomatis . Di sinilah github.com/icza/minquery
muncul dalam gambar (pengungkapan:Saya penulisnya ). Ini menyediakan pembungkus untuk mengonfigurasi dan menjalankan find
Mon MongoDB perintah, memungkinkan Anda untuk menentukan kursor, dan setelah menjalankan kueri, itu memberi Anda kembali kursor baru untuk digunakan untuk kueri kumpulan hasil berikutnya. Pembungkusnya adalah MinQuery
jenis yang sangat mirip dengan mgo.Query
tetapi mendukung menentukan min
Mon MongoDB melalui MinQuery.Cursor()
metode.
Solusi di atas menggunakan minquery
terlihat seperti ini:
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")
Dan itu saja. newCursor
adalah kursor yang akan digunakan untuk mengambil kumpulan berikutnya.
Catatan #1: Saat memanggil MinQuery.All()
, Anda harus memberikan nama bidang kursor, ini akan digunakan untuk membuat data kursor (dan akhirnya string kursor).
Catatan #2: Jika Anda mengambil sebagian hasil (dengan menggunakan MinQuery.Select()
), Anda harus menyertakan semua bidang yang merupakan bagian dari kursor (entri indeks) meskipun Anda tidak bermaksud menggunakannya secara langsung, jika tidak MinQuery.All()
tidak akan memiliki semua nilai bidang kursor, sehingga tidak akan dapat membuat nilai kursor yang tepat.
Lihat dokumen paket minquery
di sini:https://godoc.org/github.com/icza/minquery, ini agak pendek dan mudah-mudahan bersih.