Skip to content

Commit 888f65e

Browse files
odsodeandre
authored andcommitted
feat(cache): add support for Redis MGET
Tried to keep the change minimal: * Follows existing precedent of using first key for errors (Delete) * Result[V] type which propagates redis.Miss errors
1 parent 5cd68b9 commit 888f65e

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

runtimes/go/storage/cache/basic.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ func (s *StringKeyspace[K]) Get(ctx context.Context, key K) (string, error) {
3737
return s.basicKeyspace.Get(ctx, key)
3838
}
3939

40+
// MultiGet gets the values stored at multiple keys.
41+
// For each key, the result contains an Err field indicating success or failure.
42+
// If Err is nil, Value contains the cached value.
43+
// If Err matches Miss, the key was not found.
44+
//
45+
// See https://redis.io/commands/mget/ for more information.
46+
func (s *StringKeyspace[K]) MultiGet(ctx context.Context, keys ...K) ([]Result[string], error) {
47+
return s.basicKeyspace.MultiGet(ctx, keys...)
48+
}
49+
4050
// Set updates the value stored at key to val.
4151
//
4252
// See https://redis.io/commands/set/ for more information.
@@ -230,6 +240,16 @@ func (s *IntKeyspace[K]) Get(ctx context.Context, key K) (int64, error) {
230240
return s.basicKeyspace.Get(ctx, key)
231241
}
232242

243+
// MultiGet gets the values stored at multiple keys.
244+
// For each key, the result contains an Err field indicating success or failure.
245+
// If Err is nil, Value contains the cached value.
246+
// If Err matches Miss, the key was not found.
247+
//
248+
// See https://redis.io/commands/mget/ for more information.
249+
func (s *IntKeyspace[K]) MultiGet(ctx context.Context, keys ...K) ([]Result[int64], error) {
250+
return s.basicKeyspace.MultiGet(ctx, keys...)
251+
}
252+
233253
// Set updates the value stored at key to val.
234254
//
235255
// See https://redis.io/commands/set/ for more information.
@@ -371,6 +391,16 @@ func (s *FloatKeyspace[K]) Get(ctx context.Context, key K) (float64, error) {
371391
return s.basicKeyspace.Get(ctx, key)
372392
}
373393

394+
// MultiGet gets the values stored at multiple keys.
395+
// For each key, the result contains an Err field indicating success or failure.
396+
// If Err is nil, Value contains the cached value.
397+
// If Err matches Miss, the key was not found.
398+
//
399+
// See https://redis.io/commands/mget/ for more information.
400+
func (s *FloatKeyspace[K]) MultiGet(ctx context.Context, keys ...K) ([]Result[float64], error) {
401+
return s.basicKeyspace.MultiGet(ctx, keys...)
402+
}
403+
374404
// Set updates the value stored at key to val.
375405
//
376406
// See https://redis.io/commands/set/ for more information.
@@ -498,6 +528,43 @@ func (s *basicKeyspace[K, V]) Get(ctx context.Context, key K) (val V, err error)
498528
return val, err
499529
}
500530

