mirror of
https://github.com/redis/go-redis.git
synced 2025-04-16 09:23:06 +03:00
Redis 8.0 introduces new key `io-thread` in the response for client info. The key needs to be parsed. If an unknown key is observed, the client will return an error.
5599 lines
113 KiB
Go
5599 lines
113 KiB
Go
package redis
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9/internal"
|
|
"github.com/redis/go-redis/v9/internal/hscan"
|
|
"github.com/redis/go-redis/v9/internal/proto"
|
|
"github.com/redis/go-redis/v9/internal/util"
|
|
)
|
|
|
|
type Cmder interface {
|
|
// command name.
|
|
// e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster".
|
|
Name() string
|
|
|
|
// full command name.
|
|
// e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster info".
|
|
FullName() string
|
|
|
|
// all args of the command.
|
|
// e.g. "set k v ex 10" -> "[set k v ex 10]".
|
|
Args() []interface{}
|
|
|
|
// format request and response string.
|
|
// e.g. "set k v ex 10" -> "set k v ex 10: OK", "get k" -> "get k: v".
|
|
String() string
|
|
|
|
stringArg(int) string
|
|
firstKeyPos() int8
|
|
SetFirstKeyPos(int8)
|
|
|
|
readTimeout() *time.Duration
|
|
readReply(rd *proto.Reader) error
|
|
readRawReply(rd *proto.Reader) error
|
|
SetErr(error)
|
|
Err() error
|
|
}
|
|
|
|
func setCmdsErr(cmds []Cmder, e error) {
|
|
for _, cmd := range cmds {
|
|
if cmd.Err() == nil {
|
|
cmd.SetErr(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdsFirstErr(cmds []Cmder) error {
|
|
for _, cmd := range cmds {
|
|
if err := cmd.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeCmds(wr *proto.Writer, cmds []Cmder) error {
|
|
for _, cmd := range cmds {
|
|
if err := writeCmd(wr, cmd); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeCmd(wr *proto.Writer, cmd Cmder) error {
|
|
return wr.WriteArgs(cmd.Args())
|
|
}
|
|
|
|
func cmdFirstKeyPos(cmd Cmder) int {
|
|
if pos := cmd.firstKeyPos(); pos != 0 {
|
|
return int(pos)
|
|
}
|
|
|
|
switch cmd.Name() {
|
|
case "eval", "evalsha", "eval_ro", "evalsha_ro":
|
|
if cmd.stringArg(2) != "0" {
|
|
return 3
|
|
}
|
|
|
|
return 0
|
|
case "publish":
|
|
return 1
|
|
case "memory":
|
|
// https://github.com/redis/redis/issues/7493
|
|
if cmd.stringArg(1) == "usage" {
|
|
return 2
|
|
}
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func cmdString(cmd Cmder, val interface{}) string {
|
|
b := make([]byte, 0, 64)
|
|
|
|
for i, arg := range cmd.Args() {
|
|
if i > 0 {
|
|
b = append(b, ' ')
|
|
}
|
|
b = internal.AppendArg(b, arg)
|
|
}
|
|
|
|
if err := cmd.Err(); err != nil {
|
|
b = append(b, ": "...)
|
|
b = append(b, err.Error()...)
|
|
} else if val != nil {
|
|
b = append(b, ": "...)
|
|
b = internal.AppendArg(b, val)
|
|
}
|
|
|
|
return util.BytesToString(b)
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type baseCmd struct {
|
|
ctx context.Context
|
|
args []interface{}
|
|
err error
|
|
keyPos int8
|
|
rawVal interface{}
|
|
_readTimeout *time.Duration
|
|
}
|
|
|
|
var _ Cmder = (*Cmd)(nil)
|
|
|
|
func (cmd *baseCmd) Name() string {
|
|
if len(cmd.args) == 0 {
|
|
return ""
|
|
}
|
|
// Cmd name must be lower cased.
|
|
return internal.ToLower(cmd.stringArg(0))
|
|
}
|
|
|
|
func (cmd *baseCmd) FullName() string {
|
|
switch name := cmd.Name(); name {
|
|
case "cluster", "command":
|
|
if len(cmd.args) == 1 {
|
|
return name
|
|
}
|
|
if s2, ok := cmd.args[1].(string); ok {
|
|
return name + " " + s2
|
|
}
|
|
return name
|
|
default:
|
|
return name
|
|
}
|
|
}
|
|
|
|
func (cmd *baseCmd) Args() []interface{} {
|
|
return cmd.args
|
|
}
|
|
|
|
func (cmd *baseCmd) stringArg(pos int) string {
|
|
if pos < 0 || pos >= len(cmd.args) {
|
|
return ""
|
|
}
|
|
arg := cmd.args[pos]
|
|
switch v := arg.(type) {
|
|
case string:
|
|
return v
|
|
case []byte:
|
|
return string(v)
|
|
default:
|
|
// TODO: consider using appendArg
|
|
return fmt.Sprint(v)
|
|
}
|
|
}
|
|
|
|
func (cmd *baseCmd) firstKeyPos() int8 {
|
|
return cmd.keyPos
|
|
}
|
|
|
|
func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) {
|
|
cmd.keyPos = keyPos
|
|
}
|
|
|
|
func (cmd *baseCmd) SetErr(e error) {
|
|
cmd.err = e
|
|
}
|
|
|
|
func (cmd *baseCmd) Err() error {
|
|
return cmd.err
|
|
}
|
|
|
|
func (cmd *baseCmd) readTimeout() *time.Duration {
|
|
return cmd._readTimeout
|
|
}
|
|
|
|
func (cmd *baseCmd) setReadTimeout(d time.Duration) {
|
|
cmd._readTimeout = &d
|
|
}
|
|
|
|
func (cmd *baseCmd) readRawReply(rd *proto.Reader) (err error) {
|
|
cmd.rawVal, err = rd.ReadReply()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type Cmd struct {
|
|
baseCmd
|
|
|
|
val interface{}
|
|
}
|
|
|
|
func NewCmd(ctx context.Context, args ...interface{}) *Cmd {
|
|
return &Cmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *Cmd) SetVal(val interface{}) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *Cmd) Val() interface{} {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *Cmd) Result() (interface{}, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *Cmd) Text() (string, error) {
|
|
if cmd.err != nil {
|
|
return "", cmd.err
|
|
}
|
|
return toString(cmd.val)
|
|
}
|
|
|
|
func toString(val interface{}) (string, error) {
|
|
switch val := val.(type) {
|
|
case string:
|
|
return val, nil
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for String", val)
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Int() (int, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
switch val := cmd.val.(type) {
|
|
case int64:
|
|
return int(val), nil
|
|
case string:
|
|
return strconv.Atoi(val)
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Int", val)
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Int64() (int64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return toInt64(cmd.val)
|
|
}
|
|
|
|
func toInt64(val interface{}) (int64, error) {
|
|
switch val := val.(type) {
|
|
case int64:
|
|
return val, nil
|
|
case string:
|
|
return strconv.ParseInt(val, 10, 64)
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Int64", val)
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Uint64() (uint64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return toUint64(cmd.val)
|
|
}
|
|
|
|
func toUint64(val interface{}) (uint64, error) {
|
|
switch val := val.(type) {
|
|
case int64:
|
|
return uint64(val), nil
|
|
case string:
|
|
return strconv.ParseUint(val, 10, 64)
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Uint64", val)
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Float32() (float32, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return toFloat32(cmd.val)
|
|
}
|
|
|
|
func toFloat32(val interface{}) (float32, error) {
|
|
switch val := val.(type) {
|
|
case int64:
|
|
return float32(val), nil
|
|
case string:
|
|
f, err := strconv.ParseFloat(val, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return float32(f), nil
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Float32", val)
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Float64() (float64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return toFloat64(cmd.val)
|
|
}
|
|
|
|
func toFloat64(val interface{}) (float64, error) {
|
|
switch val := val.(type) {
|
|
case int64:
|
|
return float64(val), nil
|
|
case string:
|
|
return strconv.ParseFloat(val, 64)
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Float64", val)
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Bool() (bool, error) {
|
|
if cmd.err != nil {
|
|
return false, cmd.err
|
|
}
|
|
return toBool(cmd.val)
|
|
}
|
|
|
|
func toBool(val interface{}) (bool, error) {
|
|
switch val := val.(type) {
|
|
case bool:
|
|
return val, nil
|
|
case int64:
|
|
return val != 0, nil
|
|
case string:
|
|
return strconv.ParseBool(val)
|
|
default:
|
|
err := fmt.Errorf("redis: unexpected type=%T for Bool", val)
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) Slice() ([]interface{}, error) {
|
|
if cmd.err != nil {
|
|
return nil, cmd.err
|
|
}
|
|
switch val := cmd.val.(type) {
|
|
case []interface{}:
|
|
return val, nil
|
|
default:
|
|
return nil, fmt.Errorf("redis: unexpected type=%T for Slice", val)
|
|
}
|
|
}
|
|
|
|
func (cmd *Cmd) StringSlice() ([]string, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ss := make([]string, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toString(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ss[i] = val
|
|
}
|
|
return ss, nil
|
|
}
|
|
|
|
func (cmd *Cmd) Int64Slice() ([]int64, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nums := make([]int64, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toInt64(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nums[i] = val
|
|
}
|
|
return nums, nil
|
|
}
|
|
|
|
func (cmd *Cmd) Uint64Slice() ([]uint64, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nums := make([]uint64, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toUint64(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nums[i] = val
|
|
}
|
|
return nums, nil
|
|
}
|
|
|
|
func (cmd *Cmd) Float32Slice() ([]float32, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
floats := make([]float32, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toFloat32(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
floats[i] = val
|
|
}
|
|
return floats, nil
|
|
}
|
|
|
|
func (cmd *Cmd) Float64Slice() ([]float64, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
floats := make([]float64, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toFloat64(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
floats[i] = val
|
|
}
|
|
return floats, nil
|
|
}
|
|
|
|
func (cmd *Cmd) BoolSlice() ([]bool, error) {
|
|
slice, err := cmd.Slice()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bools := make([]bool, len(slice))
|
|
for i, iface := range slice {
|
|
val, err := toBool(iface)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bools[i] = val
|
|
}
|
|
return bools, nil
|
|
}
|
|
|
|
func (cmd *Cmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadReply()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type SliceCmd struct {
|
|
baseCmd
|
|
|
|
val []interface{}
|
|
}
|
|
|
|
var _ Cmder = (*SliceCmd)(nil)
|
|
|
|
func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd {
|
|
return &SliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *SliceCmd) SetVal(val []interface{}) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *SliceCmd) Val() []interface{} {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *SliceCmd) Result() ([]interface{}, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *SliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
// Scan scans the results from the map into a destination struct. The map keys
|
|
// are matched in the Redis struct fields by the `redis:"field"` tag.
|
|
func (cmd *SliceCmd) Scan(dst interface{}) error {
|
|
if cmd.err != nil {
|
|
return cmd.err
|
|
}
|
|
|
|
// Pass the list of keys and values.
|
|
// Skip the first two args for: HMGET key
|
|
var args []interface{}
|
|
if cmd.args[0] == "hmget" {
|
|
args = cmd.args[2:]
|
|
} else {
|
|
// Otherwise, it's: MGET field field ...
|
|
args = cmd.args[1:]
|
|
}
|
|
|
|
return hscan.Scan(dst, args, cmd.val)
|
|
}
|
|
|
|
func (cmd *SliceCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadSlice()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type StatusCmd struct {
|
|
baseCmd
|
|
|
|
val string
|
|
}
|
|
|
|
var _ Cmder = (*StatusCmd)(nil)
|
|
|
|
func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd {
|
|
return &StatusCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *StatusCmd) SetVal(val string) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *StatusCmd) Val() string {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *StatusCmd) Result() (string, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *StatusCmd) Bytes() ([]byte, error) {
|
|
return util.StringToBytes(cmd.val), cmd.err
|
|
}
|
|
|
|
func (cmd *StatusCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *StatusCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadString()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type IntCmd struct {
|
|
baseCmd
|
|
|
|
val int64
|
|
}
|
|
|
|
var _ Cmder = (*IntCmd)(nil)
|
|
|
|
func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd {
|
|
return &IntCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *IntCmd) SetVal(val int64) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *IntCmd) Val() int64 {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *IntCmd) Result() (int64, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *IntCmd) Uint64() (uint64, error) {
|
|
return uint64(cmd.val), cmd.err
|
|
}
|
|
|
|
func (cmd *IntCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadInt()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type IntSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []int64
|
|
}
|
|
|
|
var _ Cmder = (*IntSliceCmd)(nil)
|
|
|
|
func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd {
|
|
return &IntSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *IntSliceCmd) SetVal(val []int64) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *IntSliceCmd) Val() []int64 {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *IntSliceCmd) Result() ([]int64, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *IntSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]int64, n)
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if cmd.val[i], err = rd.ReadInt(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type DurationCmd struct {
|
|
baseCmd
|
|
|
|
val time.Duration
|
|
precision time.Duration
|
|
}
|
|
|
|
var _ Cmder = (*DurationCmd)(nil)
|
|
|
|
func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interface{}) *DurationCmd {
|
|
return &DurationCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
precision: precision,
|
|
}
|
|
}
|
|
|
|
func (cmd *DurationCmd) SetVal(val time.Duration) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *DurationCmd) Val() time.Duration {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *DurationCmd) Result() (time.Duration, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *DurationCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *DurationCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch n {
|
|
// -2 if the key does not exist
|
|
// -1 if the key exists but has no associated expire
|
|
case -2, -1:
|
|
cmd.val = time.Duration(n)
|
|
default:
|
|
cmd.val = time.Duration(n) * cmd.precision
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type TimeCmd struct {
|
|
baseCmd
|
|
|
|
val time.Time
|
|
}
|
|
|
|
var _ Cmder = (*TimeCmd)(nil)
|
|
|
|
func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd {
|
|
return &TimeCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *TimeCmd) SetVal(val time.Time) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *TimeCmd) Val() time.Time {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *TimeCmd) Result() (time.Time, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *TimeCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *TimeCmd) readReply(rd *proto.Reader) error {
|
|
if err := rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
second, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
microsecond, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = time.Unix(second, microsecond*1000)
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type BoolCmd struct {
|
|
baseCmd
|
|
|
|
val bool
|
|
}
|
|
|
|
var _ Cmder = (*BoolCmd)(nil)
|
|
|
|
func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd {
|
|
return &BoolCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *BoolCmd) SetVal(val bool) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *BoolCmd) Val() bool {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *BoolCmd) Result() (bool, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *BoolCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *BoolCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadBool()
|
|
|
|
// `SET key value NX` returns nil when key already exists. But
|
|
// `SETNX key value` returns bool (0/1). So convert nil to bool.
|
|
if err == Nil {
|
|
cmd.val = false
|
|
err = nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type StringCmd struct {
|
|
baseCmd
|
|
|
|
val string
|
|
}
|
|
|
|
var _ Cmder = (*StringCmd)(nil)
|
|
|
|
func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd {
|
|
return &StringCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *StringCmd) SetVal(val string) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *StringCmd) Val() string {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *StringCmd) Result() (string, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *StringCmd) Bytes() ([]byte, error) {
|
|
return util.StringToBytes(cmd.val), cmd.err
|
|
}
|
|
|
|
func (cmd *StringCmd) Bool() (bool, error) {
|
|
if cmd.err != nil {
|
|
return false, cmd.err
|
|
}
|
|
return strconv.ParseBool(cmd.val)
|
|
}
|
|
|
|
func (cmd *StringCmd) Int() (int, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return strconv.Atoi(cmd.Val())
|
|
}
|
|
|
|
func (cmd *StringCmd) Int64() (int64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return strconv.ParseInt(cmd.Val(), 10, 64)
|
|
}
|
|
|
|
func (cmd *StringCmd) Uint64() (uint64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return strconv.ParseUint(cmd.Val(), 10, 64)
|
|
}
|
|
|
|
func (cmd *StringCmd) Float32() (float32, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
f, err := strconv.ParseFloat(cmd.Val(), 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return float32(f), nil
|
|
}
|
|
|
|
func (cmd *StringCmd) Float64() (float64, error) {
|
|
if cmd.err != nil {
|
|
return 0, cmd.err
|
|
}
|
|
return strconv.ParseFloat(cmd.Val(), 64)
|
|
}
|
|
|
|
func (cmd *StringCmd) Time() (time.Time, error) {
|
|
if cmd.err != nil {
|
|
return time.Time{}, cmd.err
|
|
}
|
|
return time.Parse(time.RFC3339Nano, cmd.Val())
|
|
}
|
|
|
|
func (cmd *StringCmd) Scan(val interface{}) error {
|
|
if cmd.err != nil {
|
|
return cmd.err
|
|
}
|
|
return proto.Scan([]byte(cmd.val), val)
|
|
}
|
|
|
|
func (cmd *StringCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *StringCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadString()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type FloatCmd struct {
|
|
baseCmd
|
|
|
|
val float64
|
|
}
|
|
|
|
var _ Cmder = (*FloatCmd)(nil)
|
|
|
|
func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd {
|
|
return &FloatCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *FloatCmd) SetVal(val float64) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *FloatCmd) Val() float64 {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *FloatCmd) Result() (float64, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *FloatCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = rd.ReadFloat()
|
|
return err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
|
|
func (cmd *FloatSliceCmd) Val() []float64 {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *FloatSliceCmd) Result() ([]float64, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *FloatSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make([]float64, n)
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
switch num, err := rd.ReadFloat(); {
|
|
case err == Nil:
|
|
cmd.val[i] = 0
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
cmd.val[i] = num
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type StringSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []string
|
|
}
|
|
|
|
var _ Cmder = (*StringSliceCmd)(nil)
|
|
|
|
func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd {
|
|
return &StringSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) SetVal(val []string) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) Val() []string {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) Result() ([]string, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) ScanSlice(container interface{}) error {
|
|
return proto.ScanSlice(cmd.Val(), container)
|
|
}
|
|
|
|
func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]string, n)
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
switch s, err := rd.ReadString(); {
|
|
case err == Nil:
|
|
cmd.val[i] = ""
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
cmd.val[i] = s
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *KeyValueSliceCmd) SetVal(val []KeyValue) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *KeyValueSliceCmd) Val() []KeyValue {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *KeyValueSliceCmd) Result() ([]KeyValue, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *KeyValueSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
// Many commands will respond to two formats:
|
|
// 1. 1) "one"
|
|
// 2. (double) 1
|
|
// 2. 1) "two"
|
|
// 2. (double) 2
|
|
//
|
|
// OR:
|
|
// 1. "two"
|
|
// 2. (double) 2
|
|
// 3. "one"
|
|
// 4. (double) 1
|
|
func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the n is 0, can't continue reading.
|
|
if n == 0 {
|
|
cmd.val = make([]KeyValue, 0)
|
|
return nil
|
|
}
|
|
|
|
typ, err := rd.PeekReplyType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
array := typ == proto.RespArray
|
|
|
|
if array {
|
|
cmd.val = make([]KeyValue, n)
|
|
} else {
|
|
cmd.val = make([]KeyValue, n/2)
|
|
}
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if array {
|
|
if err = rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if cmd.val[i].Key, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val[i].Value, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type BoolSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []bool
|
|
}
|
|
|
|
var _ Cmder = (*BoolSliceCmd)(nil)
|
|
|
|
func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd {
|
|
return &BoolSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *BoolSliceCmd) SetVal(val []bool) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *BoolSliceCmd) Val() []bool {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *BoolSliceCmd) Result() ([]bool, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *BoolSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]bool, n)
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if cmd.val[i], err = rd.ReadBool(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type MapStringStringCmd struct {
|
|
baseCmd
|
|
|
|
val map[string]string
|
|
}
|
|
|
|
var _ Cmder = (*MapStringStringCmd)(nil)
|
|
|
|
func NewMapStringStringCmd(ctx context.Context, args ...interface{}) *MapStringStringCmd {
|
|
return &MapStringStringCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *MapStringStringCmd) Val() map[string]string {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *MapStringStringCmd) SetVal(val map[string]string) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *MapStringStringCmd) Result() (map[string]string, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *MapStringStringCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
// Scan scans the results from the map into a destination struct. The map keys
|
|
// are matched in the Redis struct fields by the `redis:"field"` tag.
|
|
func (cmd *MapStringStringCmd) Scan(dest interface{}) error {
|
|
if cmd.err != nil {
|
|
return cmd.err
|
|
}
|
|
|
|
strct, err := hscan.Struct(dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range cmd.val {
|
|
if err := strct.Scan(k, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cmd *MapStringStringCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make(map[string]string, n)
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val[key] = value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type MapStringIntCmd struct {
|
|
baseCmd
|
|
|
|
val map[string]int64
|
|
}
|
|
|
|
var _ Cmder = (*MapStringIntCmd)(nil)
|
|
|
|
func NewMapStringIntCmd(ctx context.Context, args ...interface{}) *MapStringIntCmd {
|
|
return &MapStringIntCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *MapStringIntCmd) SetVal(val map[string]int64) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *MapStringIntCmd) Val() map[string]int64 {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *MapStringIntCmd) Result() (map[string]int64, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *MapStringIntCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *MapStringIntCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make(map[string]int64, n)
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nn, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[key] = nn
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
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) {
|
|
readType, err := rd.PeekReplyType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make(map[string][]interface{})
|
|
|
|
if readType == proto.RespMap {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
k, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nn, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[k] = make([]interface{}, nn)
|
|
for j := 0; j < nn; j++ {
|
|
value, err := rd.ReadReply()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[k][j] = value
|
|
}
|
|
}
|
|
} else if readType == proto.RespArray {
|
|
// RESP2 response
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
// Each entry in this array is itself an array with key details
|
|
itemLen, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[key] = make([]interface{}, 0, itemLen-1)
|
|
for j := 1; j < itemLen; j++ {
|
|
// Read the inner array for timestamp-value pairs
|
|
data, err := rd.ReadReply()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[key] = append(cmd.val[key], data)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type StringStructMapCmd struct {
|
|
baseCmd
|
|
|
|
val map[string]struct{}
|
|
}
|
|
|
|
var _ Cmder = (*StringStructMapCmd)(nil)
|
|
|
|
func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd {
|
|
return &StringStructMapCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *StringStructMapCmd) Val() map[string]struct{} {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *StringStructMapCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make(map[string]struct{}, n)
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[key] = struct{}{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XMessage struct {
|
|
ID string
|
|
Values map[string]interface{}
|
|
}
|
|
|
|
type XMessageSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []XMessage
|
|
}
|
|
|
|
var _ Cmder = (*XMessageSliceCmd)(nil)
|
|
|
|
func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSliceCmd {
|
|
return &XMessageSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XMessageSliceCmd) SetVal(val []XMessage) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XMessageSliceCmd) Val() []XMessage {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XMessageSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) (err error) {
|
|
cmd.val, err = readXMessageSlice(rd)
|
|
return err
|
|
}
|
|
|
|
func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msgs := make([]XMessage, n)
|
|
for i := 0; i < len(msgs); i++ {
|
|
if msgs[i], err = readXMessage(rd); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return msgs, nil
|
|
}
|
|
|
|
func readXMessage(rd *proto.Reader) (XMessage, error) {
|
|
if err := rd.ReadFixedArrayLen(2); err != nil {
|
|
return XMessage{}, err
|
|
}
|
|
|
|
id, err := rd.ReadString()
|
|
if err != nil {
|
|
return XMessage{}, err
|
|
}
|
|
|
|
v, err := stringInterfaceMapParser(rd)
|
|
if err != nil {
|
|
if err != proto.Nil {
|
|
return XMessage{}, err
|
|
}
|
|
}
|
|
|
|
return XMessage{
|
|
ID: id,
|
|
Values: v,
|
|
}, nil
|
|
}
|
|
|
|
func stringInterfaceMapParser(rd *proto.Reader) (map[string]interface{}, error) {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m := make(map[string]interface{}, n)
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
value, err := rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m[key] = value
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XStream struct {
|
|
Stream string
|
|
Messages []XMessage
|
|
}
|
|
|
|
type XStreamSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []XStream
|
|
}
|
|
|
|
var _ Cmder = (*XStreamSliceCmd)(nil)
|
|
|
|
func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceCmd {
|
|
return &XStreamSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XStreamSliceCmd) SetVal(val []XStream) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XStreamSliceCmd) Val() []XStream {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XStreamSliceCmd) Result() ([]XStream, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XStreamSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error {
|
|
typ, err := rd.PeekReplyType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var n int
|
|
if typ == proto.RespMap {
|
|
n, err = rd.ReadMapLen()
|
|
} else {
|
|
n, err = rd.ReadArrayLen()
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]XStream, n)
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if typ != proto.RespMap {
|
|
if err = rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if cmd.val[i].Stream, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
if cmd.val[i].Messages, err = readXMessageSlice(rd); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XPending struct {
|
|
Count int64
|
|
Lower string
|
|
Higher string
|
|
Consumers map[string]int64
|
|
}
|
|
|
|
type XPendingCmd struct {
|
|
baseCmd
|
|
val *XPending
|
|
}
|
|
|
|
var _ Cmder = (*XPendingCmd)(nil)
|
|
|
|
func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd {
|
|
return &XPendingCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XPendingCmd) SetVal(val *XPending) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XPendingCmd) Val() *XPending {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XPendingCmd) Result() (*XPending, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XPendingCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XPendingCmd) readReply(rd *proto.Reader) error {
|
|
var err error
|
|
if err = rd.ReadFixedArrayLen(4); err != nil {
|
|
return err
|
|
}
|
|
cmd.val = &XPending{}
|
|
|
|
if cmd.val.Count, err = rd.ReadInt(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val.Lower, err = rd.ReadString(); err != nil && err != Nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val.Higher, err = rd.ReadString(); err != nil && err != Nil {
|
|
return err
|
|
}
|
|
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
cmd.val.Consumers = make(map[string]int64, n)
|
|
for i := 0; i < n; i++ {
|
|
if err = rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
|
|
consumerName, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
consumerPending, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val.Consumers[consumerName] = consumerPending
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XPendingExt struct {
|
|
ID string
|
|
Consumer string
|
|
Idle time.Duration
|
|
RetryCount int64
|
|
}
|
|
|
|
type XPendingExtCmd struct {
|
|
baseCmd
|
|
val []XPendingExt
|
|
}
|
|
|
|
var _ Cmder = (*XPendingExtCmd)(nil)
|
|
|
|
func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd {
|
|
return &XPendingExtCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XPendingExtCmd) SetVal(val []XPendingExt) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XPendingExtCmd) Val() []XPendingExt {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XPendingExtCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]XPendingExt, n)
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if err = rd.ReadFixedArrayLen(4); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val[i].ID, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val[i].Consumer, err = rd.ReadString(); err != nil && err != Nil {
|
|
return err
|
|
}
|
|
|
|
idle, err := rd.ReadInt()
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
cmd.val[i].Idle = time.Duration(idle) * time.Millisecond
|
|
|
|
if cmd.val[i].RetryCount, err = rd.ReadInt(); err != nil && err != Nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
|
|
func (cmd *XAutoClaimCmd) Val() (messages []XMessage, start string) {
|
|
return cmd.val, cmd.start
|
|
}
|
|
|
|
func (cmd *XAutoClaimCmd) Result() (messages []XMessage, start string, err error) {
|
|
return cmd.val, cmd.start, cmd.err
|
|
}
|
|
|
|
func (cmd *XAutoClaimCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch n {
|
|
case 2, // Redis 6
|
|
3: // Redis 7:
|
|
// ok
|
|
default:
|
|
return fmt.Errorf("redis: got %d elements in XAutoClaim reply, wanted 2/3", n)
|
|
}
|
|
|
|
cmd.start, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val, err = readXMessageSlice(rd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if n >= 3 {
|
|
if err := rd.DiscardNext(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
|
|
func (cmd *XAutoClaimJustIDCmd) Val() (ids []string, start string) {
|
|
return cmd.val, cmd.start
|
|
}
|
|
|
|
func (cmd *XAutoClaimJustIDCmd) Result() (ids []string, start string, err error) {
|
|
return cmd.val, cmd.start, cmd.err
|
|
}
|
|
|
|
func (cmd *XAutoClaimJustIDCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch n {
|
|
case 2, // Redis 6
|
|
3: // Redis 7:
|
|
// ok
|
|
default:
|
|
return fmt.Errorf("redis: got %d elements in XAutoClaimJustID reply, wanted 2/3", n)
|
|
}
|
|
|
|
cmd.start, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nn, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make([]string, nn)
|
|
for i := 0; i < nn; i++ {
|
|
cmd.val[i], err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if n >= 3 {
|
|
if err := rd.DiscardNext(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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
|
|
val []XInfoGroup
|
|
}
|
|
|
|
type XInfoGroup struct {
|
|
Name string
|
|
Consumers int64
|
|
Pending int64
|
|
LastDeliveredID string
|
|
EntriesRead int64
|
|
Lag int64
|
|
}
|
|
|
|
var _ Cmder = (*XInfoGroupsCmd)(nil)
|
|
|
|
func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd {
|
|
return &XInfoGroupsCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: []interface{}{"xinfo", "groups", stream},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XInfoGroupsCmd) SetVal(val []XInfoGroup) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XInfoGroupsCmd) Val() []XInfoGroup {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XInfoGroupsCmd) Result() ([]XInfoGroup, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XInfoGroupsCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]XInfoGroup, n)
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
group := &cmd.val[i]
|
|
|
|
nn, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var key string
|
|
for j := 0; j < nn; j++ {
|
|
key, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch key {
|
|
case "name":
|
|
group.Name, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "consumers":
|
|
group.Consumers, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "pending":
|
|
group.Pending, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "last-delivered-id":
|
|
group.LastDeliveredID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "entries-read":
|
|
group.EntriesRead, err = rd.ReadInt()
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
case "lag":
|
|
group.Lag, err = rd.ReadInt()
|
|
|
|
// lag: the number of entries in the stream that are still waiting to be delivered
|
|
// to the group's consumers, or a NULL(Nil) when that number can't be determined.
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("redis: unexpected key %q in XINFO GROUPS reply", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XInfoStreamCmd struct {
|
|
baseCmd
|
|
val *XInfoStream
|
|
}
|
|
|
|
type XInfoStream struct {
|
|
Length int64
|
|
RadixTreeKeys int64
|
|
RadixTreeNodes int64
|
|
Groups int64
|
|
LastGeneratedID string
|
|
MaxDeletedEntryID string
|
|
EntriesAdded int64
|
|
FirstEntry XMessage
|
|
LastEntry XMessage
|
|
RecordedFirstEntryID string
|
|
}
|
|
|
|
var _ Cmder = (*XInfoStreamCmd)(nil)
|
|
|
|
func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd {
|
|
return &XInfoStreamCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: []interface{}{"xinfo", "stream", stream},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XInfoStreamCmd) SetVal(val *XInfoStream) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XInfoStreamCmd) Val() *XInfoStream {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XInfoStreamCmd) Result() (*XInfoStream, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XInfoStreamCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = &XInfoStream{}
|
|
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch key {
|
|
case "length":
|
|
cmd.val.Length, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "radix-tree-keys":
|
|
cmd.val.RadixTreeKeys, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "radix-tree-nodes":
|
|
cmd.val.RadixTreeNodes, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "groups":
|
|
cmd.val.Groups, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "last-generated-id":
|
|
cmd.val.LastGeneratedID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "max-deleted-entry-id":
|
|
cmd.val.MaxDeletedEntryID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "entries-added":
|
|
cmd.val.EntriesAdded, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "first-entry":
|
|
cmd.val.FirstEntry, err = readXMessage(rd)
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
case "last-entry":
|
|
cmd.val.LastEntry, err = readXMessage(rd)
|
|
if err != nil && err != Nil {
|
|
return err
|
|
}
|
|
case "recorded-first-entry-id":
|
|
cmd.val.RecordedFirstEntryID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM reply", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type XInfoStreamFullCmd struct {
|
|
baseCmd
|
|
val *XInfoStreamFull
|
|
}
|
|
|
|
type XInfoStreamFull struct {
|
|
Length int64
|
|
RadixTreeKeys int64
|
|
RadixTreeNodes int64
|
|
LastGeneratedID string
|
|
MaxDeletedEntryID string
|
|
EntriesAdded int64
|
|
Entries []XMessage
|
|
Groups []XInfoStreamGroup
|
|
RecordedFirstEntryID string
|
|
}
|
|
|
|
type XInfoStreamGroup struct {
|
|
Name string
|
|
LastDeliveredID string
|
|
EntriesRead int64
|
|
Lag int64
|
|
PelCount int64
|
|
Pending []XInfoStreamGroupPending
|
|
Consumers []XInfoStreamConsumer
|
|
}
|
|
|
|
type XInfoStreamGroupPending struct {
|
|
ID string
|
|
Consumer string
|
|
DeliveryTime time.Time
|
|
DeliveryCount int64
|
|
}
|
|
|
|
type XInfoStreamConsumer struct {
|
|
Name string
|
|
SeenTime time.Time
|
|
ActiveTime time.Time
|
|
PelCount int64
|
|
Pending []XInfoStreamConsumerPending
|
|
}
|
|
|
|
type XInfoStreamConsumerPending struct {
|
|
ID string
|
|
DeliveryTime time.Time
|
|
DeliveryCount int64
|
|
}
|
|
|
|
var _ Cmder = (*XInfoStreamFullCmd)(nil)
|
|
|
|
func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStreamFullCmd {
|
|
return &XInfoStreamFullCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *XInfoStreamFullCmd) SetVal(val *XInfoStreamFull) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *XInfoStreamFullCmd) Val() *XInfoStreamFull {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *XInfoStreamFullCmd) Result() (*XInfoStreamFull, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *XInfoStreamFullCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = &XInfoStreamFull{}
|
|
|
|
for i := 0; i < n; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch key {
|
|
case "length":
|
|
cmd.val.Length, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "radix-tree-keys":
|
|
cmd.val.RadixTreeKeys, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "radix-tree-nodes":
|
|
cmd.val.RadixTreeNodes, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "last-generated-id":
|
|
cmd.val.LastGeneratedID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "entries-added":
|
|
cmd.val.EntriesAdded, err = rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "entries":
|
|
cmd.val.Entries, err = readXMessageSlice(rd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "groups":
|
|
cmd.val.Groups, err = readStreamGroups(rd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "max-deleted-entry-id":
|
|
cmd.val.MaxDeletedEntryID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "recorded-first-entry-id":
|
|
cmd.val.RecordedFirstEntryID, err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groups := make([]XInfoStreamGroup, 0, n)
|
|
for i := 0; i < n; i++ {
|
|
nn, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
group := XInfoStreamGroup{}
|
|
|
|
for j := 0; j < nn; j++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch key {
|
|
case "name":
|
|
group.Name, err = rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case "last-delivered-id":
|
|
group.LastDeliveredID, err = rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case "entries-read":
|
|
group.EntriesRead, err = rd.ReadInt()
|
|
if err != nil && err != Nil {
|
|
return nil, err
|
|
}
|
|
case "lag":
|
|
// lag: the number of entries in the stream that are still waiting to be delivered
|
|
// to the group's consumers, or a NULL(Nil) when that number can't be determined.
|
|
group.Lag, err = rd.ReadInt()
|
|
if err != nil && err != Nil {
|
|
return nil, err
|
|
}
|
|
case "pel-count":
|
|
group.PelCount, err = rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case "pending":
|
|
group.Pending, err = readXInfoStreamGroupPending(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case "consumers":
|
|
group.Consumers, err = readXInfoStreamConsumers(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key)
|
|
}
|
|
}
|
|
|
|
groups = append(groups, group)
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, error) {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pending := make([]XInfoStreamGroupPending, 0, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
if err = rd.ReadFixedArrayLen(4); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := XInfoStreamGroupPending{}
|
|
|
|
p.ID, err = rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Consumer, err = rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delivery, err := rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond))
|
|
|
|
p.DeliveryCount, err = rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pending = append(pending, p)
|
|
}
|
|
|
|
return pending, nil
|
|
}
|
|
|
|
func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
consumers := make([]XInfoStreamConsumer, 0, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
nn, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c := XInfoStreamConsumer{}
|
|
|
|
for f := 0; f < nn; f++ {
|
|
cKey, err := rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch cKey {
|
|
case "name":
|
|
c.Name, err = rd.ReadString()
|
|
case "seen-time":
|
|
seen, err := rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.SeenTime = time.UnixMilli(seen)
|
|
case "active-time":
|
|
active, err := rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.ActiveTime = time.UnixMilli(active)
|
|
case "pel-count":
|
|
c.PelCount, err = rd.ReadInt()
|
|
case "pending":
|
|
pendingNumber, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.Pending = make([]XInfoStreamConsumerPending, 0, pendingNumber)
|
|
|
|
for pn := 0; pn < pendingNumber; pn++ {
|
|
if err = rd.ReadFixedArrayLen(3); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := XInfoStreamConsumerPending{}
|
|
|
|
p.ID, err = rd.ReadString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delivery, err := rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond))
|
|
|
|
p.DeliveryCount, err = rd.ReadInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.Pending = append(c.Pending, p)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("redis: unexpected content %s "+
|
|
"in XINFO STREAM FULL reply", cKey)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
consumers = append(consumers, c)
|
|
}
|
|
|
|
return consumers, nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type ZSliceCmd struct {
|
|
baseCmd
|
|
|
|
val []Z
|
|
}
|
|
|
|
var _ Cmder = (*ZSliceCmd)(nil)
|
|
|
|
func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd {
|
|
return &ZSliceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *ZSliceCmd) SetVal(val []Z) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *ZSliceCmd) Val() []Z {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *ZSliceCmd) Result() ([]Z, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *ZSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the n is 0, can't continue reading.
|
|
if n == 0 {
|
|
cmd.val = make([]Z, 0)
|
|
return nil
|
|
}
|
|
|
|
typ, err := rd.PeekReplyType()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
array := typ == proto.RespArray
|
|
|
|
if array {
|
|
cmd.val = make([]Z, n)
|
|
} else {
|
|
cmd.val = make([]Z, n/2)
|
|
}
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
if array {
|
|
if err = rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if cmd.val[i].Member, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.val[i].Score, err = rd.ReadFloat(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type ZWithKeyCmd struct {
|
|
baseCmd
|
|
|
|
val *ZWithKey
|
|
}
|
|
|
|
var _ Cmder = (*ZWithKeyCmd)(nil)
|
|
|
|
func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd {
|
|
return &ZWithKeyCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *ZWithKeyCmd) SetVal(val *ZWithKey) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *ZWithKeyCmd) Val() *ZWithKey {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *ZWithKeyCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) (err error) {
|
|
if err = rd.ReadFixedArrayLen(3); err != nil {
|
|
return err
|
|
}
|
|
cmd.val = &ZWithKey{}
|
|
|
|
if cmd.val.Key, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
if cmd.val.Member, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
if cmd.val.Score, err = rd.ReadFloat(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type ScanCmd struct {
|
|
baseCmd
|
|
|
|
page []string
|
|
cursor uint64
|
|
|
|
process cmdable
|
|
}
|
|
|
|
var _ Cmder = (*ScanCmd)(nil)
|
|
|
|
func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *ScanCmd {
|
|
return &ScanCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
process: process,
|
|
}
|
|
}
|
|
|
|
func (cmd *ScanCmd) SetVal(page []string, cursor uint64) {
|
|
cmd.page = page
|
|
cmd.cursor = cursor
|
|
}
|
|
|
|
func (cmd *ScanCmd) Val() (keys []string, cursor uint64) {
|
|
return cmd.page, cmd.cursor
|
|
}
|
|
|
|
func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) {
|
|
return cmd.page, cmd.cursor, cmd.err
|
|
}
|
|
|
|
func (cmd *ScanCmd) String() string {
|
|
return cmdString(cmd, cmd.page)
|
|
}
|
|
|
|
func (cmd *ScanCmd) readReply(rd *proto.Reader) error {
|
|
if err := rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
|
|
cursor, err := rd.ReadUint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.cursor = cursor
|
|
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.page = make([]string, n)
|
|
|
|
for i := 0; i < len(cmd.page); i++ {
|
|
if cmd.page[i], err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Iterator creates a new ScanIterator.
|
|
func (cmd *ScanCmd) Iterator() *ScanIterator {
|
|
return &ScanIterator{
|
|
cmd: cmd,
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type ClusterNode struct {
|
|
ID string
|
|
Addr string
|
|
NetworkingMetadata map[string]string
|
|
}
|
|
|
|
type ClusterSlot struct {
|
|
Start int
|
|
End int
|
|
Nodes []ClusterNode
|
|
}
|
|
|
|
type ClusterSlotsCmd struct {
|
|
baseCmd
|
|
|
|
val []ClusterSlot
|
|
}
|
|
|
|
var _ Cmder = (*ClusterSlotsCmd)(nil)
|
|
|
|
func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsCmd {
|
|
return &ClusterSlotsCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *ClusterSlotsCmd) SetVal(val []ClusterSlot) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *ClusterSlotsCmd) Val() []ClusterSlot {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *ClusterSlotsCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]ClusterSlot, n)
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
n, err = rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n < 2 {
|
|
return fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n)
|
|
}
|
|
|
|
start, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
end, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// subtract start and end.
|
|
nodes := make([]ClusterNode, n-2)
|
|
|
|
for j := 0; j < len(nodes); j++ {
|
|
nn, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if nn < 2 || nn > 4 {
|
|
return fmt.Errorf("got %d elements in cluster info address, expected 2, 3, or 4", n)
|
|
}
|
|
|
|
ip, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
port, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nodes[j].Addr = net.JoinHostPort(ip, port)
|
|
|
|
if nn >= 3 {
|
|
id, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodes[j].ID = id
|
|
}
|
|
|
|
if nn >= 4 {
|
|
metadataLength, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
networkingMetadata := make(map[string]string, metadataLength)
|
|
|
|
for i := 0; i < metadataLength; i++ {
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
networkingMetadata[key] = value
|
|
}
|
|
|
|
nodes[j].NetworkingMetadata = networkingMetadata
|
|
}
|
|
}
|
|
|
|
cmd.val[i] = ClusterSlot{
|
|
Start: int(start),
|
|
End: int(end),
|
|
Nodes: nodes,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// GeoLocation is used with GeoAdd to add geospatial location.
|
|
type GeoLocation struct {
|
|
Name string
|
|
Longitude, Latitude, Dist float64
|
|
GeoHash int64
|
|
}
|
|
|
|
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
|
|
type GeoRadiusQuery struct {
|
|
Radius float64
|
|
// Can be m, km, ft, or mi. Default is km.
|
|
Unit string
|
|
WithCoord bool
|
|
WithDist bool
|
|
WithGeoHash bool
|
|
Count int
|
|
// Can be ASC or DESC. Default is no sort order.
|
|
Sort string
|
|
Store string
|
|
StoreDist string
|
|
|
|
// WithCoord+WithDist+WithGeoHash
|
|
withLen int
|
|
}
|
|
|
|
type GeoLocationCmd struct {
|
|
baseCmd
|
|
|
|
q *GeoRadiusQuery
|
|
locations []GeoLocation
|
|
}
|
|
|
|
var _ Cmder = (*GeoLocationCmd)(nil)
|
|
|
|
func NewGeoLocationCmd(ctx context.Context, q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd {
|
|
return &GeoLocationCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: geoLocationArgs(q, args...),
|
|
},
|
|
q: q,
|
|
}
|
|
}
|
|
|
|
func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} {
|
|
args = append(args, q.Radius)
|
|
if q.Unit != "" {
|
|
args = append(args, q.Unit)
|
|
} else {
|
|
args = append(args, "km")
|
|
}
|
|
if q.WithCoord {
|
|
args = append(args, "withcoord")
|
|
q.withLen++
|
|
}
|
|
if q.WithDist {
|
|
args = append(args, "withdist")
|
|
q.withLen++
|
|
}
|
|
if q.WithGeoHash {
|
|
args = append(args, "withhash")
|
|
q.withLen++
|
|
}
|
|
if q.Count > 0 {
|
|
args = append(args, "count", q.Count)
|
|
}
|
|
if q.Sort != "" {
|
|
args = append(args, q.Sort)
|
|
}
|
|
if q.Store != "" {
|
|
args = append(args, "store")
|
|
args = append(args, q.Store)
|
|
}
|
|
if q.StoreDist != "" {
|
|
args = append(args, "storedist")
|
|
args = append(args, q.StoreDist)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func (cmd *GeoLocationCmd) SetVal(locations []GeoLocation) {
|
|
cmd.locations = locations
|
|
}
|
|
|
|
func (cmd *GeoLocationCmd) Val() []GeoLocation {
|
|
return cmd.locations
|
|
}
|
|
|
|
func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) {
|
|
return cmd.locations, cmd.err
|
|
}
|
|
|
|
func (cmd *GeoLocationCmd) String() string {
|
|
return cmdString(cmd, cmd.locations)
|
|
}
|
|
|
|
func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.locations = make([]GeoLocation, n)
|
|
|
|
for i := 0; i < len(cmd.locations); i++ {
|
|
// only name
|
|
if cmd.q.withLen == 0 {
|
|
if cmd.locations[i].Name, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
// +name
|
|
if err = rd.ReadFixedArrayLen(cmd.q.withLen + 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cmd.locations[i].Name, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
if cmd.q.WithDist {
|
|
if cmd.locations[i].Dist, err = rd.ReadFloat(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if cmd.q.WithGeoHash {
|
|
if cmd.locations[i].GeoHash, err = rd.ReadInt(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if cmd.q.WithCoord {
|
|
if err = rd.ReadFixedArrayLen(2); err != nil {
|
|
return err
|
|
}
|
|
if cmd.locations[i].Longitude, err = rd.ReadFloat(); err != nil {
|
|
return err
|
|
}
|
|
if cmd.locations[i].Latitude, err = rd.ReadFloat(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type GeoPosCmd struct {
|
|
baseCmd
|
|
|
|
val []*GeoPos
|
|
}
|
|
|
|
var _ Cmder = (*GeoPosCmd)(nil)
|
|
|
|
func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd {
|
|
return &GeoPosCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *GeoPosCmd) SetVal(val []*GeoPos) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *GeoPosCmd) Val() []*GeoPos {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *GeoPosCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]*GeoPos, n)
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
err = rd.ReadFixedArrayLen(2)
|
|
if err != nil {
|
|
if err == Nil {
|
|
cmd.val[i] = nil
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
|
|
longitude, err := rd.ReadFloat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
latitude, err := rd.ReadFloat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val[i] = &GeoPos{
|
|
Longitude: longitude,
|
|
Latitude: latitude,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
|
|
var _ Cmder = (*CommandsInfoCmd)(nil)
|
|
|
|
func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoCmd {
|
|
return &CommandsInfoCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *CommandsInfoCmd) SetVal(val map[string]*CommandInfo) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *CommandsInfoCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
|
|
const numArgRedis5 = 6
|
|
const numArgRedis6 = 7
|
|
const numArgRedis7 = 10
|
|
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make(map[string]*CommandInfo, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
nn, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch nn {
|
|
case numArgRedis5, numArgRedis6, numArgRedis7:
|
|
// ok
|
|
default:
|
|
return fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6/7/10", nn)
|
|
}
|
|
|
|
cmdInfo := &CommandInfo{}
|
|
if cmdInfo.Name, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
|
|
arity, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.Arity = int8(arity)
|
|
|
|
flagLen, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.Flags = make([]string, flagLen)
|
|
for f := 0; f < len(cmdInfo.Flags); f++ {
|
|
switch s, err := rd.ReadString(); {
|
|
case err == Nil:
|
|
cmdInfo.Flags[f] = ""
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
if !cmdInfo.ReadOnly && s == "readonly" {
|
|
cmdInfo.ReadOnly = true
|
|
}
|
|
cmdInfo.Flags[f] = s
|
|
}
|
|
}
|
|
|
|
firstKeyPos, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.FirstKeyPos = int8(firstKeyPos)
|
|
|
|
lastKeyPos, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.LastKeyPos = int8(lastKeyPos)
|
|
|
|
stepCount, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.StepCount = int8(stepCount)
|
|
|
|
if nn >= numArgRedis6 {
|
|
aclFlagLen, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdInfo.ACLFlags = make([]string, aclFlagLen)
|
|
for f := 0; f < len(cmdInfo.ACLFlags); f++ {
|
|
switch s, err := rd.ReadString(); {
|
|
case err == Nil:
|
|
cmdInfo.ACLFlags[f] = ""
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
cmdInfo.ACLFlags[f] = s
|
|
}
|
|
}
|
|
}
|
|
|
|
if nn >= numArgRedis7 {
|
|
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
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type cmdsInfoCache struct {
|
|
fn func(ctx context.Context) (map[string]*CommandInfo, error)
|
|
|
|
once internal.Once
|
|
cmds map[string]*CommandInfo
|
|
}
|
|
|
|
func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, error)) *cmdsInfoCache {
|
|
return &cmdsInfoCache{
|
|
fn: fn,
|
|
}
|
|
}
|
|
|
|
func (c *cmdsInfoCache) Get(ctx context.Context) (map[string]*CommandInfo, error) {
|
|
err := c.once.Do(func() error {
|
|
cmds, err := c.fn(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
c.cmds = cmds
|
|
return nil
|
|
})
|
|
return c.cmds, err
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type SlowLog struct {
|
|
ID int64
|
|
Time time.Time
|
|
Duration time.Duration
|
|
Args []string
|
|
// These are also optional fields emitted only by Redis 4.0 or greater:
|
|
// https://redis.io/commands/slowlog#output-format
|
|
ClientAddr string
|
|
ClientName string
|
|
}
|
|
|
|
type SlowLogCmd struct {
|
|
baseCmd
|
|
|
|
val []SlowLog
|
|
}
|
|
|
|
var _ Cmder = (*SlowLogCmd)(nil)
|
|
|
|
func NewSlowLogCmd(ctx context.Context, args ...interface{}) *SlowLogCmd {
|
|
return &SlowLogCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *SlowLogCmd) SetVal(val []SlowLog) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *SlowLogCmd) Val() []SlowLog {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *SlowLogCmd) Result() ([]SlowLog, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *SlowLogCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *SlowLogCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val = make([]SlowLog, n)
|
|
|
|
for i := 0; i < len(cmd.val); i++ {
|
|
nn, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if nn < 4 {
|
|
return fmt.Errorf("redis: got %d elements in slowlog get, expected at least 4", nn)
|
|
}
|
|
|
|
if cmd.val[i].ID, err = rd.ReadInt(); err != nil {
|
|
return err
|
|
}
|
|
|
|
createdAt, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[i].Time = time.Unix(createdAt, 0)
|
|
|
|
costs, err := rd.ReadInt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[i].Duration = time.Duration(costs) * time.Microsecond
|
|
|
|
cmdLen, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cmdLen < 1 {
|
|
return fmt.Errorf("redis: got %d elements commands reply in slowlog get, expected at least 1", cmdLen)
|
|
}
|
|
|
|
cmd.val[i].Args = make([]string, cmdLen)
|
|
for f := 0; f < len(cmd.val[i].Args); f++ {
|
|
cmd.val[i].Args[f], err = rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if nn >= 5 {
|
|
if cmd.val[i].ClientAddr, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if nn >= 6 {
|
|
if cmd.val[i].ClientName, err = rd.ReadString(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *MapStringInterfaceCmd) SetVal(val map[string]interface{}) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *MapStringInterfaceCmd) Val() map[string]interface{} {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *MapStringInterfaceCmd) Result() (map[string]interface{}, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *MapStringInterfaceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *MapStringInterfaceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make(map[string]interface{}, n)
|
|
for i := 0; i < n; i++ {
|
|
k, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v, err := rd.ReadReply()
|
|
if err != nil {
|
|
if err == Nil {
|
|
cmd.val[k] = Nil
|
|
continue
|
|
}
|
|
if err, ok := err.(proto.RedisError); ok {
|
|
cmd.val[k] = err
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
cmd.val[k] = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *MapStringStringSliceCmd) SetVal(val []map[string]string) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *MapStringStringSliceCmd) Val() []map[string]string {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *MapStringStringSliceCmd) Result() ([]map[string]string, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *MapStringStringSliceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.val = make([]map[string]string, n)
|
|
for i := 0; i < n; i++ {
|
|
nn, err := rd.ReadMapLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[i] = make(map[string]string, nn)
|
|
for f := 0; f < nn; f++ {
|
|
k, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.val[i][k] = v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// MapStringInterfaceCmd represents a command that returns a map of strings to interface{}.
|
|
type MapMapStringInterfaceCmd struct {
|
|
baseCmd
|
|
val map[string]interface{}
|
|
}
|
|
|
|
func NewMapMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapMapStringInterfaceCmd {
|
|
return &MapMapStringInterfaceCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *MapMapStringInterfaceCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *MapMapStringInterfaceCmd) SetVal(val map[string]interface{}) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *MapMapStringInterfaceCmd) Result() (map[string]interface{}, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {
|
|
n, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data := make(map[string]interface{}, n/2)
|
|
for i := 0; i < n; i += 2 {
|
|
_, err := rd.ReadArrayLen()
|
|
if err != nil {
|
|
cmd.err = err
|
|
}
|
|
key, err := rd.ReadString()
|
|
if err != nil {
|
|
cmd.err = err
|
|
}
|
|
value, err := rd.ReadString()
|
|
if err != nil {
|
|
cmd.err = err
|
|
}
|
|
data[key] = value
|
|
}
|
|
|
|
cmd.val = data
|
|
return nil
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
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
|
|
Watch int // redis version 7.4 RC1, number of keys this client is currently watching.
|
|
QueryBuf int // qbuf, query buffer length (0 means no query pending)
|
|
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
|
|
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
|
|
MultiMem int // redis version 7.0, memory is used up by buffered multi commands
|
|
BufferSize int // rbs, usable size of buffer
|
|
BufferPeak int // rbp, peak used size of buffer in last 5 sec interval
|
|
OutputBufferLength int // obl, output buffer length
|
|
OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full)
|
|
OutputMemory int // omem, output buffer memory usage
|
|
TotalMemory int // tot-mem, total memory consumed by this client in its various buffers
|
|
IoThread int // io-thread id
|
|
Events string // file descriptor events (see below)
|
|
LastCmd string // cmd, last command played
|
|
User string // the authenticated username of the client
|
|
Redir int64 // client id of current client tracking redirection
|
|
Resp int // redis version 7.0, client RESP protocol version
|
|
LibName string // redis version 7.2, client library name
|
|
LibVer string // redis version 7.2, client library version
|
|
}
|
|
|
|
type ClientInfoCmd struct {
|
|
baseCmd
|
|
|
|
val *ClientInfo
|
|
}
|
|
|
|
var _ Cmder = (*ClientInfoCmd)(nil)
|
|
|
|
func NewClientInfoCmd(ctx context.Context, args ...interface{}) *ClientInfoCmd {
|
|
return &ClientInfoCmd{
|
|
baseCmd: baseCmd{
|
|
ctx: ctx,
|
|
args: args,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cmd *ClientInfoCmd) SetVal(val *ClientInfo) {
|
|
cmd.val = val
|
|
}
|
|
|
|
func (cmd *ClientInfoCmd) String() string {
|
|
return cmdString(cmd, cmd.val)
|
|
}
|
|
|
|
func (cmd *ClientInfoCmd) Val() *ClientInfo {
|
|
return cmd.val
|
|
}
|
|
|
|
func (cmd *ClientInfoCmd) Result() (*ClientInfo, error) {
|
|
return cmd.val, cmd.err
|
|
}
|
|
|
|
func (cmd *ClientInfoCmd) readReply(rd *proto.Reader) (err error) {
|
|
txt, err := rd.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// sds o = catClientInfoString(sdsempty(), c);
|
|
// o = sdscatlen(o,"\n",1);
|
|
// addReplyVerbatim(c,o,sdslen(o),"txt");
|
|
// sdsfree(o);
|
|
cmd.val, err = parseClientInfo(strings.TrimSpace(txt))
|
|
return err
|
|
}
|
|
|
|
// fmt.Sscanf() cannot handle null values
|
|
func parseClientInfo(txt string) (info *ClientInfo, err error) {
|
|
info = &ClientInfo{}
|
|
for _, s := range strings.Split(txt, " ") {
|
|
kv := strings.Split(s, "=")
|
|
if len(kv) != 2 {
|
|
return nil, fmt.Errorf("redis: unexpected client info data (%s)", s)
|
|
}
|
|
key, val := kv[0], kv[1]
|
|
|
|
switch key {
|
|
case "id":
|
|
info.ID, err = strconv.ParseInt(val, 10, 64)
|
|
case "addr":
|
|
info.Addr = val
|
|
case "laddr":
|
|
info.LAddr = val
|
|
case "fd":
|
|
info.FD, err = strconv.ParseInt(val, 10, 64)
|
|
case "name":
|
|
info.Name = val
|
|
case "age":
|
|
var age int
|
|
if age, err = strconv.Atoi(val); err == nil {
|
|
info.Age = time.Duration(age) * time.Second
|
|
}
|
|
case "idle":
|
|
var idle int
|
|
if idle, err = strconv.Atoi(val); err == nil {
|
|
info.Idle = time.Duration(idle) * time.Second
|
|
}
|
|
case "flags":
|
|
if val == "N" {
|
|
break
|
|
}
|
|
|
|
for i := 0; i < len(val); i++ {
|
|
switch val[i] {
|
|
case 'S':
|
|
info.Flags |= ClientSlave
|
|
case 'O':
|
|
info.Flags |= ClientSlave | ClientMonitor
|
|
case 'M':
|
|
info.Flags |= ClientMaster
|
|
case 'P':
|
|
info.Flags |= ClientPubSub
|
|
case 'x':
|
|
info.Flags |= ClientMulti
|
|
case 'b':
|
|
info.Flags |= ClientBlocked
|
|
case 't':
|
|
info.Flags |= ClientTracking
|
|
case 'R':
|
|
info.Flags |= ClientTrackingBrokenRedir
|
|
case 'B':
|
|
info.Flags |= ClientTrackingBCAST
|
|
case 'd':
|
|
info.Flags |= ClientDirtyCAS
|
|
case 'c':
|
|
info.Flags |= ClientCloseAfterCommand
|
|
case 'u':
|
|
info.Flags |= ClientUnBlocked
|
|
case 'A':
|
|
info.Flags |= ClientCloseASAP
|
|
case 'U':
|
|
info.Flags |= ClientUnixSocket
|
|
case 'r':
|
|
info.Flags |= ClientReadOnly
|
|
case 'e':
|
|
info.Flags |= ClientNoEvict
|
|
case 'T':
|
|
info.Flags |= ClientNoTouch
|
|
default:
|
|
return nil, fmt.Errorf("redis: unexpected client info flags(%s)", string(val[i]))
|
|
}
|
|
}
|
|
case "db":
|
|
info.DB, err = strconv.Atoi(val)
|
|
case "sub":
|
|
info.Sub, err = strconv.Atoi(val)
|
|
case "psub":
|
|
info.PSub, err = strconv.Atoi(val)
|
|
case "ssub":
|
|
info.SSub, err = strconv.Atoi(val)
|
|
case "multi":
|
|
info.Multi, err = strconv.Atoi(val)
|
|
case "watch":
|
|
info.Watch, err = strconv.Atoi(val)
|
|
case "qbuf":
|
|
info.QueryBuf, err = strconv.Atoi(val)
|
|
case "qbuf-free":
|
|
info.QueryBufFree, err = strconv.Atoi(val)
|
|
case "argv-mem":
|
|
info.ArgvMem, err = strconv.Atoi(val)
|
|
case "multi-mem":
|
|
info.MultiMem, err = strconv.Atoi(val)
|
|
case "rbs":
|
|
info.BufferSize, err = strconv.Atoi(val)
|
|
case "rbp":
|
|
info.BufferPeak, err = strconv.Atoi(val)
|
|
case "obl":
|
|
info.OutputBufferLength, err = strconv.Atoi(val)
|
|
case "oll":
|
|
info.OutputListLength, err = strconv.Atoi(val)
|
|
case "omem":
|
|
info.OutputMemory, err = strconv.Atoi(val)
|
|
case "tot-mem":
|
|
info.TotalMemory, err = strconv.Atoi(val)
|
|
case "events":
|
|
info.Events = val
|
|
case "cmd":
|
|
info.LastCmd = val
|
|
case "user":
|
|
info.User = val
|
|
case "redir":
|
|
info.Redir, err = strconv.ParseInt(val, 10, 64)
|
|
case "resp":
|
|
info.Resp, err = strconv.Atoi(val)
|
|
case "lib-name":
|
|
info.LibName = val
|
|
case "lib-ver":
|
|
info.LibVer = val
|
|
case "io-thread":
|
|
info.IoThread, err = strconv.Atoi(val)
|
|
default:
|
|
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// -------------------------------------------
|
|
|
|
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
|
|
pk, _ := rd.Peek(1)
|
|
cmd.mu.Unlock()
|
|
if len(pk) != 0 && st == monitorStatusStart {
|
|
cmd.mu.Lock()
|
|
line, err := rd.ReadString()
|
|
cmd.mu.Unlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.ch <- line
|
|
}
|
|
if st == monitorStatusStop {
|
|
cancel()
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cmd *MonitorCmd) Start() {
|
|
cmd.mu.Lock()
|
|
defer cmd.mu.Unlock()
|
|
cmd.status = monitorStatusStart
|
|
}
|
|
|
|
func (cmd *MonitorCmd) Stop() {
|
|
cmd.mu.Lock()
|
|
defer cmd.mu.Unlock()
|
|
cmd.status = monitorStatusStop
|
|
}
|