redis/command.go

5484 lines
110 KiB
Go
Raw Permalink Normal View History

2013-09-29 12:06:49 +04:00
package redis
import (
"bufio"
2020-03-11 17:26:42 +03:00
"context"
2013-09-29 12:06:49 +04:00
"fmt"
2018-08-02 14:48:46 +03:00
"net"
"regexp"
2013-09-29 12:06:49 +04:00
"strconv"
"strings"
"sync"
2013-09-29 12:06:49 +04:00
"time"
2023-01-23 09:48:54 +03:00
"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/util"
2013-09-29 12:06:49 +04:00
)
type Cmder interface {
// command name.
// e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster".
2017-05-09 12:44:36 +03:00
Name() string
// full command name.
// e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster info".
2020-05-09 17:34:50 +03:00
FullName() string
// all args of the command.
// e.g. "set k v ex 10" -> "[set k v ex 10]".
2017-09-26 11:29:22 +03:00
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".
2020-02-14 15:30:07 +03:00
String() string
2017-09-26 11:29:22 +03:00
stringArg(int) string
2020-09-23 10:29:13 +03:00
firstKeyPos() int8
SetFirstKeyPos(int8)
2016-06-17 15:09:38 +03:00
2013-09-29 12:06:49 +04:00
readTimeout() *time.Duration
2019-08-24 11:55:13 +03:00
readReply(rd *proto.Reader) error
2013-09-29 12:06:49 +04:00
2020-02-03 12:53:47 +03:00
SetErr(error)
2013-09-29 12:06:49 +04:00
Err() error
}
2013-09-29 13:41:04 +04:00
func setCmdsErr(cmds []Cmder, e error) {
for _, cmd := range cmds {
2017-08-31 15:22:47 +03:00
if cmd.Err() == nil {
2020-02-03 12:53:47 +03:00
cmd.SetErr(e)
2017-08-31 15:22:47 +03:00
}
}
}
2018-08-12 11:11:01 +03:00
func cmdsFirstErr(cmds []Cmder) error {
2017-08-31 15:22:47 +03:00
for _, cmd := range cmds {
if err := cmd.Err(); err != nil {
return err
}
2013-09-29 13:41:04 +04:00
}
2017-08-31 15:22:47 +03:00
return nil
2013-09-29 13:41:04 +04:00
}
func writeCmds(wr *proto.Writer, cmds []Cmder) error {
for _, cmd := range cmds {
if err := writeCmd(wr, cmd); err != nil {
return err
}
}
2018-08-15 11:53:15 +03:00
return nil
}
func writeCmd(wr *proto.Writer, cmd Cmder) error {
return wr.WriteArgs(cmd.Args())
}
func cmdFirstKeyPos(cmd Cmder) int {
2020-09-23 10:29:13 +03:00
if pos := cmd.firstKeyPos(); pos != 0 {
return int(pos)
}
2017-05-09 12:44:36 +03:00
switch cmd.Name() {
case "eval", "evalsha", "eval_ro", "evalsha_ro":
2017-09-26 11:29:22 +03:00
if cmd.stringArg(2) != "0" {
return 3
}
2018-01-24 21:38:47 +03:00
return 0
2017-04-12 13:00:20 +03:00
case "publish":
return 1
case "memory":
// https://github.com/redis/redis/issues/7493
if cmd.stringArg(1) == "usage" {
return 2
}
}
2022-06-04 16:07:28 +03:00
return 1
}
2020-05-09 17:30:16 +03:00
func cmdString(cmd Cmder, val interface{}) string {
2020-06-09 16:29:53 +03:00
b := make([]byte, 0, 64)
2020-05-09 17:30:16 +03:00
for i, arg := range cmd.Args() {
if i > 0 {
b = append(b, ' ')
}
2020-06-09 16:29:53 +03:00
b = internal.AppendArg(b, arg)
2020-05-09 17:30:16 +03:00
}
if err := cmd.Err(); err != nil {
b = append(b, ": "...)
b = append(b, err.Error()...)
} else if val != nil {
b = append(b, ": "...)
2020-06-09 16:29:53 +03:00
b = internal.AppendArg(b, val)
2020-05-09 17:30:16 +03:00
}
return util.BytesToString(b)
2020-05-09 17:30:16 +03:00
}
2013-09-29 12:06:49 +04:00
//------------------------------------------------------------------------------
type baseCmd struct {
2020-09-23 10:29:13 +03:00
ctx context.Context
args []interface{}
err error
keyPos int8
2013-09-29 12:06:49 +04:00
2016-03-08 18:18:52 +03:00
_readTimeout *time.Duration
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*Cmd)(nil)
func (cmd *baseCmd) Name() string {
2019-08-24 11:55:13 +03:00
if len(cmd.args) == 0 {
2019-08-12 14:53:00 +03:00
return ""
}
2019-08-12 14:53:00 +03:00
// Cmd name must be lower cased.
return internal.ToLower(cmd.stringArg(0))
2013-09-29 12:06:49 +04:00
}
2020-05-09 17:34:50 +03:00
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
}
}
2017-09-26 11:29:22 +03:00
func (cmd *baseCmd) Args() []interface{} {
2019-08-24 11:55:13 +03:00
return cmd.args
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
func (cmd *baseCmd) stringArg(pos int) string {
2019-08-24 11:55:13 +03:00
if pos < 0 || pos >= len(cmd.args) {
return ""
}
2022-02-18 06:36:04 +03:00
arg := cmd.args[pos]
switch v := arg.(type) {
case string:
return v
default:
2022-02-18 10:45:35 +03:00
// TODO: consider using appendArg
return fmt.Sprint(v)
2022-02-18 06:36:04 +03:00
}
}
2020-09-23 10:29:13 +03:00
func (cmd *baseCmd) firstKeyPos() int8 {
return cmd.keyPos
}
func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) {
2020-09-23 10:29:13 +03:00
cmd.keyPos = keyPos
}
2020-02-03 12:53:47 +03:00
func (cmd *baseCmd) SetErr(e error) {
cmd.err = e
}
func (cmd *baseCmd) Err() error {
return cmd.err
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
func (cmd *baseCmd) readTimeout() *time.Duration {
return cmd._readTimeout
}
func (cmd *baseCmd) setReadTimeout(d time.Duration) {
cmd._readTimeout = &d
}
//------------------------------------------------------------------------------
type Cmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val interface{}
2013-09-29 12:06:49 +04:00
}
2020-03-11 17:26:42 +03:00
func NewCmd(ctx context.Context, args ...interface{}) *Cmd {
2016-06-17 15:09:38 +03:00
return &Cmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2016-06-17 15:09:38 +03:00
}
2013-09-29 12:06:49 +04:00
}
2020-02-14 15:30:07 +03:00
func (cmd *Cmd) String() string {
return cmdString(cmd, cmd.val)
}
func (cmd *Cmd) SetVal(val interface{}) {
cmd.val = val
}
2013-09-29 12:06:49 +04:00
func (cmd *Cmd) Val() interface{} {
return cmd.val
}
2014-01-09 12:17:38 +04:00
func (cmd *Cmd) Result() (interface{}, error) {
return cmd.val, cmd.err
}
2020-02-14 15:30:07 +03:00
func (cmd *Cmd) Text() (string, error) {
2018-08-12 11:11:01 +03:00
if cmd.err != nil {
return "", cmd.err
}
return toString(cmd.val)
}
func toString(val interface{}) (string, error) {
switch val := val.(type) {
2018-08-12 11:11:01 +03:00
case string:
return val, nil
default:
err := fmt.Errorf("redis: unexpected type=%T for String", val)
return "", err
}
}
2018-08-21 14:11:19 +03:00
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:
2018-08-27 08:29:25 +03:00
err := fmt.Errorf("redis: unexpected type=%T for Int", val)
2018-08-21 14:11:19 +03:00
return 0, err
}
}
2018-08-12 11:11:01 +03:00
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) {
2018-08-12 11:11:01 +03:00
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) {
2018-08-12 11:11:01 +03:00
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
}
}
2019-04-10 15:27:06 +03:00
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) {
2019-04-10 15:27:06 +03:00
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
}
}
2018-08-12 11:11:01 +03:00
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) {
2018-08-12 11:11:01 +03:00
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) {
2023-06-13 12:20:21 +03:00
case bool:
return val, nil
2018-08-12 11:11:01 +03:00
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
}
2014-01-09 12:17:38 +04:00
}
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
}
2020-09-17 12:27:16 +03:00
func (cmd *Cmd) readReply(rd *proto.Reader) (err error) {
cmd.val, err = rd.ReadReply()
2020-09-17 12:27:16 +03:00
return err
2014-01-09 12:17:38 +04:00
}
//------------------------------------------------------------------------------
type SliceCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val []interface{}
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*SliceCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd {
2017-01-13 14:39:59 +03:00
return &SliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2014-01-09 12:17:38 +04:00
}
func (cmd *SliceCmd) SetVal(val []interface{}) {
cmd.val = val
}
2014-01-09 12:17:38 +04:00
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
2014-01-09 12:17:38 +04:00
}
2013-09-29 12:06:49 +04:00
//------------------------------------------------------------------------------
type StatusCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val string
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*StatusCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd {
2017-01-13 14:39:59 +03:00
return &StatusCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2015-01-24 15:12:48 +03:00
}
func (cmd *StatusCmd) SetVal(val string) {
cmd.val = val
}
2013-09-29 12:06:49 +04:00
func (cmd *StatusCmd) Val() string {
2014-01-09 12:17:38 +04:00
return cmd.val
}
func (cmd *StatusCmd) Result() (string, error) {
return cmd.val, cmd.err
}
func (cmd *StatusCmd) String() string {
return cmdString(cmd, cmd.val)
}
2020-09-17 12:27:16 +03:00
func (cmd *StatusCmd) readReply(rd *proto.Reader) (err error) {
cmd.val, err = rd.ReadString()
return err
2013-09-29 12:06:49 +04:00
}
//------------------------------------------------------------------------------
type IntCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val int64
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*IntCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd {
2017-01-13 14:39:59 +03:00
return &IntCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *IntCmd) SetVal(val int64) {
cmd.val = val
}
2013-09-29 12:06:49 +04:00
func (cmd *IntCmd) Val() int64 {
2014-01-09 12:17:38 +04:00
return cmd.val
}
func (cmd *IntCmd) Result() (int64, error) {
return cmd.val, cmd.err
}
2019-09-27 14:38:55 +03:00
func (cmd *IntCmd) Uint64() (uint64, error) {
return uint64(cmd.val), cmd.err
}
2014-01-09 12:17:38 +04:00
func (cmd *IntCmd) String() string {
return cmdString(cmd, cmd.val)
}
2020-09-17 12:27:16 +03:00
func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) {
cmd.val, err = rd.ReadInt()
2020-09-17 12:27:16 +03:00
return err
2013-09-29 12:06:49 +04:00
}
//------------------------------------------------------------------------------
2019-06-26 14:45:38 +03:00
type IntSliceCmd struct {
baseCmd
val []int64
}
var _ Cmder = (*IntSliceCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd {
2019-06-26 14:45:38 +03:00
return &IntSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2019-06-26 14:45:38 +03:00
}
}
func (cmd *IntSliceCmd) SetVal(val []int64) {
cmd.val = val
}
2019-06-26 14:45:38 +03:00
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
2019-06-26 14:45:38 +03:00
}
}
return nil
2019-06-26 14:45:38 +03:00
}
//------------------------------------------------------------------------------
type DurationCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val time.Duration
precision time.Duration
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*DurationCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interface{}) *DurationCmd {
return &DurationCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
precision: precision,
}
}
func (cmd *DurationCmd) SetVal(val time.Duration) {
cmd.val = val
}
2014-01-09 12:17:38 +04:00
func (cmd *DurationCmd) Val() time.Duration {
return cmd.val
}
2014-01-09 12:17:38 +04:00
func (cmd *DurationCmd) Result() (time.Duration, error) {
return cmd.val, cmd.err
}
func (cmd *DurationCmd) String() string {
return cmdString(cmd, cmd.val)
}
2018-08-17 13:56:37 +03:00
func (cmd *DurationCmd) readReply(rd *proto.Reader) error {
n, err := rd.ReadInt()
2020-09-17 12:27:16 +03:00
if err != nil {
return err
}
2019-05-31 13:16:10 +03:00
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
}
2014-01-09 12:17:38 +04:00
return nil
}
//------------------------------------------------------------------------------
2016-10-14 14:39:02 +03:00
type TimeCmd struct {
baseCmd
val time.Time
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*TimeCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd {
2016-10-14 14:39:02 +03:00
return &TimeCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2016-10-14 14:39:02 +03:00
}
}
func (cmd *TimeCmd) SetVal(val time.Time) {
cmd.val = val
}
2016-10-14 14:39:02 +03:00
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)
}
2018-08-17 13:56:37 +03:00
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
2018-08-02 14:48:46 +03:00
}
2016-10-14 14:39:02 +03:00
//------------------------------------------------------------------------------
2013-09-29 12:06:49 +04:00
type BoolCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val bool
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*BoolCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd {
2017-01-13 14:39:59 +03:00
return &BoolCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *BoolCmd) SetVal(val bool) {
cmd.val = val
}
2014-01-09 12:17:38 +04:00
func (cmd *BoolCmd) Val() bool {
return cmd.val
2013-09-29 12:06:49 +04:00
}
2014-01-09 12:17:38 +04:00
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()
2015-10-07 17:56:49 +03:00
// `SET key value NX` returns nil when key already exists. But
// `SETNX key value` returns bool (0/1). So convert nil to bool.
2020-09-17 12:27:16 +03:00
if err == Nil {
cmd.val = false
err = nil
}
return err
2013-09-29 12:06:49 +04:00
}
//------------------------------------------------------------------------------
type StringCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
2018-08-16 13:25:19 +03:00
val string
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*StringCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd {
2017-01-13 14:39:59 +03:00
return &StringCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *StringCmd) SetVal(val string) {
cmd.val = val
}
2013-09-29 12:06:49 +04:00
func (cmd *StringCmd) Val() string {
2018-08-16 13:25:19 +03:00
return cmd.val
2014-01-09 12:17:38 +04:00
}
func (cmd *StringCmd) Result() (string, error) {
return cmd.val, cmd.err
}
func (cmd *StringCmd) Bytes() ([]byte, error) {
return util.StringToBytes(cmd.val), cmd.err
2014-01-09 12:17:38 +04:00
}
2021-03-17 10:50:02 +03:00
func (cmd *StringCmd) Bool() (bool, error) {
if cmd.err != nil {
return false, cmd.err
}
return strconv.ParseBool(cmd.val)
}
2018-08-21 14:11:19 +03:00
func (cmd *StringCmd) Int() (int, error) {
if cmd.err != nil {
return 0, cmd.err
}
return strconv.Atoi(cmd.Val())
}
2014-07-31 16:18:23 +04:00
func (cmd *StringCmd) Int64() (int64, error) {
if cmd.err != nil {
return 0, cmd.err
}
return strconv.ParseInt(cmd.Val(), 10, 64)
2014-07-31 16:18:23 +04:00
}
func (cmd *StringCmd) Uint64() (uint64, error) {
if cmd.err != nil {
return 0, cmd.err
}
return strconv.ParseUint(cmd.Val(), 10, 64)
2014-07-31 16:18:23 +04:00
}
2019-04-10 15:27:06 +03:00
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
}
2014-07-31 16:18:23 +04:00
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
}
2018-08-16 13:25:19 +03:00
return proto.Scan([]byte(cmd.val), val)
2014-07-31 16:18:23 +04:00
}
2014-01-09 12:17:38 +04:00
func (cmd *StringCmd) String() string {
return cmdString(cmd, cmd.val)
}
2020-09-17 12:27:16 +03:00
func (cmd *StringCmd) readReply(rd *proto.Reader) (err error) {
cmd.val, err = rd.ReadString()
return err
2013-09-29 12:06:49 +04:00
}
//------------------------------------------------------------------------------
type FloatCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val float64
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*FloatCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd {
2017-01-13 14:39:59 +03:00
return &FloatCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *FloatCmd) SetVal(val float64) {
cmd.val = val
}
2013-09-29 12:06:49 +04:00
func (cmd *FloatCmd) Val() float64 {
2014-01-09 12:17:38 +04:00
return cmd.val
2013-09-29 12:06:49 +04:00
}
2015-08-29 13:08:27 +03:00
func (cmd *FloatCmd) Result() (float64, error) {
return cmd.val, cmd.err
2015-08-29 13:08:27 +03:00
}
2014-01-09 12:17:38 +04:00
func (cmd *FloatCmd) String() string {
return cmdString(cmd, cmd.val)
2013-09-29 12:06:49 +04:00
}
2020-09-17 12:27:16 +03:00
func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) {
cmd.val, err = rd.ReadFloat()
2020-09-17 12:27:16 +03:00
return err
2013-09-29 12:06:49 +04:00
}
//------------------------------------------------------------------------------
2021-03-05 21:02:53 +03:00
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,
},
}
}
func (cmd *FloatSliceCmd) SetVal(val []float64) {
cmd.val = val
}
2021-03-05 21:02:53 +03:00
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
2021-03-05 21:02:53 +03:00
}
}
return nil
2021-03-05 21:02:53 +03:00
}
//------------------------------------------------------------------------------
2013-09-29 12:06:49 +04:00
type StringSliceCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val []string
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*StringSliceCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd {
2017-01-13 14:39:59 +03:00
return &StringSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *StringSliceCmd) SetVal(val []string) {
cmd.val = val
}
2014-01-09 12:17:38 +04:00
func (cmd *StringSliceCmd) Val() []string {
return cmd.val
2013-09-29 12:06:49 +04:00
}
func (cmd *StringSliceCmd) Result() ([]string, error) {
return cmd.val, cmd.err
}
2014-01-09 12:17:38 +04:00
func (cmd *StringSliceCmd) String() string {
return cmdString(cmd, cmd.val)
}
2017-02-01 11:36:33 +03:00
func (cmd *StringSliceCmd) ScanSlice(container interface{}) error {
return proto.ScanSlice(cmd.Val(), container)
}
2018-08-17 13:56:37 +03:00
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
}
//------------------------------------------------------------------------------
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,
},
}
}
2022-08-08 07:06:04 +03:00
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
2019-06-26 14:45:38 +03:00
}
2018-08-02 14:48:46 +03:00
}
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
2018-08-02 14:48:46 +03:00
}
2013-09-29 12:06:49 +04:00
//------------------------------------------------------------------------------
type BoolSliceCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val []bool
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*BoolSliceCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd {
2017-01-13 14:39:59 +03:00
return &BoolSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *BoolSliceCmd) SetVal(val []bool) {
cmd.val = val
}
2014-01-09 12:17:38 +04:00
func (cmd *BoolSliceCmd) Val() []bool {
return cmd.val
2013-09-29 12:06:49 +04:00
}
2014-01-09 12:17:38 +04:00
func (cmd *BoolSliceCmd) Result() ([]bool, error) {
return cmd.val, cmd.err
}
func (cmd *BoolSliceCmd) String() string {
return cmdString(cmd, cmd.val)
}
2018-08-17 13:56:37 +03:00
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
2018-08-02 14:48:46 +03:00
}
}
return nil
2018-08-02 14:48:46 +03:00
}
2013-09-29 12:06:49 +04:00
//------------------------------------------------------------------------------
type MapStringStringCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
val map[string]string
2013-09-29 12:06:49 +04:00
}
var _ Cmder = (*MapStringStringCmd)(nil)
2017-09-26 11:29:22 +03:00
func NewMapStringStringCmd(ctx context.Context, args ...interface{}) *MapStringStringCmd {
return &MapStringStringCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *MapStringStringCmd) Val() map[string]string {
2014-01-09 12:17:38 +04:00
return cmd.val
}
chore: sync master (#2051) * Upgrade redis-server version (#1833) * Upgrade redis-server version Signed-off-by: monkey <golang@88.com> * XAutoClaim changed the return value Signed-off-by: monkey <golang@88.com> * add cmd: geosearch, geosearchstore (#1836) * add cmd: geosearch, geosearchstore Signed-off-by: monkey92t <golang@88.com> * GeoSearchQuery and GeoSearchLocationQuery changed to pointer passing Signed-off-by: monkey92t <golang@88.com> * Added missing method XInfoStreamFull to Cmdable interface * Run go mod tidy in redisotel Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com> * Revert "ConnPool check fd for bad conns (#1824)" (#1849) This reverts commit 346bfafddd36dd52d51b064033048de5552ee91e. * Automate release process (#1852) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 (#1832) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.5 to 1.14.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.10.5...v1.14.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Upgrade gomega to v1.15.0 Signed-off-by: monkey92t <golang@88.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monkey92t <golang@88.com> * Add version.go * Fix otel example * Fix package name in release script * More fixes for otel example * And more * Fix release.sh * Release v8.11.3 (release.sh) * Create an annotated tag to give release.yml chance to run * Tweak tag.sh * Add Cmd.Slice helper to cast to []interface{} (#1859) * after the connection pool is closed, no new connections should be added (#1863) * after the connection pool is closed, no new connections should be added Signed-off-by: monkey92t <golang@88.com> * remove runGoroutine Signed-off-by: monkey92t <golang@88.com> * pool.popIdle add p.closed check Signed-off-by: monkey92t <golang@88.com> * upgrade golangci-lint v1.42.0 Signed-off-by: monkey92t <golang@88.com> * Bump github.com/onsi/gomega from 1.15.0 to 1.16.0 (#1865) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add go 1.17 to the build matrix * Remove go 1.15 from build matrix * Add scan struct example (#1870) * Replace release job * Bump github.com/cespare/xxhash/v2 from 2.1.1 to 2.1.2 (#1872) Bumps [github.com/cespare/xxhash/v2](https://github.com/cespare/xxhash) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/cespare/xxhash/releases) - [Commits](https://github.com/cespare/xxhash/compare/v2.1.1...v2.1.2) --- updated-dependencies: - dependency-name: github.com/cespare/xxhash/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix tag script to push tag by tag * Fix releasing.md * Fix/pubsub ping mutex (#1878) * Fix PubSub.Ping to hold the lock * Fix PubSub.Ping to hold the lock * add write cmd data-race test Signed-off-by: monkey92t <golang@88.com> Co-authored-by: monkey92t <golang@88.com> * chore: cleanup OpenTelemetry example * chore: gofmt all code * Refactor TestParseURL This is in preparation for supporting query parameters in ParseURL: - use an expected *Options instance to execute assertions on - extract assertions into helper function - enable parallel testing - condense test table * Add query parameter parsing to ParseURL() Before this change, ParseURL would only accept a very restricted set of URLs (it returned an error, if it encountered any parameter). This commit introduces the ability to process URLs like redis://localhost/1?dial_timeout=10s and similar. Go programs which were providing a configuration tunable (e.g. CLI flag, config entry or environment variable) to configure the Redis connection now don't need to perform this task themselves. * chore: add links to readme * chore: fix discussions link * empty hooks.withContext removed * chore: gofmt * chore: use conventional commits and auto-generate changelog * feat: add acl auth support for sentinels * chore: swap to acl auth at the test-level * Add support for BLMove command * chore: update dependencies * chore: update link * feat: add SetVal method for each command * feat: add Cmd.{String,Int,Float,Bool}Slice helpers and an example * chore: tweak GH actions to run all jobs * chore: add Lua scripting example * Fix Redis Cluster issue during roll outs of new nodes with same addr (#1914) * fix: recycle connections in some Redis Cluster scenarios This issue was surfaced in a Cloud Provider solution that used for rolling out new nodes using the same address (hostname) of the nodes that will be replaced in a Redis Cluster, while the former ones once depromoted as Slaves would continue in service during some mintues for redirecting traffic. The solution basically identifies when the connection could be stale since a MOVED response will be returned using the same address (hostname) that is being used by the connection. At that moment we consider the connection as no longer usable forcing to recycle the connection. * chore: lazy reload when moved or ask * chore: use conv commit message * chore: release v8.11.4 (release.sh) * fix: add whitespace for avoid unlikely colisions * fix: format * chore: fix links * chore: use ctx parameter in cmdInfo * Bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5 (#1925) Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.4 to 1.16.5. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v1.16.4...v1.16.5) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: add support for time.Duration write and scan * test: add test case for setting and scanning durations * chore: fix linter * fix(extra/redisotel): set span.kind attribute to client According to the opentelemetry specification this should always be set to client for database client libraries. I've also removed the SetAttributes call and instead set the attributes during creation of the span. This is what the library SHOULD be doing according to the opentelemetry api specification. * chore: update otel example * fix: update some argument counts in pre-allocs In some cases number of pre-allocated places in argument array is missing 1 or 2 elements, which results in re-allocation of twice as large array * chore: add example how to delete keys without a ttl * chore: don't enable all lints * chore(deps): bump github.com/onsi/gomega from 1.16.0 to 1.17.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.16.0...v1.17.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * feat: Add redis v7's NX, XX, GT, LT expire variants * chore: add missing readme * chore: tweak feature links * chore: remove Discord * fix: set timeout for WAIT command. Fixes #1963 * build: update `go` directive in `go.mod` to 1.17 This commit enables support for module graph pruning and lazy module loading for projects that are at Go 1.17 or higher. Reference: https://go.dev/ref/mod#go-mod-file-go Reference: https://go.dev/ref/mod#graph-pruning Reference: https://go.dev/ref/mod#lazy-loading Signed-off-by: Eng Zer Jun <engzerjun@gmail.com> * chore: update link * chore: export cmder.SetFirstKeyPos to support build module commands * feat(redisotel): ability to override TracerProvider (#1998) * fix: add missing Expire methods to Cmdable This is a followup to https://github.com/go-redis/redis/pull/1928 * chore(deps): bump github.com/onsi/gomega from 1.17.0 to 1.18.1 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.17.0 to 1.18.1. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.17.0...v1.18.1) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update README.md (#2011) chore: add fmt library in example code * chore: instrumentation name and version (#2012) * fix: invalid type assert in stringArg * chore: cleanup * fix: example/otel compile error (#2028) * fix: rename Golang to Go (#2030) https://go.dev/doc/faq#go_or_golang * feat: add support for passing extra attributes added to spans * feat: set net.peer.name and net.peer.port in otel example * chore: tweak Uptrace copy * feat: add support for COPY command (#2016) * feat: add support for acl sentinel auth in universal client * chore(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore: add hll example * chore: tweak release script * chore: release v8.11.5 (release.sh) * chore: add discord back Co-authored-by: Eugene Ponizovsky <ponizovsky@gmail.com> Co-authored-by: Bogdan Drutu <bogdandrutu@gmail.com> Co-authored-by: Vladimir Mihailenco <vladimir.webdev@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kishan B <kishancs46@gmail.com> Co-authored-by: Dominik Menke <dom@digineo.de> Co-authored-by: Gökhan Özeloğlu <gozeloglu@gmail.com> Co-authored-by: Justin Sievenpiper <justin@sievenpiper.co> Co-authored-by: Алексей Романовский <aromanovsky@epiphan.com> Co-authored-by: Stavros Panakakakis <stavrospanakakis@gmail.com> Co-authored-by: Pau Freixes <pfreixes@gmail.com> Co-authored-by: Ethan Hur <ethan0311@gmail.com> Co-authored-by: Jackie <18378976+Pyrodash@users.noreply.github.com> Co-authored-by: Kristinn Björgvin Árdal <kristinnardalsecondary@gmail.com> Co-authored-by: ffenix113 <razerer@bigmir.net> Co-authored-by: Bastien Penavayre <bastienPenava@gmail.com> Co-authored-by: James3 Li(李麒傑) <james3_li@asus.com> Co-authored-by: Eng Zer Jun <engzerjun@gmail.com> Co-authored-by: gzjiangtao2014 <gzjiangtao2014@corp.netease.com> Co-authored-by: Nelz <nelz9999@users.noreply.github.com> Co-authored-by: Daniel Richter <Nexyz9@gmail.com> Co-authored-by: Seyed Ali Ghaffari <ali.ghaffari@outlook.com> Co-authored-by: lintanghui <lintanghui@bilibili.com> Co-authored-by: hidu <duv123+github@gmail.com> Co-authored-by: Jonas Lergell <jonas.lergell@volvocars.com> Co-authored-by: Alex Kahn <alexanderkahn@gmail.com>
2022-03-19 07:40:31 +03:00
func (cmd *MapStringStringCmd) SetVal(val map[string]string) {
cmd.val = val
2013-09-29 12:06:49 +04:00
}
func (cmd *MapStringStringCmd) Result() (map[string]string, error) {
2014-01-09 12:17:38 +04:00
return cmd.val, cmd.err
}
func (cmd *MapStringStringCmd) String() string {
2014-01-09 12:17:38 +04:00
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.
chore: sync master (#2051) * Upgrade redis-server version (#1833) * Upgrade redis-server version Signed-off-by: monkey <golang@88.com> * XAutoClaim changed the return value Signed-off-by: monkey <golang@88.com> * add cmd: geosearch, geosearchstore (#1836) * add cmd: geosearch, geosearchstore Signed-off-by: monkey92t <golang@88.com> * GeoSearchQuery and GeoSearchLocationQuery changed to pointer passing Signed-off-by: monkey92t <golang@88.com> * Added missing method XInfoStreamFull to Cmdable interface * Run go mod tidy in redisotel Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com> * Revert "ConnPool check fd for bad conns (#1824)" (#1849) This reverts commit 346bfafddd36dd52d51b064033048de5552ee91e. * Automate release process (#1852) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 (#1832) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.5 to 1.14.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.10.5...v1.14.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Upgrade gomega to v1.15.0 Signed-off-by: monkey92t <golang@88.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monkey92t <golang@88.com> * Add version.go * Fix otel example * Fix package name in release script * More fixes for otel example * And more * Fix release.sh * Release v8.11.3 (release.sh) * Create an annotated tag to give release.yml chance to run * Tweak tag.sh * Add Cmd.Slice helper to cast to []interface{} (#1859) * after the connection pool is closed, no new connections should be added (#1863) * after the connection pool is closed, no new connections should be added Signed-off-by: monkey92t <golang@88.com> * remove runGoroutine Signed-off-by: monkey92t <golang@88.com> * pool.popIdle add p.closed check Signed-off-by: monkey92t <golang@88.com> * upgrade golangci-lint v1.42.0 Signed-off-by: monkey92t <golang@88.com> * Bump github.com/onsi/gomega from 1.15.0 to 1.16.0 (#1865) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add go 1.17 to the build matrix * Remove go 1.15 from build matrix * Add scan struct example (#1870) * Replace release job * Bump github.com/cespare/xxhash/v2 from 2.1.1 to 2.1.2 (#1872) Bumps [github.com/cespare/xxhash/v2](https://github.com/cespare/xxhash) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/cespare/xxhash/releases) - [Commits](https://github.com/cespare/xxhash/compare/v2.1.1...v2.1.2) --- updated-dependencies: - dependency-name: github.com/cespare/xxhash/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix tag script to push tag by tag * Fix releasing.md * Fix/pubsub ping mutex (#1878) * Fix PubSub.Ping to hold the lock * Fix PubSub.Ping to hold the lock * add write cmd data-race test Signed-off-by: monkey92t <golang@88.com> Co-authored-by: monkey92t <golang@88.com> * chore: cleanup OpenTelemetry example * chore: gofmt all code * Refactor TestParseURL This is in preparation for supporting query parameters in ParseURL: - use an expected *Options instance to execute assertions on - extract assertions into helper function - enable parallel testing - condense test table * Add query parameter parsing to ParseURL() Before this change, ParseURL would only accept a very restricted set of URLs (it returned an error, if it encountered any parameter). This commit introduces the ability to process URLs like redis://localhost/1?dial_timeout=10s and similar. Go programs which were providing a configuration tunable (e.g. CLI flag, config entry or environment variable) to configure the Redis connection now don't need to perform this task themselves. * chore: add links to readme * chore: fix discussions link * empty hooks.withContext removed * chore: gofmt * chore: use conventional commits and auto-generate changelog * feat: add acl auth support for sentinels * chore: swap to acl auth at the test-level * Add support for BLMove command * chore: update dependencies * chore: update link * feat: add SetVal method for each command * feat: add Cmd.{String,Int,Float,Bool}Slice helpers and an example * chore: tweak GH actions to run all jobs * chore: add Lua scripting example * Fix Redis Cluster issue during roll outs of new nodes with same addr (#1914) * fix: recycle connections in some Redis Cluster scenarios This issue was surfaced in a Cloud Provider solution that used for rolling out new nodes using the same address (hostname) of the nodes that will be replaced in a Redis Cluster, while the former ones once depromoted as Slaves would continue in service during some mintues for redirecting traffic. The solution basically identifies when the connection could be stale since a MOVED response will be returned using the same address (hostname) that is being used by the connection. At that moment we consider the connection as no longer usable forcing to recycle the connection. * chore: lazy reload when moved or ask * chore: use conv commit message * chore: release v8.11.4 (release.sh) * fix: add whitespace for avoid unlikely colisions * fix: format * chore: fix links * chore: use ctx parameter in cmdInfo * Bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5 (#1925) Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.4 to 1.16.5. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v1.16.4...v1.16.5) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: add support for time.Duration write and scan * test: add test case for setting and scanning durations * chore: fix linter * fix(extra/redisotel): set span.kind attribute to client According to the opentelemetry specification this should always be set to client for database client libraries. I've also removed the SetAttributes call and instead set the attributes during creation of the span. This is what the library SHOULD be doing according to the opentelemetry api specification. * chore: update otel example * fix: update some argument counts in pre-allocs In some cases number of pre-allocated places in argument array is missing 1 or 2 elements, which results in re-allocation of twice as large array * chore: add example how to delete keys without a ttl * chore: don't enable all lints * chore(deps): bump github.com/onsi/gomega from 1.16.0 to 1.17.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.16.0...v1.17.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * feat: Add redis v7's NX, XX, GT, LT expire variants * chore: add missing readme * chore: tweak feature links * chore: remove Discord * fix: set timeout for WAIT command. Fixes #1963 * build: update `go` directive in `go.mod` to 1.17 This commit enables support for module graph pruning and lazy module loading for projects that are at Go 1.17 or higher. Reference: https://go.dev/ref/mod#go-mod-file-go Reference: https://go.dev/ref/mod#graph-pruning Reference: https://go.dev/ref/mod#lazy-loading Signed-off-by: Eng Zer Jun <engzerjun@gmail.com> * chore: update link * chore: export cmder.SetFirstKeyPos to support build module commands * feat(redisotel): ability to override TracerProvider (#1998) * fix: add missing Expire methods to Cmdable This is a followup to https://github.com/go-redis/redis/pull/1928 * chore(deps): bump github.com/onsi/gomega from 1.17.0 to 1.18.1 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.17.0 to 1.18.1. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.17.0...v1.18.1) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update README.md (#2011) chore: add fmt library in example code * chore: instrumentation name and version (#2012) * fix: invalid type assert in stringArg * chore: cleanup * fix: example/otel compile error (#2028) * fix: rename Golang to Go (#2030) https://go.dev/doc/faq#go_or_golang * feat: add support for passing extra attributes added to spans * feat: set net.peer.name and net.peer.port in otel example * chore: tweak Uptrace copy * feat: add support for COPY command (#2016) * feat: add support for acl sentinel auth in universal client * chore(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore: add hll example * chore: tweak release script * chore: release v8.11.5 (release.sh) * chore: add discord back Co-authored-by: Eugene Ponizovsky <ponizovsky@gmail.com> Co-authored-by: Bogdan Drutu <bogdandrutu@gmail.com> Co-authored-by: Vladimir Mihailenco <vladimir.webdev@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kishan B <kishancs46@gmail.com> Co-authored-by: Dominik Menke <dom@digineo.de> Co-authored-by: Gökhan Özeloğlu <gozeloglu@gmail.com> Co-authored-by: Justin Sievenpiper <justin@sievenpiper.co> Co-authored-by: Алексей Романовский <aromanovsky@epiphan.com> Co-authored-by: Stavros Panakakakis <stavrospanakakis@gmail.com> Co-authored-by: Pau Freixes <pfreixes@gmail.com> Co-authored-by: Ethan Hur <ethan0311@gmail.com> Co-authored-by: Jackie <18378976+Pyrodash@users.noreply.github.com> Co-authored-by: Kristinn Björgvin Árdal <kristinnardalsecondary@gmail.com> Co-authored-by: ffenix113 <razerer@bigmir.net> Co-authored-by: Bastien Penavayre <bastienPenava@gmail.com> Co-authored-by: James3 Li(李麒傑) <james3_li@asus.com> Co-authored-by: Eng Zer Jun <engzerjun@gmail.com> Co-authored-by: gzjiangtao2014 <gzjiangtao2014@corp.netease.com> Co-authored-by: Nelz <nelz9999@users.noreply.github.com> Co-authored-by: Daniel Richter <Nexyz9@gmail.com> Co-authored-by: Seyed Ali Ghaffari <ali.ghaffari@outlook.com> Co-authored-by: lintanghui <lintanghui@bilibili.com> Co-authored-by: hidu <duv123+github@gmail.com> Co-authored-by: Jonas Lergell <jonas.lergell@volvocars.com> Co-authored-by: Alex Kahn <alexanderkahn@gmail.com>
2022-03-19 07:40:31 +03:00
func (cmd *MapStringStringCmd) Scan(dest interface{}) error {
if cmd.err != nil {
return cmd.err
}
2021-08-29 19:37:17 +03:00
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
}
2013-09-29 12:06:49 +04:00
cmd.val = make(map[string]string, n)
for i := 0; i < n; i++ {
key, err := rd.ReadString()
if err != nil {
return err
}
2018-08-02 14:48:46 +03:00
value, err := rd.ReadString()
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
cmd.val[key] = value
}
return nil
2018-08-02 14:48:46 +03:00
}
2013-09-29 12:06:49 +04:00
//------------------------------------------------------------------------------
2022-08-08 10:28:46 +03:00
type MapStringIntCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2015-01-25 15:05:19 +03:00
val map[string]int64
}
2022-08-08 10:28:46 +03:00
var _ Cmder = (*MapStringIntCmd)(nil)
2017-09-26 11:29:22 +03:00
2022-08-08 10:28:46 +03:00
func NewMapStringIntCmd(ctx context.Context, args ...interface{}) *MapStringIntCmd {
return &MapStringIntCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2015-01-25 15:05:19 +03:00
}
2022-08-08 10:28:46 +03:00
func (cmd *MapStringIntCmd) SetVal(val map[string]int64) {
cmd.val = val
}
2022-08-08 10:28:46 +03:00
func (cmd *MapStringIntCmd) Val() map[string]int64 {
2015-01-25 15:05:19 +03:00
return cmd.val
}
2022-08-08 10:28:46 +03:00
func (cmd *MapStringIntCmd) Result() (map[string]int64, error) {
2015-01-25 15:05:19 +03:00
return cmd.val, cmd.err
}
2022-08-08 10:28:46 +03:00
func (cmd *MapStringIntCmd) String() string {
2015-01-25 15:05:19 +03:00
return cmdString(cmd, cmd.val)
}
2022-08-08 10:28:46 +03:00
func (cmd *MapStringIntCmd) readReply(rd *proto.Reader) error {
n, err := rd.ReadMapLen()
if err != nil {
return err
}
2015-01-25 15:05:19 +03:00
cmd.val = make(map[string]int64, n)
for i := 0; i < n; i++ {
key, err := rd.ReadString()
if err != nil {
return err
}
2018-08-02 14:48:46 +03:00
nn, err := rd.ReadInt()
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
cmd.val[key] = nn
}
return nil
2018-08-02 14:48:46 +03:00
}
// ------------------------------------------------------------------------------
type MapStringSliceInterfaceCmd struct {
baseCmd
val map[string][]interface{}
}
func NewMapStringSliceInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringSliceInterfaceCmd {
return &MapStringSliceInterfaceCmd{
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
}
}
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) {
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
}
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
}
}
return nil
}
2015-01-25 15:05:19 +03:00
//------------------------------------------------------------------------------
2017-11-19 19:36:23 +03:00
type StringStructMapCmd struct {
baseCmd
val map[string]struct{}
}
var _ Cmder = (*StringStructMapCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd {
2017-11-19 19:36:23 +03:00
return &StringStructMapCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-11-19 19:36:23 +03:00
}
}
func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) {
cmd.val = val
}
2017-11-19 19:36:23 +03:00
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)
}
2018-08-17 13:56:37 +03:00
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
2018-08-02 14:48:46 +03:00
}
cmd.val[key] = struct{}{}
}
return nil
2017-11-25 05:06:13 +03:00
}
2018-08-02 14:48:46 +03:00
//------------------------------------------------------------------------------
2017-11-25 05:06:13 +03:00
type XMessage struct {
ID string
Values map[string]interface{}
}
2018-08-02 14:48:46 +03:00
type XMessageSliceCmd struct {
2017-11-25 05:06:13 +03:00
baseCmd
2018-08-02 14:48:46 +03:00
val []XMessage
2017-11-25 05:06:13 +03:00
}
2018-08-02 14:48:46 +03:00
var _ Cmder = (*XMessageSliceCmd)(nil)
2017-11-25 05:06:13 +03:00
2020-03-11 17:26:42 +03:00
func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSliceCmd {
2018-08-02 14:48:46 +03:00
return &XMessageSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-11-25 05:06:13 +03:00
}
}
func (cmd *XMessageSliceCmd) SetVal(val []XMessage) {
cmd.val = val
}
2018-08-02 14:48:46 +03:00
func (cmd *XMessageSliceCmd) Val() []XMessage {
2017-11-25 05:06:13 +03:00
return cmd.val
}
2018-08-02 14:48:46 +03:00
func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) {
2017-11-25 05:06:13 +03:00
return cmd.val, cmd.err
}
2018-08-02 14:48:46 +03:00
func (cmd *XMessageSliceCmd) String() string {
2017-11-25 05:06:13 +03:00
return cmdString(cmd, cmd.val)
}
func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) (err error) {
2020-09-24 09:06:17 +03:00
cmd.val, err = readXMessageSlice(rd)
return err
}
func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) {
n, err := rd.ReadArrayLen()
2020-09-17 12:27:16 +03:00
if err != nil {
2020-09-24 09:06:17 +03:00
return nil, err
2017-11-25 05:06:13 +03:00
}
2020-09-24 09:06:17 +03:00
msgs := make([]XMessage, n)
for i := 0; i < len(msgs); i++ {
if msgs[i], err = readXMessage(rd); err != nil {
2020-09-24 09:06:17 +03:00
return nil, err
}
}
return msgs, nil
2017-11-25 05:06:13 +03:00
}
2020-09-22 20:45:23 +03:00
func readXMessage(rd *proto.Reader) (XMessage, error) {
if err := rd.ReadFixedArrayLen(2); err != nil {
2020-09-22 20:45:23 +03:00
return XMessage{}, err
}
2018-08-02 14:48:46 +03:00
2020-09-22 20:45:23 +03:00
id, err := rd.ReadString()
if err != nil {
return XMessage{}, err
}
2018-08-02 14:48:46 +03:00
v, err := stringInterfaceMapParser(rd)
2020-09-22 20:45:23 +03:00
if err != nil {
if err != proto.Nil {
return XMessage{}, err
}
}
return XMessage{
ID: id,
Values: v,
2020-09-22 20:45:23 +03:00
}, 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++ {
2018-08-16 13:25:19 +03:00
key, err := rd.ReadString()
2018-08-02 14:48:46 +03:00
if err != nil {
return nil, err
}
2017-11-25 05:06:13 +03:00
2018-08-16 13:25:19 +03:00
value, err := rd.ReadString()
2018-08-02 14:48:46 +03:00
if err != nil {
return nil, err
}
2017-11-25 05:06:13 +03:00
2018-08-02 14:48:46 +03:00
m[key] = value
2017-11-25 05:06:13 +03:00
}
2018-08-02 14:48:46 +03:00
return m, nil
2017-11-25 05:06:13 +03:00
}
//------------------------------------------------------------------------------
2018-08-02 14:48:46 +03:00
type XStream struct {
Stream string
Messages []XMessage
}
type XStreamSliceCmd struct {
2017-11-25 05:06:13 +03:00
baseCmd
2018-08-02 14:48:46 +03:00
val []XStream
2017-11-25 05:06:13 +03:00
}
2018-08-02 14:48:46 +03:00
var _ Cmder = (*XStreamSliceCmd)(nil)
2017-11-25 05:06:13 +03:00
2020-03-11 17:26:42 +03:00
func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceCmd {
2018-08-02 14:48:46 +03:00
return &XStreamSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-11-25 05:06:13 +03:00
}
}
func (cmd *XStreamSliceCmd) SetVal(val []XStream) {
cmd.val = val
}
2018-08-02 14:48:46 +03:00
func (cmd *XStreamSliceCmd) Val() []XStream {
2017-11-25 05:06:13 +03:00
return cmd.val
}
2018-08-02 14:48:46 +03:00
func (cmd *XStreamSliceCmd) Result() ([]XStream, error) {
2017-11-25 05:06:13 +03:00
return cmd.val, cmd.err
}
2018-08-02 14:48:46 +03:00
func (cmd *XStreamSliceCmd) String() string {
2017-11-25 05:06:13 +03:00
return cmdString(cmd, cmd.val)
}
2018-08-17 13:56:37 +03:00
func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error {
typ, err := rd.PeekReplyType()
if err != nil {
return err
}
2018-08-02 14:48:46 +03:00
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
2018-08-02 14:48:46 +03:00
}
2017-11-25 05:06:13 +03:00
}
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
2017-11-25 05:06:13 +03:00
}
2018-08-02 14:48:46 +03:00
//------------------------------------------------------------------------------
type XPending struct {
Count int64
Lower string
Higher string
Consumers map[string]int64
}
type XPendingCmd struct {
baseCmd
val *XPending
}
var _ Cmder = (*XPendingCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd {
2018-08-02 14:48:46 +03:00
return &XPendingCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2018-08-02 14:48:46 +03:00
}
}
func (cmd *XPendingCmd) SetVal(val *XPending) {
cmd.val = val
}
2018-08-02 14:48:46 +03:00
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)
}
2018-08-17 13:56:37 +03:00
func (cmd *XPendingCmd) readReply(rd *proto.Reader) error {
var err error
if err = rd.ReadFixedArrayLen(4); err != nil {
return err
}
cmd.val = &XPending{}
2017-11-25 05:06:13 +03:00
if cmd.val.Count, err = rd.ReadInt(); err != nil {
return err
}
2017-11-25 05:06:13 +03:00
if cmd.val.Lower, err = rd.ReadString(); err != nil && err != Nil {
return err
}
2018-08-02 14:48:46 +03:00
if cmd.val.Higher, err = rd.ReadString(); err != nil && err != Nil {
return err
}
2018-08-02 14:48:46 +03:00
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
2019-06-26 14:45:38 +03:00
}
consumerName, err := rd.ReadString()
if err != nil {
return err
2017-11-25 05:06:13 +03:00
}
consumerPending, err := rd.ReadInt()
if err != nil {
return err
}
cmd.val.Consumers[consumerName] = consumerPending
}
return nil
2018-08-02 14:48:46 +03:00
}
//------------------------------------------------------------------------------
type XPendingExt struct {
2019-05-16 16:27:19 +03:00
ID string
2018-08-02 14:48:46 +03:00
Consumer string
Idle time.Duration
RetryCount int64
}
type XPendingExtCmd struct {
baseCmd
val []XPendingExt
}
var _ Cmder = (*XPendingExtCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd {
2018-08-02 14:48:46 +03:00
return &XPendingExtCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2018-08-02 14:48:46 +03:00
}
}
func (cmd *XPendingExtCmd) SetVal(val []XPendingExt) {
cmd.val = val
}
2018-08-02 14:48:46 +03:00
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)
}
2018-08-17 13:56:37 +03:00
func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error {
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
cmd.val = make([]XPendingExt, n)
2018-08-02 14:48:46 +03:00
for i := 0; i < len(cmd.val); i++ {
if err = rd.ReadFixedArrayLen(4); err != nil {
return err
}
2018-08-02 14:48:46 +03:00
if cmd.val[i].ID, err = rd.ReadString(); err != nil {
return err
}
2018-08-02 14:48:46 +03:00
if cmd.val[i].Consumer, err = rd.ReadString(); err != nil && err != Nil {
return err
}
2018-08-02 14:48:46 +03:00
idle, err := rd.ReadInt()
if err != nil && err != Nil {
return err
}
cmd.val[i].Idle = time.Duration(idle) * time.Millisecond
2018-08-02 14:48:46 +03:00
if cmd.val[i].RetryCount, err = rd.ReadInt(); err != nil && err != Nil {
return err
2017-11-25 05:06:13 +03:00
}
}
return nil
2017-11-25 05:06:13 +03:00
}
//------------------------------------------------------------------------------
2021-06-04 18:04:36 +03:00
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,
},
}
}
func (cmd *XAutoClaimCmd) SetVal(val []XMessage, start string) {
cmd.val = val
cmd.start = start
}
2021-06-04 18:04:36 +03:00
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 {
2022-06-04 17:25:12 +03:00
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
2021-06-04 18:04:36 +03:00
2022-06-04 17:25:12 +03:00
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
}
2022-06-04 17:25:12 +03:00
cmd.val, err = readXMessageSlice(rd)
if err != nil {
return err
}
2022-06-04 17:25:12 +03:00
if n >= 3 {
if err := rd.DiscardNext(); err != nil {
return err
}
}
return nil
2021-06-04 18:04:36 +03:00
}
//------------------------------------------------------------------------------
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,
},
}
}
func (cmd *XAutoClaimJustIDCmd) SetVal(val []string, start string) {
cmd.val = val
cmd.start = start
}
2021-06-04 18:04:36 +03:00
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 {
2022-06-04 17:25:12 +03:00
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
2021-06-04 18:04:36 +03:00
2022-06-04 17:25:12 +03:00
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
}
2022-06-04 17:25:12 +03:00
nn, err := rd.ReadArrayLen()
if err != nil {
return err
}
2021-06-04 18:04:36 +03:00
2022-06-04 17:25:12 +03:00
cmd.val = make([]string, nn)
for i := 0; i < nn; i++ {
cmd.val[i], err = rd.ReadString()
2021-06-04 18:04:36 +03:00
if err != nil {
return err
2021-06-04 18:04:36 +03:00
}
}
2022-06-04 17:25:12 +03:00
if n >= 3 {
if err := rd.DiscardNext(); err != nil {
return err
}
}
return nil
2021-06-04 18:04:36 +03:00
}
//------------------------------------------------------------------------------
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},
},
}
}
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
}
//------------------------------------------------------------------------------
type XInfoGroupsCmd struct {
baseCmd
2020-09-23 11:00:34 +03:00
val []XInfoGroup
}
2020-09-23 11:00:34 +03:00
type XInfoGroup struct {
Name string
Consumers int64
Pending int64
LastDeliveredID string
2022-06-04 17:25:12 +03:00
EntriesRead int64
Lag int64
}
var _ Cmder = (*XInfoGroupsCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd {
return &XInfoGroupsCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: []interface{}{"xinfo", "groups", stream},
},
}
}
func (cmd *XInfoGroupsCmd) SetVal(val []XInfoGroup) {
cmd.val = val
}
2020-09-23 11:00:34 +03:00
func (cmd *XInfoGroupsCmd) Val() []XInfoGroup {
return cmd.val
}
2020-09-23 11:00:34 +03:00
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 {
2020-09-23 11:00:34 +03:00
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
cmd.val = make([]XInfoGroup, n)
for i := 0; i < len(cmd.val); i++ {
2022-06-04 17:25:12 +03:00
group := &cmd.val[i]
nn, err := rd.ReadMapLen()
if err != nil {
2020-09-23 11:00:34 +03:00
return err
2020-09-17 12:27:16 +03:00
}
2020-09-23 11:00:34 +03:00
var key string
2022-06-04 17:25:12 +03:00
for j := 0; j < nn; j++ {
key, err = rd.ReadString()
2020-09-23 11:00:34 +03:00
if err != nil {
return err
}
switch key {
case "name":
2022-06-04 17:25:12 +03:00
group.Name, err = rd.ReadString()
if err != nil {
return err
}
case "consumers":
2022-06-04 17:25:12 +03:00
group.Consumers, err = rd.ReadInt()
if err != nil {
return err
}
case "pending":
2022-06-04 17:25:12 +03:00
group.Pending, err = rd.ReadInt()
if err != nil {
return err
}
case "last-delivered-id":
2022-06-04 17:25:12 +03:00
group.LastDeliveredID, err = rd.ReadString()
if err != nil {
return err
}
case "entries-read":
group.EntriesRead, err = rd.ReadInt()
if err != nil && err != Nil {
2022-06-04 17:25:12 +03:00
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.
if err != nil && err != Nil {
2022-06-04 17:25:12 +03:00
return err
}
default:
2022-06-04 17:25:12 +03:00
return fmt.Errorf("redis: unexpected key %q in XINFO GROUPS reply", key)
2020-09-23 11:00:34 +03:00
}
}
}
2020-09-23 11:00:34 +03:00
return nil
}
//------------------------------------------------------------------------------
2020-09-22 20:45:23 +03:00
type XInfoStreamCmd struct {
baseCmd
val *XInfoStream
}
type XInfoStream struct {
2022-06-04 17:25:12 +03:00
Length int64
RadixTreeKeys int64
RadixTreeNodes int64
Groups int64
LastGeneratedID string
MaxDeletedEntryID string
EntriesAdded int64
FirstEntry XMessage
LastEntry XMessage
RecordedFirstEntryID string
2020-09-22 20:45:23 +03:00
}
var _ Cmder = (*XInfoStreamCmd)(nil)
func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd {
return &XInfoStreamCmd{
baseCmd: baseCmd{
ctx: ctx,
args: []interface{}{"xinfo", "stream", stream},
},
}
}
func (cmd *XInfoStreamCmd) SetVal(val *XInfoStream) {
cmd.val = val
}
2020-09-22 20:45:23 +03:00
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 {
2022-06-04 17:25:12 +03:00
n, err := rd.ReadMapLen()
if err != nil {
2020-09-22 20:45:23 +03:00
return err
}
cmd.val = &XInfoStream{}
2020-09-22 20:45:23 +03:00
2022-06-04 17:25:12 +03:00
for i := 0; i < n; i++ {
2020-09-22 20:45:23 +03:00
key, err := rd.ReadString()
if err != nil {
return err
2020-09-22 20:45:23 +03:00
}
switch key {
case "length":
cmd.val.Length, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
2020-09-22 20:45:23 +03:00
case "radix-tree-keys":
cmd.val.RadixTreeKeys, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
2020-09-22 20:45:23 +03:00
case "radix-tree-nodes":
cmd.val.RadixTreeNodes, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
2020-09-22 20:45:23 +03:00
case "groups":
cmd.val.Groups, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
2020-09-22 20:45:23 +03:00
case "last-generated-id":
cmd.val.LastGeneratedID, err = rd.ReadString()
2022-06-04 17:25:12 +03:00
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
}
2020-09-22 20:45:23 +03:00
case "first-entry":
cmd.val.FirstEntry, err = readXMessage(rd)
2022-06-04 17:25:12 +03:00
if err != nil && err != Nil {
return err
2021-05-19 11:52:13 +03:00
}
2020-09-22 20:45:23 +03:00
case "last-entry":
cmd.val.LastEntry, err = readXMessage(rd)
2022-06-04 17:25:12 +03:00
if err != nil && err != Nil {
return err
}
case "recorded-first-entry-id":
cmd.val.RecordedFirstEntryID, err = rd.ReadString()
if err != nil {
return err
2021-05-19 11:52:13 +03:00
}
2020-09-22 20:45:23 +03:00
default:
2022-06-04 17:25:12 +03:00
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM reply", key)
2020-09-22 20:45:23 +03:00
}
}
return nil
2020-09-22 20:45:23 +03:00
}
//------------------------------------------------------------------------------
type XInfoStreamFullCmd struct {
baseCmd
val *XInfoStreamFull
}
type XInfoStreamFull struct {
2022-06-04 17:25:12 +03:00
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
2022-06-04 17:25:12 +03:00
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,
},
}
}
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 {
2022-06-04 17:25:12 +03:00
n, err := rd.ReadMapLen()
if err != nil {
return err
}
cmd.val = &XInfoStreamFull{}
2022-06-04 17:25:12 +03:00
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()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
case "radix-tree-keys":
cmd.val.RadixTreeKeys, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
case "radix-tree-nodes":
cmd.val.RadixTreeNodes, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
case "last-generated-id":
cmd.val.LastGeneratedID, err = rd.ReadString()
2022-06-04 17:25:12 +03:00
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)
2022-06-04 17:25:12 +03:00
if err != nil {
return err
}
case "groups":
cmd.val.Groups, err = readStreamGroups(rd)
2022-06-04 17:25:12 +03:00
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:
2022-06-04 17:25:12 +03:00
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++ {
2022-06-04 17:25:12 +03:00
nn, err := rd.ReadMapLen()
if err != nil {
return nil, err
}
group := XInfoStreamGroup{}
2022-06-04 17:25:12 +03:00
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()
2022-06-04 17:25:12 +03:00
if err != nil {
return nil, err
}
case "last-delivered-id":
group.LastDeliveredID, err = rd.ReadString()
2022-06-04 17:25:12 +03:00
if err != nil {
return nil, err
}
case "entries-read":
group.EntriesRead, err = rd.ReadInt()
if err != nil && err != Nil {
2022-06-04 17:25:12 +03:00
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.
2022-06-04 17:25:12 +03:00
group.Lag, err = rd.ReadInt()
if err != nil && err != Nil {
2022-06-04 17:25:12 +03:00
return nil, err
}
case "pel-count":
group.PelCount, err = rd.ReadInt()
2022-06-04 17:25:12 +03:00
if err != nil {
return nil, err
}
case "pending":
group.Pending, err = readXInfoStreamGroupPending(rd)
2022-06-04 17:25:12 +03:00
if err != nil {
return nil, err
}
case "consumers":
group.Consumers, err = readXInfoStreamConsumers(rd)
2022-06-04 17:25:12 +03:00
if err != nil {
return nil, err
}
default:
2022-06-04 17:25:12 +03:00
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)
2021-05-19 11:52:13 +03:00
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
}
//------------------------------------------------------------------------------
2014-07-05 14:46:27 +04:00
type ZSliceCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
2014-07-05 14:46:27 +04:00
val []Z
2013-09-29 12:06:49 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*ZSliceCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd {
2017-01-13 14:39:59 +03:00
return &ZSliceCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2013-09-29 12:06:49 +04:00
}
func (cmd *ZSliceCmd) SetVal(val []Z) {
cmd.val = val
}
2014-07-05 14:46:27 +04:00
func (cmd *ZSliceCmd) Val() []Z {
2014-01-09 12:17:38 +04:00
return cmd.val
}
2014-07-05 14:46:27 +04:00
func (cmd *ZSliceCmd) Result() ([]Z, error) {
2014-01-09 12:17:38 +04:00
return cmd.val, cmd.err
}
2014-07-05 14:46:27 +04:00
func (cmd *ZSliceCmd) String() string {
2014-01-09 12:17:38 +04:00
return cmdString(cmd, cmd.val)
}
func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
2018-08-02 14:48:46 +03:00
// If the n is 0, can't continue reading.
if n == 0 {
cmd.val = make([]Z, 0)
return nil
}
2018-08-02 14:48:46 +03:00
typ, err := rd.PeekReplyType()
if err != nil {
return err
}
array := typ == proto.RespArray
if array {
cmd.val = make([]Z, n)
} else {
2019-06-26 14:45:38 +03:00
cmd.val = make([]Z, n/2)
}
2018-08-02 14:48:46 +03:00
for i := 0; i < len(cmd.val); i++ {
if array {
if err = rd.ReadFixedArrayLen(2); err != nil {
return err
2019-06-26 14:45:38 +03:00
}
}
2018-08-02 14:48:46 +03:00
if cmd.val[i].Member, err = rd.ReadString(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
if cmd.val[i].Score, err = rd.ReadFloat(); err != nil {
return err
}
}
return nil
2018-08-02 14:48:46 +03:00
}
2014-01-09 12:17:38 +04:00
//------------------------------------------------------------------------------
2018-10-31 16:35:23 +03:00
type ZWithKeyCmd struct {
baseCmd
2019-05-16 16:27:19 +03:00
val *ZWithKey
2018-10-31 16:35:23 +03:00
}
var _ Cmder = (*ZWithKeyCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd {
2018-10-31 16:35:23 +03:00
return &ZWithKeyCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2018-10-31 16:35:23 +03:00
}
}
func (cmd *ZWithKeyCmd) SetVal(val *ZWithKey) {
cmd.val = val
}
2019-05-16 16:27:19 +03:00
func (cmd *ZWithKeyCmd) Val() *ZWithKey {
2018-10-31 16:35:23 +03:00
return cmd.val
}
2019-05-16 16:27:19 +03:00
func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) {
return cmd.val, cmd.err
2018-10-31 16:35:23 +03:00
}
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{}
2019-05-16 16:27:19 +03:00
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
}
2019-06-26 14:45:38 +03:00
return nil
2018-10-31 16:35:23 +03:00
}
//------------------------------------------------------------------------------
2014-01-09 12:17:38 +04:00
type ScanCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
2014-01-09 12:17:38 +04:00
2016-04-13 11:52:47 +03:00
page []string
cursor uint64
2017-01-03 13:44:06 +03:00
2020-03-11 17:26:42 +03:00
process cmdable
2014-01-09 12:17:38 +04:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*ScanCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *ScanCmd {
2016-04-13 11:52:47 +03:00
return &ScanCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-03 13:44:06 +03:00
process: process,
2016-04-13 11:52:47 +03:00
}
2014-01-09 12:17:38 +04:00
}
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
2014-01-09 12:17:38 +04:00
}
func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) {
return cmd.page, cmd.cursor, cmd.err
2014-01-09 12:17:38 +04:00
}
func (cmd *ScanCmd) String() string {
2016-04-13 11:52:47 +03:00
return cmdString(cmd, cmd.page)
2014-01-09 12:17:38 +04:00
}
func (cmd *ScanCmd) readReply(rd *proto.Reader) error {
if err := rd.ReadFixedArrayLen(2); err != nil {
return err
}
2023-01-20 14:09:00 +03:00
cursor, err := rd.ReadUint()
if err != nil {
return err
}
2023-01-20 14:09:00 +03:00
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
2013-09-29 12:06:49 +04:00
}
2015-01-24 15:12:48 +03:00
2017-01-03 13:44:06 +03:00
// Iterator creates a new ScanIterator.
func (cmd *ScanCmd) Iterator() *ScanIterator {
return &ScanIterator{
cmd: cmd,
}
}
2015-01-24 15:12:48 +03:00
//------------------------------------------------------------------------------
type ClusterNode struct {
ID string
Addr string
NetworkingMetadata map[string]string
}
type ClusterSlot struct {
2015-10-07 17:09:20 +03:00
Start int
End int
Nodes []ClusterNode
2015-01-24 15:12:48 +03:00
}
type ClusterSlotsCmd struct {
2015-01-24 15:12:48 +03:00
baseCmd
val []ClusterSlot
2015-01-24 15:12:48 +03:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*ClusterSlotsCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsCmd {
2017-01-13 14:39:59 +03:00
return &ClusterSlotsCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2015-01-24 15:12:48 +03:00
}
func (cmd *ClusterSlotsCmd) SetVal(val []ClusterSlot) {
cmd.val = val
}
func (cmd *ClusterSlotsCmd) Val() []ClusterSlot {
2015-01-24 15:12:48 +03:00
return cmd.val
}
func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) {
return cmd.val, cmd.err
2015-01-24 15:12:48 +03:00
}
func (cmd *ClusterSlotsCmd) String() string {
2015-01-24 15:12:48 +03:00
return cmdString(cmd, cmd.val)
}
2018-08-17 13:56:37 +03:00
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)
2022-06-04 15:02:53 +03:00
for j := 0; j < len(nodes); j++ {
nn, err := rd.ReadArrayLen()
2018-08-02 14:48:46 +03:00
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
2022-06-04 15:02:53 +03:00
if nn < 2 || nn > 4 {
return fmt.Errorf("got %d elements in cluster info address, expected 2, 3, or 4", n)
2018-08-02 14:48:46 +03:00
}
ip, err := rd.ReadString()
2018-08-02 14:48:46 +03:00
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
port, err := rd.ReadString()
2018-08-02 14:48:46 +03:00
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
2018-08-16 13:25:19 +03:00
nodes[j].Addr = net.JoinHostPort(ip, port)
2018-08-02 14:48:46 +03:00
2022-06-04 15:02:53 +03:00
if nn >= 3 {
id, err := rd.ReadString()
2018-08-02 14:48:46 +03:00
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
nodes[j].ID = id
2019-06-26 14:45:38 +03:00
}
2022-06-04 15:02:53 +03:00
if nn >= 4 {
2022-06-04 17:25:12 +03:00
metadataLength, err := rd.ReadMapLen()
2022-06-04 15:02:53 +03:00
if err != nil {
return err
}
networkingMetadata := make(map[string]string, metadataLength)
2022-06-04 15:02:53 +03:00
for i := 0; i < metadataLength; i++ {
2022-06-04 15:02:53 +03:00
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
}
2018-08-02 14:48:46 +03:00
}
2022-06-04 15:02:53 +03:00
cmd.val[i] = ClusterSlot{
Start: int(start),
End: int(end),
Nodes: nodes,
}
}
return nil
2018-08-02 14:48:46 +03:00
}
//------------------------------------------------------------------------------
2015-10-07 17:09:20 +03:00
// GeoLocation is used with GeoAdd to add geospatial location.
type GeoLocation struct {
2015-11-14 17:36:21 +03:00
Name string
Longitude, Latitude, Dist float64
GeoHash int64
}
2015-10-07 17:09:20 +03:00
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
type GeoRadiusQuery struct {
Radius float64
2015-10-07 17:09:20 +03:00
// Can be m, km, ft, or mi. Default is km.
Unit string
WithCoord bool
WithDist bool
WithGeoHash bool
Count int
2015-10-07 17:09:20 +03:00
// Can be ASC or DESC. Default is no sort order.
2017-07-19 15:32:50 +03:00
Sort string
Store string
StoreDist string
// WithCoord+WithDist+WithGeoHash
withLen int
2015-10-07 17:09:20 +03:00
}
type GeoLocationCmd struct {
baseCmd
2015-11-14 17:36:21 +03:00
q *GeoRadiusQuery
locations []GeoLocation
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*GeoLocationCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewGeoLocationCmd(ctx context.Context, q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd {
return &GeoLocationCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: geoLocationArgs(q, args...),
},
q: q,
}
}
func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} {
2015-11-14 17:36:21 +03:00
args = append(args, q.Radius)
if q.Unit != "" {
args = append(args, q.Unit)
} else {
args = append(args, "km")
}
if q.WithCoord {
2017-07-19 15:32:50 +03:00
args = append(args, "withcoord")
q.withLen++
2015-11-14 17:36:21 +03:00
}
if q.WithDist {
2017-07-19 15:32:50 +03:00
args = append(args, "withdist")
q.withLen++
2015-11-14 17:36:21 +03:00
}
if q.WithGeoHash {
2017-07-19 15:32:50 +03:00
args = append(args, "withhash")
q.withLen++
2015-11-14 17:36:21 +03:00
}
if q.Count > 0 {
2017-07-19 15:32:50 +03:00
args = append(args, "count", q.Count)
2015-11-14 17:36:21 +03:00
}
if q.Sort != "" {
args = append(args, q.Sort)
}
2017-07-19 15:32:50 +03:00
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
}
2015-10-07 17:09:20 +03:00
func (cmd *GeoLocationCmd) Val() []GeoLocation {
return cmd.locations
}
2015-10-07 17:09:20 +03:00
func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) {
return cmd.locations, cmd.err
}
2015-10-07 17:09:20 +03:00
func (cmd *GeoLocationCmd) String() string {
return cmdString(cmd, cmd.locations)
}
2018-08-17 13:56:37 +03:00
func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error {
n, err := rd.ReadArrayLen()
2020-09-17 12:27:16 +03:00
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
2019-06-26 14:45:38 +03:00
}
continue
2019-06-26 14:45:38 +03:00
}
// +name
if err = rd.ReadFixedArrayLen(cmd.q.withLen + 1); err != nil {
return err
}
2018-08-02 14:48:46 +03:00
if cmd.locations[i].Name, err = rd.ReadString(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
if cmd.q.WithDist {
if cmd.locations[i].Dist, err = rd.ReadFloat(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
}
if cmd.q.WithGeoHash {
if cmd.locations[i].GeoHash, err = rd.ReadInt(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
}
if cmd.q.WithCoord {
if err = rd.ReadFixedArrayLen(2); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
if cmd.locations[i].Longitude, err = rd.ReadFloat(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
if cmd.locations[i].Latitude, err = rd.ReadFloat(); err != nil {
return err
2018-08-02 14:48:46 +03:00
}
}
}
return nil
2018-08-02 14:48:46 +03:00
}
//------------------------------------------------------------------------------
// 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: args,
},
opt: opt,
}
}
2022-08-08 07:06:04 +03:00
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
}
//------------------------------------------------------------------------------
type GeoPos struct {
Longitude, Latitude float64
}
2016-08-22 00:32:06 +03:00
type GeoPosCmd struct {
baseCmd
2019-06-26 14:45:38 +03:00
val []*GeoPos
2016-08-22 00:32:06 +03:00
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*GeoPosCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd {
2017-01-13 14:39:59 +03:00
return &GeoPosCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
2016-08-22 00:32:06 +03:00
}
func (cmd *GeoPosCmd) SetVal(val []*GeoPos) {
cmd.val = val
}
func (cmd *GeoPosCmd) Val() []*GeoPos {
2019-06-26 14:45:38 +03:00
return cmd.val
2016-08-22 00:32:06 +03:00
}
func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) {
return cmd.val, cmd.err
2016-08-22 00:32:06 +03:00
}
func (cmd *GeoPosCmd) String() string {
2019-06-26 14:45:38 +03:00
return cmdString(cmd, cmd.val)
2016-08-22 00:32:06 +03:00
}
2018-08-17 13:56:37 +03:00
func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error {
n, err := rd.ReadArrayLen()
if err != nil {
return err
}
cmd.val = make([]*GeoPos, n)
2019-05-16 16:27:19 +03:00
for i := 0; i < len(cmd.val); i++ {
err = rd.ReadFixedArrayLen(2)
if err != nil {
if err == Nil {
cmd.val[i] = nil
continue
2019-05-16 16:27:19 +03:00
}
return err
2018-08-02 14:48:46 +03:00
}
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
2018-08-02 14:48:46 +03:00
}
//------------------------------------------------------------------------------
type CommandInfo struct {
Name string
Arity int8
Flags []string
ACLFlags []string
FirstKeyPos int8
LastKeyPos int8
StepCount int8
ReadOnly bool
}
type CommandsInfoCmd struct {
baseCmd
val map[string]*CommandInfo
}
2017-09-26 11:29:22 +03:00
var _ Cmder = (*CommandsInfoCmd)(nil)
2020-03-11 17:26:42 +03:00
func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoCmd {
2017-01-13 14:39:59 +03:00
return &CommandsInfoCmd{
2020-03-11 17:26:42 +03:00
baseCmd: baseCmd{
ctx: ctx,
args: args,
},
2017-01-13 14:39:59 +03:00
}
}
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)
}
2018-08-17 13:56:37 +03:00
func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
2020-06-05 09:11:12 +03:00
const numArgRedis5 = 6
const numArgRedis6 = 7
2022-06-04 16:07:28 +03:00
const numArgRedis7 = 10
2020-06-05 09:11:12 +03:00
n, err := rd.ReadArrayLen()
if err != nil {
return err
2018-08-02 14:48:46 +03:00
}
cmd.val = make(map[string]*CommandInfo, n)
2018-08-02 14:48:46 +03:00
for i := 0; i < n; i++ {
nn, err := rd.ReadArrayLen()
if err != nil {
return err
}
2022-06-04 16:07:28 +03:00
switch nn {
case numArgRedis5, numArgRedis6, numArgRedis7:
// ok
default:
return fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6/7/10", nn)
}
2019-06-26 14:45:38 +03:00
cmdInfo := &CommandInfo{}
if cmdInfo.Name, err = rd.ReadString(); err != nil {
return err
}
2018-08-02 14:48:46 +03:00
arity, err := rd.ReadInt()
if err != nil {
return err
}
cmdInfo.Arity = int8(arity)
2018-08-02 14:48:46 +03:00
flagLen, err := rd.ReadArrayLen()
if err != nil {
return err
}
cmdInfo.Flags = make([]string, flagLen)
for f := 0; f < len(cmdInfo.Flags); f++ {
2019-06-26 14:45:38 +03:00
switch s, err := rd.ReadString(); {
case err == Nil:
cmdInfo.Flags[f] = ""
2019-06-26 14:45:38 +03:00
case err != nil:
return err
2019-06-26 14:45:38 +03:00
default:
if !cmdInfo.ReadOnly && s == "readonly" {
cmdInfo.ReadOnly = true
}
cmdInfo.Flags[f] = s
2019-06-26 14:45:38 +03:00
}
}
2018-08-02 14:48:46 +03:00
firstKeyPos, err := rd.ReadInt()
if err != nil {
return err
}
cmdInfo.FirstKeyPos = int8(firstKeyPos)
2018-08-02 14:48:46 +03:00
lastKeyPos, err := rd.ReadInt()
if err != nil {
return err
2020-06-05 09:11:12 +03:00
}
cmdInfo.LastKeyPos = int8(lastKeyPos)
2020-06-05 09:11:12 +03:00
stepCount, err := rd.ReadInt()
if err != nil {
return err
}
cmdInfo.StepCount = int8(stepCount)
2020-06-05 09:11:12 +03:00
2022-06-04 16:07:28 +03:00
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
}
}
}
2022-06-04 16:07:28 +03:00
if nn >= numArgRedis7 {
if err := rd.DiscardNext(); err != nil {
return err
}
if err := rd.DiscardNext(); err != nil {
return err
}
if err := rd.DiscardNext(); err != nil {
return err
}
}
cmd.val[cmdInfo.Name] = cmdInfo
}
return nil
2018-08-02 14:48:46 +03:00
}
2018-03-06 15:50:48 +03:00
//------------------------------------------------------------------------------
type cmdsInfoCache struct {
fn func(ctx context.Context) (map[string]*CommandInfo, error)
2018-05-17 15:21:51 +03:00
2018-03-06 15:50:48 +03:00
once internal.Once
cmds map[string]*CommandInfo
}
func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, error)) *cmdsInfoCache {
2018-05-17 15:21:51 +03:00
return &cmdsInfoCache{
fn: fn,
}
2018-03-06 15:50:48 +03:00
}
func (c *cmdsInfoCache) Get(ctx context.Context) (map[string]*CommandInfo, error) {
2018-03-06 15:50:48 +03:00
err := c.once.Do(func() error {
cmds, err := c.fn(ctx)
2018-03-06 15:50:48 +03:00
if err != nil {
return err
}
2019-11-19 13:37:26 +03:00
// Extensions have cmd names in upper case. Convert them to lower case.
for k, v := range cmds {
lower := internal.ToLower(k)
if lower != k {
cmds[lower] = v
}
}
2019-11-19 13:37:26 +03:00
2018-03-06 15:50:48 +03:00
c.cmds = cmds
return nil
})
return c.cmds, err
}
2020-06-11 10:24:04 +03:00
//------------------------------------------------------------------------------
type SlowLog struct {
2020-09-09 12:49:45 +03:00
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
2020-06-11 10:24:04 +03:00
}
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,
},
}
}
func (cmd *SlowLogCmd) SetVal(val []SlowLog) {
cmd.val = val
}
2020-06-11 10:24:04 +03:00
func (cmd *SlowLogCmd) Val() []SlowLog {
return cmd.val
}
func (cmd *SlowLogCmd) Result() ([]SlowLog, error) {
return cmd.val, cmd.err
2020-06-11 10:24:04 +03:00
}
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)
}
2020-06-11 10:24:04 +03:00
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
2020-06-11 10:24:04 +03:00
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()
2020-06-11 10:24:04 +03:00
if err != nil {
return err
2020-06-11 10:24:04 +03:00
}
}
2020-06-11 10:24:04 +03:00
if nn >= 5 {
if cmd.val[i].ClientAddr, err = rd.ReadString(); err != nil {
return err
2020-06-11 10:24:04 +03:00
}
}
2020-09-09 12:49:45 +03:00
if nn >= 6 {
if cmd.val[i].ClientName, err = rd.ReadString(); err != nil {
return err
2020-06-11 10:24:04 +03:00
}
}
}
2020-09-09 12:49:45 +03:00
return nil
}
//-----------------------------------------------------------------------
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,
},
}
}
2022-08-08 07:06:04 +03:00
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
2020-06-11 10:24:04 +03:00
}
if err, ok := err.(proto.RedisError); ok {
cmd.val[k] = err
continue
2020-06-11 10:24:04 +03:00
}
return err
}
cmd.val[k] = v
}
return nil
}
2020-09-09 12:49:45 +03:00
//-----------------------------------------------------------------------
2020-09-09 12:49:45 +03:00
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,
},
}
}
2022-08-08 07:06:04 +03:00
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
2020-06-11 10:24:04 +03:00
}
2020-09-09 12:49:45 +03:00
v, err := rd.ReadString()
if err != nil {
return err
2020-06-11 10:24:04 +03:00
}
cmd.val[i][k] = v
2020-06-11 10:24:04 +03:00
}
}
return nil
2020-06-11 10:24:04 +03:00
}
//-----------------------------------------------------------------------
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,
},
}
}
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
}
//------------------------------------------------------------------------------
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,
},
}
}
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
}
//------------------------------------------------------------------------------
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,
},
}
}
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
}
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,
},
}
}
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
}
// 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,
},
}
}
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
}
//------------------------------------------------------------------------------
// 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,
}
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
}
// ------------------------------------------------------------------------
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,
},
}
}
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
}
// ---------------------------------------------------------------------------------------------------
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,
},
}
}
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
}
// ------------------------------------------------------------------------------------------------------------------
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,
},
}
}
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
}
// -----------------------------------------
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,
},
}
}
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
}
// --------------------------------------------------------------------------------------------------
// 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
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
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,
},
}
}
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 "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 "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
default:
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
}
if err != nil {
return nil, err
}
}
return info, nil
}
// -------------------------------------------
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,
},
}
}
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
}
// 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,
},
}
}
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))
moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`)
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" {
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]
}
}
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"},
},
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
cmd.mu.Unlock()
if pk, _ := rd.Peek(1); len(pk) != 0 && st == monitorStatusStart {
line, err := rd.ReadString()
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
}