Files
Nedyalko Dyakov c9092ec492 fix(auth): make empty-password AUTH opt-in (#4194)
* fix(auth): make empty-password AUTH opt-in

#4161 made a Username with an empty Password send AUTH user "". Redis
OSS accepts it (the nopass default user takes any password), but Redis
Enterprise and Redis Cloud databases with unauthenticated access can
reject it with WRONGPASS, so configs with a username and no password
stopped connecting.

Restore the earlier default (no AUTH without a password) and add
Options.AuthEmptyPassword (auth_empty_password=true) to opt in, on all
options types and credential providers at connect time. Public Hello
again sends AUTH only with a password. The client-side cache namespace
uses the ACL user the connection runs as.

Refs #4161

* fix(auth): keep CSC namespace per username

A Username that sends no AUTH runs as the default user, or as a
TLS-certificate user. Mapping it to the default namespace let clients
with different certificates share cache entries. It now gets its own
namespace per Username ("noauth <name>", which no ACL user can match).

Test: delete the ACL user before closing the admin client, so the user
does not leak into the ACL specs.

* fix(auth): split sentinel empty-password opt-in

Sentinel and data-node credentials are independent, so one flag for
both could force AUTH user "" onto Redis Enterprise data nodes to
authenticate a nopass sentinel user. FailoverOptions and
UniversalOptions gain SentinelAuthEmptyPassword
(sentinel_auth_empty_password=true); AuthEmptyPassword now covers the
master/replica connections only.
2026-10-26 23:32:17 +02:00

929 lines
26 KiB
Go

package redis
import (
"context"
"encoding"
"errors"
"fmt"
"io"
"net"
"reflect"
"runtime"
"strings"
"time"
"github.com/redis/go-redis/v9/internal"
)
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
// otherwise you will receive an error: (error) ERR syntax error.
// For example:
//
// rdb.Set(ctx, key, value, redis.KeepTTL)
const KeepTTL = -1
func usePrecise(dur time.Duration) bool {
return dur < time.Second || dur%time.Second != 0
}
func formatMs(ctx context.Context, dur time.Duration) int64 {
if dur > 0 && dur < time.Millisecond {
internal.Logger.Printf(
ctx,
"specified duration is %s, but minimal supported value is %s - truncating to 1ms",
dur, time.Millisecond,
)
return 1
}
return int64(dur / time.Millisecond)
}
func formatSec(ctx context.Context, dur time.Duration) int64 {
if dur > 0 && dur < time.Second {
internal.Logger.Printf(
ctx,
"specified duration is %s, but minimal supported value is %s - truncating to 1s",
dur, time.Second,
)
return 1
}
return int64(dur / time.Second)
}
func appendArgs(dst, src []interface{}) []interface{} {
if len(src) == 1 {
return appendArg(dst, src[0])
}
if cap(dst) < len(dst)+len(src) {
newDst := make([]interface{}, len(dst), len(dst)+len(src))
copy(newDst, dst)
dst = newDst
}
dst = append(dst, src...)
return dst
}
func appendArg(dst []interface{}, arg interface{}) []interface{} {
switch arg := arg.(type) {
case []string:
for _, s := range arg {
dst = append(dst, s)
}
return dst
case []interface{}:
dst = append(dst, arg...)
return dst
case map[string]interface{}:
for k, v := range arg {
dst = append(dst, k, v)
}
return dst
case map[string]string:
for k, v := range arg {
dst = append(dst, k, v)
}
return dst
case time.Time, time.Duration, encoding.BinaryMarshaler, net.IP:
return append(dst, arg)
case nil:
return dst
default:
// scan struct field
v := reflect.ValueOf(arg)
if v.Type().Kind() == reflect.Pointer {
if v.IsNil() {
// error: arg is not a valid object
return dst
}
v = v.Elem()
}
if v.Type().Kind() == reflect.Struct {
return appendStructField(dst, v)
}
return append(dst, arg)
}
}
// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
tag := typ.Field(i).Tag.Get("redis")
if tag == "" || tag == "-" {
continue
}
name, opt, _ := strings.Cut(tag, ",")
if name == "" {
continue
}
field := v.Field(i)
// miss field
if omitEmpty(opt) && isEmptyValue(field) {
continue
}
if field.CanInterface() {
dst = append(dst, name, field.Interface())
}
}
return dst
}
func omitEmpty(opt string) bool {
for opt != "" {
var name string
name, opt, _ = strings.Cut(opt, ",")
if name == "omitempty" {
return true
}
}
return false
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
case reflect.Struct:
if v.Type() == reflect.TypeOf(time.Time{}) {
return v.IsZero()
}
// Only supports the struct time.Time,
// subsequent iterations will follow the func Scan support decoder.
}
return false
}
type Cmdable interface {
Pipeline() Pipeliner
Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
TxPipeline() Pipeliner
Command(ctx context.Context) *CommandsInfoCmd
CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd
CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd
CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd
ClientGetName(ctx context.Context) *StringCmd
Echo(ctx context.Context, message interface{}) *StringCmd
Ping(ctx context.Context) *StatusCmd
Quit(ctx context.Context) *StatusCmd
Unlink(ctx context.Context, keys ...string) *IntCmd
BgRewriteAOF(ctx context.Context) *StatusCmd
BgSave(ctx context.Context) *StatusCmd
ClientKill(ctx context.Context, ipPort string) *StatusCmd
ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd
ClientList(ctx context.Context) *StringCmd
ClientInfo(ctx context.Context) *ClientInfoCmd
ClientPause(ctx context.Context, dur time.Duration) *BoolCmd
ClientUnpause(ctx context.Context) *BoolCmd
ClientID(ctx context.Context) *IntCmd
ClientUnblock(ctx context.Context, id int64) *IntCmd
ClientUnblockWithError(ctx context.Context, id int64) *IntCmd
ClientMaintNotifications(ctx context.Context, enabled bool, endpointType string) *StatusCmd
ClientTracking(ctx context.Context, on bool, opt *ClientTrackingOptions) *StatusCmd
ClientTrackingOn(ctx context.Context, opt *ClientTrackingOptions) *StatusCmd
ClientTrackingOff(ctx context.Context) *StatusCmd
ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd
ConfigResetStat(ctx context.Context) *StatusCmd
ConfigSet(ctx context.Context, parameter, value string) *StatusCmd
ConfigRewrite(ctx context.Context) *StatusCmd
DBSize(ctx context.Context) *IntCmd
FlushAll(ctx context.Context) *StatusCmd
FlushAllAsync(ctx context.Context) *StatusCmd
FlushDB(ctx context.Context) *StatusCmd
FlushDBAsync(ctx context.Context) *StatusCmd
Info(ctx context.Context, section ...string) *StringCmd
InfoMap(ctx context.Context, section ...string) *InfoCmd
LastSave(ctx context.Context) *IntCmd
Save(ctx context.Context) *StatusCmd
Shutdown(ctx context.Context) *StatusCmd
ShutdownSave(ctx context.Context) *StatusCmd
ShutdownNoSave(ctx context.Context) *StatusCmd
SlaveOf(ctx context.Context, host, port string) *StatusCmd
ReplicaOf(ctx context.Context, host, port string) *StatusCmd
SlowLogGet(ctx context.Context, num int64) *SlowLogCmd
SlowLogLen(ctx context.Context) *IntCmd
SlowLogReset(ctx context.Context) *StatusCmd
Time(ctx context.Context) *TimeCmd
DebugObject(ctx context.Context, key string) *StringCmd
MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd
Latency(ctx context.Context) *LatencyCmd
LatencyReset(ctx context.Context, events ...interface{}) *StatusCmd
ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
ACLCmdable
ArrayCmdable
BitMapCmdable
ClusterCmdable
GenericCmdable
GeoCmdable
HashCmdable
HyperLogLogCmdable
ListCmdable
ProbabilisticCmdable
PubSubCmdable
ScriptingFunctionsCmdable
SearchCmdable
SetCmdable
SortedSetCmdable
StringCmdable
StreamCmdable
TimeseriesCmdable
JSONCmdable
VectorSetCmdable
BlessCmdable
}
type StatefulCmdable interface {
Cmdable
Auth(ctx context.Context, password string) *StatusCmd
AuthACL(ctx context.Context, username, password string) *StatusCmd
Select(ctx context.Context, index int) *StatusCmd
SwapDB(ctx context.Context, index1, index2 int) *StatusCmd
ClientSetName(ctx context.Context, name string) *BoolCmd
ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd
Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd
}
var (
_ Cmdable = (*Client)(nil)
_ Cmdable = (*Tx)(nil)
_ Cmdable = (*Ring)(nil)
_ Cmdable = (*ClusterClient)(nil)
_ Cmdable = (*Pipeline)(nil)
)
type cmdable func(ctx context.Context, cmd Cmder) error
type statefulCmdable func(ctx context.Context, cmd Cmder) error
//------------------------------------------------------------------------------
func (c statefulCmdable) Auth(ctx context.Context, password string) *StatusCmd {
cmd := NewStatusCmd(ctx, "auth", password)
_ = c(ctx, cmd)
return cmd
}
// AuthACL Perform an AUTH command, using the given user and pass.
// Should be used to authenticate the current connection with one of the connections defined in the ACL list
// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
func (c statefulCmdable) AuthACL(ctx context.Context, username, password string) *StatusCmd {
cmd := NewStatusCmd(ctx, "auth", username, password)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd {
cmd := NewIntCmd(ctx, "wait", numSlaves, int(timeout/time.Millisecond))
cmd.setReadTimeout(timeout)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) WaitAOF(ctx context.Context, numLocal, numSlaves int, timeout time.Duration) *IntSliceCmd {
cmd := NewIntSliceCmd(ctx, "waitAOF", numLocal, numSlaves, int(timeout/time.Millisecond))
cmd.setReadTimeout(timeout)
_ = c(ctx, cmd)
return cmd
}
func (c statefulCmdable) Select(ctx context.Context, index int) *StatusCmd {
cmd := NewStatusCmd(ctx, "select", index)
_ = c(ctx, cmd)
return cmd
}
func (c statefulCmdable) SwapDB(ctx context.Context, index1, index2 int) *StatusCmd {
cmd := NewStatusCmd(ctx, "swapdb", index1, index2)
_ = c(ctx, cmd)
return cmd
}
// ClientSetName assigns a name to the connection.
func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCmd {
cmd := NewBoolCmd(ctx, "client", "setname", name)
_ = c(ctx, cmd)
return cmd
}
// ClientSetInfo sends a CLIENT SETINFO command with the provided info.
func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd {
err := info.Validate()
if err != nil {
panic(err.Error())
}
var cmd *StatusCmd
if info.LibName != nil {
libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, internal.ReplaceSpaces(runtime.Version()))
cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName)
} else {
cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer)
}
_ = c(ctx, cmd)
return cmd
}
// Validate checks if only one field in the struct is non-nil.
func (info LibraryInfo) Validate() error {
if info.LibName != nil && info.LibVer != nil {
return errors.New("both LibName and LibVer cannot be set at the same time")
}
if info.LibName == nil && info.LibVer == nil {
return errors.New("at least one of LibName and LibVer should be set")
}
return nil
}
// Hello sets the resp protocol used.
// It sends AUTH only when password is not empty.
func (c statefulCmdable) Hello(ctx context.Context,
ver int, username, password, clientName string,
) *MapStringInterfaceCmd {
return c.hello(ctx, ver, username, password, clientName, password != "")
}
// hello sends HELLO with AUTH when auth is true.
func (c statefulCmdable) hello(ctx context.Context,
ver int, username, password, clientName string, auth bool,
) *MapStringInterfaceCmd {
args := make([]interface{}, 0, 7)
args = append(args, "hello", ver)
if auth {
if username == "" {
username = "default"
}
args = append(args, "auth", username, password)
}
if clientName != "" {
args = append(args, "setname", clientName)
}
cmd := NewMapStringInterfaceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
//------------------------------------------------------------------------------
func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd {
cmd := NewCommandsInfoCmd(ctx, "command")
_ = c(ctx, cmd)
return cmd
}
// FilterBy is used for the `CommandList` command parameter.
type FilterBy struct {
Module string
ACLCat string
Pattern string
}
func (c cmdable) CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd {
args := make([]interface{}, 0, 5)
args = append(args, "command", "list")
if filter != nil {
if filter.Module != "" {
args = append(args, "filterby", "module", filter.Module)
} else if filter.ACLCat != "" {
args = append(args, "filterby", "aclcat", filter.ACLCat)
} else if filter.Pattern != "" {
args = append(args, "filterby", "pattern", filter.Pattern)
}
}
cmd := NewStringSliceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd {
args := make([]interface{}, 2+len(commands))
args[0] = "command"
args[1] = "getkeys"
copy(args[2:], commands)
cmd := NewStringSliceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd {
args := make([]interface{}, 2+len(commands))
args[0] = "command"
args[1] = "getkeysandflags"
copy(args[2:], commands)
cmd := NewKeyFlagsCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
// ClientGetName returns the name of the connection.
func (c cmdable) ClientGetName(ctx context.Context) *StringCmd {
cmd := NewStringCmd(ctx, "client", "getname")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Echo(ctx context.Context, message interface{}) *StringCmd {
cmd := NewStringCmd(ctx, "echo", message)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Ping(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "ping")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Do(ctx context.Context, args ...interface{}) *Cmd {
cmd := NewCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
// DoRaw executes a command and returns the raw RESP protocol bytes without parsing.
func (c cmdable) DoRaw(ctx context.Context, args ...interface{}) *RawCmd {
cmd := NewRawCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
// DoRawWriteTo executes a command and streams raw RESP bytes directly to w without intermediate allocations.
func (c cmdable) DoRawWriteTo(ctx context.Context, w io.Writer, args ...interface{}) *RawWriteToCmd {
cmd := NewRawWriteToCmd(ctx, w, args...)
_ = c(ctx, cmd)
return cmd
}
// Quit closes the connection.
//
// Deprecated: Just close the connection instead as of Redis 7.2.0.
func (c cmdable) Quit(_ context.Context) *StatusCmd {
panic("not implemented")
}
//------------------------------------------------------------------------------
func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "bgrewriteaof")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) BgSave(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "bgsave")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd {
cmd := NewStatusCmd(ctx, "client", "kill", ipPort)
_ = c(ctx, cmd)
return cmd
}
// ClientKillByFilter is new style syntax, while the ClientKill is old
//
// CLIENT KILL <option> [value] ... <option> [value]
func (c cmdable) ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd {
args := make([]interface{}, 2+len(keys))
args[0] = "client"
args[1] = "kill"
for i, key := range keys {
args[2+i] = key
}
cmd := NewIntCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientList(ctx context.Context) *StringCmd {
cmd := NewStringCmd(ctx, "client", "list")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientPause(ctx context.Context, dur time.Duration) *BoolCmd {
cmd := NewBoolCmd(ctx, "client", "pause", formatMs(ctx, dur))
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientUnpause(ctx context.Context) *BoolCmd {
cmd := NewBoolCmd(ctx, "client", "unpause")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientID(ctx context.Context) *IntCmd {
cmd := NewIntCmd(ctx, "client", "id")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientUnblock(ctx context.Context, id int64) *IntCmd {
cmd := NewIntCmd(ctx, "client", "unblock", id)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd {
cmd := NewIntCmd(ctx, "client", "unblock", id, "error")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ClientInfo(ctx context.Context) *ClientInfoCmd {
cmd := NewClientInfoCmd(ctx, "client", "info")
_ = c(ctx, cmd)
return cmd
}
// ClientMaintNotifications enables or disables maintenance notifications for maintenance upgrades.
// When enabled, the client will receive push notifications about Redis maintenance events.
func (c cmdable) ClientMaintNotifications(ctx context.Context, enabled bool, endpointType string) *StatusCmd {
args := []interface{}{"client", "maint_notifications"}
if enabled {
if endpointType == "" {
endpointType = "none"
}
args = append(args, "on", "moving-endpoint-type", endpointType)
} else {
args = append(args, "off")
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
// ClientTrackingOptions configures CLIENT TRACKING ON. See
// https://redis.io/commands/client-tracking/ for semantics.
type ClientTrackingOptions struct {
Redirect int64
Bcast bool
Prefixes []string
OptIn bool
OptOut bool
NoLoop bool
}
// ClientTracking enables or disables server-assisted client-side caching for
// the ONE connection that happens to serve this command. On a pooled client
// that connection is arbitrary, so this is only meaningful on a dedicated
// connection (see Client.Conn). When on is false, opt is ignored. Invalid
// option combinations are reported via the returned command's Err and nothing
// is sent to the server.
//
// Must not be combined with the built-in client-side cache: on a client
// configured with Options.ClientSideCache or ClientSideCacheConfig this
// command is rejected, because changing a pool connection's tracking state
// would silently break the cache's invalidation.
func (c cmdable) ClientTracking(ctx context.Context, on bool, opt *ClientTrackingOptions) *StatusCmd {
if !on {
return c.ClientTrackingOff(ctx)
}
return c.ClientTrackingOn(ctx, opt)
}
// ClientTrackingOn enables tracking on the serving connection. See
// ClientTracking for the pooled-client and built-in-CSC caveats.
func (c cmdable) ClientTrackingOn(ctx context.Context, opt *ClientTrackingOptions) *StatusCmd {
args := []interface{}{"client", "tracking", "on"}
if opt != nil {
if err := validateClientTrackingOptions(opt); err != nil {
cmd := NewStatusCmd(ctx, args...)
cmd.SetErr(err)
return cmd
}
args = appendClientTrackingOptions(args, opt)
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
// ClientTrackingOff disables tracking on the serving connection. See
// ClientTracking for the pooled-client and built-in-CSC caveats.
func (c cmdable) ClientTrackingOff(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "client", "tracking", "off")
_ = c(ctx, cmd)
return cmd
}
func validateClientTrackingOptions(opt *ClientTrackingOptions) error {
if opt.OptIn && opt.OptOut {
return errors.New("redis: CLIENT TRACKING OPTIN and OPTOUT are mutually exclusive")
}
if opt.Bcast && (opt.OptIn || opt.OptOut) {
return errors.New("redis: CLIENT TRACKING BCAST cannot be combined with OPTIN or OPTOUT")
}
if len(opt.Prefixes) > 0 && !opt.Bcast {
return errors.New("redis: CLIENT TRACKING PREFIX requires BCAST")
}
return nil
}
func appendClientTrackingOptions(args []interface{}, opt *ClientTrackingOptions) []interface{} {
if opt.Redirect != 0 {
args = append(args, "redirect", opt.Redirect)
}
if opt.Bcast {
args = append(args, "bcast")
}
for _, p := range opt.Prefixes {
args = append(args, "prefix", p)
}
if opt.OptIn {
args = append(args, "optin")
}
if opt.OptOut {
args = append(args, "optout")
}
if opt.NoLoop {
args = append(args, "noloop")
}
return args
}
// ------------------------------------------------------------------------------------------------
func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd {
cmd := NewMapStringStringCmd(ctx, "config", "get", parameter)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ConfigResetStat(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "config", "resetstat")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ConfigSet(ctx context.Context, parameter, value string) *StatusCmd {
cmd := NewStatusCmd(ctx, "config", "set", parameter, value)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ConfigRewrite(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "config", "rewrite")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) DBSize(ctx context.Context) *IntCmd {
cmd := NewIntCmd(ctx, "dbsize")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FlushAll(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "flushall")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FlushAllAsync(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "flushall", "async")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FlushDB(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "flushdb")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) FlushDBAsync(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "flushdb", "async")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Info(ctx context.Context, sections ...string) *StringCmd {
args := make([]interface{}, 1+len(sections))
args[0] = "info"
for i, section := range sections {
args[i+1] = section
}
cmd := NewStringCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) InfoMap(ctx context.Context, sections ...string) *InfoCmd {
args := make([]interface{}, 1+len(sections))
args[0] = "info"
for i, section := range sections {
args[i+1] = section
}
cmd := NewInfoCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) LastSave(ctx context.Context) *IntCmd {
cmd := NewIntCmd(ctx, "lastsave")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Save(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "save")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) shutdown(ctx context.Context, modifier string) *StatusCmd {
var args []interface{}
if modifier == "" {
args = []interface{}{"shutdown"}
} else {
args = []interface{}{"shutdown", modifier}
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
if err := cmd.Err(); err != nil {
if err == io.EOF {
// Server quit as expected.
cmd.err = nil
}
} else {
// Server did not quit. String reply contains the reason.
cmd.err = errors.New(cmd.val)
cmd.val = ""
}
return cmd
}
func (c cmdable) Shutdown(ctx context.Context) *StatusCmd {
return c.shutdown(ctx, "")
}
func (c cmdable) ShutdownSave(ctx context.Context) *StatusCmd {
return c.shutdown(ctx, "save")
}
func (c cmdable) ShutdownNoSave(ctx context.Context) *StatusCmd {
return c.shutdown(ctx, "nosave")
}
// SlaveOf sets a Redis server as a replica of another, or promotes it to being a master.
//
// Deprecated: Use ReplicaOf instead as of Redis 5.0.0.
func (c cmdable) SlaveOf(ctx context.Context, host, port string) *StatusCmd {
cmd := NewStatusCmd(ctx, "slaveof", host, port)
_ = c(ctx, cmd)
return cmd
}
// ReplicaOf sets a Redis server as a replica of another, or promotes it to being a master.
func (c cmdable) ReplicaOf(ctx context.Context, host, port string) *StatusCmd {
cmd := NewStatusCmd(ctx, "replicaof", host, port)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) SlowLogGet(ctx context.Context, num int64) *SlowLogCmd {
cmd := NewSlowLogCmd(ctx, "slowlog", "get", num)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) SlowLogLen(ctx context.Context) *IntCmd {
cmd := NewIntCmd(ctx, "slowlog", "len")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) SlowLogReset(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "slowlog", "reset")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Latency(ctx context.Context) *LatencyCmd {
cmd := NewLatencyCmd(ctx, "latency", "latest")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) LatencyReset(ctx context.Context, events ...interface{}) *StatusCmd {
args := make([]interface{}, 2+len(events))
args[0] = "latency"
args[1] = "reset"
copy(args[2:], events)
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Sync(_ context.Context) {
panic("not implemented")
}
func (c cmdable) Time(ctx context.Context) *TimeCmd {
cmd := NewTimeCmd(ctx, "time")
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) DebugObject(ctx context.Context, key string) *StringCmd {
cmd := NewStringCmd(ctx, "debug", "object", key)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd {
args := []interface{}{"memory", "usage", key}
if len(samples) > 0 {
if len(samples) != 1 {
cmd := NewIntCmd(ctx)
cmd.SetErr(errors.New("MemoryUsage expects single sample count"))
return cmd
}
args = append(args, "SAMPLES", samples[0])
}
cmd := NewIntCmd(ctx, args...)
cmd.SetFirstKeyPos(2)
_ = c(ctx, cmd)
return cmd
}
//------------------------------------------------------------------------------
// ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis.
// `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]`
type ModuleLoadexConfig struct {
Path string
Conf map[string]interface{}
Args []interface{}
}
func (c *ModuleLoadexConfig) toArgs() []interface{} {
args := make([]interface{}, 3, 4+len(c.Conf)*3+len(c.Args))
args[0] = "MODULE"
args[1] = "LOADEX"
args[2] = c.Path
for k, v := range c.Conf {
args = append(args, "CONFIG", k, v)
}
// ARGS comes once, and every argument after it goes to the module.
if len(c.Args) > 0 {
args = append(args, "ARGS")
args = append(args, c.Args...)
}
return args
}
// ModuleLoadex Redis `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` command.
func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd {
if conf == nil {
cmd := NewStringCmd(ctx)
cmd.SetErr(errors.New("redis: ModuleLoadex nil config"))
return cmd
}
cmd := NewStringCmd(ctx, conf.toArgs()...)
_ = c(ctx, cmd)
return cmd
}
/*
Monitor - represents a Redis MONITOR command, allowing the user to capture
and process all commands sent to a Redis server. This mimics the behavior of
MONITOR in the redis-cli.
Notes:
- Using MONITOR blocks the connection to the server for itself. It needs a dedicated connection
- The user should create a channel of type string
- This runs concurrently in the background. Trigger via the Start and Stop functions
See further: Redis MONITOR command: https://redis.io/commands/monitor
*/
func (c cmdable) Monitor(ctx context.Context, ch chan string) *MonitorCmd {
cmd := newMonitorCmd(ctx, ch)
_ = c(ctx, cmd)
return cmd
}