pax_global_header00006660000000000000000000000064134057276410014523gustar00rootroot0000000000000052 comment=7f89fbac80bcc62ce920b6dbc6ca60238d7725d1 redis-6.15.0/000077500000000000000000000000001340572764100127225ustar00rootroot00000000000000redis-6.15.0/.gitignore000066400000000000000000000000221340572764100147040ustar00rootroot00000000000000*.rdb testdata/*/ redis-6.15.0/.travis.yml000066400000000000000000000003311340572764100150300ustar00rootroot00000000000000sudo: false language: go services: - redis-server go: - 1.9.x - 1.10.x - 1.11.x - tip matrix: allow_failures: - go: tip install: - go get github.com/onsi/ginkgo - go get github.com/onsi/gomega redis-6.15.0/CHANGELOG.md000066400000000000000000000021241340572764100145320ustar00rootroot00000000000000# Changelog ## Unreleased - Cluster and Ring pipelines process commands for each node in its own goroutine. ## 6.14 - Added Options.MinIdleConns. - Added Options.MaxConnAge. - PoolStats.FreeConns is renamed to PoolStats.IdleConns. - Add Client.Do to simplify creating custom commands. - Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. - Lower memory usage. ## v6.13 - Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. - Cluster client was optimized to use much less memory when reloading cluster state. - PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. - Dialer.KeepAlive is set to 5 minutes by default. ## v6.12 - ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup redis-6.15.0/LICENSE000066400000000000000000000024461340572764100137350ustar00rootroot00000000000000Copyright (c) 2013 The github.com/go-redis/redis Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. redis-6.15.0/Makefile000066400000000000000000000011121340572764100143550ustar00rootroot00000000000000all: testdeps go test ./... go test ./... -short -race env GOOS=linux GOARCH=386 go test ./... go vet go get github.com/gordonklaus/ineffassign ineffassign . testdeps: testdata/redis/src/redis-server bench: testdeps go test ./... -test.run=NONE -test.bench=. -test.benchmem .PHONY: all test testdeps bench testdata/redis: mkdir -p $@ wget -qO- https://github.com/antirez/redis/archive/unstable.tar.gz | tar xvz --strip-components=1 -C $@ testdata/redis/src/redis-server: testdata/redis sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ } func ExampleClient() { err := client.Set("key", "value", 0).Err() if err != nil { panic(err) } val, err := client.Get("key").Result() if err != nil { panic(err) } fmt.Println("key", val) val2, err := client.Get("key2").Result() if err == redis.Nil { fmt.Println("key2 does not exist") } else if err != nil { panic(err) } else { fmt.Println("key2", val2) } // Output: key value // key2 does not exist } ``` ## Howto Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. ## Look and feel Some corner cases: ```go // SET key value EX 10 NX set, err := client.SetNX("key", "value", 10*time.Second).Result() // SORT list LIMIT 0 2 ASC vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() // ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ Min: "-inf", Max: "+inf", Offset: 0, Count: 2, }).Result() // ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() // EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() ``` ## Benchmark go-redis vs redigo: ``` BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op ``` Redis Cluster: ``` BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op ``` ## See also - [Golang PostgreSQL ORM](https://github.com/go-pg/pg) - [Golang msgpack](https://github.com/vmihailenco/msgpack) - [Golang message task queue](https://github.com/go-msgqueue/msgqueue) redis-6.15.0/bench_test.go000066400000000000000000000071301340572764100153700ustar00rootroot00000000000000package redis_test import ( "bytes" "fmt" "strings" "testing" "time" "github.com/go-redis/redis" ) func benchmarkRedisClient(poolSize int) *redis.Client { client := redis.NewClient(&redis.Options{ Addr: ":6379", DialTimeout: time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, PoolSize: poolSize, }) if err := client.FlushDB().Err(); err != nil { panic(err) } return client } func BenchmarkRedisPing(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.Ping().Err(); err != nil { b.Fatal(err) } } }) } func BenchmarkRedisGetNil(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.Get("key").Err(); err != redis.Nil { b.Fatal(err) } } }) } type setStringBenchmark struct { poolSize int valueSize int } func (bm setStringBenchmark) String() string { return fmt.Sprintf("pool=%d value=%d", bm.poolSize, bm.valueSize) } func BenchmarkRedisSetString(b *testing.B) { benchmarks := []setStringBenchmark{ {10, 64}, {10, 1024}, {10, 64 * 1024}, {10, 1024 * 1024}, {10, 10 * 1024 * 1024}, {100, 64}, {100, 1024}, {100, 64 * 1024}, {100, 1024 * 1024}, {100, 10 * 1024 * 1024}, } for _, bm := range benchmarks { b.Run(bm.String(), func(b *testing.B) { client := benchmarkRedisClient(bm.poolSize) defer client.Close() value := strings.Repeat("1", bm.valueSize) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := client.Set("key", value, 0).Err() if err != nil { b.Fatal(err) } } }) }) } } func BenchmarkRedisSetGetBytes(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() value := bytes.Repeat([]byte{'1'}, 10000) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.Set("key", value, 0).Err(); err != nil { b.Fatal(err) } got, err := client.Get("key").Bytes() if err != nil { b.Fatal(err) } if !bytes.Equal(got, value) { b.Fatalf("got != value") } } }) } func BenchmarkRedisMGet(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() if err := client.MSet("key1", "hello1", "key2", "hello2").Err(); err != nil { b.Fatal(err) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.MGet("key1", "key2").Err(); err != nil { b.Fatal(err) } } }) } func BenchmarkSetExpire(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.Set("key", "hello", 0).Err(); err != nil { b.Fatal(err) } if err := client.Expire("key", time.Second).Err(); err != nil { b.Fatal(err) } } }) } func BenchmarkPipeline(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Set("key", "hello", 0) pipe.Expire("key", time.Second) return nil }) if err != nil { b.Fatal(err) } } }) } func BenchmarkZAdd(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := client.ZAdd("key", redis.Z{ Score: float64(1), Member: "hello", }).Err() if err != nil { b.Fatal(err) } } }) } redis-6.15.0/cluster.go000066400000000000000000001004201340572764100147270ustar00rootroot00000000000000package redis import ( "context" "crypto/tls" "fmt" "math" "math/rand" "net" "runtime" "sort" "sync" "sync/atomic" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/hashtag" "github.com/go-redis/redis/internal/pool" "github.com/go-redis/redis/internal/proto" "github.com/go-redis/redis/internal/singleflight" ) var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") // ClusterOptions are used to configure a cluster client and should be // passed to NewClusterClient. type ClusterOptions struct { // A seed list of host:port addresses of cluster nodes. Addrs []string // The maximum number of retries before giving up. Command is retried // on network errors and MOVED/ASK redirects. // Default is 8 retries. MaxRedirects int // Enables read-only commands on slave nodes. ReadOnly bool // Allows routing read-only commands to the closest master or slave node. // It automatically enables ReadOnly. RouteByLatency bool // Allows routing read-only commands to the random master or slave node. // It automatically enables ReadOnly. RouteRandomly bool // Optional function that returns cluster slots information. // It is useful to manually create cluster of standalone Redis servers // and load-balance read/write operations between master and slaves. // It can use service like ZooKeeper to maintain configuration information // and Cluster.ReloadState to manually trigger state reloading. ClusterSlots func() ([]ClusterSlot, error) // Optional hook that is called when a new node is created. OnNewNode func(*Client) // Following options are copied from Options struct. OnConnect func(*Conn) error Password string MaxRetries int MinRetryBackoff time.Duration MaxRetryBackoff time.Duration DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration // PoolSize applies per cluster node and not for the whole cluster. PoolSize int MinIdleConns int MaxConnAge time.Duration PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration TLSConfig *tls.Config } func (opt *ClusterOptions) init() { if opt.MaxRedirects == -1 { opt.MaxRedirects = 0 } else if opt.MaxRedirects == 0 { opt.MaxRedirects = 8 } if (opt.RouteByLatency || opt.RouteRandomly) && opt.ClusterSlots == nil { opt.ReadOnly = true } if opt.PoolSize == 0 { opt.PoolSize = 5 * runtime.NumCPU() } switch opt.ReadTimeout { case -1: opt.ReadTimeout = 0 case 0: opt.ReadTimeout = 3 * time.Second } switch opt.WriteTimeout { case -1: opt.WriteTimeout = 0 case 0: opt.WriteTimeout = opt.ReadTimeout } switch opt.MinRetryBackoff { case -1: opt.MinRetryBackoff = 0 case 0: opt.MinRetryBackoff = 8 * time.Millisecond } switch opt.MaxRetryBackoff { case -1: opt.MaxRetryBackoff = 0 case 0: opt.MaxRetryBackoff = 512 * time.Millisecond } } func (opt *ClusterOptions) clientOptions() *Options { const disableIdleCheck = -1 return &Options{ OnConnect: opt.OnConnect, MaxRetries: opt.MaxRetries, MinRetryBackoff: opt.MinRetryBackoff, MaxRetryBackoff: opt.MaxRetryBackoff, Password: opt.Password, readOnly: opt.ReadOnly, DialTimeout: opt.DialTimeout, ReadTimeout: opt.ReadTimeout, WriteTimeout: opt.WriteTimeout, PoolSize: opt.PoolSize, MinIdleConns: opt.MinIdleConns, MaxConnAge: opt.MaxConnAge, PoolTimeout: opt.PoolTimeout, IdleTimeout: opt.IdleTimeout, IdleCheckFrequency: disableIdleCheck, TLSConfig: opt.TLSConfig, } } //------------------------------------------------------------------------------ type clusterNode struct { Client *Client latency uint32 // atomic generation uint32 // atomic loading uint32 // atomic } func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { opt := clOpt.clientOptions() opt.Addr = addr node := clusterNode{ Client: NewClient(opt), } node.latency = math.MaxUint32 if clOpt.RouteByLatency { go node.updateLatency() } if clOpt.OnNewNode != nil { clOpt.OnNewNode(node.Client) } return &node } func (n *clusterNode) String() string { return n.Client.String() } func (n *clusterNode) Close() error { return n.Client.Close() } func (n *clusterNode) updateLatency() { const probes = 10 var latency uint32 for i := 0; i < probes; i++ { start := time.Now() n.Client.Ping() probe := uint32(time.Since(start) / time.Microsecond) latency = (latency + probe) / 2 } atomic.StoreUint32(&n.latency, latency) } func (n *clusterNode) Latency() time.Duration { latency := atomic.LoadUint32(&n.latency) return time.Duration(latency) * time.Microsecond } func (n *clusterNode) MarkAsLoading() { atomic.StoreUint32(&n.loading, uint32(time.Now().Unix())) } func (n *clusterNode) Loading() bool { const minute = int64(time.Minute / time.Second) loading := atomic.LoadUint32(&n.loading) if loading == 0 { return false } if time.Now().Unix()-int64(loading) < minute { return true } atomic.StoreUint32(&n.loading, 0) return false } func (n *clusterNode) Generation() uint32 { return atomic.LoadUint32(&n.generation) } func (n *clusterNode) SetGeneration(gen uint32) { for { v := atomic.LoadUint32(&n.generation) if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) { break } } } //------------------------------------------------------------------------------ type clusterNodes struct { opt *ClusterOptions mu sync.RWMutex allAddrs []string allNodes map[string]*clusterNode clusterAddrs []string closed bool nodeCreateGroup singleflight.Group _generation uint32 // atomic } func newClusterNodes(opt *ClusterOptions) *clusterNodes { return &clusterNodes{ opt: opt, allAddrs: opt.Addrs, allNodes: make(map[string]*clusterNode), } } func (c *clusterNodes) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.closed { return nil } c.closed = true var firstErr error for _, node := range c.allNodes { if err := node.Client.Close(); err != nil && firstErr == nil { firstErr = err } } c.allNodes = nil c.clusterAddrs = nil return firstErr } func (c *clusterNodes) Addrs() ([]string, error) { var addrs []string c.mu.RLock() closed := c.closed if !closed { if len(c.clusterAddrs) > 0 { addrs = c.clusterAddrs } else { addrs = c.allAddrs } } c.mu.RUnlock() if closed { return nil, pool.ErrClosed } if len(addrs) == 0 { return nil, errClusterNoNodes } return addrs, nil } func (c *clusterNodes) NextGeneration() uint32 { return atomic.AddUint32(&c._generation, 1) } // GC removes unused nodes. func (c *clusterNodes) GC(generation uint32) { var collected []*clusterNode c.mu.Lock() for addr, node := range c.allNodes { if node.Generation() >= generation { continue } c.clusterAddrs = remove(c.clusterAddrs, addr) delete(c.allNodes, addr) collected = append(collected, node) } c.mu.Unlock() for _, node := range collected { _ = node.Client.Close() } } func (c *clusterNodes) Get(addr string) (*clusterNode, error) { var node *clusterNode var err error c.mu.RLock() if c.closed { err = pool.ErrClosed } else { node = c.allNodes[addr] } c.mu.RUnlock() return node, err } func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { node, err := c.Get(addr) if err != nil { return nil, err } if node != nil { return node, nil } v, err := c.nodeCreateGroup.Do(addr, func() (interface{}, error) { node := newClusterNode(c.opt, addr) return node, nil }) c.mu.Lock() defer c.mu.Unlock() if c.closed { return nil, pool.ErrClosed } node, ok := c.allNodes[addr] if ok { _ = v.(*clusterNode).Close() return node, err } node = v.(*clusterNode) c.allAddrs = appendIfNotExists(c.allAddrs, addr) if err == nil { c.clusterAddrs = append(c.clusterAddrs, addr) } c.allNodes[addr] = node return node, err } func (c *clusterNodes) All() ([]*clusterNode, error) { c.mu.RLock() defer c.mu.RUnlock() if c.closed { return nil, pool.ErrClosed } cp := make([]*clusterNode, 0, len(c.allNodes)) for _, node := range c.allNodes { cp = append(cp, node) } return cp, nil } func (c *clusterNodes) Random() (*clusterNode, error) { addrs, err := c.Addrs() if err != nil { return nil, err } n := rand.Intn(len(addrs)) return c.GetOrCreate(addrs[n]) } //------------------------------------------------------------------------------ type clusterSlot struct { start, end int nodes []*clusterNode } type clusterSlotSlice []*clusterSlot func (p clusterSlotSlice) Len() int { return len(p) } func (p clusterSlotSlice) Less(i, j int) bool { return p[i].start < p[j].start } func (p clusterSlotSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } type clusterState struct { nodes *clusterNodes Masters []*clusterNode Slaves []*clusterNode slots []*clusterSlot generation uint32 createdAt time.Time } func newClusterState( nodes *clusterNodes, slots []ClusterSlot, origin string, ) (*clusterState, error) { c := clusterState{ nodes: nodes, slots: make([]*clusterSlot, 0, len(slots)), generation: nodes.NextGeneration(), createdAt: time.Now(), } originHost, _, _ := net.SplitHostPort(origin) isLoopbackOrigin := isLoopback(originHost) for _, slot := range slots { var nodes []*clusterNode for i, slotNode := range slot.Nodes { addr := slotNode.Addr if !isLoopbackOrigin { addr = replaceLoopbackHost(addr, originHost) } node, err := c.nodes.GetOrCreate(addr) if err != nil { return nil, err } node.SetGeneration(c.generation) nodes = append(nodes, node) if i == 0 { c.Masters = appendUniqueNode(c.Masters, node) } else { c.Slaves = appendUniqueNode(c.Slaves, node) } } c.slots = append(c.slots, &clusterSlot{ start: slot.Start, end: slot.End, nodes: nodes, }) } sort.Sort(clusterSlotSlice(c.slots)) time.AfterFunc(time.Minute, func() { nodes.GC(c.generation) }) return &c, nil } func replaceLoopbackHost(nodeAddr, originHost string) string { nodeHost, nodePort, err := net.SplitHostPort(nodeAddr) if err != nil { return nodeAddr } nodeIP := net.ParseIP(nodeHost) if nodeIP == nil { return nodeAddr } if !nodeIP.IsLoopback() { return nodeAddr } // Use origin host which is not loopback and node port. return net.JoinHostPort(originHost, nodePort) } func isLoopback(host string) bool { ip := net.ParseIP(host) if ip == nil { return true } return ip.IsLoopback() } func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { nodes := c.slotNodes(slot) if len(nodes) > 0 { return nodes[0], nil } return c.nodes.Random() } func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { nodes := c.slotNodes(slot) switch len(nodes) { case 0: return c.nodes.Random() case 1: return nodes[0], nil case 2: if slave := nodes[1]; !slave.Loading() { return slave, nil } return nodes[0], nil default: var slave *clusterNode for i := 0; i < 10; i++ { n := rand.Intn(len(nodes)-1) + 1 slave = nodes[n] if !slave.Loading() { return slave, nil } } // All slaves are loading - use master. return nodes[0], nil } } func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { const threshold = time.Millisecond nodes := c.slotNodes(slot) if len(nodes) == 0 { return c.nodes.Random() } var node *clusterNode for _, n := range nodes { if n.Loading() { continue } if node == nil || node.Latency()-n.Latency() > threshold { node = n } } return node, nil } func (c *clusterState) slotRandomNode(slot int) *clusterNode { nodes := c.slotNodes(slot) n := rand.Intn(len(nodes)) return nodes[n] } func (c *clusterState) slotNodes(slot int) []*clusterNode { i := sort.Search(len(c.slots), func(i int) bool { return c.slots[i].end >= slot }) if i >= len(c.slots) { return nil } x := c.slots[i] if slot >= x.start && slot <= x.end { return x.nodes } return nil } //------------------------------------------------------------------------------ type clusterStateHolder struct { load func() (*clusterState, error) state atomic.Value reloading uint32 // atomic } func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder { return &clusterStateHolder{ load: fn, } } func (c *clusterStateHolder) Reload() (*clusterState, error) { state, err := c.load() if err != nil { return nil, err } c.state.Store(state) return state, nil } func (c *clusterStateHolder) LazyReload() { if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { return } go func() { defer atomic.StoreUint32(&c.reloading, 0) _, err := c.Reload() if err != nil { return } time.Sleep(100 * time.Millisecond) }() } func (c *clusterStateHolder) Get() (*clusterState, error) { v := c.state.Load() if v != nil { state := v.(*clusterState) if time.Since(state.createdAt) > time.Minute { c.LazyReload() } return state, nil } return c.Reload() } func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { state, err := c.Reload() if err == nil { return state, nil } return c.Get() } //------------------------------------------------------------------------------ // ClusterClient is a Redis Cluster client representing a pool of zero // or more underlying connections. It's safe for concurrent use by // multiple goroutines. type ClusterClient struct { cmdable ctx context.Context opt *ClusterOptions nodes *clusterNodes state *clusterStateHolder cmdsInfoCache *cmdsInfoCache process func(Cmder) error processPipeline func([]Cmder) error processTxPipeline func([]Cmder) error } // NewClusterClient returns a Redis Cluster client as described in // http://redis.io/topics/cluster-spec. func NewClusterClient(opt *ClusterOptions) *ClusterClient { opt.init() c := &ClusterClient{ opt: opt, nodes: newClusterNodes(opt), } c.state = newClusterStateHolder(c.loadState) c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) c.process = c.defaultProcess c.processPipeline = c.defaultProcessPipeline c.processTxPipeline = c.defaultProcessTxPipeline c.init() if opt.IdleCheckFrequency > 0 { go c.reaper(opt.IdleCheckFrequency) } return c } func (c *ClusterClient) init() { c.cmdable.setProcessor(c.Process) } // ReloadState reloads cluster state. If available it calls ClusterSlots func // to get cluster slots information. func (c *ClusterClient) ReloadState() error { _, err := c.state.Reload() return err } func (c *ClusterClient) Context() context.Context { if c.ctx != nil { return c.ctx } return context.Background() } func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { if ctx == nil { panic("nil context") } c2 := c.copy() c2.ctx = ctx return c2 } func (c *ClusterClient) copy() *ClusterClient { cp := *c cp.init() return &cp } // Options returns read-only Options that were used to create the client. func (c *ClusterClient) Options() *ClusterOptions { return c.opt } func (c *ClusterClient) retryBackoff(attempt int) time.Duration { return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) } func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { addrs, err := c.nodes.Addrs() if err != nil { return nil, err } var firstErr error for _, addr := range addrs { node, err := c.nodes.Get(addr) if err != nil { return nil, err } if node == nil { continue } info, err := node.Client.Command().Result() if err == nil { return info, nil } if firstErr == nil { firstErr = err } } return nil, firstErr } func (c *ClusterClient) cmdInfo(name string) *CommandInfo { cmdsInfo, err := c.cmdsInfoCache.Get() if err != nil { return nil } info := cmdsInfo[name] if info == nil { internal.Logf("info for cmd=%s not found", name) } return info } func cmdSlot(cmd Cmder, pos int) int { if pos == 0 { return hashtag.RandomSlot() } firstKey := cmd.stringArg(pos) return hashtag.Slot(firstKey) } func (c *ClusterClient) cmdSlot(cmd Cmder) int { cmdInfo := c.cmdInfo(cmd.Name()) return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) } func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { state, err := c.state.Get() if err != nil { return 0, nil, err } cmdInfo := c.cmdInfo(cmd.Name()) slot := cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { if c.opt.RouteByLatency { node, err := state.slotClosestNode(slot) return slot, node, err } if c.opt.RouteRandomly { node := state.slotRandomNode(slot) return slot, node, nil } node, err := state.slotSlaveNode(slot) return slot, node, err } node, err := state.slotMasterNode(slot) return slot, node, err } func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { state, err := c.state.Get() if err != nil { return nil, err } nodes := state.slotNodes(slot) if len(nodes) > 0 { return nodes[0], nil } return c.nodes.Random() } func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { if len(keys) == 0 { return fmt.Errorf("redis: Watch requires at least one key") } slot := hashtag.Slot(keys[0]) for _, key := range keys[1:] { if hashtag.Slot(key) != slot { err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") return err } } node, err := c.slotMasterNode(slot) if err != nil { return err } for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } err = node.Client.Watch(fn, keys...) if err == nil { break } if internal.IsRetryableError(err, true) { c.state.LazyReload() continue } moved, ask, addr := internal.IsMovedError(err) if moved || ask { c.state.LazyReload() node, err = c.nodes.GetOrCreate(addr) if err != nil { return err } continue } if err == pool.ErrClosed { node, err = c.slotMasterNode(slot) if err != nil { return err } continue } return err } return err } // Close closes the cluster client, releasing any open resources. // // It is rare to Close a ClusterClient, as the ClusterClient is meant // to be long-lived and shared between many goroutines. func (c *ClusterClient) Close() error { return c.nodes.Close() } // Do creates a Cmd from the args and processes the cmd. func (c *ClusterClient) Do(args ...interface{}) *Cmd { cmd := NewCmd(args...) c.Process(cmd) return cmd } func (c *ClusterClient) WrapProcess( fn func(oldProcess func(Cmder) error) func(Cmder) error, ) { c.process = fn(c.process) } func (c *ClusterClient) Process(cmd Cmder) error { return c.process(cmd) } func (c *ClusterClient) defaultProcess(cmd Cmder) error { var node *clusterNode var ask bool for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } if node == nil { var err error _, node, err = c.cmdSlotAndNode(cmd) if err != nil { cmd.setErr(err) break } } var err error if ask { pipe := node.Client.Pipeline() _ = pipe.Process(NewCmd("ASKING")) _ = pipe.Process(cmd) _, err = pipe.Exec() _ = pipe.Close() ask = false } else { err = node.Client.Process(cmd) } // If there is no error - we are done. if err == nil { break } // If slave is loading - pick another node. if c.opt.ReadOnly && internal.IsLoadingError(err) { node.MarkAsLoading() node = nil continue } if internal.IsRetryableError(err, true) { c.state.LazyReload() // First retry the same node. if attempt == 0 { continue } // Second try random node. node, err = c.nodes.Random() if err != nil { break } continue } var moved bool var addr string moved, ask, addr = internal.IsMovedError(err) if moved || ask { c.state.LazyReload() node, err = c.nodes.GetOrCreate(addr) if err != nil { break } continue } if err == pool.ErrClosed { node = nil continue } break } return cmd.Err() } // ForEachMaster concurrently calls the fn on each master node in the cluster. // It returns the first error if any. func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { state, err := c.state.ReloadOrGet() if err != nil { return err } var wg sync.WaitGroup errCh := make(chan error, 1) for _, master := range state.Masters { wg.Add(1) go func(node *clusterNode) { defer wg.Done() err := fn(node.Client) if err != nil { select { case errCh <- err: default: } } }(master) } wg.Wait() select { case err := <-errCh: return err default: return nil } } // ForEachSlave concurrently calls the fn on each slave node in the cluster. // It returns the first error if any. func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { state, err := c.state.ReloadOrGet() if err != nil { return err } var wg sync.WaitGroup errCh := make(chan error, 1) for _, slave := range state.Slaves { wg.Add(1) go func(node *clusterNode) { defer wg.Done() err := fn(node.Client) if err != nil { select { case errCh <- err: default: } } }(slave) } wg.Wait() select { case err := <-errCh: return err default: return nil } } // ForEachNode concurrently calls the fn on each known node in the cluster. // It returns the first error if any. func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { state, err := c.state.ReloadOrGet() if err != nil { return err } var wg sync.WaitGroup errCh := make(chan error, 1) worker := func(node *clusterNode) { defer wg.Done() err := fn(node.Client) if err != nil { select { case errCh <- err: default: } } } for _, node := range state.Masters { wg.Add(1) go worker(node) } for _, node := range state.Slaves { wg.Add(1) go worker(node) } wg.Wait() select { case err := <-errCh: return err default: return nil } } // PoolStats returns accumulated connection pool stats. func (c *ClusterClient) PoolStats() *PoolStats { var acc PoolStats state, _ := c.state.Get() if state == nil { return &acc } for _, node := range state.Masters { s := node.Client.connPool.Stats() acc.Hits += s.Hits acc.Misses += s.Misses acc.Timeouts += s.Timeouts acc.TotalConns += s.TotalConns acc.IdleConns += s.IdleConns acc.StaleConns += s.StaleConns } for _, node := range state.Slaves { s := node.Client.connPool.Stats() acc.Hits += s.Hits acc.Misses += s.Misses acc.Timeouts += s.Timeouts acc.TotalConns += s.TotalConns acc.IdleConns += s.IdleConns acc.StaleConns += s.StaleConns } return &acc } func (c *ClusterClient) loadState() (*clusterState, error) { if c.opt.ClusterSlots != nil { slots, err := c.opt.ClusterSlots() if err != nil { return nil, err } return newClusterState(c.nodes, slots, "") } addrs, err := c.nodes.Addrs() if err != nil { return nil, err } var firstErr error for _, addr := range addrs { node, err := c.nodes.GetOrCreate(addr) if err != nil { if firstErr == nil { firstErr = err } continue } slots, err := node.Client.ClusterSlots().Result() if err != nil { if firstErr == nil { firstErr = err } continue } return newClusterState(c.nodes, slots, node.Client.opt.Addr) } return nil, firstErr } // reaper closes idle connections to the cluster. func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { ticker := time.NewTicker(idleCheckFrequency) defer ticker.Stop() for range ticker.C { nodes, err := c.nodes.All() if err != nil { break } for _, node := range nodes { _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() if err != nil { internal.Logf("ReapStaleConns failed: %s", err) } } } } func (c *ClusterClient) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *ClusterClient) WrapProcessPipeline( fn func(oldProcess func([]Cmder) error) func([]Cmder) error, ) { c.processPipeline = fn(c.processPipeline) } func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { cmdsMap := newCmdsMap() err := c.mapCmdsByNode(cmds, cmdsMap) if err != nil { setCmdsErr(cmds, err) return err } for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } failedCmds := newCmdsMap() var wg sync.WaitGroup for node, cmds := range cmdsMap.m { wg.Add(1) go func(node *clusterNode, cmds []Cmder) { defer wg.Done() cn, err := node.Client.getConn() if err != nil { if err == pool.ErrClosed { c.mapCmdsByNode(cmds, failedCmds) } else { setCmdsErr(cmds, err) } return } err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) node.Client.releaseConnStrict(cn, err) }(node, cmds) } wg.Wait() if len(failedCmds.m) == 0 { break } cmdsMap = failedCmds } return cmdsFirstErr(cmds) } type cmdsMap struct { mu sync.Mutex m map[*clusterNode][]Cmder } func newCmdsMap() *cmdsMap { return &cmdsMap{ m: make(map[*clusterNode][]Cmder), } } func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error { state, err := c.state.Get() if err != nil { setCmdsErr(cmds, err) return err } cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) for _, cmd := range cmds { var node *clusterNode var err error if cmdsAreReadOnly { _, node, err = c.cmdSlotAndNode(cmd) } else { slot := c.cmdSlot(cmd) node, err = state.slotMasterNode(slot) } if err != nil { return err } cmdsMap.mu.Lock() cmdsMap.m[node] = append(cmdsMap.m[node], cmd) cmdsMap.mu.Unlock() } return nil } func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { for _, cmd := range cmds { cmdInfo := c.cmdInfo(cmd.Name()) if cmdInfo == nil || !cmdInfo.ReadOnly { return false } } return true } func (c *ClusterClient) pipelineProcessCmds( node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, ) error { err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { return writeCmd(wr, cmds...) }) if err != nil { setCmdsErr(cmds, err) failedCmds.mu.Lock() failedCmds.m[node] = cmds failedCmds.mu.Unlock() return err } err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { return c.pipelineReadCmds(node, rd, cmds, failedCmds) }) return err } func (c *ClusterClient) pipelineReadCmds( node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, ) error { var firstErr error for _, cmd := range cmds { err := cmd.readReply(rd) if err == nil { continue } if c.checkMovedErr(cmd, err, failedCmds) { continue } if internal.IsRedisError(err) { continue } failedCmds.mu.Lock() failedCmds.m[node] = append(failedCmds.m[node], cmd) failedCmds.mu.Unlock() if firstErr == nil { firstErr = err } } return firstErr } func (c *ClusterClient) checkMovedErr( cmd Cmder, err error, failedCmds *cmdsMap, ) bool { moved, ask, addr := internal.IsMovedError(err) if moved { c.state.LazyReload() node, err := c.nodes.GetOrCreate(addr) if err != nil { return false } failedCmds.mu.Lock() failedCmds.m[node] = append(failedCmds.m[node], cmd) failedCmds.mu.Unlock() return true } if ask { node, err := c.nodes.GetOrCreate(addr) if err != nil { return false } failedCmds.mu.Lock() failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd) failedCmds.mu.Unlock() return true } return false } // TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. func (c *ClusterClient) TxPipeline() Pipeliner { pipe := Pipeline{ exec: c.processTxPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.TxPipeline().Pipelined(fn) } func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { state, err := c.state.Get() if err != nil { return err } cmdsMap := c.mapCmdsBySlot(cmds) for slot, cmds := range cmdsMap { node, err := state.slotMasterNode(slot) if err != nil { setCmdsErr(cmds, err) continue } cmdsMap := map[*clusterNode][]Cmder{node: cmds} for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } failedCmds := newCmdsMap() var wg sync.WaitGroup for node, cmds := range cmdsMap { wg.Add(1) go func(node *clusterNode, cmds []Cmder) { defer wg.Done() cn, err := node.Client.getConn() if err != nil { if err == pool.ErrClosed { c.mapCmdsByNode(cmds, failedCmds) } else { setCmdsErr(cmds, err) } return } err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) node.Client.releaseConnStrict(cn, err) }(node, cmds) } wg.Wait() if len(failedCmds.m) == 0 { break } cmdsMap = failedCmds.m } } return cmdsFirstErr(cmds) } func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { cmdsMap := make(map[int][]Cmder) for _, cmd := range cmds { slot := c.cmdSlot(cmd) cmdsMap[slot] = append(cmdsMap[slot], cmd) } return cmdsMap } func (c *ClusterClient) txPipelineProcessCmds( node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, ) error { err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { return txPipelineWriteMulti(wr, cmds) }) if err != nil { setCmdsErr(cmds, err) failedCmds.mu.Lock() failedCmds.m[node] = cmds failedCmds.mu.Unlock() return err } err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { err := c.txPipelineReadQueued(rd, cmds, failedCmds) if err != nil { setCmdsErr(cmds, err) return err } return pipelineReadCmds(rd, cmds) }) return err } func (c *ClusterClient) txPipelineReadQueued( rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, ) error { // Parse queued replies. var statusCmd StatusCmd if err := statusCmd.readReply(rd); err != nil { return err } for _, cmd := range cmds { err := statusCmd.readReply(rd) if err == nil { continue } if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { continue } return err } // Parse number of replies. line, err := rd.ReadLine() if err != nil { if err == Nil { err = TxFailedErr } return err } switch line[0] { case proto.ErrorReply: err := proto.ParseErrorReply(line) for _, cmd := range cmds { if !c.checkMovedErr(cmd, err, failedCmds) { break } } return err case proto.ArrayReply: // ok default: err := fmt.Errorf("redis: expected '*', but got line %q", line) return err } return nil } func (c *ClusterClient) pubSub(channels []string) *PubSub { var node *clusterNode pubsub := &PubSub{ opt: c.opt.clientOptions(), newConn: func(channels []string) (*pool.Conn, error) { if node == nil { var slot int if len(channels) > 0 { slot = hashtag.Slot(channels[0]) } else { slot = -1 } masterNode, err := c.slotMasterNode(slot) if err != nil { return nil, err } node = masterNode } return node.Client.newConn() }, closeConn: func(cn *pool.Conn) error { return node.Client.connPool.CloseConn(cn) }, } pubsub.init() return pubsub } // Subscribe subscribes the client to the specified channels. // Channels can be omitted to create empty subscription. func (c *ClusterClient) Subscribe(channels ...string) *PubSub { pubsub := c.pubSub(channels) if len(channels) > 0 { _ = pubsub.Subscribe(channels...) } return pubsub } // PSubscribe subscribes the client to the given patterns. // Patterns can be omitted to create empty subscription. func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { pubsub := c.pubSub(channels) if len(channels) > 0 { _ = pubsub.PSubscribe(channels...) } return pubsub } func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { for _, n := range nodes { if n == node { return nodes } } return append(nodes, node) } func appendIfNotExists(ss []string, es ...string) []string { loop: for _, e := range es { for _, s := range ss { if s == e { continue loop } } ss = append(ss, e) } return ss } func remove(ss []string, es ...string) []string { if len(es) == 0 { return ss[:0] } for _, e := range es { for i, s := range ss { if s == e { ss = append(ss[:i], ss[i+1:]...) break } } } return ss } redis-6.15.0/cluster_commands.go000066400000000000000000000005701340572764100166150ustar00rootroot00000000000000package redis import "sync/atomic" func (c *ClusterClient) DBSize() *IntCmd { cmd := NewIntCmd("dbsize") var size int64 err := c.ForEachMaster(func(master *Client) error { n, err := master.DBSize().Result() if err != nil { return err } atomic.AddInt64(&size, n) return nil }) if err != nil { cmd.setErr(err) return cmd } cmd.val = size return cmd } redis-6.15.0/cluster_test.go000066400000000000000000000623611340572764100160010ustar00rootroot00000000000000package redis_test import ( "bytes" "fmt" "net" "strconv" "strings" "sync" "testing" "time" "github.com/go-redis/redis" "github.com/go-redis/redis/internal/hashtag" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) type clusterScenario struct { ports []string nodeIds []string processes map[string]*redisProcess clients map[string]*redis.Client } func (s *clusterScenario) masters() []*redis.Client { result := make([]*redis.Client, 3) for pos, port := range s.ports[:3] { result[pos] = s.clients[port] } return result } func (s *clusterScenario) slaves() []*redis.Client { result := make([]*redis.Client, 3) for pos, port := range s.ports[3:] { result[pos] = s.clients[port] } return result } func (s *clusterScenario) addrs() []string { addrs := make([]string, len(s.ports)) for i, port := range s.ports { addrs[i] = net.JoinHostPort("127.0.0.1", port) } return addrs } func (s *clusterScenario) clusterClientUnsafe(opt *redis.ClusterOptions) *redis.ClusterClient { opt.Addrs = s.addrs() return redis.NewClusterClient(opt) } func (s *clusterScenario) clusterClient(opt *redis.ClusterOptions) *redis.ClusterClient { client := s.clusterClientUnsafe(opt) err := eventually(func() error { if opt.ClusterSlots != nil { return nil } state, err := client.LoadState() if err != nil { return err } if !state.IsConsistent() { return fmt.Errorf("cluster state is not consistent") } return nil }, 30*time.Second) if err != nil { panic(err) } return client } func startCluster(scenario *clusterScenario) error { // Start processes and collect node ids for pos, port := range scenario.ports { process, err := startRedis(port, "--cluster-enabled", "yes") if err != nil { return err } client := redis.NewClient(&redis.Options{ Addr: ":" + port, }) info, err := client.ClusterNodes().Result() if err != nil { return err } scenario.processes[port] = process scenario.clients[port] = client scenario.nodeIds[pos] = info[:40] } // Meet cluster nodes. for _, client := range scenario.clients { err := client.ClusterMeet("127.0.0.1", scenario.ports[0]).Err() if err != nil { return err } } // Bootstrap masters. slots := []int{0, 5000, 10000, 16384} for pos, master := range scenario.masters() { err := master.ClusterAddSlotsRange(slots[pos], slots[pos+1]-1).Err() if err != nil { return err } } // Bootstrap slaves. for idx, slave := range scenario.slaves() { masterId := scenario.nodeIds[idx] // Wait until master is available err := eventually(func() error { s := slave.ClusterNodes().Val() wanted := masterId if !strings.Contains(s, wanted) { return fmt.Errorf("%q does not contain %q", s, wanted) } return nil }, 10*time.Second) if err != nil { return err } err = slave.ClusterReplicate(masterId).Err() if err != nil { return err } } // Wait until all nodes have consistent info. wanted := []redis.ClusterSlot{{ Start: 0, End: 4999, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8220", }, { Id: "", Addr: "127.0.0.1:8223", }}, }, { Start: 5000, End: 9999, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8221", }, { Id: "", Addr: "127.0.0.1:8224", }}, }, { Start: 10000, End: 16383, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8222", }, { Id: "", Addr: "127.0.0.1:8225", }}, }} for _, client := range scenario.clients { err := eventually(func() error { res, err := client.ClusterSlots().Result() if err != nil { return err } return assertSlotsEqual(res, wanted) }, 30*time.Second) if err != nil { return err } } return nil } func assertSlotsEqual(slots, wanted []redis.ClusterSlot) error { outerLoop: for _, s2 := range wanted { for _, s1 := range slots { if slotEqual(s1, s2) { continue outerLoop } } return fmt.Errorf("%v not found in %v", s2, slots) } return nil } func slotEqual(s1, s2 redis.ClusterSlot) bool { if s1.Start != s2.Start { return false } if s1.End != s2.End { return false } if len(s1.Nodes) != len(s2.Nodes) { return false } for i, n1 := range s1.Nodes { if n1.Addr != s2.Nodes[i].Addr { return false } } return true } func stopCluster(scenario *clusterScenario) error { for _, client := range scenario.clients { if err := client.Close(); err != nil { return err } } for _, process := range scenario.processes { if err := process.Close(); err != nil { return err } } return nil } //------------------------------------------------------------------------------ var _ = Describe("ClusterClient", func() { var failover bool var opt *redis.ClusterOptions var client *redis.ClusterClient assertClusterClient := func() { It("should GET/SET/DEL", func() { err := client.Get("A").Err() Expect(err).To(Equal(redis.Nil)) err = client.Set("A", "VALUE", 0).Err() Expect(err).NotTo(HaveOccurred()) Eventually(func() string { return client.Get("A").Val() }, 30*time.Second).Should(Equal("VALUE")) cnt, err := client.Del("A").Result() Expect(err).NotTo(HaveOccurred()) Expect(cnt).To(Equal(int64(1))) }) It("GET follows redirects", func() { err := client.Set("A", "VALUE", 0).Err() Expect(err).NotTo(HaveOccurred()) if !failover { Eventually(func() int64 { nodes, err := client.Nodes("A") if err != nil { return 0 } return nodes[1].Client.DBSize().Val() }, 30*time.Second).Should(Equal(int64(1))) Eventually(func() error { return client.SwapNodes("A") }, 30*time.Second).ShouldNot(HaveOccurred()) } v, err := client.Get("A").Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("VALUE")) }) It("SET follows redirects", func() { if !failover { Eventually(func() error { return client.SwapNodes("A") }, 30*time.Second).ShouldNot(HaveOccurred()) } err := client.Set("A", "VALUE", 0).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.Get("A").Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("VALUE")) }) It("distributes keys", func() { for i := 0; i < 100; i++ { err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err() Expect(err).NotTo(HaveOccurred()) } client.ForEachMaster(func(master *redis.Client) error { defer GinkgoRecover() Eventually(func() string { return master.Info("keyspace").Val() }, 30*time.Second).Should(Or( ContainSubstring("keys=31"), ContainSubstring("keys=29"), ContainSubstring("keys=40"), )) return nil }) }) It("distributes keys when using EVAL", func() { script := redis.NewScript(` local r = redis.call('SET', KEYS[1], ARGV[1]) return r `) var key string for i := 0; i < 100; i++ { key = fmt.Sprintf("key%d", i) err := script.Run(client, []string{key}, "value").Err() Expect(err).NotTo(HaveOccurred()) } client.ForEachMaster(func(master *redis.Client) error { defer GinkgoRecover() Eventually(func() string { return master.Info("keyspace").Val() }, 30*time.Second).Should(Or( ContainSubstring("keys=31"), ContainSubstring("keys=29"), ContainSubstring("keys=40"), )) return nil }) }) It("supports Watch", func() { var incr func(string) error // Transactionally increments key using GET and SET commands. incr = func(key string) error { err := client.Watch(func(tx *redis.Tx) error { n, err := tx.Get(key).Int64() if err != nil && err != redis.Nil { return err } _, err = tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Set(key, strconv.FormatInt(n+1, 10), 0) return nil }) return err }, key) if err == redis.TxFailedErr { return incr(key) } return err } var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func() { defer GinkgoRecover() defer wg.Done() err := incr("key") Expect(err).NotTo(HaveOccurred()) }() } wg.Wait() Eventually(func() string { return client.Get("key").Val() }, 30*time.Second).Should(Equal("100")) }) Describe("pipelining", func() { var pipe *redis.Pipeline assertPipeline := func() { keys := []string{"A", "B", "C", "D", "E", "F", "G"} It("follows redirects", func() { if !failover { for _, key := range keys { Eventually(func() error { return client.SwapNodes(key) }, 30*time.Second).ShouldNot(HaveOccurred()) } } for i, key := range keys { pipe.Set(key, key+"_value", 0) pipe.Expire(key, time.Duration(i+1)*time.Hour) } cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(14)) _ = client.ForEachNode(func(node *redis.Client) error { defer GinkgoRecover() Eventually(func() int64 { return node.DBSize().Val() }, 30*time.Second).ShouldNot(BeZero()) return nil }) if !failover { for _, key := range keys { Eventually(func() error { return client.SwapNodes(key) }, 30*time.Second).ShouldNot(HaveOccurred()) } } for _, key := range keys { pipe.Get(key) pipe.TTL(key) } cmds, err = pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(14)) for i, key := range keys { get := cmds[i*2].(*redis.StringCmd) Expect(get.Val()).To(Equal(key + "_value")) ttl := cmds[(i*2)+1].(*redis.DurationCmd) dur := time.Duration(i+1) * time.Hour Expect(ttl.Val()).To(BeNumerically("~", dur, 30*time.Second)) } }) It("works with missing keys", func() { pipe.Set("A", "A_value", 0) pipe.Set("C", "C_value", 0) _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) a := pipe.Get("A") b := pipe.Get("B") c := pipe.Get("C") cmds, err := pipe.Exec() Expect(err).To(Equal(redis.Nil)) Expect(cmds).To(HaveLen(3)) Expect(a.Err()).NotTo(HaveOccurred()) Expect(a.Val()).To(Equal("A_value")) Expect(b.Err()).To(Equal(redis.Nil)) Expect(b.Val()).To(Equal("")) Expect(c.Err()).NotTo(HaveOccurred()) Expect(c.Val()).To(Equal("C_value")) }) } Describe("with Pipeline", func() { BeforeEach(func() { pipe = client.Pipeline().(*redis.Pipeline) }) AfterEach(func() { Expect(pipe.Close()).NotTo(HaveOccurred()) }) assertPipeline() }) Describe("with TxPipeline", func() { BeforeEach(func() { pipe = client.TxPipeline().(*redis.Pipeline) }) AfterEach(func() { Expect(pipe.Close()).NotTo(HaveOccurred()) }) assertPipeline() }) }) It("supports PubSub", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() Eventually(func() error { _, err := client.Publish("mychannel", "hello").Result() if err != nil { return err } msg, err := pubsub.ReceiveTimeout(time.Second) if err != nil { return err } _, ok := msg.(*redis.Message) if !ok { return fmt.Errorf("got %T, wanted *redis.Message", msg) } return nil }, 30*time.Second).ShouldNot(HaveOccurred()) }) } Describe("ClusterClient", func() { BeforeEach(func() { opt = redisClusterOptions() client = cluster.clusterClient(opt) err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(client.Close()).NotTo(HaveOccurred()) }) It("returns pool stats", func() { stats := client.PoolStats() Expect(stats).To(BeAssignableToTypeOf(&redis.PoolStats{})) }) It("removes idle connections", func() { stats := client.PoolStats() Expect(stats.TotalConns).NotTo(BeZero()) Expect(stats.IdleConns).NotTo(BeZero()) time.Sleep(2 * time.Second) stats = client.PoolStats() Expect(stats.TotalConns).To(BeZero()) Expect(stats.IdleConns).To(BeZero()) }) It("returns an error when there are no attempts left", func() { opt := redisClusterOptions() opt.MaxRedirects = -1 client := cluster.clusterClient(opt) Eventually(func() error { return client.SwapNodes("A") }, 30*time.Second).ShouldNot(HaveOccurred()) err := client.Get("A").Err() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("MOVED")) Expect(client.Close()).NotTo(HaveOccurred()) }) It("calls fn for every master node", func() { for i := 0; i < 10; i++ { Expect(client.Set(strconv.Itoa(i), "", 0).Err()).NotTo(HaveOccurred()) } err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) size, err := client.DBSize().Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) It("should CLUSTER SLOTS", func() { res, err := client.ClusterSlots().Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(3)) wanted := []redis.ClusterSlot{{ Start: 0, End: 4999, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8220", }, { Id: "", Addr: "127.0.0.1:8223", }}, }, { Start: 5000, End: 9999, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8221", }, { Id: "", Addr: "127.0.0.1:8224", }}, }, { Start: 10000, End: 16383, Nodes: []redis.ClusterNode{{ Id: "", Addr: "127.0.0.1:8222", }, { Id: "", Addr: "127.0.0.1:8225", }}, }} Expect(assertSlotsEqual(res, wanted)).NotTo(HaveOccurred()) }) It("should CLUSTER NODES", func() { res, err := client.ClusterNodes().Result() Expect(err).NotTo(HaveOccurred()) Expect(len(res)).To(BeNumerically(">", 400)) }) It("should CLUSTER INFO", func() { res, err := client.ClusterInfo().Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(ContainSubstring("cluster_known_nodes:6")) }) It("should CLUSTER KEYSLOT", func() { hashSlot, err := client.ClusterKeySlot("somekey").Result() Expect(err).NotTo(HaveOccurred()) Expect(hashSlot).To(Equal(int64(hashtag.Slot("somekey")))) }) It("should CLUSTER COUNT-FAILURE-REPORTS", func() { n, err := client.ClusterCountFailureReports(cluster.nodeIds[0]).Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) }) It("should CLUSTER COUNTKEYSINSLOT", func() { n, err := client.ClusterCountKeysInSlot(10).Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) }) It("should CLUSTER SAVECONFIG", func() { res, err := client.ClusterSaveConfig().Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(Equal("OK")) }) It("should CLUSTER SLAVES", func() { nodesList, err := client.ClusterSlaves(cluster.nodeIds[0]).Result() Expect(err).NotTo(HaveOccurred()) Expect(nodesList).Should(ContainElement(ContainSubstring("slave"))) Expect(nodesList).Should(HaveLen(1)) }) It("should RANDOMKEY", func() { const nkeys = 100 for i := 0; i < nkeys; i++ { err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err() Expect(err).NotTo(HaveOccurred()) } var keys []string addKey := func(key string) { for _, k := range keys { if k == key { return } } keys = append(keys, key) } for i := 0; i < nkeys*10; i++ { key := client.RandomKey().Val() addKey(key) } Expect(len(keys)).To(BeNumerically("~", nkeys, nkeys/10)) }) assertClusterClient() }) Describe("ClusterClient failover", func() { BeforeEach(func() { failover = true opt = redisClusterOptions() opt.MinRetryBackoff = 250 * time.Millisecond opt.MaxRetryBackoff = time.Second client = cluster.clusterClient(opt) err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) err = client.ForEachSlave(func(slave *redis.Client) error { defer GinkgoRecover() Eventually(func() int64 { return slave.DBSize().Val() }, "30s").Should(Equal(int64(0))) return nil }) Expect(err).NotTo(HaveOccurred()) state, err := client.LoadState() Eventually(func() bool { state, err = client.LoadState() if err != nil { return false } return state.IsConsistent() }, "30s").Should(BeTrue()) for _, slave := range state.Slaves { err = slave.Client.ClusterFailover().Err() Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { state, _ := client.LoadState() return state.IsConsistent() }, "30s").Should(BeTrue()) } }) AfterEach(func() { failover = false Expect(client.Close()).NotTo(HaveOccurred()) }) assertClusterClient() }) Describe("ClusterClient with RouteByLatency", func() { BeforeEach(func() { opt = redisClusterOptions() opt.RouteByLatency = true client = cluster.clusterClient(opt) err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) err = client.ForEachSlave(func(slave *redis.Client) error { Eventually(func() int64 { return client.DBSize().Val() }, 30*time.Second).Should(Equal(int64(0))) return nil }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { err := client.ForEachSlave(func(slave *redis.Client) error { return slave.ReadWrite().Err() }) Expect(err).NotTo(HaveOccurred()) err = client.Close() Expect(err).NotTo(HaveOccurred()) }) assertClusterClient() }) Describe("ClusterClient with ClusterSlots", func() { BeforeEach(func() { failover = true opt = redisClusterOptions() opt.ClusterSlots = func() ([]redis.ClusterSlot, error) { slots := []redis.ClusterSlot{{ Start: 0, End: 4999, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard1Port, }}, }, { Start: 5000, End: 9999, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard2Port, }}, }, { Start: 10000, End: 16383, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard3Port, }}, }} return slots, nil } client = cluster.clusterClient(opt) err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) err = client.ForEachSlave(func(slave *redis.Client) error { Eventually(func() int64 { return client.DBSize().Val() }, 30*time.Second).Should(Equal(int64(0))) return nil }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { failover = false err := client.Close() Expect(err).NotTo(HaveOccurred()) }) assertClusterClient() }) Describe("ClusterClient with RouteRandomly and ClusterSlots", func() { BeforeEach(func() { failover = true opt = redisClusterOptions() opt.RouteRandomly = true opt.ClusterSlots = func() ([]redis.ClusterSlot, error) { slots := []redis.ClusterSlot{{ Start: 0, End: 4999, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard1Port, }}, }, { Start: 5000, End: 9999, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard2Port, }}, }, { Start: 10000, End: 16383, Nodes: []redis.ClusterNode{{ Addr: ":" + ringShard3Port, }}, }} return slots, nil } client = cluster.clusterClient(opt) err := client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) err = client.ForEachSlave(func(slave *redis.Client) error { Eventually(func() int64 { return client.DBSize().Val() }, 30*time.Second).Should(Equal(int64(0))) return nil }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { failover = false err := client.Close() Expect(err).NotTo(HaveOccurred()) }) assertClusterClient() }) }) var _ = Describe("ClusterClient without nodes", func() { var client *redis.ClusterClient BeforeEach(func() { client = redis.NewClusterClient(&redis.ClusterOptions{}) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("Ping returns an error", func() { err := client.Ping().Err() Expect(err).To(MatchError("redis: cluster has no nodes")) }) It("pipeline returns an error", func() { _, err := client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) Expect(err).To(MatchError("redis: cluster has no nodes")) }) }) var _ = Describe("ClusterClient without valid nodes", func() { var client *redis.ClusterClient BeforeEach(func() { client = redis.NewClusterClient(&redis.ClusterOptions{ Addrs: []string{redisAddr}, }) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("returns an error", func() { err := client.Ping().Err() Expect(err).To(MatchError("ERR This instance has cluster support disabled")) }) It("pipeline returns an error", func() { _, err := client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) Expect(err).To(MatchError("ERR This instance has cluster support disabled")) }) }) var _ = Describe("ClusterClient with unavailable Cluster", func() { var client *redis.ClusterClient BeforeEach(func() { for _, node := range cluster.clients { err := node.ClientPause(5 * time.Second).Err() Expect(err).NotTo(HaveOccurred()) } opt := redisClusterOptions() opt.ReadTimeout = 250 * time.Millisecond opt.WriteTimeout = 250 * time.Millisecond opt.MaxRedirects = 1 client = cluster.clusterClientUnsafe(opt) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("recovers when Cluster recovers", func() { err := client.Ping().Err() Expect(err).To(HaveOccurred()) Eventually(func() error { return client.Ping().Err() }, "30s").ShouldNot(HaveOccurred()) }) }) var _ = Describe("ClusterClient timeout", func() { var client *redis.ClusterClient AfterEach(func() { _ = client.Close() }) testTimeout := func() { It("Ping timeouts", func() { err := client.Ping().Err() Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) It("Pipeline timeouts", func() { _, err := client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) It("Tx timeouts", func() { err := client.Watch(func(tx *redis.Tx) error { return tx.Ping().Err() }, "foo") Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) It("Tx Pipeline timeouts", func() { err := client.Watch(func(tx *redis.Tx) error { _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) return err }, "foo") Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) } const pause = 5 * time.Second Context("read/write timeout", func() { BeforeEach(func() { opt := redisClusterOptions() opt.ReadTimeout = 250 * time.Millisecond opt.WriteTimeout = 250 * time.Millisecond opt.MaxRedirects = 1 client = cluster.clusterClient(opt) err := client.ForEachNode(func(client *redis.Client) error { return client.ClientPause(pause).Err() }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { _ = client.ForEachNode(func(client *redis.Client) error { defer GinkgoRecover() Eventually(func() error { return client.Ping().Err() }, 2*pause).ShouldNot(HaveOccurred()) return nil }) }) testTimeout() }) }) //------------------------------------------------------------------------------ func newClusterScenario() *clusterScenario { return &clusterScenario{ ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"}, nodeIds: make([]string, 6), processes: make(map[string]*redisProcess, 6), clients: make(map[string]*redis.Client, 6), } } func BenchmarkClusterPing(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") } cluster := newClusterScenario() if err := startCluster(cluster); err != nil { b.Fatal(err) } defer stopCluster(cluster) client := cluster.clusterClient(redisClusterOptions()) defer client.Close() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := client.Ping().Err() if err != nil { b.Fatal(err) } } }) } func BenchmarkClusterSetString(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") } cluster := newClusterScenario() if err := startCluster(cluster); err != nil { b.Fatal(err) } defer stopCluster(cluster) client := cluster.clusterClient(redisClusterOptions()) defer client.Close() value := string(bytes.Repeat([]byte{'1'}, 10000)) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := client.Set("key", value, 0).Err() if err != nil { b.Fatal(err) } } }) } func BenchmarkClusterReloadState(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") } cluster := newClusterScenario() if err := startCluster(cluster); err != nil { b.Fatal(err) } defer stopCluster(cluster) client := cluster.clusterClient(redisClusterOptions()) defer client.Close() b.ResetTimer() for i := 0; i < b.N; i++ { err := client.ReloadState() if err != nil { b.Fatal(err) } } } redis-6.15.0/command.go000066400000000000000000001115171340572764100146750ustar00rootroot00000000000000package redis import ( "fmt" "net" "strconv" "strings" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/proto" ) type Cmder interface { Name() string Args() []interface{} stringArg(int) string readReply(rd *proto.Reader) error setErr(error) readTimeout() *time.Duration 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 writeCmd(wr *proto.Writer, cmds ...Cmder) error { for _, cmd := range cmds { err := wr.WriteArgs(cmd.Args()) if err != nil { return err } } return nil } func cmdString(cmd Cmder, val interface{}) string { var ss []string for _, arg := range cmd.Args() { ss = append(ss, fmt.Sprint(arg)) } s := strings.Join(ss, " ") if err := cmd.Err(); err != nil { return s + ": " + err.Error() } if val != nil { switch vv := val.(type) { case []byte: return s + ": " + string(vv) default: return s + ": " + fmt.Sprint(val) } } return s } func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { switch cmd.Name() { case "eval", "evalsha": if cmd.stringArg(2) != "0" { return 3 } return 0 case "publish": return 1 } if info == nil { return 0 } return int(info.FirstKeyPos) } //------------------------------------------------------------------------------ type baseCmd struct { _args []interface{} err error _readTimeout *time.Duration } var _ Cmder = (*Cmd)(nil) func (cmd *baseCmd) Err() error { return cmd.err } func (cmd *baseCmd) Args() []interface{} { return cmd._args } func (cmd *baseCmd) stringArg(pos int) string { if pos < 0 || pos >= len(cmd._args) { return "" } s, _ := cmd._args[pos].(string) return s } func (cmd *baseCmd) Name() string { if len(cmd._args) > 0 { // Cmd name must be lower cased. s := internal.ToLower(cmd.stringArg(0)) cmd._args[0] = s return s } return "" } func (cmd *baseCmd) readTimeout() *time.Duration { return cmd._readTimeout } func (cmd *baseCmd) setReadTimeout(d time.Duration) { cmd._readTimeout = &d } func (cmd *baseCmd) setErr(e error) { cmd.err = e } //------------------------------------------------------------------------------ type Cmd struct { baseCmd val interface{} } func NewCmd(args ...interface{}) *Cmd { return &Cmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *Cmd) Val() interface{} { return cmd.val } func (cmd *Cmd) Result() (interface{}, error) { return cmd.val, cmd.err } func (cmd *Cmd) String() (string, error) { if cmd.err != nil { return "", cmd.err } switch val := cmd.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 } switch val := cmd.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 } switch val := cmd.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) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err } switch val := cmd.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 } switch val := cmd.val.(type) { 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) readReply(rd *proto.Reader) error { cmd.val, cmd.err = rd.ReadReply(sliceParser) return cmd.err } // Implements proto.MultiBulkParse func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { vals := make([]interface{}, 0, n) for i := int64(0); i < n; i++ { v, err := rd.ReadReply(sliceParser) if err != nil { if err == Nil { vals = append(vals, nil) continue } if err, ok := err.(proto.RedisError); ok { vals = append(vals, err) continue } return nil, err } switch v := v.(type) { case string: vals = append(vals, v) default: vals = append(vals, v) } } return vals, nil } //------------------------------------------------------------------------------ type SliceCmd struct { baseCmd val []interface{} } var _ Cmder = (*SliceCmd)(nil) func NewSliceCmd(args ...interface{}) *SliceCmd { return &SliceCmd{ baseCmd: baseCmd{_args: args}, } } 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) } func (cmd *SliceCmd) readReply(rd *proto.Reader) error { var v interface{} v, cmd.err = rd.ReadArrayReply(sliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]interface{}) return nil } //------------------------------------------------------------------------------ type StatusCmd struct { baseCmd val string } var _ Cmder = (*StatusCmd)(nil) func NewStatusCmd(args ...interface{}) *StatusCmd { return &StatusCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *StatusCmd) Val() string { return cmd.val } func (cmd *StatusCmd) Result() (string, error) { return cmd.val, cmd.err } func (cmd *StatusCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StatusCmd) readReply(rd *proto.Reader) error { cmd.val, cmd.err = rd.ReadString() return cmd.err } //------------------------------------------------------------------------------ type IntCmd struct { baseCmd val int64 } var _ Cmder = (*IntCmd)(nil) func NewIntCmd(args ...interface{}) *IntCmd { return &IntCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *IntCmd) Val() int64 { return cmd.val } func (cmd *IntCmd) Result() (int64, error) { return cmd.val, cmd.err } func (cmd *IntCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *IntCmd) readReply(rd *proto.Reader) error { cmd.val, cmd.err = rd.ReadIntReply() return cmd.err } //------------------------------------------------------------------------------ type DurationCmd struct { baseCmd val time.Duration precision time.Duration } var _ Cmder = (*DurationCmd)(nil) func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { return &DurationCmd{ baseCmd: baseCmd{_args: args}, precision: precision, } } 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 { var n int64 n, cmd.err = rd.ReadIntReply() if cmd.err != nil { return cmd.err } cmd.val = time.Duration(n) * cmd.precision return nil } //------------------------------------------------------------------------------ type TimeCmd struct { baseCmd val time.Time } var _ Cmder = (*TimeCmd)(nil) func NewTimeCmd(args ...interface{}) *TimeCmd { return &TimeCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(timeParser) if cmd.err != nil { return cmd.err } cmd.val = v.(time.Time) return nil } // Implements proto.MultiBulkParse func timeParser(rd *proto.Reader, n int64) (interface{}, error) { if n != 2 { return nil, fmt.Errorf("got %d elements, expected 2", n) } sec, err := rd.ReadInt() if err != nil { return nil, err } microsec, err := rd.ReadInt() if err != nil { return nil, err } return time.Unix(sec, microsec*1000), nil } //------------------------------------------------------------------------------ type BoolCmd struct { baseCmd val bool } var _ Cmder = (*BoolCmd)(nil) func NewBoolCmd(args ...interface{}) *BoolCmd { return &BoolCmd{ baseCmd: baseCmd{_args: args}, } } 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) error { var v interface{} v, cmd.err = rd.ReadReply(nil) // `SET key value NX` returns nil when key already exists. But // `SETNX key value` returns bool (0/1). So convert nil to bool. // TODO: is this okay? if cmd.err == Nil { cmd.val = false cmd.err = nil return nil } if cmd.err != nil { return cmd.err } switch v := v.(type) { case int64: cmd.val = v == 1 return nil case string: cmd.val = v == "OK" return nil default: cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) return cmd.err } } //------------------------------------------------------------------------------ type StringCmd struct { baseCmd val string } var _ Cmder = (*StringCmd)(nil) func NewStringCmd(args ...interface{}) *StringCmd { return &StringCmd{ baseCmd: baseCmd{_args: args}, } } 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 []byte(cmd.val), cmd.err } 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) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err } return strconv.ParseFloat(cmd.Val(), 64) } 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) error { cmd.val, cmd.err = rd.ReadString() return cmd.err } //------------------------------------------------------------------------------ type FloatCmd struct { baseCmd val float64 } var _ Cmder = (*FloatCmd)(nil) func NewFloatCmd(args ...interface{}) *FloatCmd { return &FloatCmd{ baseCmd: baseCmd{_args: args}, } } 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) error { cmd.val, cmd.err = rd.ReadFloatReply() return cmd.err } //------------------------------------------------------------------------------ type StringSliceCmd struct { baseCmd val []string } var _ Cmder = (*StringSliceCmd)(nil) func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { return &StringSliceCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(stringSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]string) return nil } // Implements proto.MultiBulkParse func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { ss := make([]string, 0, n) for i := int64(0); i < n; i++ { s, err := rd.ReadString() if err == Nil { ss = append(ss, "") } else if err != nil { return nil, err } else { ss = append(ss, s) } } return ss, nil } //------------------------------------------------------------------------------ type BoolSliceCmd struct { baseCmd val []bool } var _ Cmder = (*BoolSliceCmd)(nil) func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { return &BoolSliceCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(boolSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]bool) return nil } // Implements proto.MultiBulkParse func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { bools := make([]bool, 0, n) for i := int64(0); i < n; i++ { n, err := rd.ReadIntReply() if err != nil { return nil, err } bools = append(bools, n == 1) } return bools, nil } //------------------------------------------------------------------------------ type StringStringMapCmd struct { baseCmd val map[string]string } var _ Cmder = (*StringStringMapCmd)(nil) func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { return &StringStringMapCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *StringStringMapCmd) Val() map[string]string { return cmd.val } func (cmd *StringStringMapCmd) Result() (map[string]string, error) { return cmd.val, cmd.err } func (cmd *StringStringMapCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { var v interface{} v, cmd.err = rd.ReadArrayReply(stringStringMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]string) return nil } // Implements proto.MultiBulkParse func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { m := make(map[string]string, n/2) for i := int64(0); i < n; i += 2 { 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 StringIntMapCmd struct { baseCmd val map[string]int64 } var _ Cmder = (*StringIntMapCmd)(nil) func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { return &StringIntMapCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *StringIntMapCmd) Val() map[string]int64 { return cmd.val } func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { return cmd.val, cmd.err } func (cmd *StringIntMapCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { var v interface{} v, cmd.err = rd.ReadArrayReply(stringIntMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]int64) return nil } // Implements proto.MultiBulkParse func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { m := make(map[string]int64, n/2) for i := int64(0); i < n; i += 2 { key, err := rd.ReadString() if err != nil { return nil, err } n, err := rd.ReadIntReply() if err != nil { return nil, err } m[key] = n } return m, nil } //------------------------------------------------------------------------------ type StringStructMapCmd struct { baseCmd val map[string]struct{} } var _ Cmder = (*StringStructMapCmd)(nil) func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { return &StringStructMapCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(stringStructMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]struct{}) return nil } // Implements proto.MultiBulkParse func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { m := make(map[string]struct{}, n) for i := int64(0); i < n; i++ { key, err := rd.ReadString() if err != nil { return nil, err } m[key] = struct{}{} } return m, nil } //------------------------------------------------------------------------------ type XMessage struct { ID string Values map[string]interface{} } type XMessageSliceCmd struct { baseCmd val []XMessage } var _ Cmder = (*XMessageSliceCmd)(nil) func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { return &XMessageSliceCmd{ baseCmd: baseCmd{_args: args}, } } 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) error { var v interface{} v, cmd.err = rd.ReadArrayReply(xMessageSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]XMessage) return nil } // Implements proto.MultiBulkParse func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { msgs := make([]XMessage, 0, n) for i := int64(0); i < n; i++ { _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { id, err := rd.ReadString() if err != nil { return nil, err } v, err := rd.ReadArrayReply(stringInterfaceMapParser) if err != nil { return nil, err } msgs = append(msgs, XMessage{ ID: id, Values: v.(map[string]interface{}), }) return nil, nil }) if err != nil { return nil, err } } return msgs, nil } // Implements proto.MultiBulkParse func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { m := make(map[string]interface{}, n/2) for i := int64(0); i < n; i += 2 { 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(args ...interface{}) *XStreamSliceCmd { return &XStreamSliceCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]XStream) return nil } // Implements proto.MultiBulkParse func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { ret := make([]XStream, 0, n) for i := int64(0); i < n; i++ { _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { if n != 2 { return nil, fmt.Errorf("got %d, wanted 2", n) } stream, err := rd.ReadString() if err != nil { return nil, err } v, err := rd.ReadArrayReply(xMessageSliceParser) if err != nil { return nil, err } ret = append(ret, XStream{ Stream: stream, Messages: v.([]XMessage), }) return nil, nil }) if err != nil { return nil, err } } return ret, 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(args ...interface{}) *XPendingCmd { return &XPendingCmd{ baseCmd: baseCmd{_args: args}, } } 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 info interface{} info, cmd.err = rd.ReadArrayReply(xPendingParser) if cmd.err != nil { return cmd.err } cmd.val = info.(*XPending) return nil } func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { if n != 4 { return nil, fmt.Errorf("got %d, wanted 4", n) } count, err := rd.ReadIntReply() if err != nil { return nil, err } lower, err := rd.ReadString() if err != nil && err != Nil { return nil, err } higher, err := rd.ReadString() if err != nil && err != Nil { return nil, err } pending := &XPending{ Count: count, Lower: lower, Higher: higher, } _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { for i := int64(0); i < n; i++ { _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { if n != 2 { return nil, fmt.Errorf("got %d, wanted 2", n) } consumerName, err := rd.ReadString() if err != nil { return nil, err } consumerPending, err := rd.ReadInt() if err != nil { return nil, err } if pending.Consumers == nil { pending.Consumers = make(map[string]int64) } pending.Consumers[consumerName] = consumerPending return nil, nil }) if err != nil { return nil, err } } return nil, nil }) if err != nil && err != Nil { return nil, err } return pending, 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(args ...interface{}) *XPendingExtCmd { return &XPendingExtCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var info interface{} info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) if cmd.err != nil { return cmd.err } cmd.val = info.([]XPendingExt) return nil } func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { ret := make([]XPendingExt, 0, n) for i := int64(0); i < n; i++ { _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { if n != 4 { return nil, fmt.Errorf("got %d, wanted 4", n) } id, err := rd.ReadString() if err != nil { return nil, err } consumer, err := rd.ReadString() if err != nil && err != Nil { return nil, err } idle, err := rd.ReadIntReply() if err != nil && err != Nil { return nil, err } retryCount, err := rd.ReadIntReply() if err != nil && err != Nil { return nil, err } ret = append(ret, XPendingExt{ Id: id, Consumer: consumer, Idle: time.Duration(idle) * time.Millisecond, RetryCount: retryCount, }) return nil, nil }) if err != nil { return nil, err } } return ret, nil } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ type ZSliceCmd struct { baseCmd val []Z } var _ Cmder = (*ZSliceCmd)(nil) func NewZSliceCmd(args ...interface{}) *ZSliceCmd { return &ZSliceCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(zSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]Z) return nil } // Implements proto.MultiBulkParse func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { zz := make([]Z, n/2) for i := int64(0); i < n; i += 2 { var err error z := &zz[i/2] z.Member, err = rd.ReadString() if err != nil { return nil, err } z.Score, err = rd.ReadFloatReply() if err != nil { return nil, err } } return zz, nil } //------------------------------------------------------------------------------ type ZWithKeyCmd struct { baseCmd val ZWithKey } var _ Cmder = (*ZWithKeyCmd)(nil) func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { return &ZWithKeyCmd{ baseCmd: baseCmd{_args: args}, } } 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) error { var v interface{} v, cmd.err = rd.ReadArrayReply(zWithKeyParser) if cmd.err != nil { return cmd.err } cmd.val = v.(ZWithKey) return nil } // Implements proto.MultiBulkParse func zWithKeyParser(rd *proto.Reader, n int64) (interface{}, error) { if n != 3 { return nil, fmt.Errorf("got %d elements, expected 3", n) } var z ZWithKey var err error z.Key, err = rd.ReadString() if err != nil { return nil, err } z.Member, err = rd.ReadString() if err != nil { return nil, err } z.Score, err = rd.ReadFloatReply() if err != nil { return nil, err } return z, nil } //------------------------------------------------------------------------------ type ScanCmd struct { baseCmd page []string cursor uint64 process func(cmd Cmder) error } var _ Cmder = (*ScanCmd)(nil) func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { return &ScanCmd{ baseCmd: baseCmd{_args: args}, process: process, } } 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 { cmd.page, cmd.cursor, cmd.err = rd.ReadScanReply() return cmd.err } // Iterator creates a new ScanIterator. func (cmd *ScanCmd) Iterator() *ScanIterator { return &ScanIterator{ cmd: cmd, } } //------------------------------------------------------------------------------ type ClusterNode struct { Id string Addr string } type ClusterSlot struct { Start int End int Nodes []ClusterNode } type ClusterSlotsCmd struct { baseCmd val []ClusterSlot } var _ Cmder = (*ClusterSlotsCmd)(nil) func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { return &ClusterSlotsCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]ClusterSlot) return nil } // Implements proto.MultiBulkParse func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { slots := make([]ClusterSlot, n) for i := 0; i < len(slots); i++ { n, err := rd.ReadArrayLen() if err != nil { return nil, err } if n < 2 { err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) return nil, err } start, err := rd.ReadIntReply() if err != nil { return nil, err } end, err := rd.ReadIntReply() if err != nil { return nil, err } nodes := make([]ClusterNode, n-2) for j := 0; j < len(nodes); j++ { n, err := rd.ReadArrayLen() if err != nil { return nil, err } if n != 2 && n != 3 { err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) return nil, err } ip, err := rd.ReadString() if err != nil { return nil, err } port, err := rd.ReadString() if err != nil { return nil, err } nodes[j].Addr = net.JoinHostPort(ip, port) if n == 3 { id, err := rd.ReadString() if err != nil { return nil, err } nodes[j].Id = id } } slots[i] = ClusterSlot{ Start: int(start), End: int(end), Nodes: nodes, } } return slots, 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 } type GeoLocationCmd struct { baseCmd q *GeoRadiusQuery locations []GeoLocation } var _ Cmder = (*GeoLocationCmd)(nil) func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { 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") } if q.WithDist { args = append(args, "withdist") } if q.WithGeoHash { args = append(args, "withhash") } 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 &GeoLocationCmd{ baseCmd: baseCmd{_args: args}, q: q, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) if cmd.err != nil { return cmd.err } cmd.locations = v.([]GeoLocation) return nil } func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { return func(rd *proto.Reader, n int64) (interface{}, error) { var loc GeoLocation var err error loc.Name, err = rd.ReadString() if err != nil { return nil, err } if q.WithDist { loc.Dist, err = rd.ReadFloatReply() if err != nil { return nil, err } } if q.WithGeoHash { loc.GeoHash, err = rd.ReadIntReply() if err != nil { return nil, err } } if q.WithCoord { n, err := rd.ReadArrayLen() if err != nil { return nil, err } if n != 2 { return nil, fmt.Errorf("got %d coordinates, expected 2", n) } loc.Longitude, err = rd.ReadFloatReply() if err != nil { return nil, err } loc.Latitude, err = rd.ReadFloatReply() if err != nil { return nil, err } } return &loc, nil } } func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { return func(rd *proto.Reader, n int64) (interface{}, error) { locs := make([]GeoLocation, 0, n) for i := int64(0); i < n; i++ { v, err := rd.ReadReply(newGeoLocationParser(q)) if err != nil { return nil, err } switch vv := v.(type) { case string: locs = append(locs, GeoLocation{ Name: vv, }) case *GeoLocation: locs = append(locs, *vv) default: return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) } } return locs, nil } } //------------------------------------------------------------------------------ type GeoPos struct { Longitude, Latitude float64 } type GeoPosCmd struct { baseCmd positions []*GeoPos } var _ Cmder = (*GeoPosCmd)(nil) func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { return &GeoPosCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *GeoPosCmd) Val() []*GeoPos { return cmd.positions } func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { return cmd.Val(), cmd.Err() } func (cmd *GeoPosCmd) String() string { return cmdString(cmd, cmd.positions) } func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { var v interface{} v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) if cmd.err != nil { return cmd.err } cmd.positions = v.([]*GeoPos) return nil } func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { positions := make([]*GeoPos, 0, n) for i := int64(0); i < n; i++ { v, err := rd.ReadReply(geoPosParser) if err != nil { if err == Nil { positions = append(positions, nil) continue } return nil, err } switch v := v.(type) { case *GeoPos: positions = append(positions, v) default: return nil, fmt.Errorf("got %T, expected *GeoPos", v) } } return positions, nil } func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { var pos GeoPos var err error pos.Longitude, err = rd.ReadFloatReply() if err != nil { return nil, err } pos.Latitude, err = rd.ReadFloatReply() if err != nil { return nil, err } return &pos, nil } //------------------------------------------------------------------------------ type CommandInfo struct { Name string Arity int8 Flags []string FirstKeyPos int8 LastKeyPos int8 StepCount int8 ReadOnly bool } type CommandsInfoCmd struct { baseCmd val map[string]*CommandInfo } var _ Cmder = (*CommandsInfoCmd)(nil) func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { return &CommandsInfoCmd{ baseCmd: baseCmd{_args: args}, } } 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 { var v interface{} v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]*CommandInfo) return nil } // Implements proto.MultiBulkParse func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { m := make(map[string]*CommandInfo, n) for i := int64(0); i < n; i++ { v, err := rd.ReadReply(commandInfoParser) if err != nil { return nil, err } vv := v.(*CommandInfo) m[vv.Name] = vv } return m, nil } func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { var cmd CommandInfo var err error if n != 6 { return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) } cmd.Name, err = rd.ReadString() if err != nil { return nil, err } arity, err := rd.ReadIntReply() if err != nil { return nil, err } cmd.Arity = int8(arity) flags, err := rd.ReadReply(stringSliceParser) if err != nil { return nil, err } cmd.Flags = flags.([]string) firstKeyPos, err := rd.ReadIntReply() if err != nil { return nil, err } cmd.FirstKeyPos = int8(firstKeyPos) lastKeyPos, err := rd.ReadIntReply() if err != nil { return nil, err } cmd.LastKeyPos = int8(lastKeyPos) stepCount, err := rd.ReadIntReply() if err != nil { return nil, err } cmd.StepCount = int8(stepCount) for _, flag := range cmd.Flags { if flag == "readonly" { cmd.ReadOnly = true break } } return &cmd, nil } //------------------------------------------------------------------------------ type cmdsInfoCache struct { fn func() (map[string]*CommandInfo, error) once internal.Once cmds map[string]*CommandInfo } func newCmdsInfoCache(fn func() (map[string]*CommandInfo, error)) *cmdsInfoCache { return &cmdsInfoCache{ fn: fn, } } func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { err := c.once.Do(func() error { cmds, err := c.fn() if err != nil { return err } c.cmds = cmds return nil }) return c.cmds, err } redis-6.15.0/command_test.go000066400000000000000000000025251340572764100157320ustar00rootroot00000000000000package redis_test import ( "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Cmd", func() { var client *redis.Client BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("implements Stringer", func() { set := client.Set("foo", "bar", 0) Expect(set.String()).To(Equal("set foo bar: OK")) get := client.Get("foo") Expect(get.String()).To(Equal("get foo: bar")) }) It("has val/err", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello")) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) }) It("has helpers", func() { set := client.Set("key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred()) n, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(10))) un, err := client.Get("key").Uint64() Expect(err).NotTo(HaveOccurred()) Expect(un).To(Equal(uint64(10))) f, err := client.Get("key").Float64() Expect(err).NotTo(HaveOccurred()) Expect(f).To(Equal(float64(10))) }) }) redis-6.15.0/commands.go000066400000000000000000001747301340572764100150660ustar00rootroot00000000000000package redis import ( "errors" "io" "time" "github.com/go-redis/redis/internal" ) func usePrecise(dur time.Duration) bool { return dur < time.Second || dur%time.Second != 0 } func formatMs(dur time.Duration) int64 { if dur > 0 && dur < time.Millisecond { internal.Logf( "specified duration is %s, but minimal supported value is %s", dur, time.Millisecond, ) } return int64(dur / time.Millisecond) } func formatSec(dur time.Duration) int64 { if dur > 0 && dur < time.Second { internal.Logf( "specified duration is %s, but minimal supported value is %s", dur, time.Second, ) } return int64(dur / time.Second) } func appendArgs(dst, src []interface{}) []interface{} { if len(src) == 1 { if ss, ok := src[0].([]string); ok { for _, s := range ss { dst = append(dst, s) } return dst } } for _, v := range src { dst = append(dst, v) } return dst } type Cmdable interface { Pipeline() Pipeliner Pipelined(fn func(Pipeliner) error) ([]Cmder, error) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) TxPipeline() Pipeliner Command() *CommandsInfoCmd ClientGetName() *StringCmd Echo(message interface{}) *StringCmd Ping() *StatusCmd Quit() *StatusCmd Del(keys ...string) *IntCmd Unlink(keys ...string) *IntCmd Dump(key string) *StringCmd Exists(keys ...string) *IntCmd Expire(key string, expiration time.Duration) *BoolCmd ExpireAt(key string, tm time.Time) *BoolCmd Keys(pattern string) *StringSliceCmd Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd Move(key string, db int64) *BoolCmd ObjectRefCount(key string) *IntCmd ObjectEncoding(key string) *StringCmd ObjectIdleTime(key string) *DurationCmd Persist(key string) *BoolCmd PExpire(key string, expiration time.Duration) *BoolCmd PExpireAt(key string, tm time.Time) *BoolCmd PTTL(key string) *DurationCmd RandomKey() *StringCmd Rename(key, newkey string) *StatusCmd RenameNX(key, newkey string) *BoolCmd Restore(key string, ttl time.Duration, value string) *StatusCmd RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd Sort(key string, sort *Sort) *StringSliceCmd SortStore(key, store string, sort *Sort) *IntCmd SortInterfaces(key string, sort *Sort) *SliceCmd Touch(keys ...string) *IntCmd TTL(key string) *DurationCmd Type(key string) *StatusCmd Scan(cursor uint64, match string, count int64) *ScanCmd SScan(key string, cursor uint64, match string, count int64) *ScanCmd HScan(key string, cursor uint64, match string, count int64) *ScanCmd ZScan(key string, cursor uint64, match string, count int64) *ScanCmd Append(key, value string) *IntCmd BitCount(key string, bitCount *BitCount) *IntCmd BitOpAnd(destKey string, keys ...string) *IntCmd BitOpOr(destKey string, keys ...string) *IntCmd BitOpXor(destKey string, keys ...string) *IntCmd BitOpNot(destKey string, key string) *IntCmd BitPos(key string, bit int64, pos ...int64) *IntCmd Decr(key string) *IntCmd DecrBy(key string, decrement int64) *IntCmd Get(key string) *StringCmd GetBit(key string, offset int64) *IntCmd GetRange(key string, start, end int64) *StringCmd GetSet(key string, value interface{}) *StringCmd Incr(key string) *IntCmd IncrBy(key string, value int64) *IntCmd IncrByFloat(key string, value float64) *FloatCmd MGet(keys ...string) *SliceCmd MSet(pairs ...interface{}) *StatusCmd MSetNX(pairs ...interface{}) *BoolCmd Set(key string, value interface{}, expiration time.Duration) *StatusCmd SetBit(key string, offset int64, value int) *IntCmd SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd SetRange(key string, offset int64, value string) *IntCmd StrLen(key string) *IntCmd HDel(key string, fields ...string) *IntCmd HExists(key, field string) *BoolCmd HGet(key, field string) *StringCmd HGetAll(key string) *StringStringMapCmd HIncrBy(key, field string, incr int64) *IntCmd HIncrByFloat(key, field string, incr float64) *FloatCmd HKeys(key string) *StringSliceCmd HLen(key string) *IntCmd HMGet(key string, fields ...string) *SliceCmd HMSet(key string, fields map[string]interface{}) *StatusCmd HSet(key, field string, value interface{}) *BoolCmd HSetNX(key, field string, value interface{}) *BoolCmd HVals(key string) *StringSliceCmd BLPop(timeout time.Duration, keys ...string) *StringSliceCmd BRPop(timeout time.Duration, keys ...string) *StringSliceCmd BRPopLPush(source, destination string, timeout time.Duration) *StringCmd LIndex(key string, index int64) *StringCmd LInsert(key, op string, pivot, value interface{}) *IntCmd LInsertBefore(key string, pivot, value interface{}) *IntCmd LInsertAfter(key string, pivot, value interface{}) *IntCmd LLen(key string) *IntCmd LPop(key string) *StringCmd LPush(key string, values ...interface{}) *IntCmd LPushX(key string, value interface{}) *IntCmd LRange(key string, start, stop int64) *StringSliceCmd LRem(key string, count int64, value interface{}) *IntCmd LSet(key string, index int64, value interface{}) *StatusCmd LTrim(key string, start, stop int64) *StatusCmd RPop(key string) *StringCmd RPopLPush(source, destination string) *StringCmd RPush(key string, values ...interface{}) *IntCmd RPushX(key string, value interface{}) *IntCmd SAdd(key string, members ...interface{}) *IntCmd SCard(key string) *IntCmd SDiff(keys ...string) *StringSliceCmd SDiffStore(destination string, keys ...string) *IntCmd SInter(keys ...string) *StringSliceCmd SInterStore(destination string, keys ...string) *IntCmd SIsMember(key string, member interface{}) *BoolCmd SMembers(key string) *StringSliceCmd SMembersMap(key string) *StringStructMapCmd SMove(source, destination string, member interface{}) *BoolCmd SPop(key string) *StringCmd SPopN(key string, count int64) *StringSliceCmd SRandMember(key string) *StringCmd SRandMemberN(key string, count int64) *StringSliceCmd SRem(key string, members ...interface{}) *IntCmd SUnion(keys ...string) *StringSliceCmd SUnionStore(destination string, keys ...string) *IntCmd XAdd(a *XAddArgs) *StringCmd XDel(stream string, ids ...string) *IntCmd XLen(stream string) *IntCmd XRange(stream, start, stop string) *XMessageSliceCmd XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd XRevRange(stream string, start, stop string) *XMessageSliceCmd XRevRangeN(stream string, start, stop string, count int64) *XMessageSliceCmd XRead(a *XReadArgs) *XStreamSliceCmd XReadStreams(streams ...string) *XStreamSliceCmd XGroupCreate(stream, group, start string) *StatusCmd XGroupCreateMkStream(stream, group, start string) *StatusCmd XGroupSetID(stream, group, start string) *StatusCmd XGroupDestroy(stream, group string) *IntCmd XGroupDelConsumer(stream, group, consumer string) *IntCmd XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd XAck(stream, group string, ids ...string) *IntCmd XPending(stream, group string) *XPendingCmd XPendingExt(a *XPendingExtArgs) *XPendingExtCmd XClaim(a *XClaimArgs) *XMessageSliceCmd XClaimJustID(a *XClaimArgs) *StringSliceCmd XTrim(key string, maxLen int64) *IntCmd XTrimApprox(key string, maxLen int64) *IntCmd BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd ZAdd(key string, members ...Z) *IntCmd ZAddNX(key string, members ...Z) *IntCmd ZAddXX(key string, members ...Z) *IntCmd ZAddCh(key string, members ...Z) *IntCmd ZAddNXCh(key string, members ...Z) *IntCmd ZAddXXCh(key string, members ...Z) *IntCmd ZIncr(key string, member Z) *FloatCmd ZIncrNX(key string, member Z) *FloatCmd ZIncrXX(key string, member Z) *FloatCmd ZCard(key string) *IntCmd ZCount(key, min, max string) *IntCmd ZLexCount(key, min, max string) *IntCmd ZIncrBy(key string, increment float64, member string) *FloatCmd ZInterStore(destination string, store ZStore, keys ...string) *IntCmd ZPopMax(key string, count ...int64) *ZSliceCmd ZPopMin(key string, count ...int64) *ZSliceCmd ZRange(key string, start, stop int64) *StringSliceCmd ZRangeWithScores(key string, start, stop int64) *ZSliceCmd ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd ZRank(key, member string) *IntCmd ZRem(key string, members ...interface{}) *IntCmd ZRemRangeByRank(key string, start, stop int64) *IntCmd ZRemRangeByScore(key, min, max string) *IntCmd ZRemRangeByLex(key, min, max string) *IntCmd ZRevRange(key string, start, stop int64) *StringSliceCmd ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd ZRevRank(key, member string) *IntCmd ZScore(key, member string) *FloatCmd ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd PFAdd(key string, els ...interface{}) *IntCmd PFCount(keys ...string) *IntCmd PFMerge(dest string, keys ...string) *StatusCmd BgRewriteAOF() *StatusCmd BgSave() *StatusCmd ClientKill(ipPort string) *StatusCmd ClientKillByFilter(keys ...string) *IntCmd ClientList() *StringCmd ClientPause(dur time.Duration) *BoolCmd ClientID() *IntCmd ConfigGet(parameter string) *SliceCmd ConfigResetStat() *StatusCmd ConfigSet(parameter, value string) *StatusCmd ConfigRewrite() *StatusCmd DBSize() *IntCmd FlushAll() *StatusCmd FlushAllAsync() *StatusCmd FlushDB() *StatusCmd FlushDBAsync() *StatusCmd Info(section ...string) *StringCmd LastSave() *IntCmd Save() *StatusCmd Shutdown() *StatusCmd ShutdownSave() *StatusCmd ShutdownNoSave() *StatusCmd SlaveOf(host, port string) *StatusCmd Time() *TimeCmd Eval(script string, keys []string, args ...interface{}) *Cmd EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd ScriptExists(hashes ...string) *BoolSliceCmd ScriptFlush() *StatusCmd ScriptKill() *StatusCmd ScriptLoad(script string) *StringCmd DebugObject(key string) *StringCmd Publish(channel string, message interface{}) *IntCmd PubSubChannels(pattern string) *StringSliceCmd PubSubNumSub(channels ...string) *StringIntMapCmd PubSubNumPat() *IntCmd ClusterSlots() *ClusterSlotsCmd ClusterNodes() *StringCmd ClusterMeet(host, port string) *StatusCmd ClusterForget(nodeID string) *StatusCmd ClusterReplicate(nodeID string) *StatusCmd ClusterResetSoft() *StatusCmd ClusterResetHard() *StatusCmd ClusterInfo() *StringCmd ClusterKeySlot(key string) *IntCmd ClusterCountFailureReports(nodeID string) *IntCmd ClusterCountKeysInSlot(slot int) *IntCmd ClusterDelSlots(slots ...int) *StatusCmd ClusterDelSlotsRange(min, max int) *StatusCmd ClusterSaveConfig() *StatusCmd ClusterSlaves(nodeID string) *StringSliceCmd ClusterFailover() *StatusCmd ClusterAddSlots(slots ...int) *StatusCmd ClusterAddSlotsRange(min, max int) *StatusCmd GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd GeoPos(key string, members ...string) *GeoPosCmd GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd GeoDist(key string, member1, member2, unit string) *FloatCmd GeoHash(key string, members ...string) *StringSliceCmd ReadOnly() *StatusCmd ReadWrite() *StatusCmd MemoryUsage(key string, samples ...int) *IntCmd } type StatefulCmdable interface { Cmdable Auth(password string) *StatusCmd Select(index int) *StatusCmd SwapDB(index1, index2 int) *StatusCmd ClientSetName(name string) *BoolCmd } var _ Cmdable = (*Client)(nil) var _ Cmdable = (*Tx)(nil) var _ Cmdable = (*Ring)(nil) var _ Cmdable = (*ClusterClient)(nil) type cmdable struct { process func(cmd Cmder) error } func (c *cmdable) setProcessor(fn func(Cmder) error) { c.process = fn } type statefulCmdable struct { cmdable process func(cmd Cmder) error } func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { c.process = fn c.cmdable.setProcessor(fn) } //------------------------------------------------------------------------------ func (c *statefulCmdable) Auth(password string) *StatusCmd { cmd := NewStatusCmd("auth", password) c.process(cmd) return cmd } func (c *cmdable) Echo(message interface{}) *StringCmd { cmd := NewStringCmd("echo", message) c.process(cmd) return cmd } func (c *cmdable) Ping() *StatusCmd { cmd := NewStatusCmd("ping") c.process(cmd) return cmd } func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) c.process(cmd) return cmd } func (c *cmdable) Quit() *StatusCmd { panic("not implemented") } func (c *statefulCmdable) Select(index int) *StatusCmd { cmd := NewStatusCmd("select", index) c.process(cmd) return cmd } func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { cmd := NewStatusCmd("swapdb", index1, index2) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) Command() *CommandsInfoCmd { cmd := NewCommandsInfoCmd("command") c.process(cmd) return cmd } func (c *cmdable) Del(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "del" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) Unlink(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "unlink" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) Dump(key string) *StringCmd { cmd := NewStringCmd("dump", key) c.process(cmd) return cmd } func (c *cmdable) Exists(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "exists" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("expire", key, formatSec(expiration)) c.process(cmd) return cmd } func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd("expireat", key, tm.Unix()) c.process(cmd) return cmd } func (c *cmdable) Keys(pattern string) *StringSliceCmd { cmd := NewStringSliceCmd("keys", pattern) c.process(cmd) return cmd } func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { cmd := NewStatusCmd( "migrate", host, port, key, db, formatMs(timeout), ) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } func (c *cmdable) Move(key string, db int64) *BoolCmd { cmd := NewBoolCmd("move", key, db) c.process(cmd) return cmd } func (c *cmdable) ObjectRefCount(key string) *IntCmd { cmd := NewIntCmd("object", "refcount", key) c.process(cmd) return cmd } func (c *cmdable) ObjectEncoding(key string) *StringCmd { cmd := NewStringCmd("object", "encoding", key) c.process(cmd) return cmd } func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "object", "idletime", key) c.process(cmd) return cmd } func (c *cmdable) Persist(key string) *BoolCmd { cmd := NewBoolCmd("persist", key) c.process(cmd) return cmd } func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) c.process(cmd) return cmd } func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd( "pexpireat", key, tm.UnixNano()/int64(time.Millisecond), ) c.process(cmd) return cmd } func (c *cmdable) PTTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Millisecond, "pttl", key) c.process(cmd) return cmd } func (c *cmdable) RandomKey() *StringCmd { cmd := NewStringCmd("randomkey") c.process(cmd) return cmd } func (c *cmdable) Rename(key, newkey string) *StatusCmd { cmd := NewStatusCmd("rename", key, newkey) c.process(cmd) return cmd } func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { cmd := NewBoolCmd("renamenx", key, newkey) c.process(cmd) return cmd } func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, formatMs(ttl), value, ) c.process(cmd) return cmd } func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, formatMs(ttl), value, "replace", ) c.process(cmd) return cmd } type Sort struct { By string Offset, Count int64 Get []string Order string Alpha bool } func (sort *Sort) args(key string) []interface{} { args := []interface{}{"sort", key} if sort.By != "" { args = append(args, "by", sort.By) } if sort.Offset != 0 || sort.Count != 0 { args = append(args, "limit", sort.Offset, sort.Count) } for _, get := range sort.Get { args = append(args, "get", get) } if sort.Order != "" { args = append(args, sort.Order) } if sort.Alpha { args = append(args, "alpha") } return args } func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { cmd := NewStringSliceCmd(sort.args(key)...) c.process(cmd) return cmd } func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { args := sort.args(key) if store != "" { args = append(args, "store", store) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { cmd := NewSliceCmd(sort.args(key)...) c.process(cmd) return cmd } func (c *cmdable) Touch(keys ...string) *IntCmd { args := make([]interface{}, len(keys)+1) args[0] = "touch" for i, key := range keys { args[i+1] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) TTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "ttl", key) c.process(cmd) return cmd } func (c *cmdable) Type(key string) *StatusCmd { cmd := NewStatusCmd("type", key) c.process(cmd) return cmd } func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"scan", cursor} if match != "" { args = append(args, "match", match) } if count > 0 { args = append(args, "count", count) } cmd := NewScanCmd(c.process, args...) c.process(cmd) return cmd } func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"sscan", key, cursor} if match != "" { args = append(args, "match", match) } if count > 0 { args = append(args, "count", count) } cmd := NewScanCmd(c.process, args...) c.process(cmd) return cmd } func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"hscan", key, cursor} if match != "" { args = append(args, "match", match) } if count > 0 { args = append(args, "count", count) } cmd := NewScanCmd(c.process, args...) c.process(cmd) return cmd } func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"zscan", key, cursor} if match != "" { args = append(args, "match", match) } if count > 0 { args = append(args, "count", count) } cmd := NewScanCmd(c.process, args...) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) Append(key, value string) *IntCmd { cmd := NewIntCmd("append", key, value) c.process(cmd) return cmd } type BitCount struct { Start, End int64 } func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { args := []interface{}{"bitcount", key} if bitCount != nil { args = append( args, bitCount.Start, bitCount.End, ) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { args := make([]interface{}, 3+len(keys)) args[0] = "bitop" args[1] = op args[2] = destKey for i, key := range keys { args[3+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { return c.bitOp("and", destKey, keys...) } func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { return c.bitOp("or", destKey, keys...) } func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { return c.bitOp("xor", destKey, keys...) } func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { return c.bitOp("not", destKey, key) } func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { args := make([]interface{}, 3+len(pos)) args[0] = "bitpos" args[1] = key args[2] = bit switch len(pos) { case 0: case 1: args[3] = pos[0] case 2: args[3] = pos[0] args[4] = pos[1] default: panic("too many arguments") } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) Decr(key string) *IntCmd { cmd := NewIntCmd("decr", key) c.process(cmd) return cmd } func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { cmd := NewIntCmd("decrby", key, decrement) c.process(cmd) return cmd } // Redis `GET key` command. It returns redis.Nil error when key does not exist. func (c *cmdable) Get(key string) *StringCmd { cmd := NewStringCmd("get", key) c.process(cmd) return cmd } func (c *cmdable) GetBit(key string, offset int64) *IntCmd { cmd := NewIntCmd("getbit", key, offset) c.process(cmd) return cmd } func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { cmd := NewStringCmd("getrange", key, start, end) c.process(cmd) return cmd } func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { cmd := NewStringCmd("getset", key, value) c.process(cmd) return cmd } func (c *cmdable) Incr(key string) *IntCmd { cmd := NewIntCmd("incr", key) c.process(cmd) return cmd } func (c *cmdable) IncrBy(key string, value int64) *IntCmd { cmd := NewIntCmd("incrby", key, value) c.process(cmd) return cmd } func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { cmd := NewFloatCmd("incrbyfloat", key, value) c.process(cmd) return cmd } func (c *cmdable) MGet(keys ...string) *SliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "mget" for i, key := range keys { args[1+i] = key } cmd := NewSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { args := make([]interface{}, 1, 1+len(pairs)) args[0] = "mset" args = appendArgs(args, pairs) cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { args := make([]interface{}, 1, 1+len(pairs)) args[0] = "msetnx" args = appendArgs(args, pairs) cmd := NewBoolCmd(args...) c.process(cmd) return cmd } // Redis `SET key value [expiration]` command. // // Use expiration for `SETEX`-like behavior. // Zero expiration means the key has no expiration time. func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { args := make([]interface{}, 3, 4) args[0] = "set" args[1] = key args[2] = value if expiration > 0 { if usePrecise(expiration) { args = append(args, "px", formatMs(expiration)) } else { args = append(args, "ex", formatSec(expiration)) } } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { cmd := NewIntCmd( "setbit", key, offset, value, ) c.process(cmd) return cmd } // Redis `SET key value [expiration] NX` command. // // Zero expiration means the key has no expiration time. func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd if expiration == 0 { // Use old `SETNX` to support old Redis versions. cmd = NewBoolCmd("setnx", key, value) } else { if usePrecise(expiration) { cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx") } else { cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") } } c.process(cmd) return cmd } // Redis `SET key value [expiration] XX` command. // // Zero expiration means the key has no expiration time. func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd if expiration == 0 { cmd = NewBoolCmd("set", key, value, "xx") } else { if usePrecise(expiration) { cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "xx") } else { cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") } } c.process(cmd) return cmd } func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { cmd := NewIntCmd("setrange", key, offset, value) c.process(cmd) return cmd } func (c *cmdable) StrLen(key string) *IntCmd { cmd := NewIntCmd("strlen", key) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) HDel(key string, fields ...string) *IntCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hdel" args[1] = key for i, field := range fields { args[2+i] = field } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) HExists(key, field string) *BoolCmd { cmd := NewBoolCmd("hexists", key, field) c.process(cmd) return cmd } func (c *cmdable) HGet(key, field string) *StringCmd { cmd := NewStringCmd("hget", key, field) c.process(cmd) return cmd } func (c *cmdable) HGetAll(key string) *StringStringMapCmd { cmd := NewStringStringMapCmd("hgetall", key) c.process(cmd) return cmd } func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { cmd := NewIntCmd("hincrby", key, field, incr) c.process(cmd) return cmd } func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { cmd := NewFloatCmd("hincrbyfloat", key, field, incr) c.process(cmd) return cmd } func (c *cmdable) HKeys(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hkeys", key) c.process(cmd) return cmd } func (c *cmdable) HLen(key string) *IntCmd { cmd := NewIntCmd("hlen", key) c.process(cmd) return cmd } func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hmget" args[1] = key for i, field := range fields { args[2+i] = field } cmd := NewSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { args := make([]interface{}, 2+len(fields)*2) args[0] = "hmset" args[1] = key i := 2 for k, v := range fields { args[i] = k args[i+1] = v i += 2 } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { cmd := NewBoolCmd("hset", key, field, value) c.process(cmd) return cmd } func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { cmd := NewBoolCmd("hsetnx", key, field, value) c.process(cmd) return cmd } func (c *cmdable) HVals(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hvals", key) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "blpop" for i, key := range keys { args[1+i] = key } args[len(args)-1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "brpop" for i, key := range keys { args[1+i] = key } args[len(keys)+1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { cmd := NewStringCmd( "brpoplpush", source, destination, formatSec(timeout), ) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } func (c *cmdable) LIndex(key string, index int64) *StringCmd { cmd := NewStringCmd("lindex", key, index) c.process(cmd) return cmd } func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, op, pivot, value) c.process(cmd) return cmd } func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "before", pivot, value) c.process(cmd) return cmd } func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "after", pivot, value) c.process(cmd) return cmd } func (c *cmdable) LLen(key string) *IntCmd { cmd := NewIntCmd("llen", key) c.process(cmd) return cmd } func (c *cmdable) LPop(key string) *StringCmd { cmd := NewStringCmd("lpop", key) c.process(cmd) return cmd } func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "lpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { cmd := NewIntCmd("lpushx", key, value) c.process(cmd) return cmd } func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd( "lrange", key, start, stop, ) c.process(cmd) return cmd } func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { cmd := NewIntCmd("lrem", key, count, value) c.process(cmd) return cmd } func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { cmd := NewStatusCmd("lset", key, index, value) c.process(cmd) return cmd } func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { cmd := NewStatusCmd( "ltrim", key, start, stop, ) c.process(cmd) return cmd } func (c *cmdable) RPop(key string) *StringCmd { cmd := NewStringCmd("rpop", key) c.process(cmd) return cmd } func (c *cmdable) RPopLPush(source, destination string) *StringCmd { cmd := NewStringCmd("rpoplpush", source, destination) c.process(cmd) return cmd } func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "rpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { cmd := NewIntCmd("rpushx", key, value) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "sadd" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SCard(key string) *IntCmd { cmd := NewIntCmd("scard", key) c.process(cmd) return cmd } func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sdiff" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sdiffstore" args[1] = destination for i, key := range keys { args[2+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SInter(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sinter" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sinterstore" args[1] = destination for i, key := range keys { args[2+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { cmd := NewBoolCmd("sismember", key, member) c.process(cmd) return cmd } // Redis `SMEMBERS key` command output as a slice func (c *cmdable) SMembers(key string) *StringSliceCmd { cmd := NewStringSliceCmd("smembers", key) c.process(cmd) return cmd } // Redis `SMEMBERS key` command output as a map func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { cmd := NewStringStructMapCmd("smembers", key) c.process(cmd) return cmd } func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { cmd := NewBoolCmd("smove", source, destination, member) c.process(cmd) return cmd } // Redis `SPOP key` command. func (c *cmdable) SPop(key string) *StringCmd { cmd := NewStringCmd("spop", key) c.process(cmd) return cmd } // Redis `SPOP key count` command. func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("spop", key, count) c.process(cmd) return cmd } // Redis `SRANDMEMBER key` command. func (c *cmdable) SRandMember(key string) *StringCmd { cmd := NewStringCmd("srandmember", key) c.process(cmd) return cmd } // Redis `SRANDMEMBER key count` command. func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("srandmember", key, count) c.process(cmd) return cmd } func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "srem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sunion" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sunionstore" args[1] = destination for i, key := range keys { args[2+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } //------------------------------------------------------------------------------ type XAddArgs struct { Stream string MaxLen int64 // MAXLEN N MaxLenApprox int64 // MAXLEN ~ N ID string Values map[string]interface{} } func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { args := make([]interface{}, 0, 6+len(a.Values)*2) args = append(args, "xadd") args = append(args, a.Stream) if a.MaxLen > 0 { args = append(args, "maxlen", a.MaxLen) } else if a.MaxLenApprox > 0 { args = append(args, "maxlen", "~", a.MaxLenApprox) } if a.ID != "" { args = append(args, a.ID) } else { args = append(args, "*") } for k, v := range a.Values { args = append(args, k) args = append(args, v) } cmd := NewStringCmd(args...) c.process(cmd) return cmd } func (c *cmdable) XDel(stream string, ids ...string) *IntCmd { args := []interface{}{"xdel", stream} for _, id := range ids { args = append(args, id) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) XLen(stream string) *IntCmd { cmd := NewIntCmd("xlen", stream) c.process(cmd) return cmd } func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop) c.process(cmd) return cmd } func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) c.process(cmd) return cmd } func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) c.process(cmd) return cmd } func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) c.process(cmd) return cmd } type XReadArgs struct { Streams []string Count int64 Block time.Duration } func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 5+len(a.Streams)) args = append(args, "xread") if a.Count > 0 { args = append(args, "count") args = append(args, a.Count) } if a.Block >= 0 { args = append(args, "block") args = append(args, int64(a.Block/time.Millisecond)) } args = append(args, "streams") for _, s := range a.Streams { args = append(args, s) } cmd := NewXStreamSliceCmd(args...) if a.Block >= 0 { cmd.setReadTimeout(a.Block) } c.process(cmd) return cmd } func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { return c.XRead(&XReadArgs{ Streams: streams, Block: -1, }) } func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "create", stream, group, start) c.process(cmd) return cmd } func (c *cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") c.process(cmd) return cmd } func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "setid", stream, group, start) c.process(cmd) return cmd } func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { cmd := NewIntCmd("xgroup", "destroy", stream, group) c.process(cmd) return cmd } func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) c.process(cmd) return cmd } type XReadGroupArgs struct { Group string Consumer string Streams []string Count int64 Block time.Duration NoAck bool } func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 8+len(a.Streams)) args = append(args, "xreadgroup", "group", a.Group, a.Consumer) if a.Count > 0 { args = append(args, "count", a.Count) } if a.Block >= 0 { args = append(args, "block", int64(a.Block/time.Millisecond)) } if a.NoAck { args = append(args, "noack") } args = append(args, "streams") for _, s := range a.Streams { args = append(args, s) } cmd := NewXStreamSliceCmd(args...) if a.Block >= 0 { cmd.setReadTimeout(a.Block) } c.process(cmd) return cmd } func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { args := []interface{}{"xack", stream, group} for _, id := range ids { args = append(args, id) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) XPending(stream, group string) *XPendingCmd { cmd := NewXPendingCmd("xpending", stream, group) c.process(cmd) return cmd } type XPendingExtArgs struct { Stream string Group string Start string End string Count int64 Consumer string } func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { args := make([]interface{}, 0, 7) args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) if a.Consumer != "" { args = append(args, a.Consumer) } cmd := NewXPendingExtCmd(args...) c.process(cmd) return cmd } type XClaimArgs struct { Stream string Group string Consumer string MinIdle time.Duration Messages []string } func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { args := xClaimArgs(a) cmd := NewXMessageSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { args := xClaimArgs(a) args = append(args, "justid") cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func xClaimArgs(a *XClaimArgs) []interface{} { args := make([]interface{}, 0, 4+len(a.Messages)) args = append(args, "xclaim", a.Stream, a.Group, a.Consumer, int64(a.MinIdle/time.Millisecond)) for _, id := range a.Messages { args = append(args, id) } return args } func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) c.process(cmd) return cmd } func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) c.process(cmd) return cmd } //------------------------------------------------------------------------------ // Z represents sorted set member. type Z struct { Score float64 Member interface{} } // ZWithKey represents sorted set member including the name of the key where it was popped. type ZWithKey struct { Z Key string } // ZStore is used as an arg to ZInterStore and ZUnionStore. type ZStore struct { Weights []float64 // Can be SUM, MIN or MAX. Aggregate string } // Redis `BZPOPMAX key [key ...] timeout` command. func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "bzpopmax" for i, key := range keys { args[1+i] = key } args[len(args)-1] = formatSec(timeout) cmd := NewZWithKeyCmd(args...) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } // Redis `BZPOPMIN key [key ...] timeout` command. func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "bzpopmin" for i, key := range keys { args[1+i] = key } args[len(args)-1] = formatSec(timeout) cmd := NewZWithKeyCmd(args...) cmd.setReadTimeout(timeout) c.process(cmd) return cmd } func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { for i, m := range members { a[n+2*i] = m.Score a[n+2*i+1] = m.Member } cmd := NewIntCmd(a...) c.process(cmd) return cmd } // Redis `ZADD key score member [score member ...]` command. func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { const n = 2 a := make([]interface{}, n+2*len(members)) a[0], a[1] = "zadd", key return c.zAdd(a, n, members...) } // Redis `ZADD key NX score member [score member ...]` command. func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "nx" return c.zAdd(a, n, members...) } // Redis `ZADD key XX score member [score member ...]` command. func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "xx" return c.zAdd(a, n, members...) } // Redis `ZADD key CH score member [score member ...]` command. func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "ch" return c.zAdd(a, n, members...) } // Redis `ZADD key NX CH score member [score member ...]` command. func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { const n = 4 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" return c.zAdd(a, n, members...) } // Redis `ZADD key XX CH score member [score member ...]` command. func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { const n = 4 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" return c.zAdd(a, n, members...) } func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { for i, m := range members { a[n+2*i] = m.Score a[n+2*i+1] = m.Member } cmd := NewFloatCmd(a...) c.process(cmd) return cmd } // Redis `ZADD key INCR score member` command. func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { const n = 3 a := make([]interface{}, n+2) a[0], a[1], a[2] = "zadd", key, "incr" return c.zIncr(a, n, member) } // Redis `ZADD key NX INCR score member` command. func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { const n = 4 a := make([]interface{}, n+2) a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" return c.zIncr(a, n, member) } // Redis `ZADD key XX INCR score member` command. func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { const n = 4 a := make([]interface{}, n+2) a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" return c.zIncr(a, n, member) } func (c *cmdable) ZCard(key string) *IntCmd { cmd := NewIntCmd("zcard", key) c.process(cmd) return cmd } func (c *cmdable) ZCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zcount", key, min, max) c.process(cmd) return cmd } func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zlexcount", key, min, max) c.process(cmd) return cmd } func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { cmd := NewFloatCmd("zincrby", key, increment, member) c.process(cmd) return cmd } func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { args := make([]interface{}, 3+len(keys)) args[0] = "zinterstore" args[1] = destination args[2] = len(keys) for i, key := range keys { args[3+i] = key } if len(store.Weights) > 0 { args = append(args, "weights") for _, weight := range store.Weights { args = append(args, weight) } } if store.Aggregate != "" { args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { args := []interface{}{ "zpopmax", key, } switch len(count) { case 0: break case 1: args = append(args, count[0]) default: panic("too many arguments") } cmd := NewZSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { args := []interface{}{ "zpopmin", key, } switch len(count) { case 0: break case 1: args = append(args, count[0]) default: panic("too many arguments") } cmd := NewZSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { args := []interface{}{ "zrange", key, start, stop, } if withScores { args = append(args, "withscores") } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { return c.zRange(key, start, stop, false) } func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") c.process(cmd) return cmd } type ZRangeBy struct { Min, Max string Offset, Count int64 } func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { args := []interface{}{zcmd, key, opt.Min, opt.Max} if withScores { args = append(args, "withscores") } if opt.Offset != 0 || opt.Count != 0 { args = append( args, "limit", opt.Offset, opt.Count, ) } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { return c.zRangeBy("zrangebyscore", key, opt, false) } func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { return c.zRangeBy("zrangebylex", key, opt, false) } func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} if opt.Offset != 0 || opt.Count != 0 { args = append( args, "limit", opt.Offset, opt.Count, ) } cmd := NewZSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRank(key, member string) *IntCmd { cmd := NewIntCmd("zrank", key, member) c.process(cmd) return cmd } func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "zrem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { cmd := NewIntCmd( "zremrangebyrank", key, start, stop, ) c.process(cmd) return cmd } func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebyscore", key, min, max) c.process(cmd) return cmd } func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebylex", key, min, max) c.process(cmd) return cmd } func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd("zrevrange", key, start, stop) c.process(cmd) return cmd } func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") c.process(cmd) return cmd } func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { args := []interface{}{zcmd, key, opt.Max, opt.Min} if opt.Offset != 0 || opt.Count != 0 { args = append( args, "limit", opt.Offset, opt.Count, ) } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebyscore", key, opt) } func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebylex", key, opt) } func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} if opt.Offset != 0 || opt.Count != 0 { args = append( args, "limit", opt.Offset, opt.Count, ) } cmd := NewZSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ZRevRank(key, member string) *IntCmd { cmd := NewIntCmd("zrevrank", key, member) c.process(cmd) return cmd } func (c *cmdable) ZScore(key, member string) *FloatCmd { cmd := NewFloatCmd("zscore", key, member) c.process(cmd) return cmd } func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { args := make([]interface{}, 3+len(keys)) args[0] = "zunionstore" args[1] = dest args[2] = len(keys) for i, key := range keys { args[3+i] = key } if len(store.Weights) > 0 { args = append(args, "weights") for _, weight := range store.Weights { args = append(args, weight) } } if store.Aggregate != "" { args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(els)) args[0] = "pfadd" args[1] = key args = appendArgs(args, els) cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) PFCount(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "pfcount" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { args := make([]interface{}, 2+len(keys)) args[0] = "pfmerge" args[1] = dest for i, key := range keys { args[2+i] = key } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) BgRewriteAOF() *StatusCmd { cmd := NewStatusCmd("bgrewriteaof") c.process(cmd) return cmd } func (c *cmdable) BgSave() *StatusCmd { cmd := NewStatusCmd("bgsave") c.process(cmd) return cmd } func (c *cmdable) ClientKill(ipPort string) *StatusCmd { cmd := NewStatusCmd("client", "kill", ipPort) c.process(cmd) return cmd } // ClientKillByFilter is new style synx, while the ClientKill is old // CLIENT KILL