diff --git a/find.go b/find.go index 3aae647..ecc2ab4 100644 --- a/find.go +++ b/find.go @@ -104,6 +104,65 @@ func (c *MongoClient) Find(ctx context.Context, database, name string, filter bs return out, nil } +func (c *MongoClient) FindOffset(ctx context.Context, database, name string, filter bson.M, offset, limit int64) (bson.M, error) { + // 1. Prepare to query. + collection := c.GetCollection(database, name) + + finalLimit := max(limit, c.Limit) + + pipeline := BuildPaginationPipeline(offset, finalLimit, filter, nil) + + // 2. Query + cursor, err := collection.Aggregate(ctx, pipeline) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + // 3. Build results + var facetResults []bson.M + if err = cursor.All(ctx, &facetResults); err != nil { + return nil, err + } + + root := facetResults[0] + + data := root["data"].(bson.A) + + metadata := root["metadata"].(bson.A) + + var totalValue any + if len(metadata) != 0 { + metadataRoot := metadata[0].(bson.M) + totalValue = metadataRoot["total"] + } + + var total int64 + switch v := totalValue.(type) { + case int32: + total = int64(v) + case int64: + total = v + default: + total = 0 + } + + hasMore := false + if int64(len(data)) > finalLimit { + hasMore = true + data = data[:finalLimit] + } + + out := bson.M{ + "data": data, + "offset": offset, + "limit": finalLimit, + "has_more": hasMore, + "total": total, + } + + return out, nil +} // func (c *MongoClient) FindNext(ctx context.Context, database, name string, filter bson.M, nextCursor string, limit int64) ([]bson.M, error) { // collection := c.GetCollection(database, name) diff --git a/pipeline.go b/pipeline.go index e39eda3..38ebb12 100644 --- a/pipeline.go +++ b/pipeline.go @@ -6,13 +6,10 @@ import ( ) func BuildPaginationPipeline(skip, limit int64, filter bson.M, sort bson.M) mongo.Pipeline { - return mongo.Pipeline{ + pipe := mongo.Pipeline{ // 1. GLOBAL FILTER: Always filter first to use indexes {{Key: "$match", Value: filter}}, - // 2. GLOBAL SORT: Sort here so both 'total' and 'data' facets use the same order - {{Key: "$sort", Value: sort}}, - // 3. FACET: Split the pipeline into two parallel paths {{Key: "$facet", Value: bson.D{ // Path A: Get the total count of documents matching the filter @@ -26,6 +23,13 @@ func BuildPaginationPipeline(skip, limit int64, filter bson.M, sort bson.M) mong }}, }}}, } + + // 2. GLOBAL SORT: Sort here so both 'total' and 'data' facets use the same order + if sort != nil { + pipe = append(pipe, bson.D{{Key: "$sort", Value: sort}}) + } + + return pipe } func BuildPaginationPipelineNext(limit int64, filter bson.M, sort bson.M) mongo.Pipeline {