fix(storage): return Mongo List keys in key order like the memory store

This commit is contained in:
tiennm99 committed 2026-09-30 14:20:27 +07:00
1 parent 00eccd5f5b
commit 897e37ba80
3 files changed
+31 -4

No files matched your search

+3 -2
View File
@@ -40,8 +40,9 @@ var ErrInvalidModuleName = errors.New("storage: invalid module name")
// - PutVersioned writes only if the stored version equals expectedVersion,
// then bumps it. expectedVersion == 0 means "create (or adopt a not-yet-
// versioned key)". A mismatch returns ErrConflict.
// - List returns the keys under a prefix; Scan returns those keys with their
// values, ordered by key ascending.
// - List returns the keys under a prefix, ordered by key ascending.
// - Scan returns the keys under a prefix with their values, ordered by key
// ascending.
type DocStore[T any] interface {
Get(ctx context.Context, id string) (val T, version int64, err error)
Put(ctx context.Context, id string, val T) error
+5 -2
View File
@@ -153,7 +153,8 @@ func (s *mongoDocStore[T]) Delete(ctx context.Context, id string) error {
// List returns all document IDs whose key starts with prefix, via a half-open
// range scan on _id (reusing prefixSuccessor) so it uses the _id index and
// avoids regex injection. Empty prefix returns the whole collection.
// avoids regex injection. Keys come back sorted by _id, matching the memory
// store. Empty prefix returns the whole collection.
func (s *mongoDocStore[T]) List(ctx context.Context, prefix string) ([]string, error) {
if err := validatePrefix(prefix); err != nil {
return nil, err
@@ -162,7 +163,9 @@ func (s *mongoDocStore[T]) List(ctx context.Context, prefix string) ([]string, e
if prefix != "" {
filter[mongoIDField] = bson.M{"$gte": prefix, "$lt": prefixSuccessor(prefix)}
}
cur, err := s.coll.Find(ctx, filter, options.Find().SetProjection(bson.M{mongoIDField: 1}))
cur, err := s.coll.Find(ctx, filter, options.Find().
SetProjection(bson.M{mongoIDField: 1}).
SetSort(bson.D{{Key: mongoIDField, Value: 1}}))
if err != nil {
return nil, fmt.Errorf("mongo list %s prefix=%q: %w", s.module, prefix, err)
}
+23
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"slices"
"sync"
"testing"
"time"
@@ -235,3 +236,25 @@ func TestMongoDocStore_ScanReturnsPayloadsInKeyOrder(t *testing.T) {
t.Fatalf("Scan all = %d docs, err %v", len(all), err)
}
}
// List returns keys in key order, like the memory store, so callers behave the
// same on both backends. Insertion order is deliberately reversed here.
func TestMongoDocStore_ListReturnsKeysInKeyOrder(t *testing.T) {
store, _, cleanup := mongoStore[portfolioLike](t, "coin")
defer cleanup()
ctx := context.Background()
for _, key := range []string{"user:3", "user:1", "other", "user:2"} {
if err := store.Put(ctx, key, portfolioLike{}); err != nil {
t.Fatalf("Put %s: %v", key, err)
}
}
keys, err := store.List(ctx, "user:")
if err != nil {
t.Fatalf("List: %v", err)
}
if want := []string{"user:1", "user:2", "user:3"}; !slices.Equal(keys, want) {
t.Errorf("List = %v, want %v", keys, want)
}
}