531+
func (s *basicKeyspace[K, V]) MultiGet(ctx context.Context, keys ...K) ([]Result[V], error) {
532+
const op = "multi get"
533+
ks, err := s.keys(keys, op)
534+
endTrace := s.doTrace(op, false, ks...)
535+
defer func() { endTrace(err) }()
536+
if err != nil {
537+
return nil, err
538+
}
539+
var firstKey string
540+
if len(ks) > 0 {
541+
firstKey = ks[0]
542+
}
543+
res, err := s.redis.MGet(ctx, ks...).Result()
544+
if err != nil {
545+
return nil, toErr(err, op, firstKey)
546+
}
547+
results := make([]Result[V], 0, len(res))
548+
for i, r := range res {
549+
if r == nil {
550+
results = append(results, Result[V]{Err: toErr(Miss, op, ks[i])})
551+
continue
552+
}
553+
strVal, ok := r.(string)
554+
if !ok {
555+
results = append(results, Result[V]{Err: toErr(errors.New("invalid redis value type"), op, ks[i])})
556+
continue
557+
}
558+
val, fromRedisErr := s.fromRedis(strVal)
559+
if fromRedisErr != nil {
560+
results = append(results, Result[V]{Err: toErr(fromRedisErr, op, ks[i])})
561+
continue
562+
}
563+
results = append(results, Result[V]{Value: val})
564+
}
565+
return results, nil
566+
}
567+
501568
func (s *basicKeyspace[K, V]) Set(ctx context.Context, key K, val V) error {
502569
_, _, err := s.set(ctx, key, val, 0, "set")
503570
return err

runtimes/go/storage/cache/basic_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,30 @@ func TestFloatKeyspace(t *testing.T) {
131131
}
132132
}
133133

134+
func TestMultiGet(t *testing.T) {
135+
kt := newStringTest(t)
136+
ks, ctx := kt.ks, kt.ctx
137+
138+
// Set up test data
139+
kt.Set("key1", "value1")
140+
kt.Set("key2", "value2")
141+
142+
results := must(ks.MultiGet(ctx, "key1", "key2", "missing"))
143+
if len(results) != 3 {
144+
t.Fatalf("expected 3 results, got %d", len(results))
145+
}
146+
147+
if results[0].Err != nil || results[0].Value != "value1" {
148+
t.Errorf("key1: got Err=%v, Value=%q, want Err=nil, Value=%q", results[0].Err, results[0].Value, "value1")
149+
}
150+
if results[1].Err != nil || results[1].Value != "value2" {
151+
t.Errorf("key2: got Err=%v, Value=%q, want Err=nil, Value=%q", results[1].Err, results[1].Value, "value2")
152+
}
153+
if !errors.Is(results[2].Err, Miss) {
154+
t.Errorf("missing: got Err=%v, want Miss", results[2].Err)
155+
}
156+
}
157+
134158
func newStringTest(t *testing.T) *stringTester {
135159
cluster, srv := newTestCluster(t)
136160
ks := NewStringKeyspace[string](cluster, KeyspaceConfig{

runtimes/go/storage/cache/cache.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ var Miss = errors.New("cache miss")
135135
// It must be checked against with errors.Is.
136136
var KeyExists = errors.New("key already exists")
137137

138+
// Result represents the result of a cache operation that may or may not have found a value.
139+
// If Err is nil, Value contains the cached value.
140+
// If Err matches Miss, the key was not found in the cache.
141+
// Otherwise Err contains the error that occurred.
142+
type Result[V any] struct {
143+
// Value holds the cached value if Err is nil, otherwise the zero value.
144+
Value V
145+
// Err is nil on success, Miss if the key was not found, or another error.
146+
Err error
147+
}
148+
138149
// An WriteOption customizes the behavior of a single cache write operation.
139150
type WriteOption interface {
140151
//publicapigen:keep

runtimes/go/storage/cache/struct.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ func (s *StructKeyspace[K, V]) Get(ctx context.Context, key K) (V, error) {
4949
return s.basicKeyspace.Get(ctx, key)
5050
}
5151

52+
// MultiGet gets the values stored at multiple keys.
53+
// For each key, the result contains an Err field indicating success or failure.
54+
// If Err is nil, Value contains the cached value.
55+
// If Err matches Miss, the key was not found.
56+
//
57+
// See https://redis.io/commands/mget/ for more information.
58+
func (s *StructKeyspace[K, V]) MultiGet(ctx context.Context, keys ...K) ([]Result[V], error) {
59+
return s.basicKeyspace.MultiGet(ctx, keys...)
60+
}
61+
5262
// Set updates the value stored at key to val.
5363
//
5464
// See https://redis.io/commands/set/ for more information.

0 commit comments

Comments
 (0)