Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
329 lines
9.6 KiB
Go
329 lines
9.6 KiB
Go
package rueidis
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"iter"
|
|
"time"
|
|
|
|
intl "github.com/redis/rueidis/internal/cmds"
|
|
)
|
|
|
|
// MGetCache is a helper that consults the client-side caches with multiple keys by grouping keys within the same slot into multiple GETs
|
|
func MGetCache(client Client, ctx context.Context, ttl time.Duration, keys []string) (ret map[string]RedisMessage, err error) {
|
|
if len(keys) == 0 {
|
|
return make(map[string]RedisMessage), nil
|
|
}
|
|
if isCacheDisabled(client) {
|
|
return MGet(client, ctx, keys)
|
|
}
|
|
cmds := mgetcachecmdsp.Get(len(keys), len(keys))
|
|
defer mgetcachecmdsp.Put(cmds)
|
|
for i := range cmds.s {
|
|
cmds.s[i] = CT(client.B().Get().Key(keys[i]).Cache(), ttl)
|
|
}
|
|
return doMultiCache(client, ctx, cmds.s, keys)
|
|
}
|
|
|
|
func isCacheDisabled(client Client) bool {
|
|
switch c := client.(type) {
|
|
case *singleClient:
|
|
return c.DisableCache
|
|
case *standalone:
|
|
return c.primary.Load().DisableCache
|
|
case *sentinelClient:
|
|
return c.mOpt != nil && c.mOpt.DisableCache
|
|
case *clusterClient:
|
|
return c.opt != nil && c.opt.DisableCache
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MGet is a helper that consults the redis directly with multiple keys by grouping keys within the same slot into MGET or multiple GETs
|
|
func MGet(client Client, ctx context.Context, keys []string) (ret map[string]RedisMessage, err error) {
|
|
if len(keys) == 0 {
|
|
return make(map[string]RedisMessage), nil
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientMGet(client, ctx, client.B().Mget().Key(keys...).Build(), keys)
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(len(keys), len(keys))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for i := range cmds.s {
|
|
cmds.s[i] = client.B().Get().Key(keys[i]).Build()
|
|
}
|
|
return doMultiGet(client, ctx, cmds.s, keys)
|
|
}
|
|
|
|
// MSet is a helper that consults the redis directly with multiple keys by grouping keys within the same slot into MSETs or multiple SETs
|
|
func MSet(client Client, ctx context.Context, kvs map[string]string) map[string]error {
|
|
if len(kvs) == 0 {
|
|
return make(map[string]error)
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientMSet(client, ctx, "MSET", kvs, make(map[string]error, len(kvs)))
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(0, len(kvs))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for k, v := range kvs {
|
|
cmds.s = append(cmds.s, client.B().Set().Key(k).Value(v).Build().Pin())
|
|
}
|
|
return doMultiSet(client, ctx, cmds.s)
|
|
}
|
|
|
|
// MDel is a helper that consults the redis directly with multiple keys by grouping keys within the same slot into DELs
|
|
func MDel(client Client, ctx context.Context, keys []string) map[string]error {
|
|
if len(keys) == 0 {
|
|
return make(map[string]error)
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientMDel(client, ctx, keys)
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(len(keys), len(keys))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for i, k := range keys {
|
|
cmds.s[i] = client.B().Del().Key(k).Build().Pin()
|
|
}
|
|
return doMultiSet(client, ctx, cmds.s)
|
|
}
|
|
|
|
// MSetNX is a helper that consults the redis directly with multiple keys by grouping keys within the same slot into MSETNXs or multiple SETNXs
|
|
func MSetNX(client Client, ctx context.Context, kvs map[string]string) map[string]error {
|
|
if len(kvs) == 0 {
|
|
return make(map[string]error)
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientMSet(client, ctx, "MSETNX", kvs, make(map[string]error, len(kvs)))
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(0, len(kvs))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for k, v := range kvs {
|
|
cmds.s = append(cmds.s, client.B().Set().Key(k).Value(v).Nx().Build().Pin())
|
|
}
|
|
return doMultiSet(client, ctx, cmds.s)
|
|
}
|
|
|
|
// JsonMGetCache is a helper that consults the client-side caches with multiple keys by grouping keys within the same slot into multiple JSON.GETs
|
|
func JsonMGetCache(client Client, ctx context.Context, ttl time.Duration, keys []string, path string) (ret map[string]RedisMessage, err error) {
|
|
if len(keys) == 0 {
|
|
return make(map[string]RedisMessage), nil
|
|
}
|
|
cmds := mgetcachecmdsp.Get(len(keys), len(keys))
|
|
defer mgetcachecmdsp.Put(cmds)
|
|
for i := range cmds.s {
|
|
cmds.s[i] = CT(client.B().JsonGet().Key(keys[i]).Path(path).Cache(), ttl)
|
|
}
|
|
return doMultiCache(client, ctx, cmds.s, keys)
|
|
}
|
|
|
|
// JsonMGet is a helper that consults redis directly with multiple keys by grouping keys within the same slot into JSON.MGETs or multiple JSON.GETs
|
|
func JsonMGet(client Client, ctx context.Context, keys []string, path string) (ret map[string]RedisMessage, err error) {
|
|
if len(keys) == 0 {
|
|
return make(map[string]RedisMessage), nil
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientMGet(client, ctx, client.B().JsonMget().Key(keys...).Path(path).Build(), keys)
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(len(keys), len(keys))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for i := range cmds.s {
|
|
cmds.s[i] = client.B().JsonGet().Key(keys[i]).Path(path).Build()
|
|
}
|
|
return doMultiGet(client, ctx, cmds.s, keys)
|
|
}
|
|
|
|
// JsonMSet is a helper that consults redis directly with multiple keys by grouping keys within the same slot into JSON.MSETs or multiple JSON.SETs
|
|
func JsonMSet(client Client, ctx context.Context, kvs map[string]string, path string) map[string]error {
|
|
if len(kvs) == 0 {
|
|
return make(map[string]error)
|
|
}
|
|
|
|
switch client.(type) {
|
|
case *singleClient, *standalone, *sentinelClient:
|
|
return clientJSONMSet(client, ctx, kvs, path, make(map[string]error, len(kvs)))
|
|
}
|
|
|
|
cmds := mgetcmdsp.Get(0, len(kvs))
|
|
defer mgetcmdsp.Put(cmds)
|
|
for k, v := range kvs {
|
|
cmds.s = append(cmds.s, client.B().JsonSet().Key(k).Path(path).Value(v).Build().Pin())
|
|
}
|
|
return doMultiSet(client, ctx, cmds.s)
|
|
}
|
|
|
|
// DecodeSliceOfJSON is a helper that struct-scans each RedisMessage into dest, which must be a slice of the pointer.
|
|
func DecodeSliceOfJSON[T any](result RedisResult, dest *[]T) error {
|
|
values, err := result.ToArray()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts := make([]T, len(values))
|
|
for i, v := range values {
|
|
var t T
|
|
if err = v.DecodeJSON(&t); err != nil {
|
|
if IsRedisNil(err) {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
ts[i] = t
|
|
}
|
|
*dest = ts
|
|
return nil
|
|
}
|
|
|
|
func clientMGet(client Client, ctx context.Context, cmd Completed, keys []string) (ret map[string]RedisMessage, err error) {
|
|
arr, err := client.Do(ctx, cmd).ToArray()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return arrayToKV(make(map[string]RedisMessage, len(keys)), arr, keys), nil
|
|
}
|
|
|
|
func clientMSet(client Client, ctx context.Context, mset string, kvs map[string]string, ret map[string]error) map[string]error {
|
|
cmd := client.B().Arbitrary(mset)
|
|
for k, v := range kvs {
|
|
cmd = cmd.Args(k, v)
|
|
}
|
|
ok, err := client.Do(ctx, cmd.Build()).AsBool()
|
|
if err == nil && !ok {
|
|
err = ErrMSetNXNotSet
|
|
}
|
|
for k := range kvs {
|
|
ret[k] = err
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func clientJSONMSet(client Client, ctx context.Context, kvs map[string]string, path string, ret map[string]error) map[string]error {
|
|
cmd := intl.JsonMsetTripletValue(client.B().JsonMset())
|
|
for k, v := range kvs {
|
|
cmd = cmd.Key(k).Path(path).Value(v)
|
|
}
|
|
err := client.Do(ctx, cmd.Build()).Error()
|
|
for k := range kvs {
|
|
ret[k] = err
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func clientMDel(client Client, ctx context.Context, keys []string) map[string]error {
|
|
err := client.Do(ctx, client.B().Del().Key(keys...).Build()).Error()
|
|
ret := make(map[string]error, len(keys))
|
|
for _, k := range keys {
|
|
ret[k] = err
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func doMultiCache(cc Client, ctx context.Context, cmds []CacheableTTL, keys []string) (ret map[string]RedisMessage, err error) {
|
|
ret = make(map[string]RedisMessage, len(keys))
|
|
resps := cc.DoMultiCache(ctx, cmds...)
|
|
defer resultsp.Put(&redisresults{s: resps})
|
|
for i, resp := range resps {
|
|
if err := resp.NonRedisError(); err != nil {
|
|
return nil, err
|
|
}
|
|
ret[keys[i]] = resp.val
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func doMultiGet(cc Client, ctx context.Context, cmds []Completed, keys []string) (ret map[string]RedisMessage, err error) {
|
|
ret = make(map[string]RedisMessage, len(keys))
|
|
resps := cc.DoMulti(ctx, cmds...)
|
|
defer resultsp.Put(&redisresults{s: resps})
|
|
for i, resp := range resps {
|
|
if err := resp.NonRedisError(); err != nil {
|
|
return nil, err
|
|
}
|
|
ret[keys[i]] = resp.val
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func doMultiSet(cc Client, ctx context.Context, cmds []Completed) (ret map[string]error) {
|
|
ret = make(map[string]error, len(cmds))
|
|
resps := cc.DoMulti(ctx, cmds...)
|
|
for i, resp := range resps {
|
|
if ret[cmds[i].Commands()[1]] = resp.Error(); resp.NonRedisError() == nil {
|
|
intl.PutCompletedForce(cmds[i])
|
|
}
|
|
}
|
|
resultsp.Put(&redisresults{s: resps})
|
|
return ret
|
|
}
|
|
|
|
func arrayToKV(m map[string]RedisMessage, arr []RedisMessage, keys []string) map[string]RedisMessage {
|
|
for i, resp := range arr {
|
|
m[keys[i]] = resp
|
|
}
|
|
return m
|
|
}
|
|
|
|
// ErrMSetNXNotSet is used in the MSetNX helper when the underlying MSETNX response is 0.
|
|
// Ref: https://redis.io/commands/msetnx/
|
|
var ErrMSetNXNotSet = errors.New("MSETNX: no key was set")
|
|
|
|
type Scanner struct {
|
|
next func(cursor uint64) (ScanEntry, error)
|
|
err error
|
|
}
|
|
|
|
func NewScanner(next func(cursor uint64) (ScanEntry, error)) *Scanner {
|
|
return &Scanner{next: next}
|
|
}
|
|
|
|
func (s *Scanner) scan() iter.Seq[[]string] {
|
|
return func(yield func([]string) bool) {
|
|
var e ScanEntry
|
|
for e, s.err = s.next(0); s.err == nil && yield(e.Elements) && e.Cursor != 0; {
|
|
e, s.err = s.next(e.Cursor)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) Iter() iter.Seq[string] {
|
|
return func(yield func(string) bool) {
|
|
for vs := range s.scan() {
|
|
for _, v := range vs {
|
|
if !yield(v) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) Iter2() iter.Seq2[string, string] {
|
|
return func(yield func(string, string) bool) {
|
|
for vs := range s.scan() {
|
|
for i := 0; i+1 < len(vs); i += 2 {
|
|
if !yield(vs[i], vs[i+1]) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) Err() error {
|
|
return s.err
|
|
}
|