7aacb7dfc9
continuous-integration/drone/push Build is passing
- Updated the ListPaginationOptions struct to include SkipCount and IncludeCount fields, allowing for more flexible pagination behavior. - Modified the BuildListPagination function to handle cursor pagination with count options, improving performance by running count queries in parallel with data retrieval. - Enhanced the FindAll method in the repository to support concurrent counting of documents, reducing overall latency for list operations. - Added tests for pagination behavior, ensuring accurate handling of count scenarios in both offset and cursor pagination. This update improves the efficiency and flexibility of pagination in the MongoDB repository, enhancing the overall performance of list operations.
89 lines
2.0 KiB
Go
89 lines
2.0 KiB
Go
package mongo
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
const (
|
|
// DefaultListLimit is the default page size for admin list endpoints.
|
|
DefaultListLimit = 10
|
|
// MaxListLimit is the maximum allowed page size.
|
|
MaxListLimit = 100
|
|
)
|
|
|
|
// ListPaginationOptions configures optional list pagination behavior.
|
|
type ListPaginationOptions struct {
|
|
Projection bson.M
|
|
SkipCount bool
|
|
IncludeCount bool // when true, count even when using cursor pagination
|
|
}
|
|
|
|
// BuildListPagination converts API pagination parameters into MongoDB pagination options.
|
|
// filter is used to bind cursors to the active query filters via a hash.
|
|
func BuildListPagination(
|
|
limit, offset int,
|
|
cursor, sortField, sortOrder string,
|
|
filter bson.M,
|
|
opts ListPaginationOptions,
|
|
) (Pagination, error) {
|
|
if limit <= 0 {
|
|
limit = DefaultListLimit
|
|
}
|
|
if limit > MaxListLimit {
|
|
return Pagination{}, fmt.Errorf("%w: limit must be between 1 and %d", ErrInvalidPagination, MaxListLimit)
|
|
}
|
|
|
|
if sortField == "" {
|
|
sortField = "created_at"
|
|
}
|
|
if sortOrder == "" {
|
|
sortOrder = "desc"
|
|
}
|
|
|
|
sortOrderInt := -1
|
|
if sortOrder == "asc" {
|
|
sortOrderInt = 1
|
|
}
|
|
|
|
filtersHash := HashFilter(filter)
|
|
|
|
pb := NewPaginationBuilder().
|
|
Limit(limit).
|
|
SortBy(sortField, sortOrder)
|
|
|
|
if len(opts.Projection) > 0 {
|
|
pb.Projection(opts.Projection)
|
|
}
|
|
|
|
skipCount := opts.SkipCount
|
|
if cursor != "" && !opts.IncludeCount {
|
|
skipCount = true
|
|
}
|
|
if skipCount {
|
|
pb.SkipCount(true)
|
|
}
|
|
|
|
if cursor != "" {
|
|
pageCursor, err := decodePageCursor(cursor)
|
|
if err != nil {
|
|
return Pagination{}, err
|
|
}
|
|
if !pageCursor.matches(sortField, sortOrderInt) {
|
|
return Pagination{}, fmt.Errorf("%w: cursor does not match requested sort", ErrInvalidCursor)
|
|
}
|
|
if pageCursor.FiltersHash != filtersHash {
|
|
return Pagination{}, fmt.Errorf("%w: cursor does not match current filters", ErrInvalidCursor)
|
|
}
|
|
pb.Cursor(cursor)
|
|
} else {
|
|
if offset < 0 {
|
|
return Pagination{}, fmt.Errorf("%w: offset cannot be negative", ErrInvalidPagination)
|
|
}
|
|
pb.Skip(offset)
|
|
}
|
|
|
|
return pb.Build(), nil
|
|
}
|