File: expiring_hash_api_set_builder.go

package info (click to toggle)
gitlab-agent 16.11.5-1
  • links: PTS, VCS
  • area: contrib
  • in suites: experimental
  • size: 7,072 kB
  • sloc: makefile: 193; sh: 55; ruby: 3
file content (72 lines) | stat: -rw-r--r-- 2,043 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package redistool

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/redis/rueidis"
	"google.golang.org/protobuf/proto"
)

type BuilderKV[K2 any] struct {
	HashKey K2
	// Value is the value to store in Redis.
	Value *ExpiringValue
}

type SetBuilder[K1 any, K2 any] interface {
	// Set enqueues a HSET command. Does nothing if no kvs provided.
	Set(key K1, ttl time.Duration, kvs ...BuilderKV[K2])
	// Do executes enqueued commands. Does nothing if no commands have been enqueued.
	// Builder must not be reused after this method has been called.
	Do(context.Context) error
}

type RedisSetBuilder[K1 any, K2 any] struct {
	client         rueidis.Client
	key1ToRedisKey KeyToRedisKey[K1]
	key2ToRedisKey KeyToRedisKey[K2]
	cmds           []rueidis.Completed
	setErr         error
}

func (b *RedisSetBuilder[K1, K2]) Set(key K1, ttl time.Duration, kvs ...BuilderKV[K2]) {
	if len(kvs) == 0 || b.setErr != nil {
		return
	}
	redisKey := b.key1ToRedisKey(key)
	hsetCmd := b.client.B().Hset().Key(redisKey).FieldValue()
	for _, kv := range kvs {
		redisValue, err := proto.Marshal(kv.Value)
		if err != nil {
			// This should never happen. Rather than returning an error here and complicating the API, we store the error
			// and return it when Do() is called.
			b.setErr = fmt.Errorf("failed to marshal ExpiringValue: %w", err)
			return
		}
		hsetCmd.FieldValue(b.key2ToRedisKey(kv.HashKey), rueidis.BinaryString(redisValue))
	}
	b.cmds = append(b.cmds,
		hsetCmd.Build(),
		b.client.B().Pexpire().Key(redisKey).Milliseconds(ttl.Milliseconds()).Build(),
	)
}

func (b *RedisSetBuilder[K1, K2]) Do(ctx context.Context) error {
	if b.setErr != nil { // must be checked before the b.cmd length.
		return b.setErr
	}
	if len(b.cmds) == 0 {
		return nil
	}
	multi := make([]rueidis.Completed, 0, len(b.cmds)+2)
	multi = append(multi, b.client.B().Multi().Build())
	multi = append(multi, b.cmds...)
	multi = append(multi, b.client.B().Exec().Build())

	resp := b.client.DoMulti(ctx, multi...)

	return errors.Join(MultiErrors(resp)...)
}