package redis import ( "bufio" "context" "fmt" "net" "regexp" "strconv" "strings" "sync" "time" "github.com/redis/go-redis/v9/internal" "github.com/redis/go-redis/v9/internal/hscan" "github.com/redis/go-redis/v9/internal/proto" "github.com/redis/go-redis/v9/internal/routing" "github.com/redis/go-redis/v9/internal/util" ) // keylessCommands contains Redis commands that have empty key specifications (9th slot empty) // Only includes core Redis commands, excludes FT.*, ts.*, timeseries.*, search.* and subcommands var keylessCommands = map[string]struct{}{ "acl": {}, "asking": {}, "auth": {}, "bgrewriteaof": {}, "bgsave": {}, "client": {}, "cluster": {}, "config": {}, "debug": {}, "discard": {}, "echo": {}, "exec": {}, "failover": {}, "function": {}, "hello": {}, "latency": {}, "lolwut": {}, "module": {}, "monitor": {}, "multi": {}, "pfselftest": {}, "ping": {}, "psubscribe": {}, "psync": {}, "publish": {}, "pubsub": {}, "punsubscribe": {}, "quit": {}, "readonly": {}, "readwrite": {}, "replconf": {}, "replicaof": {}, "role": {}, "save": {}, "script": {}, "select": {}, "shutdown": {}, "slaveof": {}, "slowlog": {}, "subscribe": {}, "swapdb": {}, "sync": {}, "unsubscribe": {}, "unwatch": {}, "wait": {}, } // CmdTyper interface for getting command type type CmdTyper interface { GetCmdType() CmdType } // CmdTypeGetter interface for getting command type without circular imports type CmdTypeGetter interface { GetCmdType() CmdType } type CmdType uint8 const ( CmdTypeGeneric CmdType = iota CmdTypeString CmdTypeInt CmdTypeBool CmdTypeFloat CmdTypeStringSlice CmdTypeIntSlice CmdTypeFloatSlice CmdTypeBoolSlice CmdTypeMapStringString CmdTypeMapStringInt CmdTypeMapStringInterface CmdTypeMapStringInterfaceSlice CmdTypeSlice CmdTypeStatus CmdTypeDuration CmdTypeTime CmdTypeKeyValueSlice CmdTypeStringStructMap CmdTypeXMessageSlice CmdTypeXStreamSlice CmdTypeXPending CmdTypeXPendingExt CmdTypeXAutoClaim CmdTypeXAutoClaimJustID CmdTypeXInfoConsumers CmdTypeXInfoGroups CmdTypeXInfoStream CmdTypeXInfoStreamFull CmdTypeZSlice CmdTypeZWithKey CmdTypeScan CmdTypeClusterSlots CmdTypeGeoLocation CmdTypeGeoSearchLocation CmdTypeGeoPos CmdTypeCommandsInfo CmdTypeSlowLog CmdTypeMapStringStringSlice CmdTypeMapMapStringInterface CmdTypeKeyValues CmdTypeZSliceWithKey CmdTypeFunctionList CmdTypeFunctionStats CmdTypeLCS CmdTypeKeyFlags CmdTypeClusterLinks CmdTypeClusterShards CmdTypeRankWithScore CmdTypeClientInfo CmdTypeACLLog CmdTypeInfo CmdTypeMonitor CmdTypeJSON CmdTypeJSONSlice CmdTypeIntPointerSlice CmdTypeScanDump CmdTypeBFInfo CmdTypeCFInfo CmdTypeCMSInfo CmdTypeTopKInfo CmdTypeTDigestInfo CmdTypeFTSynDump CmdTypeAggregate CmdTypeFTInfo CmdTypeFTSpellCheck CmdTypeFTSearch CmdTypeTSTimestampValue CmdTypeTSTimestampValueSlice ) type ( CmdTypeXAutoClaimValue struct { messages []XMessage start string } CmdTypeXAutoClaimJustIDValue struct { ids []string start string } CmdTypeScanValue struct { keys []string cursor uint64 } CmdTypeKeyValuesValue struct { key string values []string } CmdTypeZSliceWithKeyValue struct { key string zSlice []Z } ) type Cmder interface { // command name. // e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster". Name() string // full command name. // e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster info". FullName() string // all args of the command. // e.g. "set k v ex 10" -> "[set k v ex 10]". Args() []interface{} // format request and response string. // e.g. "set k v ex 10" -> "set k v ex 10: OK", "get k" -> "get k: v". String() string // Clone creates a copy of the command. Clone() Cmder stringArg(int) string firstKeyPos() int8 SetFirstKeyPos(int8) stepCount() int8 SetStepCount(int8) readTimeout() *time.Duration readReply(rd *proto.Reader) error readRawReply(rd *proto.Reader) error SetErr(error) Err() error // GetCmdType returns the command type for fast value extraction GetCmdType() CmdType } func setCmdsErr(cmds []Cmder, e error) { for _, cmd := range cmds { if cmd.Err() == nil { cmd.SetErr(e) } } } func cmdsFirstErr(cmds []Cmder) error { for _, cmd := range cmds { if err := cmd.Err(); err != nil { return err } } return nil } func writeCmds(wr *proto.Writer, cmds []Cmder) error { for _, cmd := range cmds { if err := writeCmd(wr, cmd); err != nil { return err } } return nil } func writeCmd(wr *proto.Writer, cmd Cmder) error { return wr.WriteArgs(cmd.Args()) } // cmdFirstKeyPos returns the position of the first key in the command's arguments. // If the command does not have a key, it returns 0. // TODO: Use the data in CommandInfo to determine the first key position. func cmdFirstKeyPos(cmd Cmder) int { if pos := cmd.firstKeyPos(); pos != 0 { return int(pos) } name := cmd.Name() // first check if the command is keyless if _, ok := keylessCommands[name]; ok { return 0 } switch name { case "eval", "evalsha", "eval_ro", "evalsha_ro": if cmd.stringArg(2) != "0" { return 3 } return 0 case "publish": return 1 case "memory": // https://github.com/redis/redis/issues/7493 if cmd.stringArg(1) == "usage" { return 2 } } return 1 } func cmdString(cmd Cmder, val interface{}) string { b := make([]byte, 0, 64) for i, arg := range cmd.Args() { if i > 0 { b = append(b, ' ') } b = internal.AppendArg(b, arg) } if err := cmd.Err(); err != nil { b = append(b, ": "...) b = append(b, err.Error()...) } else if val != nil { b = append(b, ": "...) b = internal.AppendArg(b, val) } return util.BytesToString(b) } //------------------------------------------------------------------------------ type baseCmd struct { ctx context.Context args []interface{} err error keyPos int8 _stepCount int8 rawVal interface{} _readTimeout *time.Duration cmdType CmdType } var _ Cmder = (*Cmd)(nil) func (cmd *baseCmd) Name() string { if len(cmd.args) == 0 { return "" } // Cmd name must be lower cased. return internal.ToLower(cmd.stringArg(0)) } func (cmd *baseCmd) FullName() string { switch name := cmd.Name(); name { case "cluster", "command": if len(cmd.args) == 1 { return name } if s2, ok := cmd.args[1].(string); ok { return name + " " + s2 } return name default: return name } } func (cmd *baseCmd) Args() []interface{} { return cmd.args } func (cmd *baseCmd) stringArg(pos int) string { if pos < 0 || pos >= len(cmd.args) { return "" } arg := cmd.args[pos] switch v := arg.(type) { case string: return v case []byte: return string(v) default: // TODO: consider using appendArg return fmt.Sprint(v) } } func (cmd *baseCmd) firstKeyPos() int8 { return cmd.keyPos } func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) { cmd.keyPos = keyPos } func (cmd *baseCmd) stepCount() int8 { return cmd._stepCount } func (cmd *baseCmd) SetStepCount(stepCount int8) { cmd._stepCount = stepCount } func (cmd *baseCmd) SetErr(e error) { cmd.err = e } func (cmd *baseCmd) Err() error { return cmd.err } func (cmd *baseCmd) readTimeout() *time.Duration { return cmd._readTimeout } func (cmd *baseCmd) setReadTimeout(d time.Duration) { cmd._readTimeout = &d } func (cmd *baseCmd) readRawReply(rd *proto.Reader) (err error) { cmd.rawVal, err = rd.ReadReply() return err } func (cmd *baseCmd) GetCmdType() CmdType { return cmd.cmdType } func (cmd *baseCmd) cloneBaseCmd() baseCmd { var readTimeout *time.Duration if cmd._readTimeout != nil { timeout := *cmd._readTimeout readTimeout = &timeout } // Create a copy of args slice args := make([]interface{}, len(cmd.args)) copy(args, cmd.args) return baseCmd{ ctx: cmd.ctx, args: args, err: cmd.err, keyPos: cmd.keyPos, _stepCount: cmd._stepCount, rawVal: cmd.rawVal, _readTimeout: readTimeout, cmdType: cmd.cmdType, } } //------------------------------------------------------------------------------ type Cmd struct { baseCmd val interface{} } func NewCmd(ctx context.Context, args ...interface{}) *Cmd { return &Cmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeGeneric, }, } } func (cmd *Cmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *Cmd) SetVal(val interface{}) { cmd.val = val } func (cmd *Cmd) Val() interface{} { return cmd.val } func (cmd *Cmd) Result() (interface{}, error) { return cmd.val, cmd.err } func (cmd *Cmd) Text() (string, error) { if cmd.err != nil { return "", cmd.err } return toString(cmd.val) } func toString(val interface{}) (string, error) { switch val := val.(type) { case string: return val, nil default: err := fmt.Errorf("redis: unexpected type=%T for String", val) return "", err } } func (cmd *Cmd) Int() (int, error) { if cmd.err != nil { return 0, cmd.err } switch val := cmd.val.(type) { case int64: return int(val), nil case string: return strconv.Atoi(val) default: err := fmt.Errorf("redis: unexpected type=%T for Int", val) return 0, err } } func (cmd *Cmd) Int64() (int64, error) { if cmd.err != nil { return 0, cmd.err } return toInt64(cmd.val) } func toInt64(val interface{}) (int64, error) { switch val := val.(type) { case int64: return val, nil case string: return strconv.ParseInt(val, 10, 64) default: err := fmt.Errorf("redis: unexpected type=%T for Int64", val) return 0, err } } func (cmd *Cmd) Uint64() (uint64, error) { if cmd.err != nil { return 0, cmd.err } return toUint64(cmd.val) } func toUint64(val interface{}) (uint64, error) { switch val := val.(type) { case int64: return uint64(val), nil case string: return strconv.ParseUint(val, 10, 64) default: err := fmt.Errorf("redis: unexpected type=%T for Uint64", val) return 0, err } } func (cmd *Cmd) Float32() (float32, error) { if cmd.err != nil { return 0, cmd.err } return toFloat32(cmd.val) } func toFloat32(val interface{}) (float32, error) { switch val := val.(type) { case int64: return float32(val), nil case string: f, err := strconv.ParseFloat(val, 32) if err != nil { return 0, err } return float32(f), nil default: err := fmt.Errorf("redis: unexpected type=%T for Float32", val) return 0, err } } func (cmd *Cmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err } return toFloat64(cmd.val) } func toFloat64(val interface{}) (float64, error) { switch val := val.(type) { case int64: return float64(val), nil case string: return strconv.ParseFloat(val, 64) default: err := fmt.Errorf("redis: unexpected type=%T for Float64", val) return 0, err } } func (cmd *Cmd) Bool() (bool, error) { if cmd.err != nil { return false, cmd.err } return toBool(cmd.val) } func toBool(val interface{}) (bool, error) { switch val := val.(type) { case bool: return val, nil case int64: return val != 0, nil case string: return strconv.ParseBool(val) default: err := fmt.Errorf("redis: unexpected type=%T for Bool", val) return false, err } } func (cmd *Cmd) Slice() ([]interface{}, error) { if cmd.err != nil { return nil, cmd.err } switch val := cmd.val.(type) { case []interface{}: return val, nil default: return nil, fmt.Errorf("redis: unexpected type=%T for Slice", val) } } func (cmd *Cmd) StringSlice() ([]string, error) { slice, err := cmd.Slice() if err != nil { return nil, err } ss := make([]string, len(slice)) for i, iface := range slice { val, err := toString(iface) if err != nil { return nil, err } ss[i] = val } return ss, nil } func (cmd *Cmd) Int64Slice() ([]int64, error) { slice, err := cmd.Slice() if err != nil { return nil, err } nums := make([]int64, len(slice)) for i, iface := range slice { val, err := toInt64(iface) if err != nil { return nil, err } nums[i] = val } return nums, nil } func (cmd *Cmd) Uint64Slice() ([]uint64, error) { slice, err := cmd.Slice() if err != nil { return nil, err } nums := make([]uint64, len(slice)) for i, iface := range slice { val, err := toUint64(iface) if err != nil { return nil, err } nums[i] = val } return nums, nil } func (cmd *Cmd) Float32Slice() ([]float32, error) { slice, err := cmd.Slice() if err != nil { return nil, err } floats := make([]float32, len(slice)) for i, iface := range slice { val, err := toFloat32(iface) if err != nil { return nil, err } floats[i] = val } return floats, nil } func (cmd *Cmd) Float64Slice() ([]float64, error) { slice, err := cmd.Slice() if err != nil { return nil, err } floats := make([]float64, len(slice)) for i, iface := range slice { val, err := toFloat64(iface) if err != nil { return nil, err } floats[i] = val } return floats, nil } func (cmd *Cmd) BoolSlice() ([]bool, error) { slice, err := cmd.Slice() if err != nil { return nil, err } bools := make([]bool, len(slice)) for i, iface := range slice { val, err := toBool(iface) if err != nil { return nil, err } bools[i] = val } return bools, nil } func (cmd *Cmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadReply() return err } func (cmd *Cmd) Clone() Cmder { return &Cmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type SliceCmd struct { baseCmd val []interface{} } var _ Cmder = (*SliceCmd)(nil) func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd { return &SliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeSlice, }, } } func (cmd *SliceCmd) SetVal(val []interface{}) { cmd.val = val } func (cmd *SliceCmd) Val() []interface{} { return cmd.val } func (cmd *SliceCmd) Result() ([]interface{}, error) { return cmd.val, cmd.err } func (cmd *SliceCmd) String() string { return cmdString(cmd, cmd.val) } // Scan scans the results from the map into a destination struct. The map keys // are matched in the Redis struct fields by the `redis:"field"` tag. func (cmd *SliceCmd) Scan(dst interface{}) error { if cmd.err != nil { return cmd.err } // Pass the list of keys and values. // Skip the first two args for: HMGET key var args []interface{} if cmd.args[0] == "hmget" { args = cmd.args[2:] } else { // Otherwise, it's: MGET field field ... args = cmd.args[1:] } return hscan.Scan(dst, args, cmd.val) } func (cmd *SliceCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadSlice() return err } func (cmd *SliceCmd) Clone() Cmder { var val []interface{} if cmd.val != nil { val = make([]interface{}, len(cmd.val)) copy(val, cmd.val) } return &SliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type StatusCmd struct { baseCmd val string } var _ Cmder = (*StatusCmd)(nil) func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd { return &StatusCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeStatus, }, } } func (cmd *StatusCmd) SetVal(val string) { cmd.val = val } func (cmd *StatusCmd) Val() string { return cmd.val } func (cmd *StatusCmd) Result() (string, error) { return cmd.val, cmd.err } func (cmd *StatusCmd) Bytes() ([]byte, error) { return util.StringToBytes(cmd.val), cmd.err } func (cmd *StatusCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StatusCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadString() return err } func (cmd *StatusCmd) Clone() Cmder { return &StatusCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type IntCmd struct { baseCmd val int64 } var _ Cmder = (*IntCmd)(nil) func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd { return &IntCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeInt, }, } } func (cmd *IntCmd) SetVal(val int64) { cmd.val = val } func (cmd *IntCmd) Val() int64 { return cmd.val } func (cmd *IntCmd) Result() (int64, error) { return cmd.val, cmd.err } func (cmd *IntCmd) Uint64() (uint64, error) { return uint64(cmd.val), cmd.err } func (cmd *IntCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadInt() return err } func (cmd *IntCmd) Clone() Cmder { return &IntCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ // DigestCmd is a command that returns a uint64 xxh3 hash digest. // // This command is specifically designed for the Redis DIGEST command, // which returns the xxh3 hash of a key's value as a hex string. // The hex string is automatically parsed to a uint64 value. // // The digest can be used for optimistic locking with SetIFDEQ, SetIFDNE, // and DelExArgs commands. // // For examples of client-side digest generation and usage patterns, see: // example/digest-optimistic-locking/ // // Redis 8.4+. See https://redis.io/commands/digest/ type DigestCmd struct { baseCmd val uint64 } var _ Cmder = (*DigestCmd)(nil) func NewDigestCmd(ctx context.Context, args ...interface{}) *DigestCmd { return &DigestCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, }, } } func (cmd *DigestCmd) SetVal(val uint64) { cmd.val = val } func (cmd *DigestCmd) Val() uint64 { return cmd.val } func (cmd *DigestCmd) Result() (uint64, error) { return cmd.val, cmd.err } func (cmd *DigestCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *DigestCmd) Clone() Cmder { return &DigestCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } func (cmd *DigestCmd) readReply(rd *proto.Reader) (err error) { // Redis DIGEST command returns a hex string (e.g., "a1b2c3d4e5f67890") // We parse it as a uint64 xxh3 hash value var hexStr string hexStr, err = rd.ReadString() if err != nil { return err } // Parse hex string to uint64 cmd.val, err = strconv.ParseUint(hexStr, 16, 64) return err } //------------------------------------------------------------------------------ type IntSliceCmd struct { baseCmd val []int64 } var _ Cmder = (*IntSliceCmd)(nil) func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd { return &IntSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeIntSlice, }, } } func (cmd *IntSliceCmd) SetVal(val []int64) { cmd.val = val } func (cmd *IntSliceCmd) Val() []int64 { return cmd.val } func (cmd *IntSliceCmd) Result() ([]int64, error) { return cmd.val, cmd.err } func (cmd *IntSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]int64, n) for i := 0; i < len(cmd.val); i++ { if cmd.val[i], err = rd.ReadInt(); err != nil { return err } } return nil } func (cmd *IntSliceCmd) Clone() Cmder { var val []int64 if cmd.val != nil { val = make([]int64, len(cmd.val)) copy(val, cmd.val) } return &IntSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type DurationCmd struct { baseCmd val time.Duration precision time.Duration } var _ Cmder = (*DurationCmd)(nil) func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interface{}) *DurationCmd { return &DurationCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeDuration, }, precision: precision, } } func (cmd *DurationCmd) SetVal(val time.Duration) { cmd.val = val } func (cmd *DurationCmd) Val() time.Duration { return cmd.val } func (cmd *DurationCmd) Result() (time.Duration, error) { return cmd.val, cmd.err } func (cmd *DurationCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *DurationCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadInt() if err != nil { return err } switch n { // -2 if the key does not exist // -1 if the key exists but has no associated expire case -2, -1: cmd.val = time.Duration(n) default: cmd.val = time.Duration(n) * cmd.precision } return nil } func (cmd *DurationCmd) Clone() Cmder { return &DurationCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, precision: cmd.precision, } } //------------------------------------------------------------------------------ type TimeCmd struct { baseCmd val time.Time } var _ Cmder = (*TimeCmd)(nil) func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd { return &TimeCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeTime, }, } } func (cmd *TimeCmd) SetVal(val time.Time) { cmd.val = val } func (cmd *TimeCmd) Val() time.Time { return cmd.val } func (cmd *TimeCmd) Result() (time.Time, error) { return cmd.val, cmd.err } func (cmd *TimeCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *TimeCmd) readReply(rd *proto.Reader) error { if err := rd.ReadFixedArrayLen(2); err != nil { return err } second, err := rd.ReadInt() if err != nil { return err } microsecond, err := rd.ReadInt() if err != nil { return err } cmd.val = time.Unix(second, microsecond*1000) return nil } func (cmd *TimeCmd) Clone() Cmder { return &TimeCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type BoolCmd struct { baseCmd val bool } var _ Cmder = (*BoolCmd)(nil) func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd { return &BoolCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeBool, }, } } func (cmd *BoolCmd) SetVal(val bool) { cmd.val = val } func (cmd *BoolCmd) Val() bool { return cmd.val } func (cmd *BoolCmd) Result() (bool, error) { return cmd.val, cmd.err } func (cmd *BoolCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *BoolCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadBool() // `SET key value NX` returns nil when key already exists. But // `SETNX key value` returns bool (0/1). So convert nil to bool. if err == Nil { cmd.val = false err = nil } return err } func (cmd *BoolCmd) Clone() Cmder { return &BoolCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type StringCmd struct { baseCmd val string } var _ Cmder = (*StringCmd)(nil) func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd { return &StringCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeString, }, } } func (cmd *StringCmd) SetVal(val string) { cmd.val = val } func (cmd *StringCmd) Val() string { return cmd.val } func (cmd *StringCmd) Result() (string, error) { return cmd.val, cmd.err } func (cmd *StringCmd) Bytes() ([]byte, error) { return util.StringToBytes(cmd.val), cmd.err } func (cmd *StringCmd) Bool() (bool, error) { if cmd.err != nil { return false, cmd.err } return strconv.ParseBool(cmd.val) } func (cmd *StringCmd) Int() (int, error) { if cmd.err != nil { return 0, cmd.err } return strconv.Atoi(cmd.val) } func (cmd *StringCmd) Int64() (int64, error) { if cmd.err != nil { return 0, cmd.err } return strconv.ParseInt(cmd.val, 10, 64) } func (cmd *StringCmd) Uint64() (uint64, error) { if cmd.err != nil { return 0, cmd.err } return strconv.ParseUint(cmd.val, 10, 64) } func (cmd *StringCmd) Float32() (float32, error) { if cmd.err != nil { return 0, cmd.err } f, err := strconv.ParseFloat(cmd.val, 32) if err != nil { return 0, err } return float32(f), nil } func (cmd *StringCmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err } return strconv.ParseFloat(cmd.val, 64) } func (cmd *StringCmd) Time() (time.Time, error) { if cmd.err != nil { return time.Time{}, cmd.err } return time.Parse(time.RFC3339Nano, cmd.val) } func (cmd *StringCmd) Scan(val interface{}) error { if cmd.err != nil { return cmd.err } return proto.Scan([]byte(cmd.val), val) } func (cmd *StringCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadString() return err } func (cmd *StringCmd) Clone() Cmder { return &StringCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type FloatCmd struct { baseCmd val float64 } var _ Cmder = (*FloatCmd)(nil) func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd { return &FloatCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeFloat, }, } } func (cmd *FloatCmd) SetVal(val float64) { cmd.val = val } func (cmd *FloatCmd) Val() float64 { return cmd.val } func (cmd *FloatCmd) Result() (float64, error) { return cmd.val, cmd.err } func (cmd *FloatCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadFloat() return err } func (cmd *FloatCmd) Clone() Cmder { return &FloatCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } //------------------------------------------------------------------------------ type FloatSliceCmd struct { baseCmd val []float64 } var _ Cmder = (*FloatSliceCmd)(nil) func NewFloatSliceCmd(ctx context.Context, args ...interface{}) *FloatSliceCmd { return &FloatSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeFloatSlice, }, } } func (cmd *FloatSliceCmd) SetVal(val []float64) { cmd.val = val } func (cmd *FloatSliceCmd) Val() []float64 { return cmd.val } func (cmd *FloatSliceCmd) Result() ([]float64, error) { return cmd.val, cmd.err } func (cmd *FloatSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]float64, n) for i := 0; i < len(cmd.val); i++ { switch num, err := rd.ReadFloat(); { case err == Nil: cmd.val[i] = 0 case err != nil: return err default: cmd.val[i] = num } } return nil } func (cmd *FloatSliceCmd) Clone() Cmder { var val []float64 if cmd.val != nil { val = make([]float64, len(cmd.val)) copy(val, cmd.val) } return &FloatSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type StringSliceCmd struct { baseCmd val []string } var _ Cmder = (*StringSliceCmd)(nil) func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd { return &StringSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeStringSlice, }, } } func (cmd *StringSliceCmd) SetVal(val []string) { cmd.val = val } func (cmd *StringSliceCmd) Val() []string { return cmd.val } func (cmd *StringSliceCmd) Result() ([]string, error) { return cmd.val, cmd.err } func (cmd *StringSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { return proto.ScanSlice(cmd.val, container) } func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]string, n) for i := 0; i < len(cmd.val); i++ { switch s, err := rd.ReadString(); { case err == Nil: cmd.val[i] = "" case err != nil: return err default: cmd.val[i] = s } } return nil } func (cmd *StringSliceCmd) Clone() Cmder { var val []string if cmd.val != nil { val = make([]string, len(cmd.val)) copy(val, cmd.val) } return &StringSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type KeyValue struct { Key string Value string } type KeyValueSliceCmd struct { baseCmd val []KeyValue } var _ Cmder = (*KeyValueSliceCmd)(nil) func NewKeyValueSliceCmd(ctx context.Context, args ...interface{}) *KeyValueSliceCmd { return &KeyValueSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeKeyValueSlice, }, } } func (cmd *KeyValueSliceCmd) SetVal(val []KeyValue) { cmd.val = val } func (cmd *KeyValueSliceCmd) Val() []KeyValue { return cmd.val } func (cmd *KeyValueSliceCmd) Result() ([]KeyValue, error) { return cmd.val, cmd.err } func (cmd *KeyValueSliceCmd) String() string { return cmdString(cmd, cmd.val) } // Many commands will respond to two formats: // 1. 1) "one" // 2. (double) 1 // 2. 1) "two" // 2. (double) 2 // // OR: // 1. "two" // 2. (double) 2 // 3. "one" // 4. (double) 1 func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl n, err := rd.ReadArrayLen() if err != nil { return err } // If the n is 0, can't continue reading. if n == 0 { cmd.val = make([]KeyValue, 0) return nil } typ, err := rd.PeekReplyType() if err != nil { return err } array := typ == proto.RespArray if array { cmd.val = make([]KeyValue, n) } else { cmd.val = make([]KeyValue, n/2) } for i := 0; i < len(cmd.val); i++ { if array { if err = rd.ReadFixedArrayLen(2); err != nil { return err } } if cmd.val[i].Key, err = rd.ReadString(); err != nil { return err } if cmd.val[i].Value, err = rd.ReadString(); err != nil { return err } } return nil } func (cmd *KeyValueSliceCmd) Clone() Cmder { var val []KeyValue if cmd.val != nil { val = make([]KeyValue, len(cmd.val)) copy(val, cmd.val) } return &KeyValueSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type BoolSliceCmd struct { baseCmd val []bool } var _ Cmder = (*BoolSliceCmd)(nil) func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd { return &BoolSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeBoolSlice, }, } } func (cmd *BoolSliceCmd) SetVal(val []bool) { cmd.val = val } func (cmd *BoolSliceCmd) Val() []bool { return cmd.val } func (cmd *BoolSliceCmd) Result() ([]bool, error) { return cmd.val, cmd.err } func (cmd *BoolSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]bool, n) for i := 0; i < len(cmd.val); i++ { if cmd.val[i], err = rd.ReadBool(); err != nil { return err } } return nil } func (cmd *BoolSliceCmd) Clone() Cmder { var val []bool if cmd.val != nil { val = make([]bool, len(cmd.val)) copy(val, cmd.val) } return &BoolSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type MapStringStringCmd struct { baseCmd val map[string]string } var _ Cmder = (*MapStringStringCmd)(nil) func NewMapStringStringCmd(ctx context.Context, args ...interface{}) *MapStringStringCmd { return &MapStringStringCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringString, }, } } func (cmd *MapStringStringCmd) Val() map[string]string { return cmd.val } func (cmd *MapStringStringCmd) SetVal(val map[string]string) { cmd.val = val } func (cmd *MapStringStringCmd) Result() (map[string]string, error) { return cmd.val, cmd.err } func (cmd *MapStringStringCmd) String() string { return cmdString(cmd, cmd.val) } // Scan scans the results from the map into a destination struct. The map keys // are matched in the Redis struct fields by the `redis:"field"` tag. func (cmd *MapStringStringCmd) Scan(dest interface{}) error { if cmd.err != nil { return cmd.err } strct, err := hscan.Struct(dest) if err != nil { return err } for k, v := range cmd.val { if err := strct.Scan(k, v); err != nil { return err } } return nil } func (cmd *MapStringStringCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = make(map[string]string, n) for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return err } value, err := rd.ReadString() if err != nil { return err } cmd.val[key] = value } return nil } func (cmd *MapStringStringCmd) Clone() Cmder { var val map[string]string if cmd.val != nil { val = make(map[string]string, len(cmd.val)) for k, v := range cmd.val { val[k] = v } } return &MapStringStringCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type MapStringIntCmd struct { baseCmd val map[string]int64 } var _ Cmder = (*MapStringIntCmd)(nil) func NewMapStringIntCmd(ctx context.Context, args ...interface{}) *MapStringIntCmd { return &MapStringIntCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringInt, }, } } func (cmd *MapStringIntCmd) SetVal(val map[string]int64) { cmd.val = val } func (cmd *MapStringIntCmd) Val() map[string]int64 { return cmd.val } func (cmd *MapStringIntCmd) Result() (map[string]int64, error) { return cmd.val, cmd.err } func (cmd *MapStringIntCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapStringIntCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = make(map[string]int64, n) for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return err } nn, err := rd.ReadInt() if err != nil { return err } cmd.val[key] = nn } return nil } func (cmd *MapStringIntCmd) Clone() Cmder { var val map[string]int64 if cmd.val != nil { val = make(map[string]int64, len(cmd.val)) for k, v := range cmd.val { val[k] = v } } return &MapStringIntCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // ------------------------------------------------------------------------------ type MapStringSliceInterfaceCmd struct { baseCmd val map[string][]interface{} } func NewMapStringSliceInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringSliceInterfaceCmd { return &MapStringSliceInterfaceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringInterfaceSlice, }, } } func (cmd *MapStringSliceInterfaceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapStringSliceInterfaceCmd) SetVal(val map[string][]interface{}) { cmd.val = val } func (cmd *MapStringSliceInterfaceCmd) Result() (map[string][]interface{}, error) { return cmd.val, cmd.err } func (cmd *MapStringSliceInterfaceCmd) Val() map[string][]interface{} { return cmd.val } func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) { readType, err := rd.PeekReplyType() if err != nil { return err } cmd.val = make(map[string][]interface{}) switch readType { case proto.RespMap: n, err := rd.ReadMapLen() if err != nil { return err } for i := 0; i < n; i++ { k, err := rd.ReadString() if err != nil { return err } nn, err := rd.ReadArrayLen() if err != nil { return err } cmd.val[k] = make([]interface{}, nn) for j := 0; j < nn; j++ { value, err := rd.ReadReply() if err != nil { return err } cmd.val[k][j] = value } } case proto.RespArray: // RESP2 response n, err := rd.ReadArrayLen() if err != nil { return err } for i := 0; i < n; i++ { // Each entry in this array is itself an array with key details itemLen, err := rd.ReadArrayLen() if err != nil { return err } key, err := rd.ReadString() if err != nil { return err } cmd.val[key] = make([]interface{}, 0, itemLen-1) for j := 1; j < itemLen; j++ { // Read the inner array for timestamp-value pairs data, err := rd.ReadReply() if err != nil { return err } cmd.val[key] = append(cmd.val[key], data) } } } return nil } func (cmd *MapStringSliceInterfaceCmd) Clone() Cmder { var val map[string][]interface{} if cmd.val != nil { val = make(map[string][]interface{}, len(cmd.val)) for k, v := range cmd.val { if v != nil { newSlice := make([]interface{}, len(v)) copy(newSlice, v) val[k] = newSlice } } } return &MapStringSliceInterfaceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type StringStructMapCmd struct { baseCmd val map[string]struct{} } var _ Cmder = (*StringStructMapCmd)(nil) func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd { return &StringStructMapCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeStringStructMap, }, } } func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) { cmd.val = val } func (cmd *StringStructMapCmd) Val() map[string]struct{} { return cmd.val } func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { return cmd.val, cmd.err } func (cmd *StringStructMapCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make(map[string]struct{}, n) for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return err } cmd.val[key] = struct{}{} } return nil } func (cmd *StringStructMapCmd) Clone() Cmder { var val map[string]struct{} if cmd.val != nil { val = make(map[string]struct{}, len(cmd.val)) for k := range cmd.val { val[k] = struct{}{} } } return &StringStructMapCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XMessage struct { ID string Values map[string]interface{} // MillisElapsedFromDelivery is the number of milliseconds since the entry was last delivered. // Only populated when using XREADGROUP with CLAIM argument for claimed entries. MillisElapsedFromDelivery int64 // DeliveredCount is the number of times the entry was delivered. // Only populated when using XREADGROUP with CLAIM argument for claimed entries. DeliveredCount int64 } type XMessageSliceCmd struct { baseCmd val []XMessage } var _ Cmder = (*XMessageSliceCmd)(nil) func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSliceCmd { return &XMessageSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXMessageSlice, }, } } func (cmd *XMessageSliceCmd) SetVal(val []XMessage) { cmd.val = val } func (cmd *XMessageSliceCmd) Val() []XMessage { return cmd.val } func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) { return cmd.val, cmd.err } func (cmd *XMessageSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = readXMessageSlice(rd) return err } func (cmd *XMessageSliceCmd) Clone() Cmder { var val []XMessage if cmd.val != nil { val = make([]XMessage, len(cmd.val)) for i, msg := range cmd.val { val[i] = XMessage{ ID: msg.ID, } if msg.Values != nil { val[i].Values = make(map[string]interface{}, len(msg.Values)) for k, v := range msg.Values { val[i].Values[k] = v } } } } return &XMessageSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } msgs := make([]XMessage, n) for i := 0; i < len(msgs); i++ { if msgs[i], err = readXMessage(rd); err != nil { return nil, err } } return msgs, nil } func readXMessage(rd *proto.Reader) (XMessage, error) { // Read array length can be 2 or 4 (with CLAIM metadata) n, err := rd.ReadArrayLen() if err != nil { return XMessage{}, err } if n != 2 && n != 4 { return XMessage{}, fmt.Errorf("redis: got %d elements in the XMessage array, expected 2 or 4", n) } id, err := rd.ReadString() if err != nil { return XMessage{}, err } v, err := stringInterfaceMapParser(rd) if err != nil { if err != proto.Nil { return XMessage{}, err } } msg := XMessage{ ID: id, Values: v, } if n == 4 { msg.MillisElapsedFromDelivery, err = rd.ReadInt() if err != nil { return XMessage{}, err } msg.DeliveredCount, err = rd.ReadInt() if err != nil { return XMessage{}, err } } return msg, nil } func stringInterfaceMapParser(rd *proto.Reader) (map[string]interface{}, error) { n, err := rd.ReadMapLen() if err != nil { return nil, err } m := make(map[string]interface{}, n) for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return nil, err } value, err := rd.ReadString() if err != nil { return nil, err } m[key] = value } return m, nil } //------------------------------------------------------------------------------ type XStream struct { Stream string Messages []XMessage } type XStreamSliceCmd struct { baseCmd val []XStream } var _ Cmder = (*XStreamSliceCmd)(nil) func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceCmd { return &XStreamSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXStreamSlice, }, } } func (cmd *XStreamSliceCmd) SetVal(val []XStream) { cmd.val = val } func (cmd *XStreamSliceCmd) Val() []XStream { return cmd.val } func (cmd *XStreamSliceCmd) Result() ([]XStream, error) { return cmd.val, cmd.err } func (cmd *XStreamSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { typ, err := rd.PeekReplyType() if err != nil { return err } var n int if typ == proto.RespMap { n, err = rd.ReadMapLen() } else { n, err = rd.ReadArrayLen() } if err != nil { return err } cmd.val = make([]XStream, n) for i := 0; i < len(cmd.val); i++ { if typ != proto.RespMap { if err = rd.ReadFixedArrayLen(2); err != nil { return err } } if cmd.val[i].Stream, err = rd.ReadString(); err != nil { return err } if cmd.val[i].Messages, err = readXMessageSlice(rd); err != nil { return err } } return nil } func (cmd *XStreamSliceCmd) Clone() Cmder { var val []XStream if cmd.val != nil { val = make([]XStream, len(cmd.val)) for i, stream := range cmd.val { val[i] = XStream{ Stream: stream.Stream, } if stream.Messages != nil { val[i].Messages = make([]XMessage, len(stream.Messages)) for j, msg := range stream.Messages { val[i].Messages[j] = XMessage{ ID: msg.ID, } if msg.Values != nil { val[i].Messages[j].Values = make(map[string]interface{}, len(msg.Values)) for k, v := range msg.Values { val[i].Messages[j].Values[k] = v } } } } } } return &XStreamSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XPending struct { Count int64 Lower string Higher string Consumers map[string]int64 } type XPendingCmd struct { baseCmd val *XPending } var _ Cmder = (*XPendingCmd)(nil) func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd { return &XPendingCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXPending, }, } } func (cmd *XPendingCmd) SetVal(val *XPending) { cmd.val = val } func (cmd *XPendingCmd) Val() *XPending { return cmd.val } func (cmd *XPendingCmd) Result() (*XPending, error) { return cmd.val, cmd.err } func (cmd *XPendingCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { var err error if err = rd.ReadFixedArrayLen(4); err != nil { return err } cmd.val = &XPending{} if cmd.val.Count, err = rd.ReadInt(); err != nil { return err } if cmd.val.Lower, err = rd.ReadString(); err != nil && err != Nil { return err } if cmd.val.Higher, err = rd.ReadString(); err != nil && err != Nil { return err } n, err := rd.ReadArrayLen() if err != nil && err != Nil { return err } cmd.val.Consumers = make(map[string]int64, n) for i := 0; i < n; i++ { if err = rd.ReadFixedArrayLen(2); err != nil { return err } consumerName, err := rd.ReadString() if err != nil { return err } consumerPending, err := rd.ReadInt() if err != nil { return err } cmd.val.Consumers[consumerName] = consumerPending } return nil } func (cmd *XPendingCmd) Clone() Cmder { var val *XPending if cmd.val != nil { val = &XPending{ Count: cmd.val.Count, Lower: cmd.val.Lower, Higher: cmd.val.Higher, } if cmd.val.Consumers != nil { val.Consumers = make(map[string]int64, len(cmd.val.Consumers)) for k, v := range cmd.val.Consumers { val.Consumers[k] = v } } } return &XPendingCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XPendingExt struct { ID string Consumer string Idle time.Duration RetryCount int64 } type XPendingExtCmd struct { baseCmd val []XPendingExt } var _ Cmder = (*XPendingExtCmd)(nil) func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd { return &XPendingExtCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXPendingExt, }, } } func (cmd *XPendingExtCmd) SetVal(val []XPendingExt) { cmd.val = val } func (cmd *XPendingExtCmd) Val() []XPendingExt { return cmd.val } func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) { return cmd.val, cmd.err } func (cmd *XPendingExtCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]XPendingExt, n) for i := 0; i < len(cmd.val); i++ { if err = rd.ReadFixedArrayLen(4); err != nil { return err } if cmd.val[i].ID, err = rd.ReadString(); err != nil { return err } if cmd.val[i].Consumer, err = rd.ReadString(); err != nil && err != Nil { return err } idle, err := rd.ReadInt() if err != nil && err != Nil { return err } cmd.val[i].Idle = time.Duration(idle) * time.Millisecond if cmd.val[i].RetryCount, err = rd.ReadInt(); err != nil && err != Nil { return err } } return nil } func (cmd *XPendingExtCmd) Clone() Cmder { var val []XPendingExt if cmd.val != nil { val = make([]XPendingExt, len(cmd.val)) copy(val, cmd.val) } return &XPendingExtCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XAutoClaimCmd struct { baseCmd start string val []XMessage } var _ Cmder = (*XAutoClaimCmd)(nil) func NewXAutoClaimCmd(ctx context.Context, args ...interface{}) *XAutoClaimCmd { return &XAutoClaimCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXAutoClaim, }, } } func (cmd *XAutoClaimCmd) SetVal(val []XMessage, start string) { cmd.val = val cmd.start = start } func (cmd *XAutoClaimCmd) Val() (messages []XMessage, start string) { return cmd.val, cmd.start } func (cmd *XAutoClaimCmd) Result() (messages []XMessage, start string, err error) { return cmd.val, cmd.start, cmd.err } func (cmd *XAutoClaimCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } switch n { case 2, // Redis 6 3: // Redis 7: // ok default: return fmt.Errorf("redis: got %d elements in XAutoClaim reply, wanted 2/3", n) } cmd.start, err = rd.ReadString() if err != nil { return err } cmd.val, err = readXMessageSlice(rd) if err != nil { return err } if n >= 3 { if err := rd.DiscardNext(); err != nil { return err } } return nil } func (cmd *XAutoClaimCmd) Clone() Cmder { var val []XMessage if cmd.val != nil { val = make([]XMessage, len(cmd.val)) for i, msg := range cmd.val { val[i] = XMessage{ ID: msg.ID, } if msg.Values != nil { val[i].Values = make(map[string]interface{}, len(msg.Values)) for k, v := range msg.Values { val[i].Values[k] = v } } } } return &XAutoClaimCmd{ baseCmd: cmd.cloneBaseCmd(), start: cmd.start, val: val, } } //------------------------------------------------------------------------------ type XAutoClaimJustIDCmd struct { baseCmd start string val []string } var _ Cmder = (*XAutoClaimJustIDCmd)(nil) func NewXAutoClaimJustIDCmd(ctx context.Context, args ...interface{}) *XAutoClaimJustIDCmd { return &XAutoClaimJustIDCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXAutoClaimJustID, }, } } func (cmd *XAutoClaimJustIDCmd) SetVal(val []string, start string) { cmd.val = val cmd.start = start } func (cmd *XAutoClaimJustIDCmd) Val() (ids []string, start string) { return cmd.val, cmd.start } func (cmd *XAutoClaimJustIDCmd) Result() (ids []string, start string, err error) { return cmd.val, cmd.start, cmd.err } func (cmd *XAutoClaimJustIDCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } switch n { case 2, // Redis 6 3: // Redis 7: // ok default: return fmt.Errorf("redis: got %d elements in XAutoClaimJustID reply, wanted 2/3", n) } cmd.start, err = rd.ReadString() if err != nil { return err } nn, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]string, nn) for i := 0; i < nn; i++ { cmd.val[i], err = rd.ReadString() if err != nil { return err } } if n >= 3 { if err := rd.DiscardNext(); err != nil { return err } } return nil } func (cmd *XAutoClaimJustIDCmd) Clone() Cmder { var val []string if cmd.val != nil { val = make([]string, len(cmd.val)) copy(val, cmd.val) } return &XAutoClaimJustIDCmd{ baseCmd: cmd.cloneBaseCmd(), start: cmd.start, val: val, } } //------------------------------------------------------------------------------ type XInfoConsumersCmd struct { baseCmd val []XInfoConsumer } type XInfoConsumer struct { Name string Pending int64 Idle time.Duration Inactive time.Duration } var _ Cmder = (*XInfoConsumersCmd)(nil) func NewXInfoConsumersCmd(ctx context.Context, stream string, group string) *XInfoConsumersCmd { return &XInfoConsumersCmd{ baseCmd: baseCmd{ ctx: ctx, args: []interface{}{"xinfo", "consumers", stream, group}, cmdType: CmdTypeXInfoConsumers, }, } } func (cmd *XInfoConsumersCmd) SetVal(val []XInfoConsumer) { cmd.val = val } func (cmd *XInfoConsumersCmd) Val() []XInfoConsumer { return cmd.val } func (cmd *XInfoConsumersCmd) Result() ([]XInfoConsumer, error) { return cmd.val, cmd.err } func (cmd *XInfoConsumersCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]XInfoConsumer, n) for i := 0; i < len(cmd.val); i++ { nn, err := rd.ReadMapLen() if err != nil { return err } var key string for f := 0; f < nn; f++ { key, err = rd.ReadString() if err != nil { return err } switch key { case "name": cmd.val[i].Name, err = rd.ReadString() case "pending": cmd.val[i].Pending, err = rd.ReadInt() case "idle": var idle int64 idle, err = rd.ReadInt() cmd.val[i].Idle = time.Duration(idle) * time.Millisecond case "inactive": var inactive int64 inactive, err = rd.ReadInt() cmd.val[i].Inactive = time.Duration(inactive) * time.Millisecond default: return fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key) } if err != nil { return err } } } return nil } func (cmd *XInfoConsumersCmd) Clone() Cmder { var val []XInfoConsumer if cmd.val != nil { val = make([]XInfoConsumer, len(cmd.val)) copy(val, cmd.val) } return &XInfoConsumersCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XInfoGroupsCmd struct { baseCmd val []XInfoGroup } type XInfoGroup struct { Name string Consumers int64 Pending int64 LastDeliveredID string EntriesRead int64 // Lag represents the number of pending messages in the stream not yet // delivered to this consumer group. Returns -1 when the lag cannot be determined. Lag int64 } var _ Cmder = (*XInfoGroupsCmd)(nil) func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd { return &XInfoGroupsCmd{ baseCmd: baseCmd{ ctx: ctx, args: []interface{}{"xinfo", "groups", stream}, cmdType: CmdTypeXInfoGroups, }, } } func (cmd *XInfoGroupsCmd) SetVal(val []XInfoGroup) { cmd.val = val } func (cmd *XInfoGroupsCmd) Val() []XInfoGroup { return cmd.val } func (cmd *XInfoGroupsCmd) Result() ([]XInfoGroup, error) { return cmd.val, cmd.err } func (cmd *XInfoGroupsCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]XInfoGroup, n) for i := 0; i < len(cmd.val); i++ { group := &cmd.val[i] nn, err := rd.ReadMapLen() if err != nil { return err } var key string for j := 0; j < nn; j++ { key, err = rd.ReadString() if err != nil { return err } switch key { case "name": group.Name, err = rd.ReadString() if err != nil { return err } case "consumers": group.Consumers, err = rd.ReadInt() if err != nil { return err } case "pending": group.Pending, err = rd.ReadInt() if err != nil { return err } case "last-delivered-id": group.LastDeliveredID, err = rd.ReadString() if err != nil { return err } case "entries-read": group.EntriesRead, err = rd.ReadInt() if err != nil && err != Nil { return err } case "lag": group.Lag, err = rd.ReadInt() // lag: the number of entries in the stream that are still waiting to be delivered // to the group's consumers, or a NULL(Nil) when that number can't be determined. // In that case, we return -1. if err != nil && err != Nil { return err } else if err == Nil { group.Lag = -1 } default: return fmt.Errorf("redis: unexpected key %q in XINFO GROUPS reply", key) } } } return nil } func (cmd *XInfoGroupsCmd) Clone() Cmder { var val []XInfoGroup if cmd.val != nil { val = make([]XInfoGroup, len(cmd.val)) copy(val, cmd.val) } return &XInfoGroupsCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XInfoStreamCmd struct { baseCmd val *XInfoStream } type XInfoStream struct { Length int64 RadixTreeKeys int64 RadixTreeNodes int64 Groups int64 LastGeneratedID string MaxDeletedEntryID string EntriesAdded int64 FirstEntry XMessage LastEntry XMessage RecordedFirstEntryID string } var _ Cmder = (*XInfoStreamCmd)(nil) func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd { return &XInfoStreamCmd{ baseCmd: baseCmd{ ctx: ctx, args: []interface{}{"xinfo", "stream", stream}, cmdType: CmdTypeXInfoStream, }, } } func (cmd *XInfoStreamCmd) SetVal(val *XInfoStream) { cmd.val = val } func (cmd *XInfoStreamCmd) Val() *XInfoStream { return cmd.val } func (cmd *XInfoStreamCmd) Result() (*XInfoStream, error) { return cmd.val, cmd.err } func (cmd *XInfoStreamCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = &XInfoStream{} for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "length": cmd.val.Length, err = rd.ReadInt() if err != nil { return err } case "radix-tree-keys": cmd.val.RadixTreeKeys, err = rd.ReadInt() if err != nil { return err } case "radix-tree-nodes": cmd.val.RadixTreeNodes, err = rd.ReadInt() if err != nil { return err } case "groups": cmd.val.Groups, err = rd.ReadInt() if err != nil { return err } case "last-generated-id": cmd.val.LastGeneratedID, err = rd.ReadString() if err != nil { return err } case "max-deleted-entry-id": cmd.val.MaxDeletedEntryID, err = rd.ReadString() if err != nil { return err } case "entries-added": cmd.val.EntriesAdded, err = rd.ReadInt() if err != nil { return err } case "first-entry": cmd.val.FirstEntry, err = readXMessage(rd) if err != nil && err != Nil { return err } case "last-entry": cmd.val.LastEntry, err = readXMessage(rd) if err != nil && err != Nil { return err } case "recorded-first-entry-id": cmd.val.RecordedFirstEntryID, err = rd.ReadString() if err != nil { return err } default: return fmt.Errorf("redis: unexpected key %q in XINFO STREAM reply", key) } } return nil } func (cmd *XInfoStreamCmd) Clone() Cmder { var val *XInfoStream if cmd.val != nil { val = &XInfoStream{ Length: cmd.val.Length, RadixTreeKeys: cmd.val.RadixTreeKeys, RadixTreeNodes: cmd.val.RadixTreeNodes, Groups: cmd.val.Groups, LastGeneratedID: cmd.val.LastGeneratedID, MaxDeletedEntryID: cmd.val.MaxDeletedEntryID, EntriesAdded: cmd.val.EntriesAdded, RecordedFirstEntryID: cmd.val.RecordedFirstEntryID, } // Clone XMessage fields val.FirstEntry = XMessage{ ID: cmd.val.FirstEntry.ID, } if cmd.val.FirstEntry.Values != nil { val.FirstEntry.Values = make(map[string]interface{}, len(cmd.val.FirstEntry.Values)) for k, v := range cmd.val.FirstEntry.Values { val.FirstEntry.Values[k] = v } } val.LastEntry = XMessage{ ID: cmd.val.LastEntry.ID, } if cmd.val.LastEntry.Values != nil { val.LastEntry.Values = make(map[string]interface{}, len(cmd.val.LastEntry.Values)) for k, v := range cmd.val.LastEntry.Values { val.LastEntry.Values[k] = v } } } return &XInfoStreamCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type XInfoStreamFullCmd struct { baseCmd val *XInfoStreamFull } type XInfoStreamFull struct { Length int64 RadixTreeKeys int64 RadixTreeNodes int64 LastGeneratedID string MaxDeletedEntryID string EntriesAdded int64 Entries []XMessage Groups []XInfoStreamGroup RecordedFirstEntryID string } type XInfoStreamGroup struct { Name string LastDeliveredID string EntriesRead int64 Lag int64 PelCount int64 Pending []XInfoStreamGroupPending Consumers []XInfoStreamConsumer } type XInfoStreamGroupPending struct { ID string Consumer string DeliveryTime time.Time DeliveryCount int64 } type XInfoStreamConsumer struct { Name string SeenTime time.Time ActiveTime time.Time PelCount int64 Pending []XInfoStreamConsumerPending } type XInfoStreamConsumerPending struct { ID string DeliveryTime time.Time DeliveryCount int64 } var _ Cmder = (*XInfoStreamFullCmd)(nil) func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStreamFullCmd { return &XInfoStreamFullCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeXInfoStreamFull, }, } } func (cmd *XInfoStreamFullCmd) SetVal(val *XInfoStreamFull) { cmd.val = val } func (cmd *XInfoStreamFullCmd) Val() *XInfoStreamFull { return cmd.val } func (cmd *XInfoStreamFullCmd) Result() (*XInfoStreamFull, error) { return cmd.val, cmd.err } func (cmd *XInfoStreamFullCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = &XInfoStreamFull{} for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "length": cmd.val.Length, err = rd.ReadInt() if err != nil { return err } case "radix-tree-keys": cmd.val.RadixTreeKeys, err = rd.ReadInt() if err != nil { return err } case "radix-tree-nodes": cmd.val.RadixTreeNodes, err = rd.ReadInt() if err != nil { return err } case "last-generated-id": cmd.val.LastGeneratedID, err = rd.ReadString() if err != nil { return err } case "entries-added": cmd.val.EntriesAdded, err = rd.ReadInt() if err != nil { return err } case "entries": cmd.val.Entries, err = readXMessageSlice(rd) if err != nil { return err } case "groups": cmd.val.Groups, err = readStreamGroups(rd) if err != nil { return err } case "max-deleted-entry-id": cmd.val.MaxDeletedEntryID, err = rd.ReadString() if err != nil { return err } case "recorded-first-entry-id": cmd.val.RecordedFirstEntryID, err = rd.ReadString() if err != nil { return err } default: return fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key) } } return nil } func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } groups := make([]XInfoStreamGroup, 0, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return nil, err } group := XInfoStreamGroup{} for j := 0; j < nn; j++ { key, err := rd.ReadString() if err != nil { return nil, err } switch key { case "name": group.Name, err = rd.ReadString() if err != nil { return nil, err } case "last-delivered-id": group.LastDeliveredID, err = rd.ReadString() if err != nil { return nil, err } case "entries-read": group.EntriesRead, err = rd.ReadInt() if err != nil && err != Nil { return nil, err } case "lag": // lag: the number of entries in the stream that are still waiting to be delivered // to the group's consumers, or a NULL(Nil) when that number can't be determined. group.Lag, err = rd.ReadInt() if err != nil && err != Nil { return nil, err } case "pel-count": group.PelCount, err = rd.ReadInt() if err != nil { return nil, err } case "pending": group.Pending, err = readXInfoStreamGroupPending(rd) if err != nil { return nil, err } case "consumers": group.Consumers, err = readXInfoStreamConsumers(rd) if err != nil { return nil, err } default: return nil, fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key) } } groups = append(groups, group) } return groups, nil } func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } pending := make([]XInfoStreamGroupPending, 0, n) for i := 0; i < n; i++ { if err = rd.ReadFixedArrayLen(4); err != nil { return nil, err } p := XInfoStreamGroupPending{} p.ID, err = rd.ReadString() if err != nil { return nil, err } p.Consumer, err = rd.ReadString() if err != nil { return nil, err } delivery, err := rd.ReadInt() if err != nil { return nil, err } p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) p.DeliveryCount, err = rd.ReadInt() if err != nil { return nil, err } pending = append(pending, p) } return pending, nil } func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } consumers := make([]XInfoStreamConsumer, 0, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return nil, err } c := XInfoStreamConsumer{} for f := 0; f < nn; f++ { cKey, err := rd.ReadString() if err != nil { return nil, err } switch cKey { case "name": c.Name, err = rd.ReadString() case "seen-time": seen, err := rd.ReadInt() if err != nil { return nil, err } c.SeenTime = time.UnixMilli(seen) case "active-time": active, err := rd.ReadInt() if err != nil { return nil, err } c.ActiveTime = time.UnixMilli(active) case "pel-count": c.PelCount, err = rd.ReadInt() case "pending": pendingNumber, err := rd.ReadArrayLen() if err != nil { return nil, err } c.Pending = make([]XInfoStreamConsumerPending, 0, pendingNumber) for pn := 0; pn < pendingNumber; pn++ { if err = rd.ReadFixedArrayLen(3); err != nil { return nil, err } p := XInfoStreamConsumerPending{} p.ID, err = rd.ReadString() if err != nil { return nil, err } delivery, err := rd.ReadInt() if err != nil { return nil, err } p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) p.DeliveryCount, err = rd.ReadInt() if err != nil { return nil, err } c.Pending = append(c.Pending, p) } default: return nil, fmt.Errorf("redis: unexpected content %s "+ "in XINFO STREAM FULL reply", cKey) } if err != nil { return nil, err } } consumers = append(consumers, c) } return consumers, nil } func (cmd *XInfoStreamFullCmd) Clone() Cmder { var val *XInfoStreamFull if cmd.val != nil { val = &XInfoStreamFull{ Length: cmd.val.Length, RadixTreeKeys: cmd.val.RadixTreeKeys, RadixTreeNodes: cmd.val.RadixTreeNodes, LastGeneratedID: cmd.val.LastGeneratedID, MaxDeletedEntryID: cmd.val.MaxDeletedEntryID, EntriesAdded: cmd.val.EntriesAdded, RecordedFirstEntryID: cmd.val.RecordedFirstEntryID, } // Clone Entries if cmd.val.Entries != nil { val.Entries = make([]XMessage, len(cmd.val.Entries)) for i, msg := range cmd.val.Entries { val.Entries[i] = XMessage{ ID: msg.ID, } if msg.Values != nil { val.Entries[i].Values = make(map[string]interface{}, len(msg.Values)) for k, v := range msg.Values { val.Entries[i].Values[k] = v } } } } // Clone Groups - simplified copy for now due to complexity if cmd.val.Groups != nil { val.Groups = make([]XInfoStreamGroup, len(cmd.val.Groups)) copy(val.Groups, cmd.val.Groups) } } return &XInfoStreamFullCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type ZSliceCmd struct { baseCmd val []Z } var _ Cmder = (*ZSliceCmd)(nil) func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd { return &ZSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeZSlice, }, } } func (cmd *ZSliceCmd) SetVal(val []Z) { cmd.val = val } func (cmd *ZSliceCmd) Val() []Z { return cmd.val } func (cmd *ZSliceCmd) Result() ([]Z, error) { return cmd.val, cmd.err } func (cmd *ZSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl n, err := rd.ReadArrayLen() if err != nil { return err } // If the n is 0, can't continue reading. if n == 0 { cmd.val = make([]Z, 0) return nil } typ, err := rd.PeekReplyType() if err != nil { return err } array := typ == proto.RespArray if array { cmd.val = make([]Z, n) } else { cmd.val = make([]Z, n/2) } for i := 0; i < len(cmd.val); i++ { if array { if err = rd.ReadFixedArrayLen(2); err != nil { return err } } if cmd.val[i].Member, err = rd.ReadString(); err != nil { return err } if cmd.val[i].Score, err = rd.ReadFloat(); err != nil { return err } } return nil } func (cmd *ZSliceCmd) Clone() Cmder { var val []Z if cmd.val != nil { val = make([]Z, len(cmd.val)) copy(val, cmd.val) } return &ZSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type ZWithKeyCmd struct { baseCmd val *ZWithKey } var _ Cmder = (*ZWithKeyCmd)(nil) func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd { return &ZWithKeyCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeZWithKey, }, } } func (cmd *ZWithKeyCmd) SetVal(val *ZWithKey) { cmd.val = val } func (cmd *ZWithKeyCmd) Val() *ZWithKey { return cmd.val } func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) { return cmd.val, cmd.err } func (cmd *ZWithKeyCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) (err error) { if err = rd.ReadFixedArrayLen(3); err != nil { return err } cmd.val = &ZWithKey{} if cmd.val.Key, err = rd.ReadString(); err != nil { return err } if cmd.val.Member, err = rd.ReadString(); err != nil { return err } if cmd.val.Score, err = rd.ReadFloat(); err != nil { return err } return nil } func (cmd *ZWithKeyCmd) Clone() Cmder { var val *ZWithKey if cmd.val != nil { val = &ZWithKey{ Z: Z{ Score: cmd.val.Score, Member: cmd.val.Member, }, Key: cmd.val.Key, } } return &ZWithKeyCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type ScanCmd struct { baseCmd page []string cursor uint64 process cmdable } var _ Cmder = (*ScanCmd)(nil) func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *ScanCmd { return &ScanCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeScan, }, process: process, } } func (cmd *ScanCmd) SetVal(page []string, cursor uint64) { cmd.page = page cmd.cursor = cursor } func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { return cmd.page, cmd.cursor } func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { return cmd.page, cmd.cursor, cmd.err } func (cmd *ScanCmd) String() string { return cmdString(cmd, cmd.page) } func (cmd *ScanCmd) readReply(rd *proto.Reader) error { if err := rd.ReadFixedArrayLen(2); err != nil { return err } cursor, err := rd.ReadUint() if err != nil { return err } cmd.cursor = cursor n, err := rd.ReadArrayLen() if err != nil { return err } cmd.page = make([]string, n) for i := 0; i < len(cmd.page); i++ { if cmd.page[i], err = rd.ReadString(); err != nil { return err } } return nil } func (cmd *ScanCmd) Clone() Cmder { var page []string if cmd.page != nil { page = make([]string, len(cmd.page)) copy(page, cmd.page) } return &ScanCmd{ baseCmd: cmd.cloneBaseCmd(), page: page, cursor: cmd.cursor, process: cmd.process, } } // Iterator creates a new ScanIterator. func (cmd *ScanCmd) Iterator() *ScanIterator { return &ScanIterator{ cmd: cmd, } } //------------------------------------------------------------------------------ type ClusterNode struct { ID string Addr string NetworkingMetadata map[string]string } type ClusterSlot struct { Start int End int Nodes []ClusterNode } type ClusterSlotsCmd struct { baseCmd val []ClusterSlot } var _ Cmder = (*ClusterSlotsCmd)(nil) func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsCmd { return &ClusterSlotsCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeClusterSlots, }, } } func (cmd *ClusterSlotsCmd) SetVal(val []ClusterSlot) { cmd.val = val } func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { return cmd.val } func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { return cmd.val, cmd.err } func (cmd *ClusterSlotsCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]ClusterSlot, n) for i := 0; i < len(cmd.val); i++ { n, err = rd.ReadArrayLen() if err != nil { return err } if n < 2 { return fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) } start, err := rd.ReadInt() if err != nil { return err } end, err := rd.ReadInt() if err != nil { return err } // subtract start and end. nodes := make([]ClusterNode, n-2) for j := 0; j < len(nodes); j++ { nn, err := rd.ReadArrayLen() if err != nil { return err } if nn < 2 || nn > 4 { return fmt.Errorf("got %d elements in cluster info address, expected 2, 3, or 4", n) } ip, err := rd.ReadString() if err != nil { return err } port, err := rd.ReadString() if err != nil { return err } nodes[j].Addr = net.JoinHostPort(ip, port) if nn >= 3 { id, err := rd.ReadString() if err != nil { return err } nodes[j].ID = id } if nn >= 4 { metadataLength, err := rd.ReadMapLen() if err != nil { return err } networkingMetadata := make(map[string]string, metadataLength) for i := 0; i < metadataLength; i++ { key, err := rd.ReadString() if err != nil { return err } value, err := rd.ReadString() if err != nil { return err } networkingMetadata[key] = value } nodes[j].NetworkingMetadata = networkingMetadata } } cmd.val[i] = ClusterSlot{ Start: int(start), End: int(end), Nodes: nodes, } } return nil } func (cmd *ClusterSlotsCmd) Clone() Cmder { var val []ClusterSlot if cmd.val != nil { val = make([]ClusterSlot, len(cmd.val)) for i, slot := range cmd.val { val[i] = ClusterSlot{ Start: slot.Start, End: slot.End, } if slot.Nodes != nil { val[i].Nodes = make([]ClusterNode, len(slot.Nodes)) for j, node := range slot.Nodes { val[i].Nodes[j] = ClusterNode{ ID: node.ID, Addr: node.Addr, } if node.NetworkingMetadata != nil { val[i].Nodes[j].NetworkingMetadata = make(map[string]string, len(node.NetworkingMetadata)) for k, v := range node.NetworkingMetadata { val[i].Nodes[j].NetworkingMetadata[k] = v } } } } } } return &ClusterSlotsCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ // GeoLocation is used with GeoAdd to add geospatial location. type GeoLocation struct { Name string Longitude, Latitude, Dist float64 GeoHash int64 } // GeoRadiusQuery is used with GeoRadius to query geospatial index. type GeoRadiusQuery struct { Radius float64 // Can be m, km, ft, or mi. Default is km. Unit string WithCoord bool WithDist bool WithGeoHash bool Count int // Can be ASC or DESC. Default is no sort order. Sort string Store string StoreDist string // WithCoord+WithDist+WithGeoHash withLen int } type GeoLocationCmd struct { baseCmd q *GeoRadiusQuery locations []GeoLocation } var _ Cmder = (*GeoLocationCmd)(nil) func NewGeoLocationCmd(ctx context.Context, q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { return &GeoLocationCmd{ baseCmd: baseCmd{ ctx: ctx, args: geoLocationArgs(q, args...), cmdType: CmdTypeGeoLocation, }, q: q, } } func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { args = append(args, q.Radius) if q.Unit != "" { args = append(args, q.Unit) } else { args = append(args, "km") } if q.WithCoord { args = append(args, "withcoord") q.withLen++ } if q.WithDist { args = append(args, "withdist") q.withLen++ } if q.WithGeoHash { args = append(args, "withhash") q.withLen++ } if q.Count > 0 { args = append(args, "count", q.Count) } if q.Sort != "" { args = append(args, q.Sort) } if q.Store != "" { args = append(args, "store") args = append(args, q.Store) } if q.StoreDist != "" { args = append(args, "storedist") args = append(args, q.StoreDist) } return args } func (cmd *GeoLocationCmd) SetVal(locations []GeoLocation) { cmd.locations = locations } func (cmd *GeoLocationCmd) Val() []GeoLocation { return cmd.locations } func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { return cmd.locations, cmd.err } func (cmd *GeoLocationCmd) String() string { return cmdString(cmd, cmd.locations) } func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.locations = make([]GeoLocation, n) for i := 0; i < len(cmd.locations); i++ { // only name if cmd.q.withLen == 0 { if cmd.locations[i].Name, err = rd.ReadString(); err != nil { return err } continue } // +name if err = rd.ReadFixedArrayLen(cmd.q.withLen + 1); err != nil { return err } if cmd.locations[i].Name, err = rd.ReadString(); err != nil { return err } if cmd.q.WithDist { if cmd.locations[i].Dist, err = rd.ReadFloat(); err != nil { return err } } if cmd.q.WithGeoHash { if cmd.locations[i].GeoHash, err = rd.ReadInt(); err != nil { return err } } if cmd.q.WithCoord { if err = rd.ReadFixedArrayLen(2); err != nil { return err } if cmd.locations[i].Longitude, err = rd.ReadFloat(); err != nil { return err } if cmd.locations[i].Latitude, err = rd.ReadFloat(); err != nil { return err } } } return nil } func (cmd *GeoLocationCmd) Clone() Cmder { var q *GeoRadiusQuery if cmd.q != nil { q = &GeoRadiusQuery{ Radius: cmd.q.Radius, Unit: cmd.q.Unit, WithCoord: cmd.q.WithCoord, WithDist: cmd.q.WithDist, WithGeoHash: cmd.q.WithGeoHash, Count: cmd.q.Count, Sort: cmd.q.Sort, Store: cmd.q.Store, StoreDist: cmd.q.StoreDist, withLen: cmd.q.withLen, } } var locations []GeoLocation if cmd.locations != nil { locations = make([]GeoLocation, len(cmd.locations)) copy(locations, cmd.locations) } return &GeoLocationCmd{ baseCmd: cmd.cloneBaseCmd(), q: q, locations: locations, } } //------------------------------------------------------------------------------ // GeoSearchQuery is used for GEOSearch/GEOSearchStore command query. type GeoSearchQuery struct { Member string // Latitude and Longitude when using FromLonLat option. Longitude float64 Latitude float64 // Distance and unit when using ByRadius option. // Can use m, km, ft, or mi. Default is km. Radius float64 RadiusUnit string // Height, width and unit when using ByBox option. // Can be m, km, ft, or mi. Default is km. BoxWidth float64 BoxHeight float64 BoxUnit string // Can be ASC or DESC. Default is no sort order. Sort string Count int CountAny bool } type GeoSearchLocationQuery struct { GeoSearchQuery WithCoord bool WithDist bool WithHash bool } type GeoSearchStoreQuery struct { GeoSearchQuery // When using the StoreDist option, the command stores the items in a // sorted set populated with their distance from the center of the circle or box, // as a floating-point number, in the same unit specified for that shape. StoreDist bool } func geoSearchLocationArgs(q *GeoSearchLocationQuery, args []interface{}) []interface{} { args = geoSearchArgs(&q.GeoSearchQuery, args) if q.WithCoord { args = append(args, "withcoord") } if q.WithDist { args = append(args, "withdist") } if q.WithHash { args = append(args, "withhash") } return args } func geoSearchArgs(q *GeoSearchQuery, args []interface{}) []interface{} { if q.Member != "" { args = append(args, "frommember", q.Member) } else { args = append(args, "fromlonlat", q.Longitude, q.Latitude) } if q.Radius > 0 { if q.RadiusUnit == "" { q.RadiusUnit = "km" } args = append(args, "byradius", q.Radius, q.RadiusUnit) } else { if q.BoxUnit == "" { q.BoxUnit = "km" } args = append(args, "bybox", q.BoxWidth, q.BoxHeight, q.BoxUnit) } if q.Sort != "" { args = append(args, q.Sort) } if q.Count > 0 { args = append(args, "count", q.Count) if q.CountAny { args = append(args, "any") } } return args } type GeoSearchLocationCmd struct { baseCmd opt *GeoSearchLocationQuery val []GeoLocation } var _ Cmder = (*GeoSearchLocationCmd)(nil) func NewGeoSearchLocationCmd( ctx context.Context, opt *GeoSearchLocationQuery, args ...interface{}, ) *GeoSearchLocationCmd { return &GeoSearchLocationCmd{ baseCmd: baseCmd{ ctx: ctx, args: geoSearchLocationArgs(opt, args), cmdType: CmdTypeGeoSearchLocation, }, opt: opt, } } func (cmd *GeoSearchLocationCmd) SetVal(val []GeoLocation) { cmd.val = val } func (cmd *GeoSearchLocationCmd) Val() []GeoLocation { return cmd.val } func (cmd *GeoSearchLocationCmd) Result() ([]GeoLocation, error) { return cmd.val, cmd.err } func (cmd *GeoSearchLocationCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]GeoLocation, n) for i := 0; i < n; i++ { _, err = rd.ReadArrayLen() if err != nil { return err } var loc GeoLocation loc.Name, err = rd.ReadString() if err != nil { return err } if cmd.opt.WithDist { loc.Dist, err = rd.ReadFloat() if err != nil { return err } } if cmd.opt.WithHash { loc.GeoHash, err = rd.ReadInt() if err != nil { return err } } if cmd.opt.WithCoord { if err = rd.ReadFixedArrayLen(2); err != nil { return err } loc.Longitude, err = rd.ReadFloat() if err != nil { return err } loc.Latitude, err = rd.ReadFloat() if err != nil { return err } } cmd.val[i] = loc } return nil } func (cmd *GeoSearchLocationCmd) Clone() Cmder { var opt *GeoSearchLocationQuery if cmd.opt != nil { opt = &GeoSearchLocationQuery{ GeoSearchQuery: GeoSearchQuery{ Member: cmd.opt.Member, Longitude: cmd.opt.Longitude, Latitude: cmd.opt.Latitude, Radius: cmd.opt.Radius, RadiusUnit: cmd.opt.RadiusUnit, BoxWidth: cmd.opt.BoxWidth, BoxHeight: cmd.opt.BoxHeight, BoxUnit: cmd.opt.BoxUnit, Sort: cmd.opt.Sort, Count: cmd.opt.Count, CountAny: cmd.opt.CountAny, }, WithCoord: cmd.opt.WithCoord, WithDist: cmd.opt.WithDist, WithHash: cmd.opt.WithHash, } } var val []GeoLocation if cmd.val != nil { val = make([]GeoLocation, len(cmd.val)) copy(val, cmd.val) } return &GeoSearchLocationCmd{ baseCmd: cmd.cloneBaseCmd(), opt: opt, val: val, } } //------------------------------------------------------------------------------ type GeoPos struct { Longitude, Latitude float64 } type GeoPosCmd struct { baseCmd val []*GeoPos } var _ Cmder = (*GeoPosCmd)(nil) func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd { return &GeoPosCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeGeoPos, }, } } func (cmd *GeoPosCmd) SetVal(val []*GeoPos) { cmd.val = val } func (cmd *GeoPosCmd) Val() []*GeoPos { return cmd.val } func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { return cmd.val, cmd.err } func (cmd *GeoPosCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]*GeoPos, n) for i := 0; i < len(cmd.val); i++ { err = rd.ReadFixedArrayLen(2) if err != nil { if err == Nil { cmd.val[i] = nil continue } return err } longitude, err := rd.ReadFloat() if err != nil { return err } latitude, err := rd.ReadFloat() if err != nil { return err } cmd.val[i] = &GeoPos{ Longitude: longitude, Latitude: latitude, } } return nil } func (cmd *GeoPosCmd) Clone() Cmder { var val []*GeoPos if cmd.val != nil { val = make([]*GeoPos, len(cmd.val)) for i, pos := range cmd.val { if pos != nil { val[i] = &GeoPos{ Longitude: pos.Longitude, Latitude: pos.Latitude, } } } } return &GeoPosCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type CommandInfo struct { Name string Arity int8 Flags []string ACLFlags []string FirstKeyPos int8 LastKeyPos int8 StepCount int8 ReadOnly bool CommandPolicy *routing.CommandPolicy } type CommandsInfoCmd struct { baseCmd val map[string]*CommandInfo } var _ Cmder = (*CommandsInfoCmd)(nil) func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoCmd { return &CommandsInfoCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeCommandsInfo, }, } } func (cmd *CommandsInfoCmd) SetVal(val map[string]*CommandInfo) { cmd.val = val } func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { return cmd.val } func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { return cmd.val, cmd.err } func (cmd *CommandsInfoCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { const numArgRedis5 = 6 const numArgRedis6 = 7 const numArgRedis7 = 10 // Also matches redis 8 n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make(map[string]*CommandInfo, n) for i := 0; i < n; i++ { nn, err := rd.ReadArrayLen() if err != nil { return err } switch nn { case numArgRedis5, numArgRedis6, numArgRedis7: // ok default: return fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6/7/10", nn) } cmdInfo := &CommandInfo{} if cmdInfo.Name, err = rd.ReadString(); err != nil { return err } arity, err := rd.ReadInt() if err != nil { return err } cmdInfo.Arity = int8(arity) flagLen, err := rd.ReadArrayLen() if err != nil { return err } cmdInfo.Flags = make([]string, flagLen) for f := 0; f < len(cmdInfo.Flags); f++ { switch s, err := rd.ReadString(); { case err == Nil: cmdInfo.Flags[f] = "" case err != nil: return err default: if !cmdInfo.ReadOnly && s == "readonly" { cmdInfo.ReadOnly = true } cmdInfo.Flags[f] = s } } firstKeyPos, err := rd.ReadInt() if err != nil { return err } cmdInfo.FirstKeyPos = int8(firstKeyPos) lastKeyPos, err := rd.ReadInt() if err != nil { return err } cmdInfo.LastKeyPos = int8(lastKeyPos) stepCount, err := rd.ReadInt() if err != nil { return err } cmdInfo.StepCount = int8(stepCount) if nn >= numArgRedis6 { aclFlagLen, err := rd.ReadArrayLen() if err != nil { return err } cmdInfo.ACLFlags = make([]string, aclFlagLen) for f := 0; f < len(cmdInfo.ACLFlags); f++ { switch s, err := rd.ReadString(); { case err == Nil: cmdInfo.ACLFlags[f] = "" case err != nil: return err default: cmdInfo.ACLFlags[f] = s } } } if nn >= numArgRedis7 { // The 8th argument is an array of tips. tipsLen, err := rd.ReadArrayLen() if err != nil { return err } rawTips := make(map[string]string, tipsLen) if cmdInfo.ReadOnly { rawTips[routing.ReadOnlyCMD] = "" } for f := 0; f < tipsLen; f++ { tip, err := rd.ReadString() if err != nil { return err } k, v, ok := strings.Cut(tip, ":") if !ok { // Handle tips that don't have a colon (like "nondeterministic_output") rawTips[tip] = "" } else { // Handle normal key:value tips rawTips[k] = v } } cmdInfo.CommandPolicy = parseCommandPolicies(rawTips, cmdInfo.FirstKeyPos) if err := rd.DiscardNext(); err != nil { return err } if err := rd.DiscardNext(); err != nil { return err } } cmd.val[cmdInfo.Name] = cmdInfo } return nil } func (cmd *CommandsInfoCmd) Clone() Cmder { var val map[string]*CommandInfo if cmd.val != nil { val = make(map[string]*CommandInfo, len(cmd.val)) for k, v := range cmd.val { if v != nil { newInfo := &CommandInfo{ Name: v.Name, Arity: v.Arity, FirstKeyPos: v.FirstKeyPos, LastKeyPos: v.LastKeyPos, StepCount: v.StepCount, ReadOnly: v.ReadOnly, CommandPolicy: v.CommandPolicy, // CommandPolicy can be shared as it's immutable } if v.Flags != nil { newInfo.Flags = make([]string, len(v.Flags)) copy(newInfo.Flags, v.Flags) } if v.ACLFlags != nil { newInfo.ACLFlags = make([]string, len(v.ACLFlags)) copy(newInfo.ACLFlags, v.ACLFlags) } val[k] = newInfo } } } return &CommandsInfoCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type cmdsInfoCache struct { fn func(ctx context.Context) (map[string]*CommandInfo, error) once internal.Once refreshLock sync.Mutex cmds map[string]*CommandInfo } func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, error)) *cmdsInfoCache { return &cmdsInfoCache{ fn: fn, } } func (c *cmdsInfoCache) Get(ctx context.Context) (map[string]*CommandInfo, error) { c.refreshLock.Lock() defer c.refreshLock.Unlock() err := c.once.Do(func() error { cmds, err := c.fn(ctx) if err != nil { return err } lowerCmds := make(map[string]*CommandInfo, len(cmds)) // Extensions have cmd names in upper case. Convert them to lower case. for k, v := range cmds { lowerCmds[internal.ToLower(k)] = v } c.cmds = lowerCmds return nil }) return c.cmds, err } func (c *cmdsInfoCache) Refresh() { c.refreshLock.Lock() defer c.refreshLock.Unlock() c.once = internal.Once{} } // ------------------------------------------------------------------------------ const requestPolicy = "request_policy" const responsePolicy = "response_policy" func parseCommandPolicies(commandInfoTips map[string]string, firstKeyPos int8) *routing.CommandPolicy { req := routing.ReqDefault resp := routing.RespDefaultKeyless if firstKeyPos > 0 { resp = routing.RespDefaultHashSlot } tips := make(map[string]string, len(commandInfoTips)) for k, v := range commandInfoTips { if k == requestPolicy { if p, err := routing.ParseRequestPolicy(v); err == nil { req = p } continue } if k == responsePolicy { if p, err := routing.ParseResponsePolicy(v); err == nil { resp = p } continue } tips[k] = v } return &routing.CommandPolicy{Request: req, Response: resp, Tips: tips} } //------------------------------------------------------------------------------ type SlowLog struct { ID int64 Time time.Time Duration time.Duration Args []string // These are also optional fields emitted only by Redis 4.0 or greater: // https://redis.io/commands/slowlog#output-format ClientAddr string ClientName string } type SlowLogCmd struct { baseCmd val []SlowLog } var _ Cmder = (*SlowLogCmd)(nil) func NewSlowLogCmd(ctx context.Context, args ...interface{}) *SlowLogCmd { return &SlowLogCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeSlowLog, }, } } func (cmd *SlowLogCmd) SetVal(val []SlowLog) { cmd.val = val } func (cmd *SlowLogCmd) Val() []SlowLog { return cmd.val } func (cmd *SlowLogCmd) Result() ([]SlowLog, error) { return cmd.val, cmd.err } func (cmd *SlowLogCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *SlowLogCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]SlowLog, n) for i := 0; i < len(cmd.val); i++ { nn, err := rd.ReadArrayLen() if err != nil { return err } if nn < 4 { return fmt.Errorf("redis: got %d elements in slowlog get, expected at least 4", nn) } if cmd.val[i].ID, err = rd.ReadInt(); err != nil { return err } createdAt, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Time = time.Unix(createdAt, 0) costs, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Duration = time.Duration(costs) * time.Microsecond cmdLen, err := rd.ReadArrayLen() if err != nil { return err } if cmdLen < 1 { return fmt.Errorf("redis: got %d elements commands reply in slowlog get, expected at least 1", cmdLen) } cmd.val[i].Args = make([]string, cmdLen) for f := 0; f < len(cmd.val[i].Args); f++ { cmd.val[i].Args[f], err = rd.ReadString() if err != nil { return err } } if nn >= 5 { if cmd.val[i].ClientAddr, err = rd.ReadString(); err != nil { return err } } if nn >= 6 { if cmd.val[i].ClientName, err = rd.ReadString(); err != nil { return err } } } return nil } func (cmd *SlowLogCmd) Clone() Cmder { var val []SlowLog if cmd.val != nil { val = make([]SlowLog, len(cmd.val)) for i, log := range cmd.val { val[i] = SlowLog{ ID: log.ID, Time: log.Time, Duration: log.Duration, ClientAddr: log.ClientAddr, ClientName: log.ClientName, } if log.Args != nil { val[i].Args = make([]string, len(log.Args)) copy(val[i].Args, log.Args) } } } return &SlowLogCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //----------------------------------------------------------------------- type Latency struct { Name string Time time.Time Latest time.Duration Max time.Duration } type LatencyCmd struct { baseCmd val []Latency } var _ Cmder = (*LatencyCmd)(nil) func NewLatencyCmd(ctx context.Context, args ...interface{}) *LatencyCmd { return &LatencyCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, }, } } func (cmd *LatencyCmd) SetVal(val []Latency) { cmd.val = val } func (cmd *LatencyCmd) Val() []Latency { return cmd.val } func (cmd *LatencyCmd) Result() ([]Latency, error) { return cmd.val, cmd.err } func (cmd *LatencyCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *LatencyCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]Latency, n) for i := 0; i < len(cmd.val); i++ { nn, err := rd.ReadArrayLen() if err != nil { return err } if nn < 3 { return fmt.Errorf("redis: got %d elements in latency get, expected at least 3", nn) } if cmd.val[i].Name, err = rd.ReadString(); err != nil { return err } createdAt, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Time = time.Unix(createdAt, 0) latest, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Latest = time.Duration(latest) * time.Millisecond maximum, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Max = time.Duration(maximum) * time.Millisecond } return nil } func (cmd *LatencyCmd) Clone() Cmder { var val []Latency if cmd.val != nil { val = make([]Latency, len(cmd.val)) copy(val, cmd.val) } return &LatencyCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //----------------------------------------------------------------------- type MapStringInterfaceCmd struct { baseCmd val map[string]interface{} } var _ Cmder = (*MapStringInterfaceCmd)(nil) func NewMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceCmd { return &MapStringInterfaceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringInterface, }, } } func (cmd *MapStringInterfaceCmd) SetVal(val map[string]interface{}) { cmd.val = val } func (cmd *MapStringInterfaceCmd) Val() map[string]interface{} { return cmd.val } func (cmd *MapStringInterfaceCmd) Result() (map[string]interface{}, error) { return cmd.val, cmd.err } func (cmd *MapStringInterfaceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapStringInterfaceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = make(map[string]interface{}, n) for i := 0; i < n; i++ { k, err := rd.ReadString() if err != nil { return err } v, err := rd.ReadReply() if err != nil { if err == Nil { cmd.val[k] = Nil continue } if err, ok := err.(proto.RedisError); ok { cmd.val[k] = err continue } return err } cmd.val[k] = v } return nil } func (cmd *MapStringInterfaceCmd) Clone() Cmder { var val map[string]interface{} if cmd.val != nil { val = make(map[string]interface{}, len(cmd.val)) for k, v := range cmd.val { val[k] = v } } return &MapStringInterfaceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //----------------------------------------------------------------------- type MapStringStringSliceCmd struct { baseCmd val []map[string]string } var _ Cmder = (*MapStringStringSliceCmd)(nil) func NewMapStringStringSliceCmd(ctx context.Context, args ...interface{}) *MapStringStringSliceCmd { return &MapStringStringSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringStringSlice, }, } } func (cmd *MapStringStringSliceCmd) SetVal(val []map[string]string) { cmd.val = val } func (cmd *MapStringStringSliceCmd) Val() []map[string]string { return cmd.val } func (cmd *MapStringStringSliceCmd) Result() ([]map[string]string, error) { return cmd.val, cmd.err } func (cmd *MapStringStringSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]map[string]string, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return err } cmd.val[i] = make(map[string]string, nn) for f := 0; f < nn; f++ { k, err := rd.ReadString() if err != nil { return err } v, err := rd.ReadString() if err != nil { return err } cmd.val[i][k] = v } } return nil } func (cmd *MapStringStringSliceCmd) Clone() Cmder { var val []map[string]string if cmd.val != nil { val = make([]map[string]string, len(cmd.val)) for i, m := range cmd.val { if m != nil { val[i] = make(map[string]string, len(m)) for k, v := range m { val[i][k] = v } } } } return &MapStringStringSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // ----------------------------------------------------------------------- // MapMapStringInterfaceCmd represents a command that returns a map of strings to interface{}. type MapMapStringInterfaceCmd struct { baseCmd val map[string]interface{} } func NewMapMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapMapStringInterfaceCmd { return &MapMapStringInterfaceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapMapStringInterface, }, } } func (cmd *MapMapStringInterfaceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapMapStringInterfaceCmd) SetVal(val map[string]interface{}) { cmd.val = val } func (cmd *MapMapStringInterfaceCmd) Result() (map[string]interface{}, error) { return cmd.val, cmd.err } func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} { return cmd.val } // readReply will try to parse the reply from the proto.Reader for both resp2 and resp3 func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) { data, err := rd.ReadReply() if err != nil { return err } resultMap := map[string]interface{}{} switch midResponse := data.(type) { case map[interface{}]interface{}: // resp3 will return map for k, v := range midResponse { stringKey, ok := k.(string) if !ok { return fmt.Errorf("redis: invalid map key %#v", k) } resultMap[stringKey] = v } case []interface{}: // resp2 will return array of arrays n := len(midResponse) for i := 0; i < n; i++ { finalArr, ok := midResponse[i].([]interface{}) // final array that we need to transform to map if !ok { return fmt.Errorf("redis: unexpected response %#v", data) } m := len(finalArr) if m%2 != 0 { // since this should be map, keys should be even number return fmt.Errorf("redis: unexpected response %#v", data) } for j := 0; j < m; j += 2 { stringKey, ok := finalArr[j].(string) // the first one if !ok { return fmt.Errorf("redis: invalid map key %#v", finalArr[i]) } resultMap[stringKey] = finalArr[j+1] // second one is value } } default: return fmt.Errorf("redis: unexpected response %#v", data) } cmd.val = resultMap return nil } func (cmd *MapMapStringInterfaceCmd) Clone() Cmder { var val map[string]interface{} if cmd.val != nil { val = make(map[string]interface{}, len(cmd.val)) for k, v := range cmd.val { val[k] = v } } return &MapMapStringInterfaceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //----------------------------------------------------------------------- type MapStringInterfaceSliceCmd struct { baseCmd val []map[string]interface{} } var _ Cmder = (*MapStringInterfaceSliceCmd)(nil) func NewMapStringInterfaceSliceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceSliceCmd { return &MapStringInterfaceSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeMapStringInterfaceSlice, }, } } func (cmd *MapStringInterfaceSliceCmd) SetVal(val []map[string]interface{}) { cmd.val = val } func (cmd *MapStringInterfaceSliceCmd) Val() []map[string]interface{} { return cmd.val } func (cmd *MapStringInterfaceSliceCmd) Result() ([]map[string]interface{}, error) { return cmd.val, cmd.err } func (cmd *MapStringInterfaceSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *MapStringInterfaceSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]map[string]interface{}, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return err } cmd.val[i] = make(map[string]interface{}, nn) for f := 0; f < nn; f++ { k, err := rd.ReadString() if err != nil { return err } v, err := rd.ReadReply() if err != nil { if err != Nil { return err } } cmd.val[i][k] = v } } return nil } func (cmd *MapStringInterfaceSliceCmd) Clone() Cmder { var val []map[string]interface{} if cmd.val != nil { val = make([]map[string]interface{}, len(cmd.val)) for i, m := range cmd.val { if m != nil { val[i] = make(map[string]interface{}, len(m)) for k, v := range m { val[i][k] = v } } } } return &MapStringInterfaceSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ type KeyValuesCmd struct { baseCmd key string val []string } var _ Cmder = (*KeyValuesCmd)(nil) func NewKeyValuesCmd(ctx context.Context, args ...interface{}) *KeyValuesCmd { return &KeyValuesCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeKeyValues, }, } } func (cmd *KeyValuesCmd) SetVal(key string, val []string) { cmd.key = key cmd.val = val } func (cmd *KeyValuesCmd) Val() (string, []string) { return cmd.key, cmd.val } func (cmd *KeyValuesCmd) Result() (string, []string, error) { return cmd.key, cmd.val, cmd.err } func (cmd *KeyValuesCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *KeyValuesCmd) readReply(rd *proto.Reader) (err error) { if err = rd.ReadFixedArrayLen(2); err != nil { return err } cmd.key, err = rd.ReadString() if err != nil { return err } n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]string, n) for i := 0; i < n; i++ { cmd.val[i], err = rd.ReadString() if err != nil { return err } } return nil } func (cmd *KeyValuesCmd) Clone() Cmder { var val []string if cmd.val != nil { val = make([]string, len(cmd.val)) copy(val, cmd.val) } return &KeyValuesCmd{ baseCmd: cmd.cloneBaseCmd(), key: cmd.key, val: val, } } //------------------------------------------------------------------------------ type ZSliceWithKeyCmd struct { baseCmd key string val []Z } var _ Cmder = (*ZSliceWithKeyCmd)(nil) func NewZSliceWithKeyCmd(ctx context.Context, args ...interface{}) *ZSliceWithKeyCmd { return &ZSliceWithKeyCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeZSliceWithKey, }, } } func (cmd *ZSliceWithKeyCmd) SetVal(key string, val []Z) { cmd.key = key cmd.val = val } func (cmd *ZSliceWithKeyCmd) Val() (string, []Z) { return cmd.key, cmd.val } func (cmd *ZSliceWithKeyCmd) Result() (string, []Z, error) { return cmd.key, cmd.val, cmd.err } func (cmd *ZSliceWithKeyCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ZSliceWithKeyCmd) readReply(rd *proto.Reader) (err error) { if err = rd.ReadFixedArrayLen(2); err != nil { return err } cmd.key, err = rd.ReadString() if err != nil { return err } n, err := rd.ReadArrayLen() if err != nil { return err } typ, err := rd.PeekReplyType() if err != nil { return err } array := typ == proto.RespArray if array { cmd.val = make([]Z, n) } else { cmd.val = make([]Z, n/2) } for i := 0; i < len(cmd.val); i++ { if array { if err = rd.ReadFixedArrayLen(2); err != nil { return err } } if cmd.val[i].Member, err = rd.ReadString(); err != nil { return err } if cmd.val[i].Score, err = rd.ReadFloat(); err != nil { return err } } return nil } func (cmd *ZSliceWithKeyCmd) Clone() Cmder { var val []Z if cmd.val != nil { val = make([]Z, len(cmd.val)) copy(val, cmd.val) } return &ZSliceWithKeyCmd{ baseCmd: cmd.cloneBaseCmd(), key: cmd.key, val: val, } } type Function struct { Name string Description string Flags []string } type Library struct { Name string Engine string Functions []Function Code string } type FunctionListCmd struct { baseCmd val []Library } var _ Cmder = (*FunctionListCmd)(nil) func NewFunctionListCmd(ctx context.Context, args ...interface{}) *FunctionListCmd { return &FunctionListCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeFunctionList, }, } } func (cmd *FunctionListCmd) SetVal(val []Library) { cmd.val = val } func (cmd *FunctionListCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *FunctionListCmd) Val() []Library { return cmd.val } func (cmd *FunctionListCmd) Result() ([]Library, error) { return cmd.val, cmd.err } func (cmd *FunctionListCmd) First() (*Library, error) { if cmd.err != nil { return nil, cmd.err } if len(cmd.val) > 0 { return &cmd.val[0], nil } return nil, Nil } func (cmd *FunctionListCmd) readReply(rd *proto.Reader) (err error) { n, err := rd.ReadArrayLen() if err != nil { return err } libraries := make([]Library, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return err } library := Library{} for f := 0; f < nn; f++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "library_name": library.Name, err = rd.ReadString() case "engine": library.Engine, err = rd.ReadString() case "functions": library.Functions, err = cmd.readFunctions(rd) case "library_code": library.Code, err = rd.ReadString() default: return fmt.Errorf("redis: function list unexpected key %s", key) } if err != nil { return err } } libraries[i] = library } cmd.val = libraries return nil } func (cmd *FunctionListCmd) readFunctions(rd *proto.Reader) ([]Function, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } functions := make([]Function, n) for i := 0; i < n; i++ { nn, err := rd.ReadMapLen() if err != nil { return nil, err } function := Function{} for f := 0; f < nn; f++ { key, err := rd.ReadString() if err != nil { return nil, err } switch key { case "name": if function.Name, err = rd.ReadString(); err != nil { return nil, err } case "description": if function.Description, err = rd.ReadString(); err != nil && err != Nil { return nil, err } case "flags": // resp set nx, err := rd.ReadArrayLen() if err != nil { return nil, err } function.Flags = make([]string, nx) for j := 0; j < nx; j++ { if function.Flags[j], err = rd.ReadString(); err != nil { return nil, err } } default: return nil, fmt.Errorf("redis: function list unexpected key %s", key) } } functions[i] = function } return functions, nil } func (cmd *FunctionListCmd) Clone() Cmder { var val []Library if cmd.val != nil { val = make([]Library, len(cmd.val)) for i, lib := range cmd.val { val[i] = Library{ Name: lib.Name, Engine: lib.Engine, Code: lib.Code, } if lib.Functions != nil { val[i].Functions = make([]Function, len(lib.Functions)) for j, fn := range lib.Functions { val[i].Functions[j] = Function{ Name: fn.Name, Description: fn.Description, } if fn.Flags != nil { val[i].Functions[j].Flags = make([]string, len(fn.Flags)) copy(val[i].Functions[j].Flags, fn.Flags) } } } } } return &FunctionListCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // FunctionStats contains information about the scripts currently executing on the server, and the available engines // - Engines: // Statistics about the engine like number of functions and number of libraries // - RunningScript: // The script currently running on the shard we're connecting to. // For Redis Enterprise and Redis Cloud, this represents the // function with the longest running time, across all the running functions, on all shards // - RunningScripts // All scripts currently running in a Redis Enterprise clustered database. // Only available on Redis Enterprise type FunctionStats struct { Engines []Engine isRunning bool rs RunningScript allrs []RunningScript } func (fs *FunctionStats) Running() bool { return fs.isRunning } func (fs *FunctionStats) RunningScript() (RunningScript, bool) { return fs.rs, fs.isRunning } // AllRunningScripts returns all scripts currently running in a Redis Enterprise clustered database. // Only available on Redis Enterprise func (fs *FunctionStats) AllRunningScripts() []RunningScript { return fs.allrs } type RunningScript struct { Name string Command []string Duration time.Duration } type Engine struct { Language string LibrariesCount int64 FunctionsCount int64 } type FunctionStatsCmd struct { baseCmd val FunctionStats } var _ Cmder = (*FunctionStatsCmd)(nil) func NewFunctionStatsCmd(ctx context.Context, args ...interface{}) *FunctionStatsCmd { return &FunctionStatsCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeFunctionStats, }, } } func (cmd *FunctionStatsCmd) SetVal(val FunctionStats) { cmd.val = val } func (cmd *FunctionStatsCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *FunctionStatsCmd) Val() FunctionStats { return cmd.val } func (cmd *FunctionStatsCmd) Result() (FunctionStats, error) { return cmd.val, cmd.err } func (cmd *FunctionStatsCmd) readReply(rd *proto.Reader) (err error) { n, err := rd.ReadMapLen() if err != nil { return err } var key string var result FunctionStats for f := 0; f < n; f++ { key, err = rd.ReadString() if err != nil { return err } switch key { case "running_script": result.rs, result.isRunning, err = cmd.readRunningScript(rd) case "engines": result.Engines, err = cmd.readEngines(rd) case "all_running_scripts": // Redis Enterprise only result.allrs, result.isRunning, err = cmd.readRunningScripts(rd) default: return fmt.Errorf("redis: function stats unexpected key %s", key) } if err != nil { return err } } cmd.val = result return nil } func (cmd *FunctionStatsCmd) readRunningScript(rd *proto.Reader) (RunningScript, bool, error) { err := rd.ReadFixedMapLen(3) if err != nil { if err == Nil { return RunningScript{}, false, nil } return RunningScript{}, false, err } var runningScript RunningScript for i := 0; i < 3; i++ { key, err := rd.ReadString() if err != nil { return RunningScript{}, false, err } switch key { case "name": runningScript.Name, err = rd.ReadString() case "duration_ms": runningScript.Duration, err = cmd.readDuration(rd) case "command": runningScript.Command, err = cmd.readCommand(rd) default: return RunningScript{}, false, fmt.Errorf("redis: function stats unexpected running_script key %s", key) } if err != nil { return RunningScript{}, false, err } } return runningScript, true, nil } func (cmd *FunctionStatsCmd) readEngines(rd *proto.Reader) ([]Engine, error) { n, err := rd.ReadMapLen() if err != nil { return nil, err } engines := make([]Engine, 0, n) for i := 0; i < n; i++ { engine := Engine{} engine.Language, err = rd.ReadString() if err != nil { return nil, err } err = rd.ReadFixedMapLen(2) if err != nil { return nil, fmt.Errorf("redis: function stats unexpected %s engine map length", engine.Language) } for i := 0; i < 2; i++ { key, err := rd.ReadString() switch key { case "libraries_count": engine.LibrariesCount, err = rd.ReadInt() case "functions_count": engine.FunctionsCount, err = rd.ReadInt() } if err != nil { return nil, err } } engines = append(engines, engine) } return engines, nil } func (cmd *FunctionStatsCmd) readDuration(rd *proto.Reader) (time.Duration, error) { t, err := rd.ReadInt() if err != nil { return time.Duration(0), err } return time.Duration(t) * time.Millisecond, nil } func (cmd *FunctionStatsCmd) readCommand(rd *proto.Reader) ([]string, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } command := make([]string, 0, n) for i := 0; i < n; i++ { x, err := rd.ReadString() if err != nil { return nil, err } command = append(command, x) } return command, nil } func (cmd *FunctionStatsCmd) readRunningScripts(rd *proto.Reader) ([]RunningScript, bool, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, false, err } runningScripts := make([]RunningScript, 0, n) for i := 0; i < n; i++ { rs, _, err := cmd.readRunningScript(rd) if err != nil { return nil, false, err } runningScripts = append(runningScripts, rs) } return runningScripts, len(runningScripts) > 0, nil } func (cmd *FunctionStatsCmd) Clone() Cmder { val := FunctionStats{ isRunning: cmd.val.isRunning, rs: cmd.val.rs, // RunningScript is a simple struct, can be copied directly } if cmd.val.Engines != nil { val.Engines = make([]Engine, len(cmd.val.Engines)) copy(val.Engines, cmd.val.Engines) } if cmd.val.allrs != nil { val.allrs = make([]RunningScript, len(cmd.val.allrs)) for i, rs := range cmd.val.allrs { val.allrs[i] = RunningScript{ Name: rs.Name, Duration: rs.Duration, } if rs.Command != nil { val.allrs[i].Command = make([]string, len(rs.Command)) copy(val.allrs[i].Command, rs.Command) } } } return &FunctionStatsCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } //------------------------------------------------------------------------------ // LCSQuery is a parameter used for the LCS command type LCSQuery struct { Key1 string Key2 string Len bool Idx bool MinMatchLen int WithMatchLen bool } // LCSMatch is the result set of the LCS command. type LCSMatch struct { MatchString string Matches []LCSMatchedPosition Len int64 } type LCSMatchedPosition struct { Key1 LCSPosition Key2 LCSPosition // only for withMatchLen is true MatchLen int64 } type LCSPosition struct { Start int64 End int64 } type LCSCmd struct { baseCmd // 1: match string // 2: match len // 3: match idx LCSMatch readType uint8 val *LCSMatch } func NewLCSCmd(ctx context.Context, q *LCSQuery) *LCSCmd { args := make([]interface{}, 3, 7) args[0] = "lcs" args[1] = q.Key1 args[2] = q.Key2 cmd := &LCSCmd{readType: 1} if q.Len { cmd.readType = 2 args = append(args, "len") } else if q.Idx { cmd.readType = 3 args = append(args, "idx") if q.MinMatchLen != 0 { args = append(args, "minmatchlen", q.MinMatchLen) } if q.WithMatchLen { args = append(args, "withmatchlen") } } cmd.baseCmd = baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeLCS, } return cmd } func (cmd *LCSCmd) SetVal(val *LCSMatch) { cmd.val = val } func (cmd *LCSCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *LCSCmd) Val() *LCSMatch { return cmd.val } func (cmd *LCSCmd) Result() (*LCSMatch, error) { return cmd.val, cmd.err } func (cmd *LCSCmd) readReply(rd *proto.Reader) (err error) { lcs := &LCSMatch{} switch cmd.readType { case 1: // match string if lcs.MatchString, err = rd.ReadString(); err != nil { return err } case 2: // match len if lcs.Len, err = rd.ReadInt(); err != nil { return err } case 3: // read LCSMatch if err = rd.ReadFixedMapLen(2); err != nil { return err } // read matches or len field for i := 0; i < 2; i++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "matches": // read array of matched positions if lcs.Matches, err = cmd.readMatchedPositions(rd); err != nil { return err } case "len": // read match length if lcs.Len, err = rd.ReadInt(); err != nil { return err } } } } cmd.val = lcs return nil } func (cmd *LCSCmd) readMatchedPositions(rd *proto.Reader) ([]LCSMatchedPosition, error) { n, err := rd.ReadArrayLen() if err != nil { return nil, err } positions := make([]LCSMatchedPosition, n) for i := 0; i < n; i++ { pn, err := rd.ReadArrayLen() if err != nil { return nil, err } if positions[i].Key1, err = cmd.readPosition(rd); err != nil { return nil, err } if positions[i].Key2, err = cmd.readPosition(rd); err != nil { return nil, err } // read match length if WithMatchLen is true if pn > 2 { if positions[i].MatchLen, err = rd.ReadInt(); err != nil { return nil, err } } } return positions, nil } func (cmd *LCSCmd) readPosition(rd *proto.Reader) (pos LCSPosition, err error) { if err = rd.ReadFixedArrayLen(2); err != nil { return pos, err } if pos.Start, err = rd.ReadInt(); err != nil { return pos, err } if pos.End, err = rd.ReadInt(); err != nil { return pos, err } return pos, nil } func (cmd *LCSCmd) Clone() Cmder { var val *LCSMatch if cmd.val != nil { val = &LCSMatch{ MatchString: cmd.val.MatchString, Len: cmd.val.Len, } if cmd.val.Matches != nil { val.Matches = make([]LCSMatchedPosition, len(cmd.val.Matches)) copy(val.Matches, cmd.val.Matches) } } return &LCSCmd{ baseCmd: cmd.cloneBaseCmd(), readType: cmd.readType, val: val, } } // ------------------------------------------------------------------------ type KeyFlags struct { Key string Flags []string } type KeyFlagsCmd struct { baseCmd val []KeyFlags } var _ Cmder = (*KeyFlagsCmd)(nil) func NewKeyFlagsCmd(ctx context.Context, args ...interface{}) *KeyFlagsCmd { return &KeyFlagsCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeKeyFlags, }, } } func (cmd *KeyFlagsCmd) SetVal(val []KeyFlags) { cmd.val = val } func (cmd *KeyFlagsCmd) Val() []KeyFlags { return cmd.val } func (cmd *KeyFlagsCmd) Result() ([]KeyFlags, error) { return cmd.val, cmd.err } func (cmd *KeyFlagsCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *KeyFlagsCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } if n == 0 { cmd.val = make([]KeyFlags, 0) return nil } cmd.val = make([]KeyFlags, n) for i := 0; i < len(cmd.val); i++ { if err = rd.ReadFixedArrayLen(2); err != nil { return err } if cmd.val[i].Key, err = rd.ReadString(); err != nil { return err } flagsLen, err := rd.ReadArrayLen() if err != nil { return err } cmd.val[i].Flags = make([]string, flagsLen) for j := 0; j < flagsLen; j++ { if cmd.val[i].Flags[j], err = rd.ReadString(); err != nil { return err } } } return nil } func (cmd *KeyFlagsCmd) Clone() Cmder { var val []KeyFlags if cmd.val != nil { val = make([]KeyFlags, len(cmd.val)) for i, kf := range cmd.val { val[i] = KeyFlags{ Key: kf.Key, } if kf.Flags != nil { val[i].Flags = make([]string, len(kf.Flags)) copy(val[i].Flags, kf.Flags) } } } return &KeyFlagsCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // --------------------------------------------------------------------------------------------------- type ClusterLink struct { Direction string Node string CreateTime int64 Events string SendBufferAllocated int64 SendBufferUsed int64 } type ClusterLinksCmd struct { baseCmd val []ClusterLink } var _ Cmder = (*ClusterLinksCmd)(nil) func NewClusterLinksCmd(ctx context.Context, args ...interface{}) *ClusterLinksCmd { return &ClusterLinksCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeClusterLinks, }, } } func (cmd *ClusterLinksCmd) SetVal(val []ClusterLink) { cmd.val = val } func (cmd *ClusterLinksCmd) Val() []ClusterLink { return cmd.val } func (cmd *ClusterLinksCmd) Result() ([]ClusterLink, error) { return cmd.val, cmd.err } func (cmd *ClusterLinksCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ClusterLinksCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]ClusterLink, n) for i := 0; i < len(cmd.val); i++ { m, err := rd.ReadMapLen() if err != nil { return err } for j := 0; j < m; j++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "direction": cmd.val[i].Direction, err = rd.ReadString() case "node": cmd.val[i].Node, err = rd.ReadString() case "create-time": cmd.val[i].CreateTime, err = rd.ReadInt() case "events": cmd.val[i].Events, err = rd.ReadString() case "send-buffer-allocated": cmd.val[i].SendBufferAllocated, err = rd.ReadInt() case "send-buffer-used": cmd.val[i].SendBufferUsed, err = rd.ReadInt() default: return fmt.Errorf("redis: unexpected key %q in CLUSTER LINKS reply", key) } if err != nil { return err } } } return nil } func (cmd *ClusterLinksCmd) Clone() Cmder { var val []ClusterLink if cmd.val != nil { val = make([]ClusterLink, len(cmd.val)) copy(val, cmd.val) } return &ClusterLinksCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // ------------------------------------------------------------------------------------------------------------------ type SlotRange struct { Start int64 End int64 } type Node struct { ID string Endpoint string IP string Hostname string Port int64 TLSPort int64 Role string ReplicationOffset int64 Health string } type ClusterShard struct { Slots []SlotRange Nodes []Node } type ClusterShardsCmd struct { baseCmd val []ClusterShard } var _ Cmder = (*ClusterShardsCmd)(nil) func NewClusterShardsCmd(ctx context.Context, args ...interface{}) *ClusterShardsCmd { return &ClusterShardsCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeClusterShards, }, } } func (cmd *ClusterShardsCmd) SetVal(val []ClusterShard) { cmd.val = val } func (cmd *ClusterShardsCmd) Val() []ClusterShard { return cmd.val } func (cmd *ClusterShardsCmd) Result() ([]ClusterShard, error) { return cmd.val, cmd.err } func (cmd *ClusterShardsCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ClusterShardsCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]ClusterShard, n) for i := 0; i < n; i++ { m, err := rd.ReadMapLen() if err != nil { return err } for j := 0; j < m; j++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "slots": l, err := rd.ReadArrayLen() if err != nil { return err } for k := 0; k < l; k += 2 { start, err := rd.ReadInt() if err != nil { return err } end, err := rd.ReadInt() if err != nil { return err } cmd.val[i].Slots = append(cmd.val[i].Slots, SlotRange{Start: start, End: end}) } case "nodes": nodesLen, err := rd.ReadArrayLen() if err != nil { return err } cmd.val[i].Nodes = make([]Node, nodesLen) for k := 0; k < nodesLen; k++ { nodeMapLen, err := rd.ReadMapLen() if err != nil { return err } for l := 0; l < nodeMapLen; l++ { nodeKey, err := rd.ReadString() if err != nil { return err } switch nodeKey { case "id": cmd.val[i].Nodes[k].ID, err = rd.ReadString() case "endpoint": cmd.val[i].Nodes[k].Endpoint, err = rd.ReadString() case "ip": cmd.val[i].Nodes[k].IP, err = rd.ReadString() case "hostname": cmd.val[i].Nodes[k].Hostname, err = rd.ReadString() case "port": cmd.val[i].Nodes[k].Port, err = rd.ReadInt() case "tls-port": cmd.val[i].Nodes[k].TLSPort, err = rd.ReadInt() case "role": cmd.val[i].Nodes[k].Role, err = rd.ReadString() case "replication-offset": cmd.val[i].Nodes[k].ReplicationOffset, err = rd.ReadInt() case "health": cmd.val[i].Nodes[k].Health, err = rd.ReadString() default: return fmt.Errorf("redis: unexpected key %q in CLUSTER SHARDS node reply", nodeKey) } if err != nil { return err } } } default: return fmt.Errorf("redis: unexpected key %q in CLUSTER SHARDS reply", key) } } } return nil } func (cmd *ClusterShardsCmd) Clone() Cmder { var val []ClusterShard if cmd.val != nil { val = make([]ClusterShard, len(cmd.val)) for i, shard := range cmd.val { val[i] = ClusterShard{} if shard.Slots != nil { val[i].Slots = make([]SlotRange, len(shard.Slots)) copy(val[i].Slots, shard.Slots) } if shard.Nodes != nil { val[i].Nodes = make([]Node, len(shard.Nodes)) copy(val[i].Nodes, shard.Nodes) } } } return &ClusterShardsCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // ----------------------------------------- type RankScore struct { Rank int64 Score float64 } type RankWithScoreCmd struct { baseCmd val RankScore } var _ Cmder = (*RankWithScoreCmd)(nil) func NewRankWithScoreCmd(ctx context.Context, args ...interface{}) *RankWithScoreCmd { return &RankWithScoreCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeRankWithScore, }, } } func (cmd *RankWithScoreCmd) SetVal(val RankScore) { cmd.val = val } func (cmd *RankWithScoreCmd) Val() RankScore { return cmd.val } func (cmd *RankWithScoreCmd) Result() (RankScore, error) { return cmd.val, cmd.err } func (cmd *RankWithScoreCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *RankWithScoreCmd) readReply(rd *proto.Reader) error { if err := rd.ReadFixedArrayLen(2); err != nil { return err } rank, err := rd.ReadInt() if err != nil { return err } score, err := rd.ReadFloat() if err != nil { return err } cmd.val = RankScore{Rank: rank, Score: score} return nil } func (cmd *RankWithScoreCmd) Clone() Cmder { return &RankWithScoreCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, // RankScore is a simple struct, can be copied directly } } // -------------------------------------------------------------------------------------------------- // ClientFlags is redis-server client flags, copy from redis/src/server.h (redis 7.0) type ClientFlags uint64 const ( ClientSlave ClientFlags = 1 << 0 /* This client is a replica */ ClientMaster ClientFlags = 1 << 1 /* This client is a master */ ClientMonitor ClientFlags = 1 << 2 /* This client is a slave monitor, see MONITOR */ ClientMulti ClientFlags = 1 << 3 /* This client is in a MULTI context */ ClientBlocked ClientFlags = 1 << 4 /* The client is waiting in a blocking operation */ ClientDirtyCAS ClientFlags = 1 << 5 /* Watched keys modified. EXEC will fail. */ ClientCloseAfterReply ClientFlags = 1 << 6 /* Close after writing entire reply. */ ClientUnBlocked ClientFlags = 1 << 7 /* This client was unblocked and is stored in server.unblocked_clients */ ClientScript ClientFlags = 1 << 8 /* This is a non-connected client used by Lua */ ClientAsking ClientFlags = 1 << 9 /* Client issued the ASKING command */ ClientCloseASAP ClientFlags = 1 << 10 /* Close this client ASAP */ ClientUnixSocket ClientFlags = 1 << 11 /* Client connected via Unix domain socket */ ClientDirtyExec ClientFlags = 1 << 12 /* EXEC will fail for errors while queueing */ ClientMasterForceReply ClientFlags = 1 << 13 /* Queue replies even if is master */ ClientForceAOF ClientFlags = 1 << 14 /* Force AOF propagation of current cmd. */ ClientForceRepl ClientFlags = 1 << 15 /* Force replication of current cmd. */ ClientPrePSync ClientFlags = 1 << 16 /* Instance don't understand PSYNC. */ ClientReadOnly ClientFlags = 1 << 17 /* Cluster client is in read-only state. */ ClientPubSub ClientFlags = 1 << 18 /* Client is in Pub/Sub mode. */ ClientPreventAOFProp ClientFlags = 1 << 19 /* Don't propagate to AOF. */ ClientPreventReplProp ClientFlags = 1 << 20 /* Don't propagate to slaves. */ ClientPreventProp ClientFlags = ClientPreventAOFProp | ClientPreventReplProp ClientPendingWrite ClientFlags = 1 << 21 /* Client has output to send but a-write handler is yet not installed. */ ClientReplyOff ClientFlags = 1 << 22 /* Don't send replies to client. */ ClientReplySkipNext ClientFlags = 1 << 23 /* Set ClientREPLY_SKIP for next cmd */ ClientReplySkip ClientFlags = 1 << 24 /* Don't send just this reply. */ ClientLuaDebug ClientFlags = 1 << 25 /* Run EVAL in debug mode. */ ClientLuaDebugSync ClientFlags = 1 << 26 /* EVAL debugging without fork() */ ClientModule ClientFlags = 1 << 27 /* Non connected client used by some module. */ ClientProtected ClientFlags = 1 << 28 /* Client should not be freed for now. */ ClientExecutingCommand ClientFlags = 1 << 29 /* Indicates that the client is currently in the process of handling a command. usually this will be marked only during call() however, blocked clients might have this flag kept until they will try to reprocess the command. */ ClientPendingCommand ClientFlags = 1 << 30 /* Indicates the client has a fully * parsed command ready for execution. */ ClientTracking ClientFlags = 1 << 31 /* Client enabled keys tracking in order to perform client side caching. */ ClientTrackingBrokenRedir ClientFlags = 1 << 32 /* Target client is invalid. */ ClientTrackingBCAST ClientFlags = 1 << 33 /* Tracking in BCAST mode. */ ClientTrackingOptIn ClientFlags = 1 << 34 /* Tracking in opt-in mode. */ ClientTrackingOptOut ClientFlags = 1 << 35 /* Tracking in opt-out mode. */ ClientTrackingCaching ClientFlags = 1 << 36 /* CACHING yes/no was given, depending on optin/optout mode. */ ClientTrackingNoLoop ClientFlags = 1 << 37 /* Don't send invalidation messages about writes performed by myself.*/ ClientInTimeoutTable ClientFlags = 1 << 38 /* This client is in the timeout table. */ ClientProtocolError ClientFlags = 1 << 39 /* Protocol error chatting with it. */ ClientCloseAfterCommand ClientFlags = 1 << 40 /* Close after executing commands * and writing entire reply. */ ClientDenyBlocking ClientFlags = 1 << 41 /* Indicate that the client should not be blocked. currently, turned on inside MULTI, Lua, RM_Call, and AOF client */ ClientReplRDBOnly ClientFlags = 1 << 42 /* This client is a replica that only wants RDB without replication buffer. */ ClientNoEvict ClientFlags = 1 << 43 /* This client is protected against client memory eviction. */ ClientAllowOOM ClientFlags = 1 << 44 /* Client used by RM_Call is allowed to fully execute scripts even when in OOM */ ClientNoTouch ClientFlags = 1 << 45 /* This client will not touch LFU/LRU stats. */ ClientPushing ClientFlags = 1 << 46 /* This client is pushing notifications. */ ) // ClientInfo is redis-server ClientInfo, not go-redis *Client type ClientInfo struct { ID int64 // redis version 2.8.12, a unique 64-bit client ID Addr string // address/port of the client LAddr string // address/port of local address client connected to (bind address) FD int64 // file descriptor corresponding to the socket Name string // the name set by the client with CLIENT SETNAME Age time.Duration // total duration of the connection in seconds Idle time.Duration // idle time of the connection in seconds Flags ClientFlags // client flags (see below) DB int // current database ID Sub int // number of channel subscriptions PSub int // number of pattern matching subscriptions SSub int // redis version 7.0.3, number of shard channel subscriptions Multi int // number of commands in a MULTI/EXEC context Watch int // redis version 7.4 RC1, number of keys this client is currently watching. QueryBuf int // qbuf, query buffer length (0 means no query pending) QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full) ArgvMem int // incomplete arguments for the next command (already extracted from query buffer) MultiMem int // redis version 7.0, memory is used up by buffered multi commands BufferSize int // rbs, usable size of buffer BufferPeak int // rbp, peak used size of buffer in last 5 sec interval OutputBufferLength int // obl, output buffer length OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full) OutputMemory int // omem, output buffer memory usage TotalMemory int // tot-mem, total memory consumed by this client in its various buffers TotalNetIn int // tot-net-in, total network input TotalNetOut int // tot-net-out, total network output TotalCmds int // tot-cmds, total number of commands processed IoThread int // io-thread id Events string // file descriptor events (see below) LastCmd string // cmd, last command played User string // the authenticated username of the client Redir int64 // client id of current client tracking redirection Resp int // redis version 7.0, client RESP protocol version LibName string // redis version 7.2, client library name LibVer string // redis version 7.2, client library version } type ClientInfoCmd struct { baseCmd val *ClientInfo } var _ Cmder = (*ClientInfoCmd)(nil) func NewClientInfoCmd(ctx context.Context, args ...interface{}) *ClientInfoCmd { return &ClientInfoCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeClientInfo, }, } } func (cmd *ClientInfoCmd) SetVal(val *ClientInfo) { cmd.val = val } func (cmd *ClientInfoCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ClientInfoCmd) Val() *ClientInfo { return cmd.val } func (cmd *ClientInfoCmd) Result() (*ClientInfo, error) { return cmd.val, cmd.err } func (cmd *ClientInfoCmd) readReply(rd *proto.Reader) (err error) { txt, err := rd.ReadString() if err != nil { return err } // sds o = catClientInfoString(sdsempty(), c); // o = sdscatlen(o,"\n",1); // addReplyVerbatim(c,o,sdslen(o),"txt"); // sdsfree(o); cmd.val, err = parseClientInfo(strings.TrimSpace(txt)) return err } // fmt.Sscanf() cannot handle null values func parseClientInfo(txt string) (info *ClientInfo, err error) { info = &ClientInfo{} for _, s := range strings.Split(txt, " ") { kv := strings.Split(s, "=") if len(kv) != 2 { return nil, fmt.Errorf("redis: unexpected client info data (%s)", s) } key, val := kv[0], kv[1] switch key { case "id": info.ID, err = strconv.ParseInt(val, 10, 64) case "addr": info.Addr = val case "laddr": info.LAddr = val case "fd": info.FD, err = strconv.ParseInt(val, 10, 64) case "name": info.Name = val case "age": var age int if age, err = strconv.Atoi(val); err == nil { info.Age = time.Duration(age) * time.Second } case "idle": var idle int if idle, err = strconv.Atoi(val); err == nil { info.Idle = time.Duration(idle) * time.Second } case "flags": if val == "N" { break } for i := 0; i < len(val); i++ { switch val[i] { case 'S': info.Flags |= ClientSlave case 'O': info.Flags |= ClientSlave | ClientMonitor case 'M': info.Flags |= ClientMaster case 'P': info.Flags |= ClientPubSub case 'x': info.Flags |= ClientMulti case 'b': info.Flags |= ClientBlocked case 't': info.Flags |= ClientTracking case 'R': info.Flags |= ClientTrackingBrokenRedir case 'B': info.Flags |= ClientTrackingBCAST case 'd': info.Flags |= ClientDirtyCAS case 'c': info.Flags |= ClientCloseAfterCommand case 'u': info.Flags |= ClientUnBlocked case 'A': info.Flags |= ClientCloseASAP case 'U': info.Flags |= ClientUnixSocket case 'r': info.Flags |= ClientReadOnly case 'e': info.Flags |= ClientNoEvict case 'T': info.Flags |= ClientNoTouch default: return nil, fmt.Errorf("redis: unexpected client info flags(%s)", string(val[i])) } } case "db": info.DB, err = strconv.Atoi(val) case "sub": info.Sub, err = strconv.Atoi(val) case "psub": info.PSub, err = strconv.Atoi(val) case "ssub": info.SSub, err = strconv.Atoi(val) case "multi": info.Multi, err = strconv.Atoi(val) case "watch": info.Watch, err = strconv.Atoi(val) case "qbuf": info.QueryBuf, err = strconv.Atoi(val) case "qbuf-free": info.QueryBufFree, err = strconv.Atoi(val) case "argv-mem": info.ArgvMem, err = strconv.Atoi(val) case "multi-mem": info.MultiMem, err = strconv.Atoi(val) case "rbs": info.BufferSize, err = strconv.Atoi(val) case "rbp": info.BufferPeak, err = strconv.Atoi(val) case "obl": info.OutputBufferLength, err = strconv.Atoi(val) case "oll": info.OutputListLength, err = strconv.Atoi(val) case "omem": info.OutputMemory, err = strconv.Atoi(val) case "tot-mem": info.TotalMemory, err = strconv.Atoi(val) case "tot-net-in": info.TotalNetIn, err = strconv.Atoi(val) case "tot-net-out": info.TotalNetOut, err = strconv.Atoi(val) case "tot-cmds": info.TotalCmds, err = strconv.Atoi(val) case "events": info.Events = val case "cmd": info.LastCmd = val case "user": info.User = val case "redir": info.Redir, err = strconv.ParseInt(val, 10, 64) case "resp": info.Resp, err = strconv.Atoi(val) case "lib-name": info.LibName = val case "lib-ver": info.LibVer = val case "io-thread": info.IoThread, err = strconv.Atoi(val) default: return nil, fmt.Errorf("redis: unexpected client info key(%s)", key) } if err != nil { return nil, err } } return info, nil } func (cmd *ClientInfoCmd) Clone() Cmder { var val *ClientInfo if cmd.val != nil { val = &ClientInfo{ ID: cmd.val.ID, Addr: cmd.val.Addr, LAddr: cmd.val.LAddr, FD: cmd.val.FD, Name: cmd.val.Name, Age: cmd.val.Age, Idle: cmd.val.Idle, Flags: cmd.val.Flags, DB: cmd.val.DB, Sub: cmd.val.Sub, PSub: cmd.val.PSub, SSub: cmd.val.SSub, Multi: cmd.val.Multi, Watch: cmd.val.Watch, QueryBuf: cmd.val.QueryBuf, QueryBufFree: cmd.val.QueryBufFree, ArgvMem: cmd.val.ArgvMem, MultiMem: cmd.val.MultiMem, BufferSize: cmd.val.BufferSize, BufferPeak: cmd.val.BufferPeak, OutputBufferLength: cmd.val.OutputBufferLength, OutputListLength: cmd.val.OutputListLength, OutputMemory: cmd.val.OutputMemory, TotalMemory: cmd.val.TotalMemory, IoThread: cmd.val.IoThread, Events: cmd.val.Events, LastCmd: cmd.val.LastCmd, User: cmd.val.User, Redir: cmd.val.Redir, Resp: cmd.val.Resp, LibName: cmd.val.LibName, LibVer: cmd.val.LibVer, } } return &ClientInfoCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // ------------------------------------------- type ACLLogEntry struct { Count int64 Reason string Context string Object string Username string AgeSeconds float64 ClientInfo *ClientInfo EntryID int64 TimestampCreated int64 TimestampLastUpdated int64 } type ACLLogCmd struct { baseCmd val []*ACLLogEntry } var _ Cmder = (*ACLLogCmd)(nil) func NewACLLogCmd(ctx context.Context, args ...interface{}) *ACLLogCmd { return &ACLLogCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeACLLog, }, } } func (cmd *ACLLogCmd) SetVal(val []*ACLLogEntry) { cmd.val = val } func (cmd *ACLLogCmd) Val() []*ACLLogEntry { return cmd.val } func (cmd *ACLLogCmd) Result() ([]*ACLLogEntry, error) { return cmd.val, cmd.err } func (cmd *ACLLogCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadArrayLen() if err != nil { return err } cmd.val = make([]*ACLLogEntry, n) for i := 0; i < n; i++ { cmd.val[i] = &ACLLogEntry{} entry := cmd.val[i] respLen, err := rd.ReadMapLen() if err != nil { return err } for j := 0; j < respLen; j++ { key, err := rd.ReadString() if err != nil { return err } switch key { case "count": entry.Count, err = rd.ReadInt() case "reason": entry.Reason, err = rd.ReadString() case "context": entry.Context, err = rd.ReadString() case "object": entry.Object, err = rd.ReadString() case "username": entry.Username, err = rd.ReadString() case "age-seconds": entry.AgeSeconds, err = rd.ReadFloat() case "client-info": txt, err := rd.ReadString() if err != nil { return err } entry.ClientInfo, err = parseClientInfo(strings.TrimSpace(txt)) if err != nil { return err } case "entry-id": entry.EntryID, err = rd.ReadInt() case "timestamp-created": entry.TimestampCreated, err = rd.ReadInt() case "timestamp-last-updated": entry.TimestampLastUpdated, err = rd.ReadInt() default: return fmt.Errorf("redis: unexpected key %q in ACL LOG reply", key) } if err != nil { return err } } } return nil } func (cmd *ACLLogCmd) Clone() Cmder { var val []*ACLLogEntry if cmd.val != nil { val = make([]*ACLLogEntry, len(cmd.val)) for i, entry := range cmd.val { if entry != nil { val[i] = &ACLLogEntry{ Count: entry.Count, Reason: entry.Reason, Context: entry.Context, Object: entry.Object, Username: entry.Username, AgeSeconds: entry.AgeSeconds, EntryID: entry.EntryID, TimestampCreated: entry.TimestampCreated, TimestampLastUpdated: entry.TimestampLastUpdated, } // Clone ClientInfo if present if entry.ClientInfo != nil { val[i].ClientInfo = &ClientInfo{ ID: entry.ClientInfo.ID, Addr: entry.ClientInfo.Addr, LAddr: entry.ClientInfo.LAddr, FD: entry.ClientInfo.FD, Name: entry.ClientInfo.Name, Age: entry.ClientInfo.Age, Idle: entry.ClientInfo.Idle, Flags: entry.ClientInfo.Flags, DB: entry.ClientInfo.DB, Sub: entry.ClientInfo.Sub, PSub: entry.ClientInfo.PSub, SSub: entry.ClientInfo.SSub, Multi: entry.ClientInfo.Multi, Watch: entry.ClientInfo.Watch, QueryBuf: entry.ClientInfo.QueryBuf, QueryBufFree: entry.ClientInfo.QueryBufFree, ArgvMem: entry.ClientInfo.ArgvMem, MultiMem: entry.ClientInfo.MultiMem, BufferSize: entry.ClientInfo.BufferSize, BufferPeak: entry.ClientInfo.BufferPeak, OutputBufferLength: entry.ClientInfo.OutputBufferLength, OutputListLength: entry.ClientInfo.OutputListLength, OutputMemory: entry.ClientInfo.OutputMemory, TotalMemory: entry.ClientInfo.TotalMemory, IoThread: entry.ClientInfo.IoThread, Events: entry.ClientInfo.Events, LastCmd: entry.ClientInfo.LastCmd, User: entry.ClientInfo.User, Redir: entry.ClientInfo.Redir, Resp: entry.ClientInfo.Resp, LibName: entry.ClientInfo.LibName, LibVer: entry.ClientInfo.LibVer, } } } } } return &ACLLogCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } // LibraryInfo holds the library info. type LibraryInfo struct { LibName *string LibVer *string } // WithLibraryName returns a valid LibraryInfo with library name only. func WithLibraryName(libName string) LibraryInfo { return LibraryInfo{LibName: &libName} } // WithLibraryVersion returns a valid LibraryInfo with library version only. func WithLibraryVersion(libVer string) LibraryInfo { return LibraryInfo{LibVer: &libVer} } // ------------------------------------------- type InfoCmd struct { baseCmd val map[string]map[string]string } var _ Cmder = (*InfoCmd)(nil) func NewInfoCmd(ctx context.Context, args ...interface{}) *InfoCmd { return &InfoCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, cmdType: CmdTypeInfo, }, } } func (cmd *InfoCmd) SetVal(val map[string]map[string]string) { cmd.val = val } func (cmd *InfoCmd) Val() map[string]map[string]string { return cmd.val } func (cmd *InfoCmd) Result() (map[string]map[string]string, error) { return cmd.val, cmd.err } func (cmd *InfoCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *InfoCmd) readReply(rd *proto.Reader) error { val, err := rd.ReadString() if err != nil { return err } section := "" scanner := bufio.NewScanner(strings.NewReader(val)) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "#") { if cmd.val == nil { cmd.val = make(map[string]map[string]string) } section = strings.TrimPrefix(line, "# ") cmd.val[section] = make(map[string]string) } else if line != "" { if section == "Modules" { moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`) kv := moduleRe.FindStringSubmatch(line) if len(kv) == 3 { cmd.val[section][kv[1]] = kv[2] } } else { kv := strings.SplitN(line, ":", 2) if len(kv) == 2 { cmd.val[section][kv[0]] = kv[1] } } } } return nil } func (cmd *InfoCmd) Item(section, key string) string { if cmd.val == nil { return "" } else if cmd.val[section] == nil { return "" } else { return cmd.val[section][key] } } func (cmd *InfoCmd) Clone() Cmder { var val map[string]map[string]string if cmd.val != nil { val = make(map[string]map[string]string, len(cmd.val)) for section, sectionMap := range cmd.val { if sectionMap != nil { val[section] = make(map[string]string, len(sectionMap)) for k, v := range sectionMap { val[section][k] = v } } } } return &InfoCmd{ baseCmd: cmd.cloneBaseCmd(), val: val, } } type MonitorStatus int const ( monitorStatusIdle MonitorStatus = iota monitorStatusStart monitorStatusStop ) type MonitorCmd struct { baseCmd ch chan string status MonitorStatus mu sync.Mutex } func newMonitorCmd(ctx context.Context, ch chan string) *MonitorCmd { return &MonitorCmd{ baseCmd: baseCmd{ ctx: ctx, args: []interface{}{"monitor"}, cmdType: CmdTypeMonitor, }, ch: ch, status: monitorStatusIdle, mu: sync.Mutex{}, } } func (cmd *MonitorCmd) String() string { return cmdString(cmd, nil) } func (cmd *MonitorCmd) readReply(rd *proto.Reader) error { ctx, cancel := context.WithCancel(cmd.ctx) go func(ctx context.Context) { for { select { case <-ctx.Done(): return default: err := cmd.readMonitor(rd, cancel) if err != nil { cmd.err = err return } } } }(ctx) return nil } func (cmd *MonitorCmd) readMonitor(rd *proto.Reader, cancel context.CancelFunc) error { for { cmd.mu.Lock() st := cmd.status pk, _ := rd.Peek(1) cmd.mu.Unlock() if len(pk) != 0 && st == monitorStatusStart { cmd.mu.Lock() line, err := rd.ReadString() cmd.mu.Unlock() if err != nil { return err } cmd.ch <- line } if st == monitorStatusStop { cancel() break } } return nil } func (cmd *MonitorCmd) Start() { cmd.mu.Lock() defer cmd.mu.Unlock() cmd.status = monitorStatusStart } func (cmd *MonitorCmd) Stop() { cmd.mu.Lock() defer cmd.mu.Unlock() cmd.status = monitorStatusStop } type VectorScoreSliceCmd struct { baseCmd val []VectorScore } var _ Cmder = (*VectorScoreSliceCmd)(nil) func NewVectorInfoSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCmd { return &VectorScoreSliceCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, }, } } func (cmd *VectorScoreSliceCmd) SetVal(val []VectorScore) { cmd.val = val } func (cmd *VectorScoreSliceCmd) Val() []VectorScore { return cmd.val } func (cmd *VectorScoreSliceCmd) Result() ([]VectorScore, error) { return cmd.val, cmd.err } func (cmd *VectorScoreSliceCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *VectorScoreSliceCmd) readReply(rd *proto.Reader) error { n, err := rd.ReadMapLen() if err != nil { return err } cmd.val = make([]VectorScore, n) for i := 0; i < n; i++ { name, err := rd.ReadString() if err != nil { return err } cmd.val[i].Name = name score, err := rd.ReadFloat() if err != nil { return err } cmd.val[i].Score = score } return nil } func (cmd *VectorScoreSliceCmd) Clone() Cmder { return &VectorScoreSliceCmd{ baseCmd: cmd.cloneBaseCmd(), val: cmd.val, } } func (cmd *MonitorCmd) Clone() Cmder { // MonitorCmd cannot be safely cloned due to channels and goroutines // Return a new MonitorCmd with the same channel return newMonitorCmd(cmd.ctx, cmd.ch) } // ExtractCommandValue extracts the value from a command result using the fast enum-based approach func ExtractCommandValue(cmd interface{}) (interface{}, error) { // First try to get the command type using the interface if cmdTypeGetter, ok := cmd.(CmdTypeGetter); ok { cmdType := cmdTypeGetter.GetCmdType() // Use fast type-based extraction switch cmdType { case CmdTypeGeneric: if genericCmd, ok := cmd.(interface { Val() interface{} Err() error }); ok { return genericCmd.Val(), genericCmd.Err() } case CmdTypeString: if stringCmd, ok := cmd.(interface { Val() string Err() error }); ok { return stringCmd.Val(), stringCmd.Err() } case CmdTypeInt: if intCmd, ok := cmd.(interface { Val() int64 Err() error }); ok { return intCmd.Val(), intCmd.Err() } case CmdTypeBool: if boolCmd, ok := cmd.(interface { Val() bool Err() error }); ok { return boolCmd.Val(), boolCmd.Err() } case CmdTypeFloat: if floatCmd, ok := cmd.(interface { Val() float64 Err() error }); ok { return floatCmd.Val(), floatCmd.Err() } case CmdTypeStatus: if statusCmd, ok := cmd.(interface { Val() string Err() error }); ok { return statusCmd.Val(), statusCmd.Err() } case CmdTypeDuration: if durationCmd, ok := cmd.(interface { Val() time.Duration Err() error }); ok { return durationCmd.Val(), durationCmd.Err() } case CmdTypeTime: if timeCmd, ok := cmd.(interface { Val() time.Time Err() error }); ok { return timeCmd.Val(), timeCmd.Err() } case CmdTypeStringStructMap: if structMapCmd, ok := cmd.(interface { Val() map[string]struct{} Err() error }); ok { return structMapCmd.Val(), structMapCmd.Err() } case CmdTypeXMessageSlice: if xMessageSliceCmd, ok := cmd.(interface { Val() []XMessage Err() error }); ok { return xMessageSliceCmd.Val(), xMessageSliceCmd.Err() } case CmdTypeXStreamSlice: if xStreamSliceCmd, ok := cmd.(interface { Val() []XStream Err() error }); ok { return xStreamSliceCmd.Val(), xStreamSliceCmd.Err() } case CmdTypeXPending: if xPendingCmd, ok := cmd.(interface { Val() *XPending Err() error }); ok { return xPendingCmd.Val(), xPendingCmd.Err() } case CmdTypeXPendingExt: if xPendingExtCmd, ok := cmd.(interface { Val() []XPendingExt Err() error }); ok { return xPendingExtCmd.Val(), xPendingExtCmd.Err() } case CmdTypeXAutoClaim: if xAutoClaimCmd, ok := cmd.(interface { Val() ([]XMessage, string) Err() error }); ok { messages, start := xAutoClaimCmd.Val() return CmdTypeXAutoClaimValue{messages: messages, start: start}, xAutoClaimCmd.Err() } case CmdTypeXAutoClaimJustID: if xAutoClaimJustIDCmd, ok := cmd.(interface { Val() ([]string, string) Err() error }); ok { ids, start := xAutoClaimJustIDCmd.Val() return CmdTypeXAutoClaimJustIDValue{ids: ids, start: start}, xAutoClaimJustIDCmd.Err() } case CmdTypeXInfoConsumers: if xInfoConsumersCmd, ok := cmd.(interface { Val() []XInfoConsumer Err() error }); ok { return xInfoConsumersCmd.Val(), xInfoConsumersCmd.Err() } case CmdTypeXInfoGroups: if xInfoGroupsCmd, ok := cmd.(interface { Val() []XInfoGroup Err() error }); ok { return xInfoGroupsCmd.Val(), xInfoGroupsCmd.Err() } case CmdTypeXInfoStream: if xInfoStreamCmd, ok := cmd.(interface { Val() *XInfoStream Err() error }); ok { return xInfoStreamCmd.Val(), xInfoStreamCmd.Err() } case CmdTypeXInfoStreamFull: if xInfoStreamFullCmd, ok := cmd.(interface { Val() *XInfoStreamFull Err() error }); ok { return xInfoStreamFullCmd.Val(), xInfoStreamFullCmd.Err() } case CmdTypeZSlice: if zSliceCmd, ok := cmd.(interface { Val() []Z Err() error }); ok { return zSliceCmd.Val(), zSliceCmd.Err() } case CmdTypeZWithKey: if zWithKeyCmd, ok := cmd.(interface { Val() *ZWithKey Err() error }); ok { return zWithKeyCmd.Val(), zWithKeyCmd.Err() } case CmdTypeScan: if scanCmd, ok := cmd.(interface { Val() ([]string, uint64) Err() error }); ok { keys, cursor := scanCmd.Val() return CmdTypeScanValue{keys: keys, cursor: cursor}, scanCmd.Err() } case CmdTypeClusterSlots: if clusterSlotsCmd, ok := cmd.(interface { Val() []ClusterSlot Err() error }); ok { return clusterSlotsCmd.Val(), clusterSlotsCmd.Err() } case CmdTypeGeoLocation: if geoLocationCmd, ok := cmd.(interface { Val() []GeoLocation Err() error }); ok { return geoLocationCmd.Val(), geoLocationCmd.Err() } case CmdTypeGeoSearchLocation: if geoSearchLocationCmd, ok := cmd.(interface { Val() []GeoLocation Err() error }); ok { return geoSearchLocationCmd.Val(), geoSearchLocationCmd.Err() } case CmdTypeGeoPos: if geoPosCmd, ok := cmd.(interface { Val() []*GeoPos Err() error }); ok { return geoPosCmd.Val(), geoPosCmd.Err() } case CmdTypeCommandsInfo: if commandsInfoCmd, ok := cmd.(interface { Val() map[string]*CommandInfo Err() error }); ok { return commandsInfoCmd.Val(), commandsInfoCmd.Err() } case CmdTypeSlowLog: if slowLogCmd, ok := cmd.(interface { Val() []SlowLog Err() error }); ok { return slowLogCmd.Val(), slowLogCmd.Err() } case CmdTypeKeyValues: if keyValuesCmd, ok := cmd.(interface { Val() (string, []string) Err() error }); ok { key, values := keyValuesCmd.Val() return CmdTypeKeyValuesValue{key: key, values: values}, keyValuesCmd.Err() } case CmdTypeZSliceWithKey: if zSliceWithKeyCmd, ok := cmd.(interface { Val() (string, []Z) Err() error }); ok { key, zSlice := zSliceWithKeyCmd.Val() return CmdTypeZSliceWithKeyValue{key: key, zSlice: zSlice}, zSliceWithKeyCmd.Err() } case CmdTypeFunctionList: if functionListCmd, ok := cmd.(interface { Val() []Library Err() error }); ok { return functionListCmd.Val(), functionListCmd.Err() } case CmdTypeFunctionStats: if functionStatsCmd, ok := cmd.(interface { Val() FunctionStats Err() error }); ok { return functionStatsCmd.Val(), functionStatsCmd.Err() } case CmdTypeLCS: if lcsCmd, ok := cmd.(interface { Val() *LCSMatch Err() error }); ok { return lcsCmd.Val(), lcsCmd.Err() } case CmdTypeKeyFlags: if keyFlagsCmd, ok := cmd.(interface { Val() []KeyFlags Err() error }); ok { return keyFlagsCmd.Val(), keyFlagsCmd.Err() } case CmdTypeClusterLinks: if clusterLinksCmd, ok := cmd.(interface { Val() []ClusterLink Err() error }); ok { return clusterLinksCmd.Val(), clusterLinksCmd.Err() } case CmdTypeClusterShards: if clusterShardsCmd, ok := cmd.(interface { Val() []ClusterShard Err() error }); ok { return clusterShardsCmd.Val(), clusterShardsCmd.Err() } case CmdTypeRankWithScore: if rankWithScoreCmd, ok := cmd.(interface { Val() RankScore Err() error }); ok { return rankWithScoreCmd.Val(), rankWithScoreCmd.Err() } case CmdTypeClientInfo: if clientInfoCmd, ok := cmd.(interface { Val() *ClientInfo Err() error }); ok { return clientInfoCmd.Val(), clientInfoCmd.Err() } case CmdTypeACLLog: if aclLogCmd, ok := cmd.(interface { Val() []*ACLLogEntry Err() error }); ok { return aclLogCmd.Val(), aclLogCmd.Err() } case CmdTypeInfo: if infoCmd, ok := cmd.(interface { Val() string Err() error }); ok { return infoCmd.Val(), infoCmd.Err() } case CmdTypeMonitor: if monitorCmd, ok := cmd.(interface { Val() string Err() error }); ok { return monitorCmd.Val(), monitorCmd.Err() } case CmdTypeJSON: if jsonCmd, ok := cmd.(interface { Val() string Err() error }); ok { return jsonCmd.Val(), jsonCmd.Err() } case CmdTypeJSONSlice: if jsonSliceCmd, ok := cmd.(interface { Val() []interface{} Err() error }); ok { return jsonSliceCmd.Val(), jsonSliceCmd.Err() } case CmdTypeIntPointerSlice: if intPointerSliceCmd, ok := cmd.(interface { Val() []*int64 Err() error }); ok { return intPointerSliceCmd.Val(), intPointerSliceCmd.Err() } case CmdTypeScanDump: if scanDumpCmd, ok := cmd.(interface { Val() ScanDump Err() error }); ok { return scanDumpCmd.Val(), scanDumpCmd.Err() } case CmdTypeBFInfo: if bfInfoCmd, ok := cmd.(interface { Val() BFInfo Err() error }); ok { return bfInfoCmd.Val(), bfInfoCmd.Err() } case CmdTypeCFInfo: if cfInfoCmd, ok := cmd.(interface { Val() CFInfo Err() error }); ok { return cfInfoCmd.Val(), cfInfoCmd.Err() } case CmdTypeCMSInfo: if cmsInfoCmd, ok := cmd.(interface { Val() CMSInfo Err() error }); ok { return cmsInfoCmd.Val(), cmsInfoCmd.Err() } case CmdTypeTopKInfo: if topKInfoCmd, ok := cmd.(interface { Val() TopKInfo Err() error }); ok { return topKInfoCmd.Val(), topKInfoCmd.Err() } case CmdTypeTDigestInfo: if tDigestInfoCmd, ok := cmd.(interface { Val() TDigestInfo Err() error }); ok { return tDigestInfoCmd.Val(), tDigestInfoCmd.Err() } case CmdTypeFTSearch: if ftSearchCmd, ok := cmd.(interface { Val() FTSearchResult Err() error }); ok { return ftSearchCmd.Val(), ftSearchCmd.Err() } case CmdTypeFTInfo: if ftInfoCmd, ok := cmd.(interface { Val() FTInfoResult Err() error }); ok { return ftInfoCmd.Val(), ftInfoCmd.Err() } case CmdTypeFTSpellCheck: if ftSpellCheckCmd, ok := cmd.(interface { Val() []SpellCheckResult Err() error }); ok { return ftSpellCheckCmd.Val(), ftSpellCheckCmd.Err() } case CmdTypeFTSynDump: if ftSynDumpCmd, ok := cmd.(interface { Val() []FTSynDumpResult Err() error }); ok { return ftSynDumpCmd.Val(), ftSynDumpCmd.Err() } case CmdTypeAggregate: if aggregateCmd, ok := cmd.(interface { Val() *FTAggregateResult Err() error }); ok { return aggregateCmd.Val(), aggregateCmd.Err() } case CmdTypeTSTimestampValue: if tsTimestampValueCmd, ok := cmd.(interface { Val() TSTimestampValue Err() error }); ok { return tsTimestampValueCmd.Val(), tsTimestampValueCmd.Err() } case CmdTypeTSTimestampValueSlice: if tsTimestampValueSliceCmd, ok := cmd.(interface { Val() []TSTimestampValue Err() error }); ok { return tsTimestampValueSliceCmd.Val(), tsTimestampValueSliceCmd.Err() } case CmdTypeStringSlice: if stringSliceCmd, ok := cmd.(interface { Val() []string Err() error }); ok { return stringSliceCmd.Val(), stringSliceCmd.Err() } case CmdTypeIntSlice: if intSliceCmd, ok := cmd.(interface { Val() []int64 Err() error }); ok { return intSliceCmd.Val(), intSliceCmd.Err() } case CmdTypeBoolSlice: if boolSliceCmd, ok := cmd.(interface { Val() []bool Err() error }); ok { return boolSliceCmd.Val(), boolSliceCmd.Err() } case CmdTypeFloatSlice: if floatSliceCmd, ok := cmd.(interface { Val() []float64 Err() error }); ok { return floatSliceCmd.Val(), floatSliceCmd.Err() } case CmdTypeSlice: if sliceCmd, ok := cmd.(interface { Val() []interface{} Err() error }); ok { return sliceCmd.Val(), sliceCmd.Err() } case CmdTypeKeyValueSlice: if keyValueSliceCmd, ok := cmd.(interface { Val() []KeyValue Err() error }); ok { return keyValueSliceCmd.Val(), keyValueSliceCmd.Err() } case CmdTypeMapStringString: if mapCmd, ok := cmd.(interface { Val() map[string]string Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } case CmdTypeMapStringInt: if mapCmd, ok := cmd.(interface { Val() map[string]int64 Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } case CmdTypeMapStringInterfaceSlice: if mapCmd, ok := cmd.(interface { Val() []map[string]interface{} Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } case CmdTypeMapStringInterface: if mapCmd, ok := cmd.(interface { Val() map[string]interface{} Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } case CmdTypeMapStringStringSlice: if mapCmd, ok := cmd.(interface { Val() []map[string]string Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } case CmdTypeMapMapStringInterface: if mapCmd, ok := cmd.(interface { Val() map[string]interface{} Err() error }); ok { return mapCmd.Val(), mapCmd.Err() } default: // For unknown command types, return nil return nil, nil } } // If we can't get the command type, return nil return nil, nil }