Rất tiếc, mgo.v2
trình điều khiển không cung cấp các lệnh gọi API để chỉ định cursor.min()
.
Nhưng có một giải pháp. mgo.Database
type cung cấp một Database.Run()
để chạy bất kỳ lệnh MongoDB nào. Các lệnh có sẵn và tài liệu của chúng có thể được tìm thấy tại đây:Các lệnh cơ sở dữ liệu
Bắt đầu với MongoDB 3.2, một find
mới có sẵn lệnh có thể được sử dụng để thực thi các truy vấn và nó hỗ trợ chỉ định min
đối số biểu thị mục nhập chỉ mục đầu tiên để bắt đầu liệt kê các kết quả từ đó.
Tốt. Những gì chúng ta cần làm là sau mỗi đợt (tài liệu của một trang) tạo min
tài liệu từ tài liệu cuối cùng của kết quả truy vấn, tài liệu này phải chứa các giá trị của mục nhập chỉ mục đã được sử dụng để thực hiện truy vấn và sau đó, lô tiếp theo (tài liệu của trang tiếp theo) có thể được lấy bằng cách đặt mục nhập chỉ mục tối thiểu này trước để thực hiện truy vấn.
Mục nhập chỉ mục này –let gọi nó là con trỏ từ bây giờ– có thể được mã hóa thành một chuỗi string
và được gửi cho khách hàng cùng với kết quả và khi khách hàng muốn trang tiếp theo, anh ta sẽ gửi lại con trỏ nói rằng anh ấy muốn kết quả bắt đầu sau con trỏ này.
Làm thủ công (cách "khó")
Lệnh được thực thi có thể ở các dạng khác nhau, nhưng tên lệnh (find
) phải đứng đầu tiên trong kết quả được sắp xếp theo thứ tự, vì vậy chúng tôi sẽ sử dụng bson.D
(giữ trật tự trái ngược với 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},
)
}
Kết quả của việc thực thi một MongoDB find
lệnh với Database.Run()
có thể được chụp bằng loại sau:
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)
}
Bây giờ chúng ta đã có kết quả, nhưng ở một phần thuộc loại []bson.Raw
. Nhưng chúng tôi muốn nó ở dạng phần []*User
. Đây là nơi Collection.NewIter()
có ích. Nó có thể chuyển đổi (không quản lý) một giá trị của loại []bson.Raw
thành bất kỳ kiểu nào mà chúng tôi thường chuyển tới Query.All()
hoặc Iter.All()
. Tốt. Hãy xem nó:
firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
Bây giờ chúng tôi có những người dùng của trang tiếp theo. Chỉ còn một việc:tạo con trỏ được sử dụng để truy cập trang tiếp theo nếu chúng ta cần nó:
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
}
Điều này là tốt, nhưng làm thế nào để chúng tôi chuyển đổi một cursorData
thành string
và ngược lại? Chúng tôi có thể sử dụng bson.Marshal()
và bson.Unmarshal()
kết hợp với mã hóa base64; việc sử dụng base64.RawURLEncoding
sẽ cung cấp cho chúng tôi một chuỗi con trỏ an toàn trên web, một chuỗi có thể được thêm vào các truy vấn URL mà không cần thoát.
Đây là một ví dụ về triển khai:
// 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
}
Và cuối cùng chúng ta đã có MongoDB mgo
hiệu quả nhưng không quá ngắn chức năng phân trang. Đọc tiếp ...
Sử dụng github.com/icza/minquery
(cách "dễ dàng")
Cách làm thủ công khá dài dòng; nó có thể được làm chung chung và tự động . Đây là nơi github.com/icza/minquery
xuất hiện trong bức tranh ( tiết lộ:Tôi là tác giả ). Nó cung cấp một trình bao bọc để định cấu hình và thực thi một MongoDB find
, cho phép bạn chỉ định một con trỏ và sau khi thực hiện truy vấn, nó sẽ trả lại cho bạn con trỏ mới sẽ được sử dụng để truy vấn loạt kết quả tiếp theo. Trình bao bọc là MinQuery
nhập tương tự như mgo.Query
nhưng nó hỗ trợ chỉ định min
của MongoDB qua MinQuery.Cursor()
phương pháp.
Giải pháp trên sử dụng minquery
trông như thế này:
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")
Và đó là tất cả. newCursor
là con trỏ sẽ được sử dụng để tìm nạp đợt tiếp theo.
Lưu ý số 1: Khi gọi MinQuery.All()
, bạn phải cung cấp tên của các trường con trỏ, tên này sẽ được sử dụng để xây dựng dữ liệu con trỏ (và cuối cùng là chuỗi con trỏ) từ đó.
Lưu ý # 2: Nếu bạn đang truy xuất một phần kết quả (bằng cách sử dụng MinQuery.Select()
), bạn phải bao gồm tất cả các trường là một phần của con trỏ (mục nhập chỉ mục) ngay cả khi bạn không có ý định sử dụng chúng trực tiếp, else MinQuery.All()
sẽ không có tất cả các giá trị của các trường con trỏ và do đó nó sẽ không thể tạo giá trị con trỏ thích hợp.
Kiểm tra tài liệu gói của minquery
tại đây:https://godoc.org/github.com/icza/minquery, nó khá ngắn gọn và hy vọng là rõ ràng.