package wmo import ( "context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" "gogs.mikescher.com/BlackForestBytes/goext/exerr" ) func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, error) { if inTok.Mode == ct.CTMEnd { return make([]TData, 0), ct.End(), nil } pipeline := mongo.Pipeline{} pf1 := "_id" pd1 := ct.SortASC pf2 := "_id" pd2 := ct.SortASC if filter != nil { pipeline = filter.FilterQuery() pf1, pd1, pf2, pd2 = filter.Pagination() } sortPrimary := pf1 sortDirPrimary := pd1 sortSecondary := &pf2 sortDirSecondary := &pd2 if pf1 == pf2 { sortSecondary = nil sortDirSecondary = nil } paginationPipeline, err := CreatePagination(c, inTok, sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, pageSize) if err != nil { return nil, ct.CursorToken{}, exerr. Wrap(err, "failed to create pagination"). WithType(exerr.TypeCursorTokenDecode). Str("collection", c.Name()). Any("inTok", inTok). Any("sortPrimary", sortPrimary). Any("sortDirPrimary", sortDirPrimary). Any("sortSecondary", sortSecondary). Any("sortDirSecondary", sortDirSecondary). Any("pageSize", pageSize). Build() } pipeline = append(pipeline, paginationPipeline...) cursor, err := c.coll.Aggregate(ctx, pipeline) if err != nil { return nil, ct.CursorToken{}, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build() } // fast branch if pageSize == nil { entries, err := c.decodeAll(ctx, cursor) if err != nil { return nil, ct.CursorToken{}, exerr.Wrap(err, "failed to all-decode entities").Build() } return entries, ct.End(), nil } entities := make([]TData, 0, cursor.RemainingBatchLength()) for (pageSize == nil || len(entities) != *pageSize) && cursor.Next(ctx) { var entry TData entry, err = c.decodeSingle(ctx, cursor) if err != nil { return nil, ct.CursorToken{}, exerr.Wrap(err, "failed to decode entity").Build() } entities = append(entities, entry) } if pageSize == nil || len(entities) < *pageSize || !cursor.TryNext(ctx) { return entities, ct.End(), nil } last := entities[len(entities)-1] c.EnsureInitializedReflection(last) nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize) if err != nil { return nil, ct.CursorToken{}, exerr.Wrap(err, "failed to create (out)-token").Build() } return entities, nextToken, nil } type countRes struct { Count int64 `bson:"c"` } func (c *Coll[TData]) Count(ctx context.Context, filter ct.Filter) (int64, error) { pipeline := filter.FilterQuery() pipeline = append(pipeline, bson.D{{Key: "$count", Value: "c"}}) cursor, err := c.coll.Aggregate(ctx, pipeline) if err != nil { return 0, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build() } if cursor.Next(ctx) { v := countRes{} err = cursor.Decode(&v) if err != nil { return 0, exerr.Wrap(err, "failed to decode entity").Build() } return v.Count, nil } return 0, nil } func (c *Coll[TData]) ListWithCount(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, int64, error) { // NOTE: Possible optimization: Cache count in CursorToken, then fetch count only on first page. count, err := c.Count(ctx, filter) if err != nil { return nil, ct.CursorToken{}, 0, err } data, token, err := c.List(ctx, filter, pageSize, inTok) if err != nil { return nil, ct.CursorToken{}, 0, err } return data, token, count, nil }