pax_global_header00006660000000000000000000000064132452634150014517gustar00rootroot0000000000000052 comment=68362cfda1eeb3a69316e7bc00169a9a8de4823a redis-6.9.2/000077500000000000000000000000001324526341500126435ustar00rootroot00000000000000redis-6.9.2/.gitignore000066400000000000000000000000221324526341500146250ustar00rootroot00000000000000*.rdb testdata/*/ redis-6.9.2/.travis.yml000066400000000000000000000003271324526341500147560ustar00rootroot00000000000000sudo: false language: go services: - redis-server go: - 1.7.x - 1.8.x - 1.9.x - tip matrix: allow_failures: - go: tip install: - go get github.com/onsi/ginkgo - go get github.com/onsi/gomega redis-6.9.2/LICENSE000066400000000000000000000024461324526341500136560ustar00rootroot00000000000000Copyright (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.9.2/Makefile000066400000000000000000000007431324526341500143070ustar00rootroot00000000000000all: testdeps go test ./... go test ./... -short -race go vet 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 '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: 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.9.2/bench_test.go000066400000000000000000000100741324526341500153120ustar00rootroot00000000000000package redis_test import ( "bytes" "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 BenchmarkRedisSetString(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() value := string(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) } } }) } 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) } } }) } func benchmarkSetRedis(b *testing.B, poolSize, payloadSize int) { client := benchmarkRedisClient(poolSize) defer client.Close() value := string(bytes.Repeat([]byte{'1'}, payloadSize)) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := client.Set("key", value, 0).Err(); err != nil { b.Fatal(err) } } }) } func BenchmarkSetRedis10Conns64Bytes(b *testing.B) { benchmarkSetRedis(b, 10, 64) } func BenchmarkSetRedis100Conns64Bytes(b *testing.B) { benchmarkSetRedis(b, 100, 64) } func BenchmarkSetRedis10Conns1KB(b *testing.B) { benchmarkSetRedis(b, 10, 1024) } func BenchmarkSetRedis100Conns1KB(b *testing.B) { benchmarkSetRedis(b, 100, 1024) } func BenchmarkSetRedis10Conns10KB(b *testing.B) { benchmarkSetRedis(b, 10, 10*1024) } func BenchmarkSetRedis100Conns10KB(b *testing.B) { benchmarkSetRedis(b, 100, 10*1024) } func BenchmarkSetRedis10Conns1MB(b *testing.B) { benchmarkSetRedis(b, 10, 1024*1024) } func BenchmarkSetRedis100Conns1MB(b *testing.B) { benchmarkSetRedis(b, 100, 1024*1024) } 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() { if err := client.ZAdd("key", redis.Z{float64(1), "hello"}).Err(); err != nil { b.Fatal(err) } } }) } redis-6.9.2/cluster.go000066400000000000000000000657151324526341500146710ustar00rootroot00000000000000package redis import ( "fmt" "math" "math/rand" "net" "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. MaxRedirects int // Enables read-only commands on slave nodes. ReadOnly bool // Allows routing read-only commands to the closest master or slave node. RouteByLatency bool // Following options are copied from Options struct. OnConnect func(*Conn) error MaxRetries int MinRetryBackoff time.Duration MaxRetryBackoff time.Duration Password string DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration // PoolSize applies per cluster node and not for the whole cluster. PoolSize int PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration } func (opt *ClusterOptions) init() { if opt.MaxRedirects == -1 { opt.MaxRedirects = 0 } else if opt.MaxRedirects == 0 { opt.MaxRedirects = 8 } if opt.RouteByLatency { opt.ReadOnly = true } 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, PoolTimeout: opt.PoolTimeout, IdleTimeout: opt.IdleTimeout, IdleCheckFrequency: disableIdleCheck, } } //------------------------------------------------------------------------------ type clusterNode struct { Client *Client latency uint32 // atomic generation uint32 // atomic loading int64 // 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() } return &node } func (n *clusterNode) Close() error { return n.Client.Close() } func (n *clusterNode) Test() error { return n.Client.ClusterInfo().Err() } 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.StoreInt64(&n.loading, time.Now().Unix()) } func (n *clusterNode) Loading() bool { const minute = int64(time.Minute / time.Second) loading := atomic.LoadInt64(&n.loading) if loading == 0 { return false } if time.Now().Unix()-loading < minute { return true } atomic.StoreInt64(&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 } 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 { c.generation++ return c.generation } // 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) 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) GetOrCreate(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() 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, node.Test() }) 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) Random() (*clusterNode, error) { addrs, err := c.Addrs() if err != nil { return nil, err } n := rand.Intn(len(addrs)) return c.GetOrCreate(addrs[n]) } //------------------------------------------------------------------------------ type clusterState struct { nodes *clusterNodes masters []*clusterNode slaves []*clusterNode slots [][]*clusterNode generation uint32 } func newClusterState(nodes *clusterNodes, slots []ClusterSlot, origin string) (*clusterState, error) { c := clusterState{ nodes: nodes, generation: nodes.NextGeneration(), slots: make([][]*clusterNode, hashtag.SlotNumber), } isLoopbackOrigin := isLoopbackAddr(origin) for _, slot := range slots { var nodes []*clusterNode for i, slotNode := range slot.Nodes { addr := slotNode.Addr if !isLoopbackOrigin && isLoopbackAddr(addr) { addr = origin } 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 = appendNode(c.masters, node) } else { c.slaves = appendNode(c.slaves, node) } } for i := slot.Start; i <= slot.End; i++ { c.slots[i] = nodes } } return &c, nil } 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() { break } } return slave, 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) slotNodes(slot int) []*clusterNode { if slot >= 0 && slot < len(c.slots) { return c.slots[slot] } return nil } //------------------------------------------------------------------------------ // 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 opt *ClusterOptions nodes *clusterNodes _state atomic.Value stateErrMu sync.RWMutex stateErr error cmdsInfoOnce internal.Once cmdsInfo map[string]*CommandInfo process func(Cmder) error processPipeline func([]Cmder) error processTxPipeline func([]Cmder) error // Reports whether slots reloading is in progress. reloading uint32 } // 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.process = c.defaultProcess c.processPipeline = c.defaultProcessPipeline c.processTxPipeline = c.defaultProcessTxPipeline c.cmdable.setProcessor(c.Process) c.reloadState() if opt.IdleCheckFrequency > 0 { go c.reaper(opt.IdleCheckFrequency) } return c } // 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) cmdInfo(name string) *CommandInfo { err := c.cmdsInfoOnce.Do(func() error { node, err := c.nodes.Random() if err != nil { return err } cmdsInfo, err := node.Client.Command().Result() if err != nil { return err } c.cmdsInfo = cmdsInfo return nil }) if err != nil { return nil } info := c.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() if err != nil { return 0, nil, err } cmdInfo := c.cmdInfo(cmd.Name()) slot := cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) if cmdInfo != nil && cmdInfo.ReadOnly && c.opt.ReadOnly { if c.opt.RouteByLatency { node, err := state.slotClosestNode(slot) return slot, node, err } 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() 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: keys don't hash to the same slot") } slot := hashtag.Slot(keys[0]) for _, key := range keys[1:] { if hashtag.Slot(key) != slot { return fmt.Errorf("redis: Watch requires all keys to be in the same slot") } } 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) { continue } moved, ask, addr := internal.IsMovedError(err) if moved || ask { c.lazyReloadState() 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() } 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 - read from master. if c.opt.ReadOnly && internal.IsLoadingError(err) { node.MarkAsLoading() continue } if internal.IsRetryableError(err, true) { 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.lazyReloadState() 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() 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() 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() 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() 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.FreeConns += s.FreeConns 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.FreeConns += s.FreeConns acc.StaleConns += s.StaleConns } return &acc } func (c *ClusterClient) lazyReloadState() { if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { return } go func() { if c.reloadState() { time.Sleep(time.Second) } atomic.StoreUint32(&c.reloading, 0) }() } func (c *ClusterClient) reloadState() bool { for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } state, err := c.loadState() if err == nil { c._state.Store(state) time.AfterFunc(time.Minute, func() { c.nodes.GC(state.generation) }) return true } c.setStateErr(err) switch err { case pool.ErrClosed, errClusterNoNodes: return false } } return false } func (c *ClusterClient) loadState() (*clusterState, error) { node, err := c.nodes.Random() if err != nil { return nil, err } slots, err := node.Client.ClusterSlots().Result() if err != nil { return nil, err } return newClusterState(c.nodes, slots, node.Client.opt.Addr) } func (c *ClusterClient) state() (*clusterState, error) { v := c._state.Load() if v != nil { return v.(*clusterState), nil } return nil, c.getStateErr() } func (c *ClusterClient) setStateErr(err error) { c.stateErrMu.Lock() c.stateErr = err c.stateErrMu.Unlock() } func (c *ClusterClient) getStateErr() error { c.stateErrMu.RLock() err := c.stateErr c.stateErrMu.RUnlock() return err } // 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, err := c.mapCmdsByNode(cmds) 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 := make(map[*clusterNode][]Cmder) for node, cmds := range cmdsMap { cn, _, err := node.Client.getConn() if err != nil { if err == pool.ErrClosed { c.remapCmds(cmds, failedCmds) } else { setCmdsErr(cmds, err) } continue } err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) if err == nil || internal.IsRedisError(err) { _ = node.Client.connPool.Put(cn) } else { _ = node.Client.connPool.Remove(cn) } } if len(failedCmds) == 0 { break } cmdsMap = failedCmds } return firstCmdsErr(cmds) } func (c *ClusterClient) mapCmdsByNode(cmds []Cmder) (map[*clusterNode][]Cmder, error) { state, err := c.state() if err != nil { setCmdsErr(cmds, err) return nil, err } cmdsMap := make(map[*clusterNode][]Cmder) for _, cmd := range cmds { slot := c.cmdSlot(cmd) node, err := state.slotMasterNode(slot) if err != nil { return nil, err } cmdsMap[node] = append(cmdsMap[node], cmd) } return cmdsMap, nil } func (c *ClusterClient) remapCmds(cmds []Cmder, failedCmds map[*clusterNode][]Cmder) { remappedCmds, err := c.mapCmdsByNode(cmds) if err != nil { setCmdsErr(cmds, err) return } for node, cmds := range remappedCmds { failedCmds[node] = cmds } } func (c *ClusterClient) pipelineProcessCmds( node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, ) error { _ = cn.SetWriteTimeout(c.opt.WriteTimeout) err := writeCmd(cn, cmds...) if err != nil { setCmdsErr(cmds, err) failedCmds[node] = cmds return err } // Set read timeout for all commands. _ = cn.SetReadTimeout(c.opt.ReadTimeout) return c.pipelineReadCmds(cn, cmds, failedCmds) } func (c *ClusterClient) pipelineReadCmds( cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, ) error { for _, cmd := range cmds { err := cmd.readReply(cn) if err == nil { continue } if c.checkMovedErr(cmd, err, failedCmds) { continue } if internal.IsRedisError(err) { continue } return err } return nil } func (c *ClusterClient) checkMovedErr( cmd Cmder, err error, failedCmds map[*clusterNode][]Cmder, ) bool { moved, ask, addr := internal.IsMovedError(err) if moved { c.lazyReloadState() node, err := c.nodes.GetOrCreate(addr) if err != nil { return false } failedCmds[node] = append(failedCmds[node], cmd) return true } if ask { node, err := c.nodes.GetOrCreate(addr) if err != nil { return false } failedCmds[node] = append(failedCmds[node], NewCmd("ASKING"), cmd) 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() 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 := make(map[*clusterNode][]Cmder) for node, cmds := range cmdsMap { cn, _, err := node.Client.getConn() if err != nil { if err == pool.ErrClosed { c.remapCmds(cmds, failedCmds) } else { setCmdsErr(cmds, err) } continue } err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) if err == nil || internal.IsRedisError(err) { _ = node.Client.connPool.Put(cn) } else { _ = node.Client.connPool.Remove(cn) } } if len(failedCmds) == 0 { break } cmdsMap = failedCmds } } return firstCmdsErr(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 map[*clusterNode][]Cmder, ) error { cn.SetWriteTimeout(c.opt.WriteTimeout) if err := txPipelineWriteMulti(cn, cmds); err != nil { setCmdsErr(cmds, err) failedCmds[node] = cmds return err } // Set read timeout for all commands. cn.SetReadTimeout(c.opt.ReadTimeout) if err := c.txPipelineReadQueued(cn, cmds, failedCmds); err != nil { setCmdsErr(cmds, err) return err } return pipelineReadCmds(cn, cmds) } func (c *ClusterClient) txPipelineReadQueued( cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, ) error { // Parse queued replies. var statusCmd StatusCmd if err := statusCmd.readReply(cn); err != nil { return err } for _, cmd := range cmds { err := statusCmd.readReply(cn) if err == nil { continue } if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { continue } return err } // Parse number of replies. line, err := cn.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 { opt := c.opt.clientOptions() var node *clusterNode return &PubSub{ opt: opt, 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) }, } } // 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 isLoopbackAddr(addr string) bool { host, _, err := net.SplitHostPort(addr) if err != nil { return false } ip := net.ParseIP(host) if ip == nil { return false } return ip.IsLoopback() } func appendNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { for _, n := range nodes { if n == node { return nodes } } return append(nodes, node) } redis-6.9.2/cluster_commands.go000066400000000000000000000005701324526341500165360ustar00rootroot00000000000000package 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.9.2/cluster_test.go000066400000000000000000000465401324526341500157230ustar00rootroot00000000000000package 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) clusterClient(opt *redis.ClusterOptions) *redis.ClusterClient { opt.Addrs = s.addrs() return redis.NewClusterClient(opt) } 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. for _, client := range scenario.clients { err := eventually(func() error { res, err := client.ClusterSlots().Result() if err != nil { return err } wanted := []redis.ClusterSlot{ {0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}}, {5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}}, {10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "127.0.0.1:8225"}}}, } return assertSlotsEqual(res, wanted) }, 30*time.Second) if err != nil { return err } } return nil } func assertSlotsEqual(slots, wanted []redis.ClusterSlot) error { outer_loop: for _, s2 := range wanted { for _, s1 := range slots { if slotEqual(s1, s2) { continue outer_loop } } 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 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("follows redirects", func() { Expect(client.Set("A", "VALUE", 0).Err()).NotTo(HaveOccurred()) slot := hashtag.Slot("A") client.SwapSlotNodes(slot) Eventually(func() string { return client.Get("A").Val() }, 30*time.Second).Should(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()) } for _, master := range cluster.masters() { Eventually(func() string { return master.Info("keyspace").Val() }, 30*time.Second).Should(Or( ContainSubstring("keys=31"), ContainSubstring("keys=29"), ContainSubstring("keys=40"), )) } }) 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()) } for _, master := range cluster.masters() { Eventually(func() string { return master.Info("keyspace").Val() }, 30*time.Second).Should(Or( ContainSubstring("keys=31"), ContainSubstring("keys=29"), ContainSubstring("keys=40"), )) } }) 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() { for _, key := range keys { slot := hashtag.Slot(key) client.SwapSlotNodes(slot) } 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 }) for _, key := range keys { slot := hashtag.Slot(key) client.SwapSlotNodes(slot) } 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, 10*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) _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) }) AfterEach(func() { _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(client.Close()).NotTo(HaveOccurred()) }) It("returns pool stats", func() { Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{})) }) It("removes idle connections", func() { stats := client.PoolStats() Expect(stats.TotalConns).NotTo(BeZero()) Expect(stats.FreeConns).NotTo(BeZero()) time.Sleep(2 * time.Second) stats = client.PoolStats() Expect(stats.TotalConns).To(BeZero()) Expect(stats.FreeConns).To(BeZero()) }) It("returns an error when there are no attempts left", func() { opt := redisClusterOptions() opt.MaxRedirects = -1 client := cluster.clusterClient(opt) slot := hashtag.Slot("A") client.SwapSlotNodes(slot) 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{ {0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}}, {5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}}, {10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "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() { opt = redisClusterOptions() opt.MinRetryBackoff = 250 * time.Millisecond opt.MaxRetryBackoff = time.Second client = cluster.clusterClient(opt) _ = client.ForEachSlave(func(slave *redis.Client) error { defer GinkgoRecover() _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Eventually(func() int64 { return slave.DBSize().Val() }, 30*time.Second).Should(Equal(int64(0))) return slave.ClusterFailover().Err() }) }) AfterEach(func() { _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(client.Close()).NotTo(HaveOccurred()) }) assertClusterClient() }) Describe("ClusterClient with RouteByLatency", func() { BeforeEach(func() { opt = redisClusterOptions() opt.RouteByLatency = true client = cluster.clusterClient(opt) _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) _ = client.ForEachSlave(func(slave *redis.Client) error { Eventually(func() int64 { return client.DBSize().Val() }, 30*time.Second).Should(Equal(int64(0))) return nil }) }) AfterEach(func() { _ = client.ForEachMaster(func(master *redis.Client) error { return master.FlushDB().Err() }) Expect(client.Close()).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 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 = 3 * time.Second Context("read/write timeout", func() { BeforeEach(func() { opt := redisClusterOptions() opt.ReadTimeout = 200 * time.Millisecond opt.WriteTimeout = 200 * 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 BenchmarkRedisClusterPing(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") } cluster := &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), } 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() { if err := client.Ping().Err(); err != nil { b.Fatal(err) } } }) } func BenchmarkRedisClusterSetString(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") } cluster := &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), } 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() { if err := client.Set("key", value, 0).Err(); err != nil { b.Fatal(err) } } }) } redis-6.9.2/command.go000066400000000000000000000463001324526341500146130ustar00rootroot00000000000000package redis import ( "bytes" "fmt" "strconv" "strings" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/pool" "github.com/go-redis/redis/internal/proto" "github.com/go-redis/redis/internal/util" ) type Cmder interface { Name() string Args() []interface{} stringArg(int) string readReply(*pool.Conn) error setErr(error) readTimeout() *time.Duration Err() error fmt.Stringer } func setCmdsErr(cmds []Cmder, e error) { for _, cmd := range cmds { if cmd.Err() == nil { cmd.setErr(e) } } } func firstCmdsErr(cmds []Cmder) error { for _, cmd := range cmds { if err := cmd.Err(); err != nil { return err } } return nil } func writeCmd(cn *pool.Conn, cmds ...Cmder) error { cn.Wb.Reset() for _, cmd := range cmds { if err := cn.Wb.Append(cmd.Args()); err != nil { return err } } _, err := cn.Write(cn.Wb.Bytes()) return err } 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 { return cmdString(cmd, cmd.val) } func (cmd *Cmd) readReply(cn *pool.Conn) error { cmd.val, cmd.err = cn.Rd.ReadReply(sliceParser) if cmd.err != nil { return cmd.err } if b, ok := cmd.val.([]byte); ok { // Bytes must be copied, because underlying memory is reused. cmd.val = string(b) } return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.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(cn *pool.Conn) error { cmd.val, cmd.err = cn.Rd.ReadStringReply() 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(cn *pool.Conn) error { cmd.val, cmd.err = cn.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(cn *pool.Conn) error { var n int64 n, cmd.err = cn.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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(timeParser) if cmd.err != nil { return cmd.err } cmd.val = v.(time.Time) return 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) } var ok = []byte("OK") func (cmd *BoolCmd) readReply(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.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 []byte: cmd.val = bytes.Equal(v, ok) return nil default: cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) return cmd.err } } //------------------------------------------------------------------------------ type StringCmd struct { baseCmd val []byte } var _ Cmder = (*StringCmd)(nil) func NewStringCmd(args ...interface{}) *StringCmd { return &StringCmd{ baseCmd: baseCmd{_args: args}, } } func (cmd *StringCmd) Val() string { return util.BytesToString(cmd.val) } func (cmd *StringCmd) Result() (string, error) { return cmd.Val(), cmd.err } func (cmd *StringCmd) Bytes() ([]byte, error) { return cmd.val, cmd.err } 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(cmd.val, val) } func (cmd *StringCmd) String() string { return cmdString(cmd, cmd.val) } func (cmd *StringCmd) readReply(cn *pool.Conn) error { cmd.val, cmd.err = cn.Rd.ReadBytesReply() 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(cn *pool.Conn) error { cmd.val, cmd.err = cn.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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(stringSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]string) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(boolSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]bool) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(stringStringMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]string) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(stringIntMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]int64) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(stringStructMapParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]struct{}) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(zSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]Z) return 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(cn *pool.Conn) error { cmd.page, cmd.cursor, cmd.err = cn.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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(clusterSlotsParser) if cmd.err != nil { return cmd.err } cmd.val = v.([]ClusterSlot) return nil } //------------------------------------------------------------------------------ // GeoLocation is used with GeoAdd to add geospatial location. type GeoLocation struct { Name string Longitude, Latitude, Dist float64 GeoHash int64 } // GeoRadiusQuery is used with GeoRadius to query geospatial index. type GeoRadiusQuery struct { Radius float64 // Can be m, km, ft, or mi. Default is km. Unit string WithCoord bool WithDist bool WithGeoHash bool Count int // Can be ASC or DESC. Default is no sort order. Sort string Store string StoreDist string } 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) if cmd.err != nil { return cmd.err } cmd.locations = v.([]GeoLocation) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(geoPosSliceParser) if cmd.err != nil { return cmd.err } cmd.positions = v.([]*GeoPos) return 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(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(commandInfoSliceParser) if cmd.err != nil { return cmd.err } cmd.val = v.(map[string]*CommandInfo) return nil } redis-6.9.2/command_test.go000066400000000000000000000025251324526341500156530ustar00rootroot00000000000000package 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.9.2/commands.go000066400000000000000000001512671324526341500150070ustar00rootroot00000000000000package redis import ( "errors" "io" "time" "github.com/go-redis/redis/internal" ) func readTimeout(timeout time.Duration) time.Duration { if timeout == 0 { return 0 } return timeout + 10*time.Second } 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) } type Cmdable interface { Pipeline() Pipeliner Pipelined(fn func(Pipeliner) error) ([]Cmder, error) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) TxPipeline() Pipeliner 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 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 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 ClientList() *StringCmd ClientPause(dur time.Duration) *BoolCmd ConfigGet(parameter string) *SliceCmd ConfigResetStat() *StatusCmd ConfigSet(parameter, value string) *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(scripts ...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 Command() *CommandsInfoCmd } type StatefulCmdable interface { Cmdable Auth(password string) *StatusCmd Select(index int) *StatusCmd SwapDB(index1, index2 int) *StatusCmd ClientSetName(name string) *BoolCmd ReadOnly() *StatusCmd ReadWrite() *StatusCmd } 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) 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(readTimeout(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+len(pairs)) args[0] = "mset" for i, pair := range pairs { args[1+i] = pair } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { args := make([]interface{}, 1+len(pairs)) args[0] = "msetnx" for i, pair := range pairs { args[1+i] = pair } 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(readTimeout(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(readTimeout(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(readTimeout(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+len(values)) args[0] = "lpush" args[1] = key for i, value := range values { args[2+i] = value } 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+len(values)) args[0] = "rpush" args[1] = key for i, value := range values { args[2+i] = value } 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+len(members)) args[0] = "sadd" args[1] = key for i, member := range members { args[2+i] = member } 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+len(members)) args[0] = "srem" args[1] = key for i, member := range members { args[2+i] = member } 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 } //------------------------------------------------------------------------------ // Z represents sorted set member. type Z struct { Score float64 Member interface{} } // ZStore is used as an arg to ZInterStore and ZUnionStore. type ZStore struct { Weights []float64 // Can be SUM, MIN or MAX. Aggregate string } 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) 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+len(members)) args[0] = "zrem" args[1] = key for i, member := range members { args[2+i] = member } 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+len(els)) args[0] = "pfadd" args[1] = key for i, el := range els { args[2+i] = el } 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 } func (c *cmdable) ClientList() *StringCmd { cmd := NewStringCmd("client", "list") c.process(cmd) return cmd } func (c *cmdable) ClientPause(dur time.Duration) *BoolCmd { cmd := NewBoolCmd("client", "pause", formatMs(dur)) c.process(cmd) return cmd } // ClientSetName assigns a name to the connection. func (c *statefulCmdable) ClientSetName(name string) *BoolCmd { cmd := NewBoolCmd("client", "setname", name) c.process(cmd) return cmd } // ClientGetName returns the name of the connection. func (c *cmdable) ClientGetName() *StringCmd { cmd := NewStringCmd("client", "getname") c.process(cmd) return cmd } func (c *cmdable) ConfigGet(parameter string) *SliceCmd { cmd := NewSliceCmd("config", "get", parameter) c.process(cmd) return cmd } func (c *cmdable) ConfigResetStat() *StatusCmd { cmd := NewStatusCmd("config", "resetstat") c.process(cmd) return cmd } func (c *cmdable) ConfigSet(parameter, value string) *StatusCmd { cmd := NewStatusCmd("config", "set", parameter, value) c.process(cmd) return cmd } // Deperecated. Use DBSize instead. func (c *cmdable) DbSize() *IntCmd { return c.DBSize() } func (c *cmdable) DBSize() *IntCmd { cmd := NewIntCmd("dbsize") c.process(cmd) return cmd } func (c *cmdable) FlushAll() *StatusCmd { cmd := NewStatusCmd("flushall") c.process(cmd) return cmd } func (c *cmdable) FlushAllAsync() *StatusCmd { cmd := NewStatusCmd("flushall", "async") c.process(cmd) return cmd } // Deprecated. Use FlushDB instead. func (c *cmdable) FlushDb() *StatusCmd { return c.FlushDB() } func (c *cmdable) FlushDB() *StatusCmd { cmd := NewStatusCmd("flushdb") c.process(cmd) return cmd } func (c *cmdable) FlushDBAsync() *StatusCmd { cmd := NewStatusCmd("flushdb", "async") c.process(cmd) return cmd } func (c *cmdable) Info(section ...string) *StringCmd { args := []interface{}{"info"} if len(section) > 0 { args = append(args, section[0]) } cmd := NewStringCmd(args...) c.process(cmd) return cmd } func (c *cmdable) LastSave() *IntCmd { cmd := NewIntCmd("lastsave") c.process(cmd) return cmd } func (c *cmdable) Save() *StatusCmd { cmd := NewStatusCmd("save") c.process(cmd) return cmd } func (c *cmdable) shutdown(modifier string) *StatusCmd { var args []interface{} if modifier == "" { args = []interface{}{"shutdown"} } else { args = []interface{}{"shutdown", modifier} } cmd := NewStatusCmd(args...) c.process(cmd) if err := cmd.Err(); err != nil { if err == io.EOF { // Server quit as expected. cmd.err = nil } } else { // Server did not quit. String reply contains the reason. cmd.err = errors.New(cmd.val) cmd.val = "" } return cmd } func (c *cmdable) Shutdown() *StatusCmd { return c.shutdown("") } func (c *cmdable) ShutdownSave() *StatusCmd { return c.shutdown("save") } func (c *cmdable) ShutdownNoSave() *StatusCmd { return c.shutdown("nosave") } func (c *cmdable) SlaveOf(host, port string) *StatusCmd { cmd := NewStatusCmd("slaveof", host, port) c.process(cmd) return cmd } func (c *cmdable) SlowLog() { panic("not implemented") } func (c *cmdable) Sync() { panic("not implemented") } func (c *cmdable) Time() *TimeCmd { cmd := NewTimeCmd("time") c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) Eval(script string, keys []string, args ...interface{}) *Cmd { cmdArgs := make([]interface{}, 3+len(keys)+len(args)) cmdArgs[0] = "eval" cmdArgs[1] = script cmdArgs[2] = len(keys) for i, key := range keys { cmdArgs[3+i] = key } pos := 3 + len(keys) for i, arg := range args { cmdArgs[pos+i] = arg } cmd := NewCmd(cmdArgs...) c.process(cmd) return cmd } func (c *cmdable) EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd { cmdArgs := make([]interface{}, 3+len(keys)+len(args)) cmdArgs[0] = "evalsha" cmdArgs[1] = sha1 cmdArgs[2] = len(keys) for i, key := range keys { cmdArgs[3+i] = key } pos := 3 + len(keys) for i, arg := range args { cmdArgs[pos+i] = arg } cmd := NewCmd(cmdArgs...) c.process(cmd) return cmd } func (c *cmdable) ScriptExists(scripts ...string) *BoolSliceCmd { args := make([]interface{}, 2+len(scripts)) args[0] = "script" args[1] = "exists" for i, script := range scripts { args[2+i] = script } cmd := NewBoolSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ScriptFlush() *StatusCmd { cmd := NewStatusCmd("script", "flush") c.process(cmd) return cmd } func (c *cmdable) ScriptKill() *StatusCmd { cmd := NewStatusCmd("script", "kill") c.process(cmd) return cmd } func (c *cmdable) ScriptLoad(script string) *StringCmd { cmd := NewStringCmd("script", "load", script) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) DebugObject(key string) *StringCmd { cmd := NewStringCmd("debug", "object", key) c.process(cmd) return cmd } //------------------------------------------------------------------------------ // Publish posts the message to the channel. func (c *cmdable) Publish(channel string, message interface{}) *IntCmd { cmd := NewIntCmd("publish", channel, message) c.process(cmd) return cmd } func (c *cmdable) PubSubChannels(pattern string) *StringSliceCmd { args := []interface{}{"pubsub", "channels"} if pattern != "*" { args = append(args, pattern) } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) PubSubNumSub(channels ...string) *StringIntMapCmd { args := make([]interface{}, 2+len(channels)) args[0] = "pubsub" args[1] = "numsub" for i, channel := range channels { args[2+i] = channel } cmd := NewStringIntMapCmd(args...) c.process(cmd) return cmd } func (c *cmdable) PubSubNumPat() *IntCmd { cmd := NewIntCmd("pubsub", "numpat") c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) ClusterSlots() *ClusterSlotsCmd { cmd := NewClusterSlotsCmd("cluster", "slots") c.process(cmd) return cmd } func (c *cmdable) ClusterNodes() *StringCmd { cmd := NewStringCmd("cluster", "nodes") c.process(cmd) return cmd } func (c *cmdable) ClusterMeet(host, port string) *StatusCmd { cmd := NewStatusCmd("cluster", "meet", host, port) c.process(cmd) return cmd } func (c *cmdable) ClusterForget(nodeID string) *StatusCmd { cmd := NewStatusCmd("cluster", "forget", nodeID) c.process(cmd) return cmd } func (c *cmdable) ClusterReplicate(nodeID string) *StatusCmd { cmd := NewStatusCmd("cluster", "replicate", nodeID) c.process(cmd) return cmd } func (c *cmdable) ClusterResetSoft() *StatusCmd { cmd := NewStatusCmd("cluster", "reset", "soft") c.process(cmd) return cmd } func (c *cmdable) ClusterResetHard() *StatusCmd { cmd := NewStatusCmd("cluster", "reset", "hard") c.process(cmd) return cmd } func (c *cmdable) ClusterInfo() *StringCmd { cmd := NewStringCmd("cluster", "info") c.process(cmd) return cmd } func (c *cmdable) ClusterKeySlot(key string) *IntCmd { cmd := NewIntCmd("cluster", "keyslot", key) c.process(cmd) return cmd } func (c *cmdable) ClusterCountFailureReports(nodeID string) *IntCmd { cmd := NewIntCmd("cluster", "count-failure-reports", nodeID) c.process(cmd) return cmd } func (c *cmdable) ClusterCountKeysInSlot(slot int) *IntCmd { cmd := NewIntCmd("cluster", "countkeysinslot", slot) c.process(cmd) return cmd } func (c *cmdable) ClusterDelSlots(slots ...int) *StatusCmd { args := make([]interface{}, 2+len(slots)) args[0] = "cluster" args[1] = "delslots" for i, slot := range slots { args[2+i] = slot } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ClusterDelSlotsRange(min, max int) *StatusCmd { size := max - min + 1 slots := make([]int, size) for i := 0; i < size; i++ { slots[i] = min + i } return c.ClusterDelSlots(slots...) } func (c *cmdable) ClusterSaveConfig() *StatusCmd { cmd := NewStatusCmd("cluster", "saveconfig") c.process(cmd) return cmd } func (c *cmdable) ClusterSlaves(nodeID string) *StringSliceCmd { cmd := NewStringSliceCmd("cluster", "slaves", nodeID) c.process(cmd) return cmd } func (c *statefulCmdable) ReadOnly() *StatusCmd { cmd := NewStatusCmd("readonly") c.process(cmd) return cmd } func (c *statefulCmdable) ReadWrite() *StatusCmd { cmd := NewStatusCmd("readwrite") c.process(cmd) return cmd } func (c *cmdable) ClusterFailover() *StatusCmd { cmd := NewStatusCmd("cluster", "failover") c.process(cmd) return cmd } func (c *cmdable) ClusterAddSlots(slots ...int) *StatusCmd { args := make([]interface{}, 2+len(slots)) args[0] = "cluster" args[1] = "addslots" for i, num := range slots { args[2+i] = num } cmd := NewStatusCmd(args...) c.process(cmd) return cmd } func (c *cmdable) ClusterAddSlotsRange(min, max int) *StatusCmd { size := max - min + 1 slots := make([]int, size) for i := 0; i < size; i++ { slots[i] = min + i } return c.ClusterAddSlots(slots...) } //------------------------------------------------------------------------------ func (c *cmdable) GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd { args := make([]interface{}, 2+3*len(geoLocation)) args[0] = "geoadd" args[1] = key for i, eachLoc := range geoLocation { args[2+3*i] = eachLoc.Longitude args[2+3*i+1] = eachLoc.Latitude args[2+3*i+2] = eachLoc.Name } cmd := NewIntCmd(args...) c.process(cmd) return cmd } func (c *cmdable) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd { cmd := NewGeoLocationCmd(query, "georadius", key, longitude, latitude) c.process(cmd) return cmd } func (c *cmdable) GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd { cmd := NewGeoLocationCmd(query, "georadius_ro", key, longitude, latitude) c.process(cmd) return cmd } func (c *cmdable) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd { cmd := NewGeoLocationCmd(query, "georadiusbymember", key, member) c.process(cmd) return cmd } func (c *cmdable) GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd { cmd := NewGeoLocationCmd(query, "georadiusbymember_ro", key, member) c.process(cmd) return cmd } func (c *cmdable) GeoDist(key string, member1, member2, unit string) *FloatCmd { if unit == "" { unit = "km" } cmd := NewFloatCmd("geodist", key, member1, member2, unit) c.process(cmd) return cmd } func (c *cmdable) GeoHash(key string, members ...string) *StringSliceCmd { args := make([]interface{}, 2+len(members)) args[0] = "geohash" args[1] = key for i, member := range members { args[2+i] = member } cmd := NewStringSliceCmd(args...) c.process(cmd) return cmd } func (c *cmdable) GeoPos(key string, members ...string) *GeoPosCmd { args := make([]interface{}, 2+len(members)) args[0] = "geopos" args[1] = key for i, member := range members { args[2+i] = member } cmd := NewGeoPosCmd(args...) c.process(cmd) return cmd } //------------------------------------------------------------------------------ func (c *cmdable) Command() *CommandsInfoCmd { cmd := NewCommandsInfoCmd("command") c.process(cmd) return cmd } redis-6.9.2/commands_test.go000066400000000000000000002677661324526341500160620ustar00rootroot00000000000000package redis_test import ( "encoding/json" "fmt" "reflect" "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/go-redis/redis" "github.com/go-redis/redis/internal/proto" ) var _ = Describe("Commands", func() { var client *redis.Client BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) Describe("server", func() { It("should Auth", func() { cmds, err := client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Auth("password") pipe.Auth("") return nil }) Expect(err).To(MatchError("ERR Client sent AUTH, but no password is set")) Expect(cmds[0].Err()).To(MatchError("ERR Client sent AUTH, but no password is set")) Expect(cmds[1].Err()).To(MatchError("ERR Client sent AUTH, but no password is set")) stats := client.PoolStats() Expect(stats.Hits).To(Equal(uint32(1))) Expect(stats.Misses).To(Equal(uint32(1))) Expect(stats.Timeouts).To(Equal(uint32(0))) Expect(stats.TotalConns).To(Equal(uint32(1))) Expect(stats.FreeConns).To(Equal(uint32(1))) }) It("should Echo", func() { pipe := client.Pipeline() echo := pipe.Echo("hello") _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(echo.Err()).NotTo(HaveOccurred()) Expect(echo.Val()).To(Equal("hello")) }) It("should Ping", func() { ping := client.Ping() Expect(ping.Err()).NotTo(HaveOccurred()) Expect(ping.Val()).To(Equal("PONG")) }) It("should Wait", func() { const wait = 3 * time.Second // assume testing on single redis instance start := time.Now() val, err := client.Wait(1, wait).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal(int64(0))) Expect(time.Now()).To(BeTemporally("~", start.Add(wait), time.Second)) }) It("should Select", func() { pipe := client.Pipeline() sel := pipe.Select(1) _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(sel.Err()).NotTo(HaveOccurred()) Expect(sel.Val()).To(Equal("OK")) }) It("should SwapDB", func() { pipe := client.Pipeline() sel := pipe.SwapDB(1, 2) _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(sel.Err()).NotTo(HaveOccurred()) Expect(sel.Val()).To(Equal("OK")) }) It("should BgRewriteAOF", func() { Skip("flaky test") val, err := client.BgRewriteAOF().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(ContainSubstring("Background append only file rewriting")) }) It("should BgSave", func() { Skip("flaky test") // workaround for "ERR Can't BGSAVE while AOF log rewriting is in progress" Eventually(func() string { return client.BgSave().Val() }, "30s").Should(Equal("Background saving started")) }) It("should ClientKill", func() { r := client.ClientKill("1.1.1.1:1111") Expect(r.Err()).To(MatchError("ERR No such client")) Expect(r.Val()).To(Equal("")) }) It("should ClientPause", func() { err := client.ClientPause(time.Second).Err() Expect(err).NotTo(HaveOccurred()) start := time.Now() err = client.Ping().Err() Expect(err).NotTo(HaveOccurred()) Expect(time.Now()).To(BeTemporally("~", start.Add(time.Second), 800*time.Millisecond)) }) It("should ClientSetName and ClientGetName", func() { pipe := client.Pipeline() set := pipe.ClientSetName("theclientname") get := pipe.ClientGetName() _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(BeTrue()) Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("theclientname")) }) It("should ConfigGet", func() { val, err := client.ConfigGet("*").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).NotTo(BeEmpty()) }) It("should ConfigResetStat", func() { r := client.ConfigResetStat() Expect(r.Err()).NotTo(HaveOccurred()) Expect(r.Val()).To(Equal("OK")) }) It("should ConfigSet", func() { configGet := client.ConfigGet("maxmemory") Expect(configGet.Err()).NotTo(HaveOccurred()) Expect(configGet.Val()).To(HaveLen(2)) Expect(configGet.Val()[0]).To(Equal("maxmemory")) configSet := client.ConfigSet("maxmemory", configGet.Val()[1].(string)) Expect(configSet.Err()).NotTo(HaveOccurred()) Expect(configSet.Val()).To(Equal("OK")) }) It("should DBSize", func() { size, err := client.DBSize().Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) It("should Info", func() { info := client.Info() Expect(info.Err()).NotTo(HaveOccurred()) Expect(info.Val()).NotTo(Equal("")) }) It("should Info cpu", func() { info := client.Info("cpu") Expect(info.Err()).NotTo(HaveOccurred()) Expect(info.Val()).NotTo(Equal("")) Expect(info.Val()).To(ContainSubstring(`used_cpu_sys`)) }) It("should LastSave", func() { lastSave := client.LastSave() Expect(lastSave.Err()).NotTo(HaveOccurred()) Expect(lastSave.Val()).NotTo(Equal(0)) }) It("should Save", func() { // workaround for "ERR Background save already in progress" Eventually(func() string { return client.Save().Val() }, "10s").Should(Equal("OK")) }) It("should SlaveOf", func() { slaveOf := client.SlaveOf("localhost", "8888") Expect(slaveOf.Err()).NotTo(HaveOccurred()) Expect(slaveOf.Val()).To(Equal("OK")) slaveOf = client.SlaveOf("NO", "ONE") Expect(slaveOf.Err()).NotTo(HaveOccurred()) Expect(slaveOf.Val()).To(Equal("OK")) }) It("should Time", func() { tm, err := client.Time().Result() Expect(err).NotTo(HaveOccurred()) Expect(tm).To(BeTemporally("~", time.Now(), 3*time.Second)) }) It("Should Command", func() { cmds, err := client.Command().Result() Expect(err).NotTo(HaveOccurred()) Expect(len(cmds)).To(BeNumerically("~", 180, 10)) cmd := cmds["mget"] Expect(cmd.Name).To(Equal("mget")) Expect(cmd.Arity).To(Equal(int8(-2))) Expect(cmd.Flags).To(ContainElement("readonly")) Expect(cmd.FirstKeyPos).To(Equal(int8(1))) Expect(cmd.LastKeyPos).To(Equal(int8(-1))) Expect(cmd.StepCount).To(Equal(int8(1))) cmd = cmds["ping"] Expect(cmd.Name).To(Equal("ping")) Expect(cmd.Arity).To(Equal(int8(-1))) Expect(cmd.Flags).To(ContainElement("stale")) Expect(cmd.Flags).To(ContainElement("fast")) Expect(cmd.FirstKeyPos).To(Equal(int8(0))) Expect(cmd.LastKeyPos).To(Equal(int8(0))) Expect(cmd.StepCount).To(Equal(int8(0))) }) }) Describe("debugging", func() { It("should DebugObject", func() { debug := client.DebugObject("foo") Expect(debug.Err()).To(HaveOccurred()) Expect(debug.Err().Error()).To(Equal("ERR no such key")) client.Set("foo", "bar", 0) debug = client.DebugObject("foo") Expect(debug.Err()).NotTo(HaveOccurred()) Expect(debug.Val()).To(ContainSubstring(`serializedlength:4`)) }) }) Describe("keys", func() { It("should Del", func() { err := client.Set("key1", "Hello", 0).Err() Expect(err).NotTo(HaveOccurred()) err = client.Set("key2", "World", 0).Err() Expect(err).NotTo(HaveOccurred()) n, err := client.Del("key1", "key2", "key3").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(2))) }) It("should Unlink", func() { err := client.Set("key1", "Hello", 0).Err() Expect(err).NotTo(HaveOccurred()) err = client.Set("key2", "World", 0).Err() Expect(err).NotTo(HaveOccurred()) n, err := client.Unlink("key1", "key2", "key3").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(2))) }) It("should Dump", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) dump := client.Dump("key") Expect(dump.Err()).NotTo(HaveOccurred()) Expect(dump.Val()).NotTo(BeEmpty()) }) It("should Exists", func() { set := client.Set("key1", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) n, err := client.Exists("key1").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) n, err = client.Exists("key2").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) n, err = client.Exists("key1", "key2").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) n, err = client.Exists("key1", "key1").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(2))) }) It("should Expire", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expire := client.Expire("key", 10*time.Second) Expect(expire.Err()).NotTo(HaveOccurred()) Expect(expire.Val()).To(Equal(true)) ttl := client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val()).To(Equal(10 * time.Second)) set = client.Set("key", "Hello World", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) ttl = client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val() < 0).To(Equal(true)) }) It("should ExpireAt", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) n, err := client.Exists("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) expireAt := client.ExpireAt("key", time.Now().Add(-time.Hour)) Expect(expireAt.Err()).NotTo(HaveOccurred()) Expect(expireAt.Val()).To(Equal(true)) n, err = client.Exists("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) }) It("should Keys", func() { mset := client.MSet("one", "1", "two", "2", "three", "3", "four", "4") Expect(mset.Err()).NotTo(HaveOccurred()) Expect(mset.Val()).To(Equal("OK")) keys := client.Keys("*o*") Expect(keys.Err()).NotTo(HaveOccurred()) Expect(keys.Val()).To(ConsistOf([]string{"four", "one", "two"})) keys = client.Keys("t??") Expect(keys.Err()).NotTo(HaveOccurred()) Expect(keys.Val()).To(Equal([]string{"two"})) keys = client.Keys("*") Expect(keys.Err()).NotTo(HaveOccurred()) Expect(keys.Val()).To(ConsistOf([]string{"four", "one", "three", "two"})) }) It("should Migrate", func() { migrate := client.Migrate("localhost", redisSecondaryPort, "key", 0, 0) Expect(migrate.Err()).NotTo(HaveOccurred()) Expect(migrate.Val()).To(Equal("NOKEY")) set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) migrate = client.Migrate("localhost", redisSecondaryPort, "key", 0, 0) Expect(migrate.Err()).To(MatchError("IOERR error or timeout writing to target instance")) Expect(migrate.Val()).To(Equal("")) }) It("should Move", func() { move := client.Move("key", 2) Expect(move.Err()).NotTo(HaveOccurred()) Expect(move.Val()).To(Equal(false)) set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) move = client.Move("key", 2) Expect(move.Err()).NotTo(HaveOccurred()) Expect(move.Val()).To(Equal(true)) get := client.Get("key") Expect(get.Err()).To(Equal(redis.Nil)) Expect(get.Val()).To(Equal("")) pipe := client.Pipeline() pipe.Select(2) get = pipe.Get("key") pipe.FlushDB() _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello")) }) It("should Object", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) refCount := client.ObjectRefCount("key") Expect(refCount.Err()).NotTo(HaveOccurred()) Expect(refCount.Val()).To(Equal(int64(1))) err := client.ObjectEncoding("key").Err() Expect(err).NotTo(HaveOccurred()) idleTime := client.ObjectIdleTime("key") Expect(idleTime.Err()).NotTo(HaveOccurred()) Expect(idleTime.Val()).To(Equal(time.Duration(0))) }) It("should Persist", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expire := client.Expire("key", 10*time.Second) Expect(expire.Err()).NotTo(HaveOccurred()) Expect(expire.Val()).To(Equal(true)) ttl := client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val()).To(Equal(10 * time.Second)) persist := client.Persist("key") Expect(persist.Err()).NotTo(HaveOccurred()) Expect(persist.Val()).To(Equal(true)) ttl = client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val() < 0).To(Equal(true)) }) It("should PExpire", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expiration := 900 * time.Millisecond pexpire := client.PExpire("key", expiration) Expect(pexpire.Err()).NotTo(HaveOccurred()) Expect(pexpire.Val()).To(Equal(true)) ttl := client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val()).To(Equal(time.Second)) pttl := client.PTTL("key") Expect(pttl.Err()).NotTo(HaveOccurred()) Expect(pttl.Val()).To(BeNumerically("~", expiration, 100*time.Millisecond)) }) It("should PExpireAt", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expiration := 900 * time.Millisecond pexpireat := client.PExpireAt("key", time.Now().Add(expiration)) Expect(pexpireat.Err()).NotTo(HaveOccurred()) Expect(pexpireat.Val()).To(Equal(true)) ttl := client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val()).To(Equal(time.Second)) pttl := client.PTTL("key") Expect(pttl.Err()).NotTo(HaveOccurred()) Expect(pttl.Val()).To(BeNumerically("~", expiration, 100*time.Millisecond)) }) It("should PTTL", func() { set := client.Set("key", "Hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expiration := time.Second expire := client.Expire("key", expiration) Expect(expire.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) pttl := client.PTTL("key") Expect(pttl.Err()).NotTo(HaveOccurred()) Expect(pttl.Val()).To(BeNumerically("~", expiration, 100*time.Millisecond)) }) It("should RandomKey", func() { randomKey := client.RandomKey() Expect(randomKey.Err()).To(Equal(redis.Nil)) Expect(randomKey.Val()).To(Equal("")) set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) randomKey = client.RandomKey() Expect(randomKey.Err()).NotTo(HaveOccurred()) Expect(randomKey.Val()).To(Equal("key")) }) It("should Rename", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) status := client.Rename("key", "key1") Expect(status.Err()).NotTo(HaveOccurred()) Expect(status.Val()).To(Equal("OK")) get := client.Get("key1") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello")) }) It("should RenameNX", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) renameNX := client.RenameNX("key", "key1") Expect(renameNX.Err()).NotTo(HaveOccurred()) Expect(renameNX.Val()).To(Equal(true)) get := client.Get("key1") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello")) }) It("should Restore", func() { err := client.Set("key", "hello", 0).Err() Expect(err).NotTo(HaveOccurred()) dump := client.Dump("key") Expect(dump.Err()).NotTo(HaveOccurred()) err = client.Del("key").Err() Expect(err).NotTo(HaveOccurred()) restore, err := client.Restore("key", 0, dump.Val()).Result() Expect(err).NotTo(HaveOccurred()) Expect(restore).To(Equal("OK")) type_, err := client.Type("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(type_).To(Equal("string")) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello")) }) It("should RestoreReplace", func() { err := client.Set("key", "hello", 0).Err() Expect(err).NotTo(HaveOccurred()) dump := client.Dump("key") Expect(dump.Err()).NotTo(HaveOccurred()) restore, err := client.RestoreReplace("key", 0, dump.Val()).Result() Expect(err).NotTo(HaveOccurred()) Expect(restore).To(Equal("OK")) type_, err := client.Type("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(type_).To(Equal("string")) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello")) }) It("should Sort", func() { size, err := client.LPush("list", "1").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(1))) size, err = client.LPush("list", "3").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(2))) size, err = client.LPush("list", "2").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(3))) els, err := client.Sort("list", &redis.Sort{ Offset: 0, Count: 2, Order: "ASC", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(els).To(Equal([]string{"1", "2"})) }) It("should Sort and Get", func() { size, err := client.LPush("list", "1").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(1))) size, err = client.LPush("list", "3").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(2))) size, err = client.LPush("list", "2").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(3))) err = client.Set("object_2", "value2", 0).Err() Expect(err).NotTo(HaveOccurred()) { els, err := client.Sort("list", &redis.Sort{ Get: []string{"object_*"}, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(els).To(Equal([]string{"", "value2", ""})) } { els, err := client.SortInterfaces("list", &redis.Sort{ Get: []string{"object_*"}, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(els).To(Equal([]interface{}{nil, "value2", nil})) } }) It("should Sort and Store", func() { size, err := client.LPush("list", "1").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(1))) size, err = client.LPush("list", "3").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(2))) size, err = client.LPush("list", "2").Result() Expect(err).NotTo(HaveOccurred()) Expect(size).To(Equal(int64(3))) n, err := client.SortStore("list", "list2", &redis.Sort{ Offset: 0, Count: 2, Order: "ASC", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(2))) els, err := client.LRange("list2", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(els).To(Equal([]string{"1", "2"})) }) It("should Touch", func() { set1 := client.Set("touch1", "hello", 0) Expect(set1.Err()).NotTo(HaveOccurred()) Expect(set1.Val()).To(Equal("OK")) set2 := client.Set("touch2", "hello", 0) Expect(set2.Err()).NotTo(HaveOccurred()) Expect(set2.Val()).To(Equal("OK")) touch := client.Touch("touch1", "touch2", "touch3") Expect(touch.Err()).NotTo(HaveOccurred()) Expect(touch.Val()).To(Equal(int64(2))) }) It("should TTL", func() { ttl := client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val() < 0).To(Equal(true)) set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) expire := client.Expire("key", 60*time.Second) Expect(expire.Err()).NotTo(HaveOccurred()) Expect(expire.Val()).To(Equal(true)) ttl = client.TTL("key") Expect(ttl.Err()).NotTo(HaveOccurred()) Expect(ttl.Val()).To(Equal(60 * time.Second)) }) It("should Type", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) type_ := client.Type("key") Expect(type_.Err()).NotTo(HaveOccurred()) Expect(type_.Val()).To(Equal("string")) }) }) Describe("scanning", func() { It("should Scan", func() { for i := 0; i < 1000; i++ { set := client.Set(fmt.Sprintf("key%d", i), "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) } keys, cursor, err := client.Scan(0, "", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(keys).NotTo(BeEmpty()) Expect(cursor).NotTo(BeZero()) }) It("should SScan", func() { for i := 0; i < 1000; i++ { sadd := client.SAdd("myset", fmt.Sprintf("member%d", i)) Expect(sadd.Err()).NotTo(HaveOccurred()) } keys, cursor, err := client.SScan("myset", 0, "", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(keys).NotTo(BeEmpty()) Expect(cursor).NotTo(BeZero()) }) It("should HScan", func() { for i := 0; i < 1000; i++ { sadd := client.HSet("myhash", fmt.Sprintf("key%d", i), "hello") Expect(sadd.Err()).NotTo(HaveOccurred()) } keys, cursor, err := client.HScan("myhash", 0, "", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(keys).NotTo(BeEmpty()) Expect(cursor).NotTo(BeZero()) }) It("should ZScan", func() { for i := 0; i < 1000; i++ { sadd := client.ZAdd("myset", redis.Z{float64(i), fmt.Sprintf("member%d", i)}) Expect(sadd.Err()).NotTo(HaveOccurred()) } keys, cursor, err := client.ZScan("myset", 0, "", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(keys).NotTo(BeEmpty()) Expect(cursor).NotTo(BeZero()) }) }) Describe("strings", func() { It("should Append", func() { n, err := client.Exists("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) append := client.Append("key", "Hello") Expect(append.Err()).NotTo(HaveOccurred()) Expect(append.Val()).To(Equal(int64(5))) append = client.Append("key", " World") Expect(append.Err()).NotTo(HaveOccurred()) Expect(append.Val()).To(Equal(int64(11))) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("Hello World")) }) It("should BitCount", func() { set := client.Set("key", "foobar", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) bitCount := client.BitCount("key", nil) Expect(bitCount.Err()).NotTo(HaveOccurred()) Expect(bitCount.Val()).To(Equal(int64(26))) bitCount = client.BitCount("key", &redis.BitCount{0, 0}) Expect(bitCount.Err()).NotTo(HaveOccurred()) Expect(bitCount.Val()).To(Equal(int64(4))) bitCount = client.BitCount("key", &redis.BitCount{1, 1}) Expect(bitCount.Err()).NotTo(HaveOccurred()) Expect(bitCount.Val()).To(Equal(int64(6))) }) It("should BitOpAnd", func() { set := client.Set("key1", "1", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) set = client.Set("key2", "0", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) bitOpAnd := client.BitOpAnd("dest", "key1", "key2") Expect(bitOpAnd.Err()).NotTo(HaveOccurred()) Expect(bitOpAnd.Val()).To(Equal(int64(1))) get := client.Get("dest") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("0")) }) It("should BitOpOr", func() { set := client.Set("key1", "1", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) set = client.Set("key2", "0", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) bitOpOr := client.BitOpOr("dest", "key1", "key2") Expect(bitOpOr.Err()).NotTo(HaveOccurred()) Expect(bitOpOr.Val()).To(Equal(int64(1))) get := client.Get("dest") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("1")) }) It("should BitOpXor", func() { set := client.Set("key1", "\xff", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) set = client.Set("key2", "\x0f", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) bitOpXor := client.BitOpXor("dest", "key1", "key2") Expect(bitOpXor.Err()).NotTo(HaveOccurred()) Expect(bitOpXor.Val()).To(Equal(int64(1))) get := client.Get("dest") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("\xf0")) }) It("should BitOpNot", func() { set := client.Set("key1", "\x00", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) bitOpNot := client.BitOpNot("dest", "key1") Expect(bitOpNot.Err()).NotTo(HaveOccurred()) Expect(bitOpNot.Val()).To(Equal(int64(1))) get := client.Get("dest") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("\xff")) }) It("should BitPos", func() { err := client.Set("mykey", "\xff\xf0\x00", 0).Err() Expect(err).NotTo(HaveOccurred()) pos, err := client.BitPos("mykey", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(12))) pos, err = client.BitPos("mykey", 1).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(0))) pos, err = client.BitPos("mykey", 0, 2).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(16))) pos, err = client.BitPos("mykey", 1, 2).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(-1))) pos, err = client.BitPos("mykey", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(16))) pos, err = client.BitPos("mykey", 1, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(-1))) pos, err = client.BitPos("mykey", 0, 2, 1).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(-1))) pos, err = client.BitPos("mykey", 0, 0, -3).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(-1))) pos, err = client.BitPos("mykey", 0, 0, 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(Equal(int64(-1))) }) It("should Decr", func() { set := client.Set("key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) decr := client.Decr("key") Expect(decr.Err()).NotTo(HaveOccurred()) Expect(decr.Val()).To(Equal(int64(9))) set = client.Set("key", "234293482390480948029348230948", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) decr = client.Decr("key") Expect(decr.Err()).To(MatchError("ERR value is not an integer or out of range")) Expect(decr.Val()).To(Equal(int64(0))) }) It("should DecrBy", func() { set := client.Set("key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) decrBy := client.DecrBy("key", 5) Expect(decrBy.Err()).NotTo(HaveOccurred()) Expect(decrBy.Val()).To(Equal(int64(5))) }) It("should Get", func() { get := client.Get("_") Expect(get.Err()).To(Equal(redis.Nil)) Expect(get.Val()).To(Equal("")) 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")) }) It("should GetBit", func() { setBit := client.SetBit("key", 7, 1) Expect(setBit.Err()).NotTo(HaveOccurred()) Expect(setBit.Val()).To(Equal(int64(0))) getBit := client.GetBit("key", 0) Expect(getBit.Err()).NotTo(HaveOccurred()) Expect(getBit.Val()).To(Equal(int64(0))) getBit = client.GetBit("key", 7) Expect(getBit.Err()).NotTo(HaveOccurred()) Expect(getBit.Val()).To(Equal(int64(1))) getBit = client.GetBit("key", 100) Expect(getBit.Err()).NotTo(HaveOccurred()) Expect(getBit.Val()).To(Equal(int64(0))) }) It("should GetRange", func() { set := client.Set("key", "This is a string", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) getRange := client.GetRange("key", 0, 3) Expect(getRange.Err()).NotTo(HaveOccurred()) Expect(getRange.Val()).To(Equal("This")) getRange = client.GetRange("key", -3, -1) Expect(getRange.Err()).NotTo(HaveOccurred()) Expect(getRange.Val()).To(Equal("ing")) getRange = client.GetRange("key", 0, -1) Expect(getRange.Err()).NotTo(HaveOccurred()) Expect(getRange.Val()).To(Equal("This is a string")) getRange = client.GetRange("key", 10, 100) Expect(getRange.Err()).NotTo(HaveOccurred()) Expect(getRange.Val()).To(Equal("string")) }) It("should GetSet", func() { incr := client.Incr("key") Expect(incr.Err()).NotTo(HaveOccurred()) Expect(incr.Val()).To(Equal(int64(1))) getSet := client.GetSet("key", "0") Expect(getSet.Err()).NotTo(HaveOccurred()) Expect(getSet.Val()).To(Equal("1")) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("0")) }) It("should Incr", func() { set := client.Set("key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) incr := client.Incr("key") Expect(incr.Err()).NotTo(HaveOccurred()) Expect(incr.Val()).To(Equal(int64(11))) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("11")) }) It("should IncrBy", func() { set := client.Set("key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) incrBy := client.IncrBy("key", 5) Expect(incrBy.Err()).NotTo(HaveOccurred()) Expect(incrBy.Val()).To(Equal(int64(15))) }) It("should IncrByFloat", func() { set := client.Set("key", "10.50", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) incrByFloat := client.IncrByFloat("key", 0.1) Expect(incrByFloat.Err()).NotTo(HaveOccurred()) Expect(incrByFloat.Val()).To(Equal(10.6)) set = client.Set("key", "5.0e3", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) incrByFloat = client.IncrByFloat("key", 2.0e2) Expect(incrByFloat.Err()).NotTo(HaveOccurred()) Expect(incrByFloat.Val()).To(Equal(float64(5200))) }) It("should IncrByFloatOverflow", func() { incrByFloat := client.IncrByFloat("key", 996945661) Expect(incrByFloat.Err()).NotTo(HaveOccurred()) Expect(incrByFloat.Val()).To(Equal(float64(996945661))) }) It("should MSetMGet", func() { mSet := client.MSet("key1", "hello1", "key2", "hello2") Expect(mSet.Err()).NotTo(HaveOccurred()) Expect(mSet.Val()).To(Equal("OK")) mGet := client.MGet("key1", "key2", "_") Expect(mGet.Err()).NotTo(HaveOccurred()) Expect(mGet.Val()).To(Equal([]interface{}{"hello1", "hello2", nil})) }) It("should MSetNX", func() { mSetNX := client.MSetNX("key1", "hello1", "key2", "hello2") Expect(mSetNX.Err()).NotTo(HaveOccurred()) Expect(mSetNX.Val()).To(Equal(true)) mSetNX = client.MSetNX("key2", "hello1", "key3", "hello2") Expect(mSetNX.Err()).NotTo(HaveOccurred()) Expect(mSetNX.Val()).To(Equal(false)) }) It("should Set with expiration", func() { err := client.Set("key", "hello", 100*time.Millisecond).Err() Expect(err).NotTo(HaveOccurred()) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello")) Eventually(func() error { return client.Get("foo").Err() }, "1s", "100ms").Should(Equal(redis.Nil)) }) It("should SetGet", 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")) }) It("should SetNX", func() { setNX := client.SetNX("key", "hello", 0) Expect(setNX.Err()).NotTo(HaveOccurred()) Expect(setNX.Val()).To(Equal(true)) setNX = client.SetNX("key", "hello2", 0) Expect(setNX.Err()).NotTo(HaveOccurred()) Expect(setNX.Val()).To(Equal(false)) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello")) }) It("should SetNX with expiration", func() { isSet, err := client.SetNX("key", "hello", time.Second).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(true)) isSet, err = client.SetNX("key", "hello2", time.Second).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(false)) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello")) }) It("should SetXX", func() { isSet, err := client.SetXX("key", "hello2", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(false)) err = client.Set("key", "hello", 0).Err() Expect(err).NotTo(HaveOccurred()) isSet, err = client.SetXX("key", "hello2", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(true)) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello2")) }) It("should SetXX with expiration", func() { isSet, err := client.SetXX("key", "hello2", time.Second).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(false)) err = client.Set("key", "hello", time.Second).Err() Expect(err).NotTo(HaveOccurred()) isSet, err = client.SetXX("key", "hello2", time.Second).Result() Expect(err).NotTo(HaveOccurred()) Expect(isSet).To(Equal(true)) val, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("hello2")) }) It("should SetRange", func() { set := client.Set("key", "Hello World", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) range_ := client.SetRange("key", 6, "Redis") Expect(range_.Err()).NotTo(HaveOccurred()) Expect(range_.Val()).To(Equal(int64(11))) get := client.Get("key") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("Hello Redis")) }) It("should StrLen", func() { set := client.Set("key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) Expect(set.Val()).To(Equal("OK")) strLen := client.StrLen("key") Expect(strLen.Err()).NotTo(HaveOccurred()) Expect(strLen.Val()).To(Equal(int64(5))) strLen = client.StrLen("_") Expect(strLen.Err()).NotTo(HaveOccurred()) Expect(strLen.Val()).To(Equal(int64(0))) }) }) Describe("hashes", func() { It("should HDel", func() { hSet := client.HSet("hash", "key", "hello") Expect(hSet.Err()).NotTo(HaveOccurred()) hDel := client.HDel("hash", "key") Expect(hDel.Err()).NotTo(HaveOccurred()) Expect(hDel.Val()).To(Equal(int64(1))) hDel = client.HDel("hash", "key") Expect(hDel.Err()).NotTo(HaveOccurred()) Expect(hDel.Val()).To(Equal(int64(0))) }) It("should HExists", func() { hSet := client.HSet("hash", "key", "hello") Expect(hSet.Err()).NotTo(HaveOccurred()) hExists := client.HExists("hash", "key") Expect(hExists.Err()).NotTo(HaveOccurred()) Expect(hExists.Val()).To(Equal(true)) hExists = client.HExists("hash", "key1") Expect(hExists.Err()).NotTo(HaveOccurred()) Expect(hExists.Val()).To(Equal(false)) }) It("should HGet", func() { hSet := client.HSet("hash", "key", "hello") Expect(hSet.Err()).NotTo(HaveOccurred()) hGet := client.HGet("hash", "key") Expect(hGet.Err()).NotTo(HaveOccurred()) Expect(hGet.Val()).To(Equal("hello")) hGet = client.HGet("hash", "key1") Expect(hGet.Err()).To(Equal(redis.Nil)) Expect(hGet.Val()).To(Equal("")) }) It("should HGetAll", func() { err := client.HSet("hash", "key1", "hello1").Err() Expect(err).NotTo(HaveOccurred()) err = client.HSet("hash", "key2", "hello2").Err() Expect(err).NotTo(HaveOccurred()) m, err := client.HGetAll("hash").Result() Expect(err).NotTo(HaveOccurred()) Expect(m).To(Equal(map[string]string{"key1": "hello1", "key2": "hello2"})) }) It("should HIncrBy", func() { hSet := client.HSet("hash", "key", "5") Expect(hSet.Err()).NotTo(HaveOccurred()) hIncrBy := client.HIncrBy("hash", "key", 1) Expect(hIncrBy.Err()).NotTo(HaveOccurred()) Expect(hIncrBy.Val()).To(Equal(int64(6))) hIncrBy = client.HIncrBy("hash", "key", -1) Expect(hIncrBy.Err()).NotTo(HaveOccurred()) Expect(hIncrBy.Val()).To(Equal(int64(5))) hIncrBy = client.HIncrBy("hash", "key", -10) Expect(hIncrBy.Err()).NotTo(HaveOccurred()) Expect(hIncrBy.Val()).To(Equal(int64(-5))) }) It("should HIncrByFloat", func() { hSet := client.HSet("hash", "field", "10.50") Expect(hSet.Err()).NotTo(HaveOccurred()) Expect(hSet.Val()).To(Equal(true)) hIncrByFloat := client.HIncrByFloat("hash", "field", 0.1) Expect(hIncrByFloat.Err()).NotTo(HaveOccurred()) Expect(hIncrByFloat.Val()).To(Equal(10.6)) hSet = client.HSet("hash", "field", "5.0e3") Expect(hSet.Err()).NotTo(HaveOccurred()) Expect(hSet.Val()).To(Equal(false)) hIncrByFloat = client.HIncrByFloat("hash", "field", 2.0e2) Expect(hIncrByFloat.Err()).NotTo(HaveOccurred()) Expect(hIncrByFloat.Val()).To(Equal(float64(5200))) }) It("should HKeys", func() { hkeys := client.HKeys("hash") Expect(hkeys.Err()).NotTo(HaveOccurred()) Expect(hkeys.Val()).To(Equal([]string{})) hset := client.HSet("hash", "key1", "hello1") Expect(hset.Err()).NotTo(HaveOccurred()) hset = client.HSet("hash", "key2", "hello2") Expect(hset.Err()).NotTo(HaveOccurred()) hkeys = client.HKeys("hash") Expect(hkeys.Err()).NotTo(HaveOccurred()) Expect(hkeys.Val()).To(Equal([]string{"key1", "key2"})) }) It("should HLen", func() { hSet := client.HSet("hash", "key1", "hello1") Expect(hSet.Err()).NotTo(HaveOccurred()) hSet = client.HSet("hash", "key2", "hello2") Expect(hSet.Err()).NotTo(HaveOccurred()) hLen := client.HLen("hash") Expect(hLen.Err()).NotTo(HaveOccurred()) Expect(hLen.Val()).To(Equal(int64(2))) }) It("should HMGet", func() { err := client.HSet("hash", "key1", "hello1").Err() Expect(err).NotTo(HaveOccurred()) err = client.HSet("hash", "key2", "hello2").Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.HMGet("hash", "key1", "key2", "_").Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]interface{}{"hello1", "hello2", nil})) }) It("should HMSet", func() { ok, err := client.HMSet("hash", map[string]interface{}{ "key1": "hello1", "key2": "hello2", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(ok).To(Equal("OK")) v, err := client.HGet("hash", "key1").Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("hello1")) v, err = client.HGet("hash", "key2").Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("hello2")) }) It("should HSet", func() { hSet := client.HSet("hash", "key", "hello") Expect(hSet.Err()).NotTo(HaveOccurred()) Expect(hSet.Val()).To(Equal(true)) hGet := client.HGet("hash", "key") Expect(hGet.Err()).NotTo(HaveOccurred()) Expect(hGet.Val()).To(Equal("hello")) }) It("should HSetNX", func() { hSetNX := client.HSetNX("hash", "key", "hello") Expect(hSetNX.Err()).NotTo(HaveOccurred()) Expect(hSetNX.Val()).To(Equal(true)) hSetNX = client.HSetNX("hash", "key", "hello") Expect(hSetNX.Err()).NotTo(HaveOccurred()) Expect(hSetNX.Val()).To(Equal(false)) hGet := client.HGet("hash", "key") Expect(hGet.Err()).NotTo(HaveOccurred()) Expect(hGet.Val()).To(Equal("hello")) }) It("should HVals", func() { err := client.HSet("hash", "key1", "hello1").Err() Expect(err).NotTo(HaveOccurred()) err = client.HSet("hash", "key2", "hello2").Err() Expect(err).NotTo(HaveOccurred()) v, err := client.HVals("hash").Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal([]string{"hello1", "hello2"})) var slice []string err = client.HVals("hash").ScanSlice(&slice) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(Equal([]string{"hello1", "hello2"})) }) }) Describe("hyperloglog", func() { It("should PFMerge", func() { pfAdd := client.PFAdd("hll1", "1", "2", "3", "4", "5") Expect(pfAdd.Err()).NotTo(HaveOccurred()) pfCount := client.PFCount("hll1") Expect(pfCount.Err()).NotTo(HaveOccurred()) Expect(pfCount.Val()).To(Equal(int64(5))) pfAdd = client.PFAdd("hll2", "a", "b", "c", "d", "e") Expect(pfAdd.Err()).NotTo(HaveOccurred()) pfMerge := client.PFMerge("hllMerged", "hll1", "hll2") Expect(pfMerge.Err()).NotTo(HaveOccurred()) pfCount = client.PFCount("hllMerged") Expect(pfCount.Err()).NotTo(HaveOccurred()) Expect(pfCount.Val()).To(Equal(int64(10))) pfCount = client.PFCount("hll1", "hll2") Expect(pfCount.Err()).NotTo(HaveOccurred()) Expect(pfCount.Val()).To(Equal(int64(10))) }) }) Describe("lists", func() { It("should BLPop", func() { rPush := client.RPush("list1", "a", "b", "c") Expect(rPush.Err()).NotTo(HaveOccurred()) bLPop := client.BLPop(0, "list1", "list2") Expect(bLPop.Err()).NotTo(HaveOccurred()) Expect(bLPop.Val()).To(Equal([]string{"list1", "a"})) }) It("should BLPopBlocks", func() { started := make(chan bool) done := make(chan bool) go func() { defer GinkgoRecover() started <- true bLPop := client.BLPop(0, "list") Expect(bLPop.Err()).NotTo(HaveOccurred()) Expect(bLPop.Val()).To(Equal([]string{"list", "a"})) done <- true }() <-started select { case <-done: Fail("BLPop is not blocked") case <-time.After(time.Second): // ok } rPush := client.RPush("list", "a") Expect(rPush.Err()).NotTo(HaveOccurred()) select { case <-done: // ok case <-time.After(time.Second): Fail("BLPop is still blocked") } }) It("should BLPop timeout", func() { val, err := client.BLPop(time.Second, "list1").Result() Expect(err).To(Equal(redis.Nil)) Expect(val).To(BeNil()) Expect(client.Ping().Err()).NotTo(HaveOccurred()) stats := client.PoolStats() Expect(stats.Hits).To(Equal(uint32(1))) Expect(stats.Misses).To(Equal(uint32(2))) Expect(stats.Timeouts).To(Equal(uint32(0))) }) It("should BRPop", func() { rPush := client.RPush("list1", "a", "b", "c") Expect(rPush.Err()).NotTo(HaveOccurred()) bRPop := client.BRPop(0, "list1", "list2") Expect(bRPop.Err()).NotTo(HaveOccurred()) Expect(bRPop.Val()).To(Equal([]string{"list1", "c"})) }) It("should BRPop blocks", func() { started := make(chan bool) done := make(chan bool) go func() { defer GinkgoRecover() started <- true brpop := client.BRPop(0, "list") Expect(brpop.Err()).NotTo(HaveOccurred()) Expect(brpop.Val()).To(Equal([]string{"list", "a"})) done <- true }() <-started select { case <-done: Fail("BRPop is not blocked") case <-time.After(time.Second): // ok } rPush := client.RPush("list", "a") Expect(rPush.Err()).NotTo(HaveOccurred()) select { case <-done: // ok case <-time.After(time.Second): Fail("BRPop is still blocked") // ok } }) It("should BRPopLPush", func() { _, err := client.BRPopLPush("list1", "list2", time.Second).Result() Expect(err).To(Equal(redis.Nil)) err = client.RPush("list1", "a", "b", "c").Err() Expect(err).NotTo(HaveOccurred()) v, err := client.BRPopLPush("list1", "list2", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("c")) }) It("should LIndex", func() { lPush := client.LPush("list", "World") Expect(lPush.Err()).NotTo(HaveOccurred()) lPush = client.LPush("list", "Hello") Expect(lPush.Err()).NotTo(HaveOccurred()) lIndex := client.LIndex("list", 0) Expect(lIndex.Err()).NotTo(HaveOccurred()) Expect(lIndex.Val()).To(Equal("Hello")) lIndex = client.LIndex("list", -1) Expect(lIndex.Err()).NotTo(HaveOccurred()) Expect(lIndex.Val()).To(Equal("World")) lIndex = client.LIndex("list", 3) Expect(lIndex.Err()).To(Equal(redis.Nil)) Expect(lIndex.Val()).To(Equal("")) }) It("should LInsert", func() { rPush := client.RPush("list", "Hello") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "World") Expect(rPush.Err()).NotTo(HaveOccurred()) lInsert := client.LInsert("list", "BEFORE", "World", "There") Expect(lInsert.Err()).NotTo(HaveOccurred()) Expect(lInsert.Val()).To(Equal(int64(3))) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"Hello", "There", "World"})) }) It("should LLen", func() { lPush := client.LPush("list", "World") Expect(lPush.Err()).NotTo(HaveOccurred()) lPush = client.LPush("list", "Hello") Expect(lPush.Err()).NotTo(HaveOccurred()) lLen := client.LLen("list") Expect(lLen.Err()).NotTo(HaveOccurred()) Expect(lLen.Val()).To(Equal(int64(2))) }) It("should LPop", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) lPop := client.LPop("list") Expect(lPop.Err()).NotTo(HaveOccurred()) Expect(lPop.Val()).To(Equal("one")) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"two", "three"})) }) It("should LPush", func() { lPush := client.LPush("list", "World") Expect(lPush.Err()).NotTo(HaveOccurred()) lPush = client.LPush("list", "Hello") Expect(lPush.Err()).NotTo(HaveOccurred()) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) }) It("should LPushX", func() { lPush := client.LPush("list", "World") Expect(lPush.Err()).NotTo(HaveOccurred()) lPushX := client.LPushX("list", "Hello") Expect(lPushX.Err()).NotTo(HaveOccurred()) Expect(lPushX.Val()).To(Equal(int64(2))) lPushX = client.LPushX("list2", "Hello") Expect(lPushX.Err()).NotTo(HaveOccurred()) Expect(lPushX.Val()).To(Equal(int64(0))) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) lRange = client.LRange("list2", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{})) }) It("should LRange", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) lRange := client.LRange("list", 0, 0) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"one"})) lRange = client.LRange("list", -3, 2) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"one", "two", "three"})) lRange = client.LRange("list", -100, 100) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"one", "two", "three"})) lRange = client.LRange("list", 5, 10) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{})) }) It("should LRem", func() { rPush := client.RPush("list", "hello") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "hello") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "key") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "hello") Expect(rPush.Err()).NotTo(HaveOccurred()) lRem := client.LRem("list", -2, "hello") Expect(lRem.Err()).NotTo(HaveOccurred()) Expect(lRem.Val()).To(Equal(int64(2))) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"hello", "key"})) }) It("should LSet", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) lSet := client.LSet("list", 0, "four") Expect(lSet.Err()).NotTo(HaveOccurred()) Expect(lSet.Val()).To(Equal("OK")) lSet = client.LSet("list", -2, "five") Expect(lSet.Err()).NotTo(HaveOccurred()) Expect(lSet.Val()).To(Equal("OK")) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"four", "five", "three"})) }) It("should LTrim", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) lTrim := client.LTrim("list", 1, -1) Expect(lTrim.Err()).NotTo(HaveOccurred()) Expect(lTrim.Val()).To(Equal("OK")) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"two", "three"})) }) It("should RPop", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) rPop := client.RPop("list") Expect(rPop.Err()).NotTo(HaveOccurred()) Expect(rPop.Val()).To(Equal("three")) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"one", "two"})) }) It("should RPopLPush", func() { rPush := client.RPush("list", "one") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "two") Expect(rPush.Err()).NotTo(HaveOccurred()) rPush = client.RPush("list", "three") Expect(rPush.Err()).NotTo(HaveOccurred()) rPopLPush := client.RPopLPush("list", "list2") Expect(rPopLPush.Err()).NotTo(HaveOccurred()) Expect(rPopLPush.Val()).To(Equal("three")) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"one", "two"})) lRange = client.LRange("list2", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"three"})) }) It("should RPush", func() { rPush := client.RPush("list", "Hello") Expect(rPush.Err()).NotTo(HaveOccurred()) Expect(rPush.Val()).To(Equal(int64(1))) rPush = client.RPush("list", "World") Expect(rPush.Err()).NotTo(HaveOccurred()) Expect(rPush.Val()).To(Equal(int64(2))) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) }) It("should RPushX", func() { rPush := client.RPush("list", "Hello") Expect(rPush.Err()).NotTo(HaveOccurred()) Expect(rPush.Val()).To(Equal(int64(1))) rPushX := client.RPushX("list", "World") Expect(rPushX.Err()).NotTo(HaveOccurred()) Expect(rPushX.Val()).To(Equal(int64(2))) rPushX = client.RPushX("list2", "World") Expect(rPushX.Err()).NotTo(HaveOccurred()) Expect(rPushX.Val()).To(Equal(int64(0))) lRange := client.LRange("list", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) lRange = client.LRange("list2", 0, -1) Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{})) }) }) Describe("sets", func() { It("should SAdd", func() { sAdd := client.SAdd("set", "Hello") Expect(sAdd.Err()).NotTo(HaveOccurred()) Expect(sAdd.Val()).To(Equal(int64(1))) sAdd = client.SAdd("set", "World") Expect(sAdd.Err()).NotTo(HaveOccurred()) Expect(sAdd.Val()).To(Equal(int64(1))) sAdd = client.SAdd("set", "World") Expect(sAdd.Err()).NotTo(HaveOccurred()) Expect(sAdd.Val()).To(Equal(int64(0))) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(ConsistOf([]string{"Hello", "World"})) }) It("should SCard", func() { sAdd := client.SAdd("set", "Hello") Expect(sAdd.Err()).NotTo(HaveOccurred()) Expect(sAdd.Val()).To(Equal(int64(1))) sAdd = client.SAdd("set", "World") Expect(sAdd.Err()).NotTo(HaveOccurred()) Expect(sAdd.Val()).To(Equal(int64(1))) sCard := client.SCard("set") Expect(sCard.Err()).NotTo(HaveOccurred()) Expect(sCard.Val()).To(Equal(int64(2))) }) It("should SDiff", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sDiff := client.SDiff("set1", "set2") Expect(sDiff.Err()).NotTo(HaveOccurred()) Expect(sDiff.Val()).To(ConsistOf([]string{"a", "b"})) }) It("should SDiffStore", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sDiffStore := client.SDiffStore("set", "set1", "set2") Expect(sDiffStore.Err()).NotTo(HaveOccurred()) Expect(sDiffStore.Val()).To(Equal(int64(2))) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(ConsistOf([]string{"a", "b"})) }) It("should SInter", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sInter := client.SInter("set1", "set2") Expect(sInter.Err()).NotTo(HaveOccurred()) Expect(sInter.Val()).To(Equal([]string{"c"})) }) It("should SInterStore", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sInterStore := client.SInterStore("set", "set1", "set2") Expect(sInterStore.Err()).NotTo(HaveOccurred()) Expect(sInterStore.Val()).To(Equal(int64(1))) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(Equal([]string{"c"})) }) It("should IsMember", func() { sAdd := client.SAdd("set", "one") Expect(sAdd.Err()).NotTo(HaveOccurred()) sIsMember := client.SIsMember("set", "one") Expect(sIsMember.Err()).NotTo(HaveOccurred()) Expect(sIsMember.Val()).To(Equal(true)) sIsMember = client.SIsMember("set", "two") Expect(sIsMember.Err()).NotTo(HaveOccurred()) Expect(sIsMember.Val()).To(Equal(false)) }) It("should SMembers", func() { sAdd := client.SAdd("set", "Hello") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "World") Expect(sAdd.Err()).NotTo(HaveOccurred()) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(ConsistOf([]string{"Hello", "World"})) }) It("should SMembersMap", func() { sAdd := client.SAdd("set", "Hello") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "World") Expect(sAdd.Err()).NotTo(HaveOccurred()) sMembersMap := client.SMembersMap("set") Expect(sMembersMap.Err()).NotTo(HaveOccurred()) Expect(sMembersMap.Val()).To(Equal(map[string]struct{}{"Hello": struct{}{}, "World": struct{}{}})) }) It("should SMove", func() { sAdd := client.SAdd("set1", "one") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "two") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "three") Expect(sAdd.Err()).NotTo(HaveOccurred()) sMove := client.SMove("set1", "set2", "two") Expect(sMove.Err()).NotTo(HaveOccurred()) Expect(sMove.Val()).To(Equal(true)) sMembers := client.SMembers("set1") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(Equal([]string{"one"})) sMembers = client.SMembers("set2") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(ConsistOf([]string{"three", "two"})) }) It("should SPop", func() { sAdd := client.SAdd("set", "one") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "two") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "three") Expect(sAdd.Err()).NotTo(HaveOccurred()) sPop := client.SPop("set") Expect(sPop.Err()).NotTo(HaveOccurred()) Expect(sPop.Val()).NotTo(Equal("")) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(HaveLen(2)) }) It("should SPopN", func() { sAdd := client.SAdd("set", "one") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "two") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "three") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "four") Expect(sAdd.Err()).NotTo(HaveOccurred()) sPopN := client.SPopN("set", 1) Expect(sPopN.Err()).NotTo(HaveOccurred()) Expect(sPopN.Val()).NotTo(Equal([]string{""})) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(HaveLen(3)) sPopN = client.SPopN("set", 4) Expect(sPopN.Err()).NotTo(HaveOccurred()) Expect(sPopN.Val()).To(HaveLen(3)) sMembers = client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(HaveLen(0)) }) It("should SRandMember and SRandMemberN", func() { err := client.SAdd("set", "one").Err() Expect(err).NotTo(HaveOccurred()) err = client.SAdd("set", "two").Err() Expect(err).NotTo(HaveOccurred()) err = client.SAdd("set", "three").Err() Expect(err).NotTo(HaveOccurred()) members, err := client.SMembers("set").Result() Expect(err).NotTo(HaveOccurred()) Expect(members).To(HaveLen(3)) member, err := client.SRandMember("set").Result() Expect(err).NotTo(HaveOccurred()) Expect(member).NotTo(Equal("")) members, err = client.SRandMemberN("set", 2).Result() Expect(err).NotTo(HaveOccurred()) Expect(members).To(HaveLen(2)) }) It("should SRem", func() { sAdd := client.SAdd("set", "one") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "two") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set", "three") Expect(sAdd.Err()).NotTo(HaveOccurred()) sRem := client.SRem("set", "one") Expect(sRem.Err()).NotTo(HaveOccurred()) Expect(sRem.Val()).To(Equal(int64(1))) sRem = client.SRem("set", "four") Expect(sRem.Err()).NotTo(HaveOccurred()) Expect(sRem.Val()).To(Equal(int64(0))) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(ConsistOf([]string{"three", "two"})) }) It("should SUnion", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sUnion := client.SUnion("set1", "set2") Expect(sUnion.Err()).NotTo(HaveOccurred()) Expect(sUnion.Val()).To(HaveLen(5)) }) It("should SUnionStore", func() { sAdd := client.SAdd("set1", "a") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "b") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set1", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "c") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "d") Expect(sAdd.Err()).NotTo(HaveOccurred()) sAdd = client.SAdd("set2", "e") Expect(sAdd.Err()).NotTo(HaveOccurred()) sUnionStore := client.SUnionStore("set", "set1", "set2") Expect(sUnionStore.Err()).NotTo(HaveOccurred()) Expect(sUnionStore.Val()).To(Equal(int64(5))) sMembers := client.SMembers("set") Expect(sMembers.Err()).NotTo(HaveOccurred()) Expect(sMembers.Val()).To(HaveLen(5)) }) }) Describe("sorted sets", func() { It("should ZAdd", func() { added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{1, "uno"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{2, "two"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{3, "two"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(0))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}, {1, "uno"}, {3, "two"}})) }) It("should ZAdd bytes", func() { added, err := client.ZAdd("zset", redis.Z{1, []byte("one")}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{1, []byte("uno")}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{2, []byte("two")}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAdd("zset", redis.Z{3, []byte("two")}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(0))) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}, {1, "uno"}, {3, "two"}})) }) It("should ZAddNX", func() { added, err := client.ZAddNX("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) added, err = client.ZAddNX("zset", redis.Z{2, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(0))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) }) It("should ZAddXX", func() { added, err := client.ZAddXX("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(0))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) added, err = client.ZAdd("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) added, err = client.ZAddXX("zset", redis.Z{2, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(0))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{2, "one"}})) }) It("should ZAddCh", func() { changed, err := client.ZAddCh("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) changed, err = client.ZAddCh("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) }) It("should ZAddNXCh", func() { changed, err := client.ZAddNXCh("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) changed, err = client.ZAddNXCh("zset", redis.Z{2, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) }) It("should ZAddXXCh", func() { changed, err := client.ZAddXXCh("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) changed, err = client.ZAddXXCh("zset", redis.Z{2, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{2, "one"}})) }) It("should ZIncr", func() { score, err := client.ZIncr("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(1))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) score, err = client.ZIncr("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(2))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{2, "one"}})) }) It("should ZIncrNX", func() { score, err := client.ZIncrNX("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(1))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) score, err = client.ZIncrNX("zset", redis.Z{1, "one"}).Result() Expect(err).To(Equal(redis.Nil)) Expect(score).To(Equal(float64(0))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{1, "one"}})) }) It("should ZIncrXX", func() { score, err := client.ZIncrXX("zset", redis.Z{1, "one"}).Result() Expect(err).To(Equal(redis.Nil)) Expect(score).To(Equal(float64(0))) vals, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) score, err = client.ZIncrXX("zset", redis.Z{1, "one"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(2))) vals, err = client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{2, "one"}})) }) It("should ZCard", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zCard := client.ZCard("zset") Expect(zCard.Err()).NotTo(HaveOccurred()) Expect(zCard.Val()).To(Equal(int64(2))) }) It("should ZCount", func() { err := client.ZAdd("zset", redis.Z{1, "one"}).Err() Expect(err).NotTo(HaveOccurred()) err = client.ZAdd("zset", redis.Z{2, "two"}).Err() Expect(err).NotTo(HaveOccurred()) err = client.ZAdd("zset", redis.Z{3, "three"}).Err() Expect(err).NotTo(HaveOccurred()) count, err := client.ZCount("zset", "-inf", "+inf").Result() Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(int64(3))) count, err = client.ZCount("zset", "(1", "3").Result() Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(int64(2))) count, err = client.ZLexCount("zset", "-", "+").Result() Expect(err).NotTo(HaveOccurred()) Expect(count).To(Equal(int64(3))) }) It("should ZIncrBy", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zIncrBy := client.ZIncrBy("zset", 2, "one") Expect(zIncrBy.Err()).NotTo(HaveOccurred()) Expect(zIncrBy.Val()).To(Equal(float64(3))) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "one"}})) }) It("should ZInterStore", func() { zAdd := client.ZAdd("zset1", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset1", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset2", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset2", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset3", redis.Z{3, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zInterStore := client.ZInterStore( "out", redis.ZStore{Weights: []float64{2, 3}}, "zset1", "zset2") Expect(zInterStore.Err()).NotTo(HaveOccurred()) Expect(zInterStore.Val()).To(Equal(int64(2))) val, err := client.ZRangeWithScores("out", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{5, "one"}, {10, "two"}})) }) It("should ZRange", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRange := client.ZRange("zset", 0, -1) Expect(zRange.Err()).NotTo(HaveOccurred()) Expect(zRange.Val()).To(Equal([]string{"one", "two", "three"})) zRange = client.ZRange("zset", 2, 3) Expect(zRange.Err()).NotTo(HaveOccurred()) Expect(zRange.Val()).To(Equal([]string{"three"})) zRange = client.ZRange("zset", -2, -1) Expect(zRange.Err()).NotTo(HaveOccurred()) Expect(zRange.Val()).To(Equal([]string{"two", "three"})) }) It("should ZRangeWithScores", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}, {3, "three"}})) val, err = client.ZRangeWithScores("zset", 2, 3).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{3, "three"}})) val, err = client.ZRangeWithScores("zset", -2, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "three"}})) }) It("should ZRangeByScore", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRangeByScore := client.ZRangeByScore("zset", redis.ZRangeBy{ Min: "-inf", Max: "+inf", }) Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) Expect(zRangeByScore.Val()).To(Equal([]string{"one", "two", "three"})) zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ Min: "1", Max: "2", }) Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) Expect(zRangeByScore.Val()).To(Equal([]string{"one", "two"})) zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ Min: "(1", Max: "2", }) Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) Expect(zRangeByScore.Val()).To(Equal([]string{"two"})) zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ Min: "(1", Max: "(2", }) Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) Expect(zRangeByScore.Val()).To(Equal([]string{})) }) It("should ZRangeByLex", func() { zAdd := client.ZAdd("zset", redis.Z{0, "a"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{0, "b"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{0, "c"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRangeByLex := client.ZRangeByLex("zset", redis.ZRangeBy{ Min: "-", Max: "+", }) Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) Expect(zRangeByLex.Val()).To(Equal([]string{"a", "b", "c"})) zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ Min: "[a", Max: "[b", }) Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) Expect(zRangeByLex.Val()).To(Equal([]string{"a", "b"})) zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ Min: "(a", Max: "[b", }) Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) Expect(zRangeByLex.Val()).To(Equal([]string{"b"})) zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ Min: "(a", Max: "(b", }) Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) Expect(zRangeByLex.Val()).To(Equal([]string{})) }) It("should ZRangeByScoreWithScoresMap", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) val, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ Min: "-inf", Max: "+inf", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}, {3, "three"}})) val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ Min: "1", Max: "2", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}})) val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ Min: "(1", Max: "2", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}})) val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ Min: "(1", Max: "(2", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{})) }) It("should ZRank", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRank := client.ZRank("zset", "three") Expect(zRank.Err()).NotTo(HaveOccurred()) Expect(zRank.Val()).To(Equal(int64(2))) zRank = client.ZRank("zset", "four") Expect(zRank.Err()).To(Equal(redis.Nil)) Expect(zRank.Val()).To(Equal(int64(0))) }) It("should ZRem", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRem := client.ZRem("zset", "two") Expect(zRem.Err()).NotTo(HaveOccurred()) Expect(zRem.Val()).To(Equal(int64(1))) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}, {3, "three"}})) }) It("should ZRemRangeByRank", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRemRangeByRank := client.ZRemRangeByRank("zset", 0, 1) Expect(zRemRangeByRank.Err()).NotTo(HaveOccurred()) Expect(zRemRangeByRank.Val()).To(Equal(int64(2))) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{3, "three"}})) }) It("should ZRemRangeByScore", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRemRangeByScore := client.ZRemRangeByScore("zset", "-inf", "(2") Expect(zRemRangeByScore.Err()).NotTo(HaveOccurred()) Expect(zRemRangeByScore.Val()).To(Equal(int64(1))) val, err := client.ZRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "three"}})) }) It("should ZRemRangeByLex", func() { zz := []redis.Z{ {0, "aaaa"}, {0, "b"}, {0, "c"}, {0, "d"}, {0, "e"}, {0, "foo"}, {0, "zap"}, {0, "zip"}, {0, "ALPHA"}, {0, "alpha"}, } for _, z := range zz { err := client.ZAdd("zset", z).Err() Expect(err).NotTo(HaveOccurred()) } n, err := client.ZRemRangeByLex("zset", "[alpha", "[omega").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(6))) vals, err := client.ZRange("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{"ALPHA", "aaaa", "zap", "zip"})) }) It("should ZRevRange", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRevRange := client.ZRevRange("zset", 0, -1) Expect(zRevRange.Err()).NotTo(HaveOccurred()) Expect(zRevRange.Val()).To(Equal([]string{"three", "two", "one"})) zRevRange = client.ZRevRange("zset", 2, 3) Expect(zRevRange.Err()).NotTo(HaveOccurred()) Expect(zRevRange.Val()).To(Equal([]string{"one"})) zRevRange = client.ZRevRange("zset", -2, -1) Expect(zRevRange.Err()).NotTo(HaveOccurred()) Expect(zRevRange.Val()).To(Equal([]string{"two", "one"})) }) It("should ZRevRangeWithScoresMap", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) val, err := client.ZRevRangeWithScores("zset", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) val, err = client.ZRevRangeWithScores("zset", 2, 3).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{1, "one"}})) val, err = client.ZRevRangeWithScores("zset", -2, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}, {1, "one"}})) }) It("should ZRevRangeByScore", func() { zadd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zadd.Err()).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByScore( "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{"three", "two", "one"})) vals, err = client.ZRevRangeByScore( "zset", redis.ZRangeBy{Max: "2", Min: "(1"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{"two"})) vals, err = client.ZRevRangeByScore( "zset", redis.ZRangeBy{Max: "(2", Min: "(1"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{})) }) It("should ZRevRangeByLex", func() { zadd := client.ZAdd("zset", redis.Z{0, "a"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{0, "b"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{0, "c"}) Expect(zadd.Err()).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByLex( "zset", redis.ZRangeBy{Max: "+", Min: "-"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{"c", "b", "a"})) vals, err = client.ZRevRangeByLex( "zset", redis.ZRangeBy{Max: "[b", Min: "(a"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{"b"})) vals, err = client.ZRevRangeByLex( "zset", redis.ZRangeBy{Max: "(b", Min: "(a"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]string{})) }) It("should ZRevRangeByScoreWithScores", func() { zadd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zadd.Err()).NotTo(HaveOccurred()) zadd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zadd.Err()).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByScoreWithScores( "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) }) It("should ZRevRangeByScoreWithScoresMap", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) val, err := client.ZRevRangeByScoreWithScores( "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) val, err = client.ZRevRangeByScoreWithScores( "zset", redis.ZRangeBy{Max: "2", Min: "(1"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{2, "two"}})) val, err = client.ZRevRangeByScoreWithScores( "zset", redis.ZRangeBy{Max: "(2", Min: "(1"}).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{})) }) It("should ZRevRank", func() { zAdd := client.ZAdd("zset", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zRevRank := client.ZRevRank("zset", "one") Expect(zRevRank.Err()).NotTo(HaveOccurred()) Expect(zRevRank.Val()).To(Equal(int64(2))) zRevRank = client.ZRevRank("zset", "four") Expect(zRevRank.Err()).To(Equal(redis.Nil)) Expect(zRevRank.Val()).To(Equal(int64(0))) }) It("should ZScore", func() { zAdd := client.ZAdd("zset", redis.Z{1.001, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zScore := client.ZScore("zset", "one") Expect(zScore.Err()).NotTo(HaveOccurred()) Expect(zScore.Val()).To(Equal(float64(1.001))) }) It("should ZUnionStore", func() { zAdd := client.ZAdd("zset1", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset1", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset2", redis.Z{1, "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset2", redis.Z{2, "two"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zAdd = client.ZAdd("zset2", redis.Z{3, "three"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zUnionStore := client.ZUnionStore( "out", redis.ZStore{Weights: []float64{2, 3}}, "zset1", "zset2") Expect(zUnionStore.Err()).NotTo(HaveOccurred()) Expect(zUnionStore.Val()).To(Equal(int64(3))) val, err := client.ZRangeWithScores("out", 0, -1).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal([]redis.Z{{5, "one"}, {9, "three"}, {10, "two"}})) }) }) Describe("Geo add and radius search", func() { BeforeEach(func() { geoAdd := client.GeoAdd( "Sicily", &redis.GeoLocation{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, &redis.GeoLocation{Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}, ) Expect(geoAdd.Err()).NotTo(HaveOccurred()) Expect(geoAdd.Val()).To(Equal(int64(2))) }) It("should not add same geo location", func() { geoAdd := client.GeoAdd( "Sicily", &redis.GeoLocation{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, ) Expect(geoAdd.Err()).NotTo(HaveOccurred()) Expect(geoAdd.Val()).To(Equal(int64(0))) }) It("should search geo radius", func() { res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ Radius: 200, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(2)) Expect(res[0].Name).To(Equal("Palermo")) Expect(res[1].Name).To(Equal("Catania")) }) It("should search geo radius with options", func() { res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ Radius: 200, Unit: "km", WithGeoHash: true, WithCoord: true, WithDist: true, Count: 2, Sort: "ASC", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(2)) Expect(res[1].Name).To(Equal("Palermo")) Expect(res[1].Dist).To(Equal(190.4424)) Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) Expect(res[1].Longitude).To(Equal(13.361389338970184)) Expect(res[1].Latitude).To(Equal(38.115556395496299)) Expect(res[0].Name).To(Equal("Catania")) Expect(res[0].Dist).To(Equal(56.4413)) Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) Expect(res[0].Longitude).To(Equal(15.087267458438873)) Expect(res[0].Latitude).To(Equal(37.50266842333162)) }) It("should search geo radius with WithDist=false", func() { res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ Radius: 200, Unit: "km", WithGeoHash: true, WithCoord: true, Count: 2, Sort: "ASC", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(2)) Expect(res[1].Name).To(Equal("Palermo")) Expect(res[1].Dist).To(Equal(float64(0))) Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) Expect(res[1].Longitude).To(Equal(13.361389338970184)) Expect(res[1].Latitude).To(Equal(38.115556395496299)) Expect(res[0].Name).To(Equal("Catania")) Expect(res[0].Dist).To(Equal(float64(0))) Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) Expect(res[0].Longitude).To(Equal(15.087267458438873)) Expect(res[0].Latitude).To(Equal(37.50266842333162)) }) It("should search geo radius by member with options", func() { res, err := client.GeoRadiusByMember("Sicily", "Catania", &redis.GeoRadiusQuery{ Radius: 200, Unit: "km", WithGeoHash: true, WithCoord: true, WithDist: true, Count: 2, Sort: "ASC", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(2)) Expect(res[0].Name).To(Equal("Catania")) Expect(res[0].Dist).To(Equal(0.0)) Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) Expect(res[0].Longitude).To(Equal(15.087267458438873)) Expect(res[0].Latitude).To(Equal(37.50266842333162)) Expect(res[1].Name).To(Equal("Palermo")) Expect(res[1].Dist).To(Equal(166.2742)) Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) Expect(res[1].Longitude).To(Equal(13.361389338970184)) Expect(res[1].Latitude).To(Equal(38.115556395496299)) }) It("should search geo radius with no results", func() { res, err := client.GeoRadius("Sicily", 99, 37, &redis.GeoRadiusQuery{ Radius: 200, Unit: "km", WithGeoHash: true, WithCoord: true, WithDist: true, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(res).To(HaveLen(0)) }) It("should get geo distance with unit options", func() { // From Redis CLI, note the difference in rounding in m vs // km on Redis itself. // // GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" // GEODIST Sicily Palermo Catania m // "166274.15156960033" // GEODIST Sicily Palermo Catania km // "166.27415156960032" dist, err := client.GeoDist("Sicily", "Palermo", "Catania", "km").Result() Expect(err).NotTo(HaveOccurred()) Expect(dist).To(BeNumerically("~", 166.27, 0.01)) dist, err = client.GeoDist("Sicily", "Palermo", "Catania", "m").Result() Expect(err).NotTo(HaveOccurred()) Expect(dist).To(BeNumerically("~", 166274.15, 0.01)) }) It("should get geo hash in string representation", func() { hashes, err := client.GeoHash("Sicily", "Palermo", "Catania").Result() Expect(err).NotTo(HaveOccurred()) Expect(hashes).To(ConsistOf([]string{"sqc8b49rny0", "sqdtr74hyu0"})) }) It("should return geo position", func() { pos, err := client.GeoPos("Sicily", "Palermo", "Catania", "NonExisting").Result() Expect(err).NotTo(HaveOccurred()) Expect(pos).To(ConsistOf([]*redis.GeoPos{ { Longitude: 13.361389338970184, Latitude: 38.1155563954963, }, { Longitude: 15.087267458438873, Latitude: 37.50266842333162, }, nil, })) }) }) Describe("marshaling/unmarshaling", func() { type convTest struct { value interface{} wanted string dest interface{} } convTests := []convTest{ {nil, "", nil}, {"hello", "hello", new(string)}, {[]byte("hello"), "hello", new([]byte)}, {int(1), "1", new(int)}, {int8(1), "1", new(int8)}, {int16(1), "1", new(int16)}, {int32(1), "1", new(int32)}, {int64(1), "1", new(int64)}, {uint(1), "1", new(uint)}, {uint8(1), "1", new(uint8)}, {uint16(1), "1", new(uint16)}, {uint32(1), "1", new(uint32)}, {uint64(1), "1", new(uint64)}, {float32(1.0), "1", new(float32)}, {float64(1.0), "1", new(float64)}, {true, "1", new(bool)}, {false, "0", new(bool)}, } It("should convert to string", func() { for _, test := range convTests { err := client.Set("key", test.value, 0).Err() Expect(err).NotTo(HaveOccurred()) s, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(s).To(Equal(test.wanted)) if test.dest == nil { continue } err = client.Get("key").Scan(test.dest) Expect(err).NotTo(HaveOccurred()) Expect(deref(test.dest)).To(Equal(test.value)) } }) }) Describe("json marshaling/unmarshaling", func() { BeforeEach(func() { value := &numberStruct{Number: 42} err := client.Set("key", value, 0).Err() Expect(err).NotTo(HaveOccurred()) }) It("should marshal custom values using json", func() { s, err := client.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(s).To(Equal(`{"Number":42}`)) }) It("should scan custom values using json", func() { value := &numberStruct{} err := client.Get("key").Scan(value) Expect(err).NotTo(HaveOccurred()) Expect(value.Number).To(Equal(42)) }) }) Describe("Eval", func() { It("returns keys and values", func() { vals, err := client.Eval( "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello", ).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]interface{}{"key", "hello"})) }) It("returns all values after an error", func() { vals, err := client.Eval( `return {12, {err="error"}, "abc"}`, nil, ).Result() Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]interface{}{int64(12), proto.RedisError("error"), "abc"})) }) }) }) type numberStruct struct { Number int } func (s *numberStruct) MarshalBinary() ([]byte, error) { return json.Marshal(s) } func (s *numberStruct) UnmarshalBinary(b []byte) error { return json.Unmarshal(b, s) } func deref(viface interface{}) interface{} { v := reflect.ValueOf(viface) for v.Kind() == reflect.Ptr { v = v.Elem() } return v.Interface() } redis-6.9.2/doc.go000066400000000000000000000000751324526341500137410ustar00rootroot00000000000000/* Package redis implements a Redis client. */ package redis redis-6.9.2/example_instrumentation_test.go000066400000000000000000000021611324526341500212070ustar00rootroot00000000000000package redis_test import ( "fmt" "github.com/go-redis/redis" ) func Example_instrumentation() { cl := redis.NewClient(&redis.Options{ Addr: ":6379", }) cl.WrapProcess(func(old func(cmd redis.Cmder) error) func(cmd redis.Cmder) error { return func(cmd redis.Cmder) error { fmt.Printf("starting processing: <%s>\n", cmd) err := old(cmd) fmt.Printf("finished processing: <%s>\n", cmd) return err } }) cl.Ping() // Output: starting processing: // finished processing: } func Example_Pipeline_instrumentation() { client := redis.NewClient(&redis.Options{ Addr: ":6379", }) client.WrapProcessPipeline(func(old func([]redis.Cmder) error) func([]redis.Cmder) error { return func(cmds []redis.Cmder) error { fmt.Printf("pipeline starting processing: %v\n", cmds) err := old(cmds) fmt.Printf("pipeline finished processing: %v\n", cmds) return err } }) client.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() pipe.Ping() return nil }) // Output: pipeline starting processing: [ping: ping: ] // pipeline finished processing: [ping: PONG ping: PONG] } redis-6.9.2/example_test.go000066400000000000000000000203311324526341500156630ustar00rootroot00000000000000package redis_test import ( "fmt" "strconv" "sync" "time" "github.com/go-redis/redis" ) var client *redis.Client func init() { client = redis.NewClient(&redis.Options{ Addr: ":6379", DialTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, PoolSize: 10, PoolTimeout: 30 * time.Second, }) client.FlushDB() } func ExampleNewClient() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) pong, err := client.Ping().Result() fmt.Println(pong, err) // Output: PONG } func ExampleParseURL() { opt, err := redis.ParseURL("redis://:qwerty@localhost:6379/1") if err != nil { panic(err) } fmt.Println("addr is", opt.Addr) fmt.Println("db is", opt.DB) fmt.Println("password is", opt.Password) // Create client as usually. _ = redis.NewClient(opt) // Output: addr is localhost:6379 // db is 1 // password is qwerty } func ExampleNewFailoverClient() { // See http://redis.io/topics/sentinel for instructions how to // setup Redis Sentinel. client := redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: "master", SentinelAddrs: []string{":26379"}, }) client.Ping() } func ExampleNewClusterClient() { // See http://redis.io/topics/cluster-tutorial for instructions // how to setup Redis Cluster. client := redis.NewClusterClient(&redis.ClusterOptions{ Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"}, }) client.Ping() } func ExampleNewRing() { client := redis.NewRing(&redis.RingOptions{ Addrs: map[string]string{ "shard1": ":7000", "shard2": ":7001", "shard3": ":7002", }, }) client.Ping() } 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 } func ExampleClient_Set() { // Last argument is expiration. Zero means the key has no // expiration time. err := client.Set("key", "value", 0).Err() if err != nil { panic(err) } // key2 will expire in an hour. err = client.Set("key2", "value", time.Hour).Err() if err != nil { panic(err) } } func ExampleClient_Incr() { result, err := client.Incr("counter").Result() if err != nil { panic(err) } fmt.Println(result) // Output: 1 } func ExampleClient_BLPop() { if err := client.RPush("queue", "message").Err(); err != nil { panic(err) } // use `client.BLPop(0, "queue")` for infinite waiting time result, err := client.BLPop(1*time.Second, "queue").Result() if err != nil { panic(err) } fmt.Println(result[0], result[1]) // Output: queue message } func ExampleClient_Scan() { client.FlushDB() for i := 0; i < 33; i++ { err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err() if err != nil { panic(err) } } var cursor uint64 var n int for { var keys []string var err error keys, cursor, err = client.Scan(cursor, "", 10).Result() if err != nil { panic(err) } n += len(keys) if cursor == 0 { break } } fmt.Printf("found %d keys\n", n) // Output: found 33 keys } func ExampleClient_Pipelined() { var incr *redis.IntCmd _, err := client.Pipelined(func(pipe redis.Pipeliner) error { incr = pipe.Incr("pipelined_counter") pipe.Expire("pipelined_counter", time.Hour) return nil }) fmt.Println(incr.Val(), err) // Output: 1 } func ExampleClient_Pipeline() { pipe := client.Pipeline() incr := pipe.Incr("pipeline_counter") pipe.Expire("pipeline_counter", time.Hour) // Execute // // INCR pipeline_counter // EXPIRE pipeline_counts 3600 // // using one client-server roundtrip. _, err := pipe.Exec() fmt.Println(incr.Val(), err) // Output: 1 } func ExampleClient_TxPipelined() { var incr *redis.IntCmd _, err := client.TxPipelined(func(pipe redis.Pipeliner) error { incr = pipe.Incr("tx_pipelined_counter") pipe.Expire("tx_pipelined_counter", time.Hour) return nil }) fmt.Println(incr.Val(), err) // Output: 1 } func ExampleClient_TxPipeline() { pipe := client.TxPipeline() incr := pipe.Incr("tx_pipeline_counter") pipe.Expire("tx_pipeline_counter", time.Hour) // Execute // // MULTI // INCR pipeline_counter // EXPIRE pipeline_counts 3600 // EXEC // // using one client-server roundtrip. _, err := pipe.Exec() fmt.Println(incr.Val(), err) // Output: 1 } func ExampleClient_Watch() { 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 wg.Done() err := incr("counter3") if err != nil { panic(err) } }() } wg.Wait() n, err := client.Get("counter3").Int64() fmt.Println(n, err) // Output: 100 } func ExamplePubSub() { pubsub := client.Subscribe("mychannel1") defer pubsub.Close() // Wait for subscription to be created before publishing message. subscr, err := pubsub.ReceiveTimeout(time.Second) if err != nil { panic(err) } fmt.Println(subscr) err = client.Publish("mychannel1", "hello").Err() if err != nil { panic(err) } msg, err := pubsub.ReceiveMessage() if err != nil { panic(err) } fmt.Println(msg.Channel, msg.Payload) // Output: subscribe: mychannel1 // mychannel1 hello } func ExamplePubSub_Receive() { pubsub := client.Subscribe("mychannel2") defer pubsub.Close() for i := 0; i < 2; i++ { // ReceiveTimeout is a low level API. Use ReceiveMessage instead. msgi, err := pubsub.ReceiveTimeout(time.Second) if err != nil { break } switch msg := msgi.(type) { case *redis.Subscription: fmt.Println("subscribed to", msg.Channel) _, err := client.Publish("mychannel2", "hello").Result() if err != nil { panic(err) } case *redis.Message: fmt.Println("received", msg.Payload, "from", msg.Channel) default: panic("unreached") } } // sent message to 1 client // received hello from mychannel2 } func ExampleScript() { IncrByXX := redis.NewScript(` if redis.call("GET", KEYS[1]) ~= false then return redis.call("INCRBY", KEYS[1], ARGV[1]) end return false `) n, err := IncrByXX.Run(client, []string{"xx_counter"}, 2).Result() fmt.Println(n, err) err = client.Set("xx_counter", "40", 0).Err() if err != nil { panic(err) } n, err = IncrByXX.Run(client, []string{"xx_counter"}, 2).Result() fmt.Println(n, err) // Output: redis: nil // 42 } func Example_customCommand() { Get := func(client *redis.Client, key string) *redis.StringCmd { cmd := redis.NewStringCmd("get", key) client.Process(cmd) return cmd } v, err := Get(client, "key_does_not_exist").Result() fmt.Printf("%q %s", v, err) // Output: "" redis: nil } func ExampleScanIterator() { iter := client.Scan(0, "", 0).Iterator() for iter.Next() { fmt.Println(iter.Val()) } if err := iter.Err(); err != nil { panic(err) } } func ExampleScanCmd_Iterator() { iter := client.Scan(0, "", 0).Iterator() for iter.Next() { fmt.Println(iter.Val()) } if err := iter.Err(); err != nil { panic(err) } } func ExampleNewUniversalClient_simple() { client := redis.NewUniversalClient(&redis.UniversalOptions{ Addrs: []string{":6379"}, }) defer client.Close() client.Ping() } func ExampleNewUniversalClient_failover() { client := redis.NewUniversalClient(&redis.UniversalOptions{ MasterName: "master", Addrs: []string{":26379"}, }) defer client.Close() client.Ping() } func ExampleNewUniversalClient_cluster() { client := redis.NewUniversalClient(&redis.UniversalOptions{ Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"}, }) defer client.Close() client.Ping() } redis-6.9.2/export_test.go000066400000000000000000000015521324526341500155550ustar00rootroot00000000000000package redis import ( "net" "time" "github.com/go-redis/redis/internal/pool" ) func (c *baseClient) Pool() pool.Pooler { return c.connPool } func (c *PubSub) SetNetConn(netConn net.Conn) { c.cn = pool.NewConn(netConn) } func (c *PubSub) ReceiveMessageTimeout(timeout time.Duration) (*Message, error) { return c.receiveMessage(timeout) } func (c *ClusterClient) SlotAddrs(slot int) []string { state, err := c.state() if err != nil { panic(err) } var addrs []string for _, n := range state.slotNodes(slot) { addrs = append(addrs, n.Client.getAddr()) } return addrs } // SwapSlot swaps a slot's master/slave address for testing MOVED redirects. func (c *ClusterClient) SwapSlotNodes(slot int) { state, err := c.state() if err != nil { panic(err) } nodes := state.slots[slot] if len(nodes) == 2 { nodes[0], nodes[1] = nodes[1], nodes[0] } } redis-6.9.2/internal/000077500000000000000000000000001324526341500144575ustar00rootroot00000000000000redis-6.9.2/internal/consistenthash/000077500000000000000000000000001324526341500175145ustar00rootroot00000000000000redis-6.9.2/internal/consistenthash/consistenthash.go000066400000000000000000000034351324526341500231050ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package consistenthash provides an implementation of a ring hash. package consistenthash import ( "hash/crc32" "sort" "strconv" ) type Hash func(data []byte) uint32 type Map struct { hash Hash replicas int keys []int // Sorted hashMap map[int]string } func New(replicas int, fn Hash) *Map { m := &Map{ replicas: replicas, hash: fn, hashMap: make(map[int]string), } if m.hash == nil { m.hash = crc32.ChecksumIEEE } return m } // Returns true if there are no items available. func (m *Map) IsEmpty() bool { return len(m.keys) == 0 } // Adds some keys to the hash. func (m *Map) Add(keys ...string) { for _, key := range keys { for i := 0; i < m.replicas; i++ { hash := int(m.hash([]byte(strconv.Itoa(i) + key))) m.keys = append(m.keys, hash) m.hashMap[hash] = key } } sort.Ints(m.keys) } // Gets the closest item in the hash to the provided key. func (m *Map) Get(key string) string { if m.IsEmpty() { return "" } hash := int(m.hash([]byte(key))) // Binary search for appropriate replica. idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) // Means we have cycled back to the first replica. if idx == len(m.keys) { idx = 0 } return m.hashMap[m.keys[idx]] } redis-6.9.2/internal/consistenthash/consistenthash_test.go000066400000000000000000000047241324526341500241460ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package consistenthash import ( "fmt" "strconv" "testing" ) func TestHashing(t *testing.T) { // Override the hash function to return easier to reason about values. Assumes // the keys can be converted to an integer. hash := New(3, func(key []byte) uint32 { i, err := strconv.Atoi(string(key)) if err != nil { panic(err) } return uint32(i) }) // Given the above hash function, this will give replicas with "hashes": // 2, 4, 6, 12, 14, 16, 22, 24, 26 hash.Add("6", "4", "2") testCases := map[string]string{ "2": "2", "11": "2", "23": "4", "27": "2", } for k, v := range testCases { if hash.Get(k) != v { t.Errorf("Asking for %s, should have yielded %s", k, v) } } // Adds 8, 18, 28 hash.Add("8") // 27 should now map to 8. testCases["27"] = "8" for k, v := range testCases { if hash.Get(k) != v { t.Errorf("Asking for %s, should have yielded %s", k, v) } } } func TestConsistency(t *testing.T) { hash1 := New(1, nil) hash2 := New(1, nil) hash1.Add("Bill", "Bob", "Bonny") hash2.Add("Bob", "Bonny", "Bill") if hash1.Get("Ben") != hash2.Get("Ben") { t.Errorf("Fetching 'Ben' from both hashes should be the same") } hash2.Add("Becky", "Ben", "Bobby") if hash1.Get("Ben") != hash2.Get("Ben") || hash1.Get("Bob") != hash2.Get("Bob") || hash1.Get("Bonny") != hash2.Get("Bonny") { t.Errorf("Direct matches should always return the same entry") } } func BenchmarkGet8(b *testing.B) { benchmarkGet(b, 8) } func BenchmarkGet32(b *testing.B) { benchmarkGet(b, 32) } func BenchmarkGet128(b *testing.B) { benchmarkGet(b, 128) } func BenchmarkGet512(b *testing.B) { benchmarkGet(b, 512) } func benchmarkGet(b *testing.B, shards int) { hash := New(50, nil) var buckets []string for i := 0; i < shards; i++ { buckets = append(buckets, fmt.Sprintf("shard-%d", i)) } hash.Add(buckets...) b.ResetTimer() for i := 0; i < b.N; i++ { hash.Get(buckets[i&(shards-1)]) } } redis-6.9.2/internal/error.go000066400000000000000000000026121324526341500161400ustar00rootroot00000000000000package internal import ( "io" "net" "strings" "github.com/go-redis/redis/internal/proto" ) func IsRetryableError(err error, retryNetError bool) bool { if IsNetworkError(err) { return retryNetError } s := err.Error() if s == "ERR max number of clients reached" { return true } if strings.HasPrefix(s, "LOADING ") { return true } if strings.HasPrefix(s, "CLUSTERDOWN ") { return true } return false } func IsRedisError(err error) bool { _, ok := err.(proto.RedisError) return ok } func IsNetworkError(err error) bool { if err == io.EOF { return true } _, ok := err.(net.Error) return ok } func IsReadOnlyError(err error) bool { return strings.HasPrefix(err.Error(), "READONLY ") } func IsBadConn(err error, allowTimeout bool) bool { if err == nil { return false } if IsRedisError(err) { return false } if allowTimeout { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { return false } } return true } func IsMovedError(err error) (moved bool, ask bool, addr string) { if !IsRedisError(err) { return } s := err.Error() if strings.HasPrefix(s, "MOVED ") { moved = true } else if strings.HasPrefix(s, "ASK ") { ask = true } else { return } ind := strings.LastIndex(s, " ") if ind == -1 { return false, false, "" } addr = s[ind+1:] return } func IsLoadingError(err error) bool { return strings.HasPrefix(err.Error(), "LOADING ") } redis-6.9.2/internal/hashtag/000077500000000000000000000000001324526341500160765ustar00rootroot00000000000000redis-6.9.2/internal/hashtag/hashtag.go000066400000000000000000000057501324526341500200530ustar00rootroot00000000000000package hashtag import ( "math/rand" "strings" ) const SlotNumber = 16384 // CRC16 implementation according to CCITT standards. // Copyright 2001-2010 Georges Menie (www.menie.org) // Copyright 2013 The Go Authors. All rights reserved. // http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c var crc16tab = [256]uint16{ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, } func Key(key string) string { if s := strings.IndexByte(key, '{'); s > -1 { if e := strings.IndexByte(key[s+1:], '}'); e > 0 { return key[s+1 : s+e+1] } } return key } func RandomSlot() int { return rand.Intn(SlotNumber) } // hashSlot returns a consistent slot number between 0 and 16383 // for any given string key. func Slot(key string) int { if key == "" { return RandomSlot() } key = Key(key) return int(crc16sum(key)) % SlotNumber } func crc16sum(key string) (crc uint16) { for i := 0; i < len(key); i++ { crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] } return } redis-6.9.2/internal/hashtag/hashtag_test.go000066400000000000000000000027501324526341500211070ustar00rootroot00000000000000package hashtag import ( "math/rand" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestGinkgoSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "hashtag") } var _ = Describe("CRC16", func() { // http://redis.io/topics/cluster-spec#keys-distribution-model It("should calculate CRC16", func() { tests := []struct { s string n uint16 }{ {"123456789", 0x31C3}, {string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 21847}, } for _, test := range tests { Expect(crc16sum(test.s)).To(Equal(test.n), "for %s", test.s) } }) }) var _ = Describe("HashSlot", func() { It("should calculate hash slots", func() { tests := []struct { key string slot int }{ {"123456789", 12739}, {"{}foo", 9500}, {"foo{}", 5542}, {"foo{}{bar}", 8363}, {"", 10503}, {"", 5176}, {string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 5463}, } // Empty keys receive random slot. rand.Seed(100) for _, test := range tests { Expect(Slot(test.key)).To(Equal(test.slot), "for %s", test.key) } }) It("should extract keys from tags", func() { tests := []struct { one, two string }{ {"foo{bar}", "bar"}, {"{foo}bar", "foo"}, {"{user1000}.following", "{user1000}.followers"}, {"foo{{bar}}zap", "{bar"}, {"foo{bar}{zap}", "bar"}, } for _, test := range tests { Expect(Slot(test.one)).To(Equal(Slot(test.two)), "for %s <-> %s", test.one, test.two) } }) }) redis-6.9.2/internal/internal.go000066400000000000000000000010011324526341500166120ustar00rootroot00000000000000package internal import ( "math/rand" "time" ) // Retry backoff with jitter sleep to prevent overloaded conditions during intervals // https://www.awsarchitectureblog.com/2015/03/backoff.html func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { if retry < 0 { retry = 0 } backoff := minBackoff << uint(retry) if backoff > maxBackoff || backoff < minBackoff { backoff = maxBackoff } if backoff == 0 { return 0 } return time.Duration(rand.Int63n(int64(backoff))) } redis-6.9.2/internal/internal_test.go000066400000000000000000000005141324526341500176610ustar00rootroot00000000000000package internal import ( "testing" "time" . "github.com/onsi/gomega" ) func TestRetryBackoff(t *testing.T) { RegisterTestingT(t) for i := -1; i <= 16; i++ { backoff := RetryBackoff(i, time.Millisecond, 512*time.Millisecond) Expect(backoff >= 0).To(BeTrue()) Expect(backoff <= 512*time.Millisecond).To(BeTrue()) } } redis-6.9.2/internal/log.go000066400000000000000000000002741324526341500155720ustar00rootroot00000000000000package internal import ( "fmt" "log" ) var Logger *log.Logger func Logf(s string, args ...interface{}) { if Logger == nil { return } Logger.Output(2, fmt.Sprintf(s, args...)) } redis-6.9.2/internal/once.go000066400000000000000000000033201324526341500157300ustar00rootroot00000000000000/* Copyright 2014 The Camlistore Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "sync" "sync/atomic" ) // A Once will perform a successful action exactly once. // // Unlike a sync.Once, this Once's func returns an error // and is re-armed on failure. type Once struct { m sync.Mutex done uint32 } // Do calls the function f if and only if Do has not been invoked // without error for this instance of Once. In other words, given // var once Once // if once.Do(f) is called multiple times, only the first call will // invoke f, even if f has a different value in each invocation unless // f returns an error. A new instance of Once is required for each // function to execute. // // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: // err := config.once.Do(func() error { return config.init(filename) }) func (o *Once) Do(f func() error) error { if atomic.LoadUint32(&o.done) == 1 { return nil } // Slow-path. o.m.Lock() defer o.m.Unlock() var err error if o.done == 0 { err = f() if err == nil { atomic.StoreUint32(&o.done, 1) } } return err } redis-6.9.2/internal/pool/000077500000000000000000000000001324526341500154305ustar00rootroot00000000000000redis-6.9.2/internal/pool/bench_test.go000066400000000000000000000030721324526341500200770ustar00rootroot00000000000000package pool_test import ( "testing" "time" "github.com/go-redis/redis/internal/pool" ) func benchmarkPoolGetPut(b *testing.B, poolSize int) { connPool := pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: poolSize, PoolTimeout: time.Second, IdleTimeout: time.Hour, IdleCheckFrequency: time.Hour, }) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { cn, _, err := connPool.Get() if err != nil { b.Fatal(err) } if err = connPool.Put(cn); err != nil { b.Fatal(err) } } }) } func BenchmarkPoolGetPut10Conns(b *testing.B) { benchmarkPoolGetPut(b, 10) } func BenchmarkPoolGetPut100Conns(b *testing.B) { benchmarkPoolGetPut(b, 100) } func BenchmarkPoolGetPut1000Conns(b *testing.B) { benchmarkPoolGetPut(b, 1000) } func benchmarkPoolGetRemove(b *testing.B, poolSize int) { connPool := pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: poolSize, PoolTimeout: time.Second, IdleTimeout: time.Hour, IdleCheckFrequency: time.Hour, }) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { cn, _, err := connPool.Get() if err != nil { b.Fatal(err) } if err := connPool.Remove(cn); err != nil { b.Fatal(err) } } }) } func BenchmarkPoolGetRemove10Conns(b *testing.B) { benchmarkPoolGetRemove(b, 10) } func BenchmarkPoolGetRemove100Conns(b *testing.B) { benchmarkPoolGetRemove(b, 100) } func BenchmarkPoolGetRemove1000Conns(b *testing.B) { benchmarkPoolGetRemove(b, 1000) } redis-6.9.2/internal/pool/conn.go000066400000000000000000000026561324526341500167250ustar00rootroot00000000000000package pool import ( "net" "sync/atomic" "time" "github.com/go-redis/redis/internal/proto" ) var noDeadline = time.Time{} type Conn struct { netConn net.Conn Rd *proto.Reader Wb *proto.WriteBuffer Inited bool usedAt atomic.Value } func NewConn(netConn net.Conn) *Conn { cn := &Conn{ netConn: netConn, Wb: proto.NewWriteBuffer(), } cn.Rd = proto.NewReader(cn.netConn) cn.SetUsedAt(time.Now()) return cn } func (cn *Conn) UsedAt() time.Time { return cn.usedAt.Load().(time.Time) } func (cn *Conn) SetUsedAt(tm time.Time) { cn.usedAt.Store(tm) } func (cn *Conn) SetNetConn(netConn net.Conn) { cn.netConn = netConn cn.Rd.Reset(netConn) } func (cn *Conn) IsStale(timeout time.Duration) bool { return timeout > 0 && time.Since(cn.UsedAt()) > timeout } func (cn *Conn) SetReadTimeout(timeout time.Duration) error { now := time.Now() cn.SetUsedAt(now) if timeout > 0 { return cn.netConn.SetReadDeadline(now.Add(timeout)) } return cn.netConn.SetReadDeadline(noDeadline) } func (cn *Conn) SetWriteTimeout(timeout time.Duration) error { now := time.Now() cn.SetUsedAt(now) if timeout > 0 { return cn.netConn.SetWriteDeadline(now.Add(timeout)) } return cn.netConn.SetWriteDeadline(noDeadline) } func (cn *Conn) Write(b []byte) (int, error) { return cn.netConn.Write(b) } func (cn *Conn) RemoteAddr() net.Addr { return cn.netConn.RemoteAddr() } func (cn *Conn) Close() error { return cn.netConn.Close() } redis-6.9.2/internal/pool/main_test.go000066400000000000000000000010141324526341500177360ustar00rootroot00000000000000package pool_test import ( "net" "sync" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestGinkgoSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "pool") } func perform(n int, cbs ...func(int)) { var wg sync.WaitGroup for _, cb := range cbs { for i := 0; i < n; i++ { wg.Add(1) go func(cb func(int), i int) { defer GinkgoRecover() defer wg.Done() cb(i) }(cb, i) } } wg.Wait() } func dummyDialer() (net.Conn, error) { return &net.TCPConn{}, nil } redis-6.9.2/internal/pool/pool.go000066400000000000000000000154631324526341500167410ustar00rootroot00000000000000package pool import ( "errors" "net" "sync" "sync/atomic" "time" "github.com/go-redis/redis/internal" ) var ErrClosed = errors.New("redis: client is closed") var ErrPoolTimeout = errors.New("redis: connection pool timeout") var timers = sync.Pool{ New: func() interface{} { t := time.NewTimer(time.Hour) t.Stop() return t }, } // Stats contains pool state information and accumulated stats. type Stats struct { Hits uint32 // number of times free connection was found in the pool Misses uint32 // number of times free connection was NOT found in the pool Timeouts uint32 // number of times a wait timeout occurred TotalConns uint32 // number of total connections in the pool FreeConns uint32 // number of free connections in the pool StaleConns uint32 // number of stale connections removed from the pool } type Pooler interface { NewConn() (*Conn, error) CloseConn(*Conn) error Get() (*Conn, bool, error) Put(*Conn) error Remove(*Conn) error Len() int FreeLen() int Stats() *Stats Close() error } type Options struct { Dialer func() (net.Conn, error) OnClose func(*Conn) error PoolSize int PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration } type ConnPool struct { opt *Options dialErrorsNum uint32 // atomic lastDialError error lastDialErrorMu sync.RWMutex queue chan struct{} connsMu sync.Mutex conns []*Conn freeConnsMu sync.Mutex freeConns []*Conn stats Stats _closed uint32 // atomic } var _ Pooler = (*ConnPool)(nil) func NewConnPool(opt *Options) *ConnPool { p := &ConnPool{ opt: opt, queue: make(chan struct{}, opt.PoolSize), conns: make([]*Conn, 0, opt.PoolSize), freeConns: make([]*Conn, 0, opt.PoolSize), } if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 { go p.reaper(opt.IdleCheckFrequency) } return p } func (p *ConnPool) NewConn() (*Conn, error) { if p.closed() { return nil, ErrClosed } if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) { return nil, p.getLastDialError() } netConn, err := p.opt.Dialer() if err != nil { p.setLastDialError(err) if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) { go p.tryDial() } return nil, err } cn := NewConn(netConn) p.connsMu.Lock() p.conns = append(p.conns, cn) p.connsMu.Unlock() return cn, nil } func (p *ConnPool) tryDial() { for { if p.closed() { return } conn, err := p.opt.Dialer() if err != nil { p.setLastDialError(err) time.Sleep(time.Second) continue } atomic.StoreUint32(&p.dialErrorsNum, 0) _ = conn.Close() return } } func (p *ConnPool) setLastDialError(err error) { p.lastDialErrorMu.Lock() p.lastDialError = err p.lastDialErrorMu.Unlock() } func (p *ConnPool) getLastDialError() error { p.lastDialErrorMu.RLock() err := p.lastDialError p.lastDialErrorMu.RUnlock() return err } // Get returns existed connection from the pool or creates a new one. func (p *ConnPool) Get() (*Conn, bool, error) { if p.closed() { return nil, false, ErrClosed } select { case p.queue <- struct{}{}: default: timer := timers.Get().(*time.Timer) timer.Reset(p.opt.PoolTimeout) select { case p.queue <- struct{}{}: if !timer.Stop() { <-timer.C } timers.Put(timer) case <-timer.C: timers.Put(timer) atomic.AddUint32(&p.stats.Timeouts, 1) return nil, false, ErrPoolTimeout } } for { p.freeConnsMu.Lock() cn := p.popFree() p.freeConnsMu.Unlock() if cn == nil { break } if cn.IsStale(p.opt.IdleTimeout) { p.CloseConn(cn) continue } atomic.AddUint32(&p.stats.Hits, 1) return cn, false, nil } atomic.AddUint32(&p.stats.Misses, 1) newcn, err := p.NewConn() if err != nil { <-p.queue return nil, false, err } return newcn, true, nil } func (p *ConnPool) popFree() *Conn { if len(p.freeConns) == 0 { return nil } idx := len(p.freeConns) - 1 cn := p.freeConns[idx] p.freeConns = p.freeConns[:idx] return cn } func (p *ConnPool) Put(cn *Conn) error { if data := cn.Rd.PeekBuffered(); data != nil { internal.Logf("connection has unread data: %q", data) return p.Remove(cn) } p.freeConnsMu.Lock() p.freeConns = append(p.freeConns, cn) p.freeConnsMu.Unlock() <-p.queue return nil } func (p *ConnPool) Remove(cn *Conn) error { _ = p.CloseConn(cn) <-p.queue return nil } func (p *ConnPool) CloseConn(cn *Conn) error { p.connsMu.Lock() for i, c := range p.conns { if c == cn { p.conns = append(p.conns[:i], p.conns[i+1:]...) break } } p.connsMu.Unlock() return p.closeConn(cn) } func (p *ConnPool) closeConn(cn *Conn) error { if p.opt.OnClose != nil { _ = p.opt.OnClose(cn) } return cn.Close() } // Len returns total number of connections. func (p *ConnPool) Len() int { p.connsMu.Lock() l := len(p.conns) p.connsMu.Unlock() return l } // FreeLen returns number of free connections. func (p *ConnPool) FreeLen() int { p.freeConnsMu.Lock() l := len(p.freeConns) p.freeConnsMu.Unlock() return l } func (p *ConnPool) Stats() *Stats { return &Stats{ Hits: atomic.LoadUint32(&p.stats.Hits), Misses: atomic.LoadUint32(&p.stats.Misses), Timeouts: atomic.LoadUint32(&p.stats.Timeouts), TotalConns: uint32(p.Len()), FreeConns: uint32(p.FreeLen()), StaleConns: atomic.LoadUint32(&p.stats.StaleConns), } } func (p *ConnPool) closed() bool { return atomic.LoadUint32(&p._closed) == 1 } func (p *ConnPool) Filter(fn func(*Conn) bool) error { var firstErr error p.connsMu.Lock() for _, cn := range p.conns { if fn(cn) { if err := p.closeConn(cn); err != nil && firstErr == nil { firstErr = err } } } p.connsMu.Unlock() return firstErr } func (p *ConnPool) Close() error { if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) { return ErrClosed } var firstErr error p.connsMu.Lock() for _, cn := range p.conns { if err := p.closeConn(cn); err != nil && firstErr == nil { firstErr = err } } p.conns = nil p.connsMu.Unlock() p.freeConnsMu.Lock() p.freeConns = nil p.freeConnsMu.Unlock() return firstErr } func (p *ConnPool) reapStaleConn() bool { if len(p.freeConns) == 0 { return false } cn := p.freeConns[0] if !cn.IsStale(p.opt.IdleTimeout) { return false } p.CloseConn(cn) p.freeConns = append(p.freeConns[:0], p.freeConns[1:]...) return true } func (p *ConnPool) ReapStaleConns() (int, error) { var n int for { p.queue <- struct{}{} p.freeConnsMu.Lock() reaped := p.reapStaleConn() p.freeConnsMu.Unlock() <-p.queue if reaped { n++ } else { break } } return n, nil } func (p *ConnPool) reaper(frequency time.Duration) { ticker := time.NewTicker(frequency) defer ticker.Stop() for range ticker.C { if p.closed() { break } n, err := p.ReapStaleConns() if err != nil { internal.Logf("ReapStaleConns failed: %s", err) continue } atomic.AddUint32(&p.stats.StaleConns, uint32(n)) } } redis-6.9.2/internal/pool/pool_single.go000066400000000000000000000015301324526341500202700ustar00rootroot00000000000000package pool type SingleConnPool struct { cn *Conn } var _ Pooler = (*SingleConnPool)(nil) func NewSingleConnPool(cn *Conn) *SingleConnPool { return &SingleConnPool{ cn: cn, } } func (p *SingleConnPool) NewConn() (*Conn, error) { panic("not implemented") } func (p *SingleConnPool) CloseConn(*Conn) error { panic("not implemented") } func (p *SingleConnPool) Get() (*Conn, bool, error) { return p.cn, false, nil } func (p *SingleConnPool) Put(cn *Conn) error { if p.cn != cn { panic("p.cn != cn") } return nil } func (p *SingleConnPool) Remove(cn *Conn) error { if p.cn != cn { panic("p.cn != cn") } return nil } func (p *SingleConnPool) Len() int { return 1 } func (p *SingleConnPool) FreeLen() int { return 0 } func (p *SingleConnPool) Stats() *Stats { return nil } func (p *SingleConnPool) Close() error { return nil } redis-6.9.2/internal/pool/pool_sticky.go000066400000000000000000000034461324526341500203250ustar00rootroot00000000000000package pool import "sync" type StickyConnPool struct { pool *ConnPool reusable bool cn *Conn closed bool mu sync.Mutex } var _ Pooler = (*StickyConnPool)(nil) func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool { return &StickyConnPool{ pool: pool, reusable: reusable, } } func (p *StickyConnPool) NewConn() (*Conn, error) { panic("not implemented") } func (p *StickyConnPool) CloseConn(*Conn) error { panic("not implemented") } func (p *StickyConnPool) Get() (*Conn, bool, error) { p.mu.Lock() defer p.mu.Unlock() if p.closed { return nil, false, ErrClosed } if p.cn != nil { return p.cn, false, nil } cn, _, err := p.pool.Get() if err != nil { return nil, false, err } p.cn = cn return cn, true, nil } func (p *StickyConnPool) putUpstream() (err error) { err = p.pool.Put(p.cn) p.cn = nil return err } func (p *StickyConnPool) Put(cn *Conn) error { p.mu.Lock() defer p.mu.Unlock() if p.closed { return ErrClosed } return nil } func (p *StickyConnPool) removeUpstream() error { err := p.pool.Remove(p.cn) p.cn = nil return err } func (p *StickyConnPool) Remove(cn *Conn) error { p.mu.Lock() defer p.mu.Unlock() if p.closed { return nil } return p.removeUpstream() } func (p *StickyConnPool) Len() int { p.mu.Lock() defer p.mu.Unlock() if p.cn == nil { return 0 } return 1 } func (p *StickyConnPool) FreeLen() int { p.mu.Lock() defer p.mu.Unlock() if p.cn == nil { return 1 } return 0 } func (p *StickyConnPool) Stats() *Stats { return nil } func (p *StickyConnPool) Close() error { p.mu.Lock() defer p.mu.Unlock() if p.closed { return ErrClosed } p.closed = true var err error if p.cn != nil { if p.reusable { err = p.putUpstream() } else { err = p.removeUpstream() } } return err } redis-6.9.2/internal/pool/pool_test.go000066400000000000000000000122211324526341500177650ustar00rootroot00000000000000package pool_test import ( "testing" "time" "github.com/go-redis/redis/internal/pool" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("ConnPool", func() { var connPool *pool.ConnPool BeforeEach(func() { connPool = pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: 10, PoolTimeout: time.Hour, IdleTimeout: time.Millisecond, IdleCheckFrequency: time.Millisecond, }) }) AfterEach(func() { connPool.Close() }) It("should unblock client when conn is removed", func() { // Reserve one connection. cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) // Reserve all other connections. var cns []*pool.Conn for i := 0; i < 9; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) cns = append(cns, cn) } started := make(chan bool, 1) done := make(chan bool, 1) go func() { defer GinkgoRecover() started <- true _, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) done <- true err = connPool.Put(cn) Expect(err).NotTo(HaveOccurred()) }() <-started // Check that Get is blocked. select { case <-done: Fail("Get is not blocked") default: // ok } err = connPool.Remove(cn) Expect(err).NotTo(HaveOccurred()) // Check that Ping is unblocked. select { case <-done: // ok case <-time.After(time.Second): Fail("Get is not unblocked") } for _, cn := range cns { err = connPool.Put(cn) Expect(err).NotTo(HaveOccurred()) } }) }) var _ = Describe("conns reaper", func() { const idleTimeout = time.Minute var connPool *pool.ConnPool var conns, idleConns, closedConns []*pool.Conn BeforeEach(func() { conns = nil closedConns = nil connPool = pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: 10, PoolTimeout: time.Second, IdleTimeout: idleTimeout, IdleCheckFrequency: time.Hour, OnClose: func(cn *pool.Conn) error { closedConns = append(closedConns, cn) return nil }, }) // add stale connections idleConns = nil for i := 0; i < 3; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) cn.SetUsedAt(time.Now().Add(-2 * idleTimeout)) conns = append(conns, cn) idleConns = append(idleConns, cn) } // add fresh connections for i := 0; i < 3; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) conns = append(conns, cn) } for _, cn := range conns { Expect(connPool.Put(cn)).NotTo(HaveOccurred()) } Expect(connPool.Len()).To(Equal(6)) Expect(connPool.FreeLen()).To(Equal(6)) n, err := connPool.ReapStaleConns() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(3)) }) AfterEach(func() { _ = connPool.Close() Expect(connPool.Len()).To(Equal(0)) Expect(connPool.FreeLen()).To(Equal(0)) Expect(len(closedConns)).To(Equal(len(conns))) Expect(closedConns).To(ConsistOf(conns)) }) It("reaps stale connections", func() { Expect(connPool.Len()).To(Equal(3)) Expect(connPool.FreeLen()).To(Equal(3)) }) It("does not reap fresh connections", func() { n, err := connPool.ReapStaleConns() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(0)) }) It("stale connections are closed", func() { Expect(len(closedConns)).To(Equal(len(idleConns))) Expect(closedConns).To(ConsistOf(idleConns)) }) It("pool is functional", func() { for j := 0; j < 3; j++ { var freeCns []*pool.Conn for i := 0; i < 3; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) Expect(cn).NotTo(BeNil()) freeCns = append(freeCns, cn) } Expect(connPool.Len()).To(Equal(3)) Expect(connPool.FreeLen()).To(Equal(0)) cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) Expect(cn).NotTo(BeNil()) conns = append(conns, cn) Expect(connPool.Len()).To(Equal(4)) Expect(connPool.FreeLen()).To(Equal(0)) err = connPool.Remove(cn) Expect(err).NotTo(HaveOccurred()) Expect(connPool.Len()).To(Equal(3)) Expect(connPool.FreeLen()).To(Equal(0)) for _, cn := range freeCns { err := connPool.Put(cn) Expect(err).NotTo(HaveOccurred()) } Expect(connPool.Len()).To(Equal(3)) Expect(connPool.FreeLen()).To(Equal(3)) } }) }) var _ = Describe("race", func() { var connPool *pool.ConnPool var C, N int BeforeEach(func() { C, N = 10, 1000 if testing.Short() { C = 4 N = 100 } }) AfterEach(func() { connPool.Close() }) It("does not happen on Get, Put, and Remove", func() { connPool = pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: 10, PoolTimeout: time.Minute, IdleTimeout: time.Millisecond, IdleCheckFrequency: time.Millisecond, }) perform(C, func(id int) { for i := 0; i < N; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) if err == nil { Expect(connPool.Put(cn)).NotTo(HaveOccurred()) } } }, func(id int) { for i := 0; i < N; i++ { cn, _, err := connPool.Get() Expect(err).NotTo(HaveOccurred()) if err == nil { Expect(connPool.Remove(cn)).NotTo(HaveOccurred()) } } }) }) }) redis-6.9.2/internal/proto/000077500000000000000000000000001324526341500156225ustar00rootroot00000000000000redis-6.9.2/internal/proto/proto_test.go000066400000000000000000000002741324526341500203560ustar00rootroot00000000000000package proto_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestGinkgoSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "proto") } redis-6.9.2/internal/proto/reader.go000066400000000000000000000141421324526341500174150ustar00rootroot00000000000000package proto import ( "bufio" "fmt" "io" "strconv" "github.com/go-redis/redis/internal/util" ) const bytesAllocLimit = 1024 * 1024 // 1mb const ( ErrorReply = '-' StatusReply = '+' IntReply = ':' StringReply = '$' ArrayReply = '*' ) //------------------------------------------------------------------------------ const Nil = RedisError("redis: nil") type RedisError string func (e RedisError) Error() string { return string(e) } //------------------------------------------------------------------------------ type MultiBulkParse func(*Reader, int64) (interface{}, error) type Reader struct { src *bufio.Reader buf []byte } func NewReader(rd io.Reader) *Reader { return &Reader{ src: bufio.NewReader(rd), buf: make([]byte, 4096), } } func (r *Reader) Reset(rd io.Reader) { r.src.Reset(rd) } func (r *Reader) PeekBuffered() []byte { if n := r.src.Buffered(); n != 0 { b, _ := r.src.Peek(n) return b } return nil } func (r *Reader) ReadN(n int) ([]byte, error) { b, err := readN(r.src, r.buf, n) if err != nil { return nil, err } r.buf = b return b, nil } func (r *Reader) ReadLine() ([]byte, error) { line, isPrefix, err := r.src.ReadLine() if err != nil { return nil, err } if isPrefix { return nil, bufio.ErrBufferFull } if len(line) == 0 { return nil, fmt.Errorf("redis: reply is empty") } if isNilReply(line) { return nil, Nil } return line, nil } func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { line, err := r.ReadLine() if err != nil { return nil, err } switch line[0] { case ErrorReply: return nil, ParseErrorReply(line) case StatusReply: return parseStatusValue(line), nil case IntReply: return util.ParseInt(line[1:], 10, 64) case StringReply: return r.readTmpBytesValue(line) case ArrayReply: n, err := parseArrayLen(line) if err != nil { return nil, err } return m(r, n) } return nil, fmt.Errorf("redis: can't parse %.100q", line) } func (r *Reader) ReadIntReply() (int64, error) { line, err := r.ReadLine() if err != nil { return 0, err } switch line[0] { case ErrorReply: return 0, ParseErrorReply(line) case IntReply: return util.ParseInt(line[1:], 10, 64) default: return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) } } func (r *Reader) ReadTmpBytesReply() ([]byte, error) { line, err := r.ReadLine() if err != nil { return nil, err } switch line[0] { case ErrorReply: return nil, ParseErrorReply(line) case StringReply: return r.readTmpBytesValue(line) case StatusReply: return parseStatusValue(line), nil default: return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line) } } func (r *Reader) ReadBytesReply() ([]byte, error) { b, err := r.ReadTmpBytesReply() if err != nil { return nil, err } cp := make([]byte, len(b)) copy(cp, b) return cp, nil } func (r *Reader) ReadStringReply() (string, error) { b, err := r.ReadTmpBytesReply() if err != nil { return "", err } return string(b), nil } func (r *Reader) ReadFloatReply() (float64, error) { b, err := r.ReadTmpBytesReply() if err != nil { return 0, err } return util.ParseFloat(b, 64) } func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { line, err := r.ReadLine() if err != nil { return nil, err } switch line[0] { case ErrorReply: return nil, ParseErrorReply(line) case ArrayReply: n, err := parseArrayLen(line) if err != nil { return nil, err } return m(r, n) default: return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line) } } func (r *Reader) ReadArrayLen() (int64, error) { line, err := r.ReadLine() if err != nil { return 0, err } switch line[0] { case ErrorReply: return 0, ParseErrorReply(line) case ArrayReply: return parseArrayLen(line) default: return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line) } } func (r *Reader) ReadScanReply() ([]string, uint64, error) { n, err := r.ReadArrayLen() if err != nil { return nil, 0, err } if n != 2 { return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) } cursor, err := r.ReadUint() if err != nil { return nil, 0, err } n, err = r.ReadArrayLen() if err != nil { return nil, 0, err } keys := make([]string, n) for i := int64(0); i < n; i++ { key, err := r.ReadStringReply() if err != nil { return nil, 0, err } keys[i] = key } return keys, cursor, err } func (r *Reader) readTmpBytesValue(line []byte) ([]byte, error) { if isNilReply(line) { return nil, Nil } replyLen, err := strconv.Atoi(string(line[1:])) if err != nil { return nil, err } b, err := r.ReadN(replyLen + 2) if err != nil { return nil, err } return b[:replyLen], nil } func (r *Reader) ReadInt() (int64, error) { b, err := r.ReadTmpBytesReply() if err != nil { return 0, err } return util.ParseInt(b, 10, 64) } func (r *Reader) ReadUint() (uint64, error) { b, err := r.ReadTmpBytesReply() if err != nil { return 0, err } return util.ParseUint(b, 10, 64) } // -------------------------------------------------------------------- func readN(r io.Reader, b []byte, n int) ([]byte, error) { if n == 0 && b == nil { return make([]byte, 0), nil } if cap(b) >= n { b = b[:n] _, err := io.ReadFull(r, b) return b, err } b = b[:cap(b)] pos := 0 for pos < n { diff := n - len(b) if diff > bytesAllocLimit { diff = bytesAllocLimit } b = append(b, make([]byte, diff)...) nn, err := io.ReadFull(r, b[pos:]) if err != nil { return nil, err } pos += nn } return b, nil } func formatInt(n int64) string { return strconv.FormatInt(n, 10) } func formatUint(u uint64) string { return strconv.FormatUint(u, 10) } func formatFloat(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) } func isNilReply(b []byte) bool { return len(b) == 3 && (b[0] == StringReply || b[0] == ArrayReply) && b[1] == '-' && b[2] == '1' } func ParseErrorReply(line []byte) error { return RedisError(string(line[1:])) } func parseStatusValue(line []byte) []byte { return line[1:] } func parseArrayLen(line []byte) (int64, error) { if isNilReply(line) { return 0, Nil } return util.ParseInt(line[1:], 10, 64) } redis-6.9.2/internal/proto/reader_test.go000066400000000000000000000040371324526341500204560ustar00rootroot00000000000000package proto_test import ( "bytes" "strings" "testing" "github.com/go-redis/redis/internal/proto" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Reader", func() { It("should read n bytes", func() { data, err := proto.NewReader(strings.NewReader("ABCDEFGHIJKLMNO")).ReadN(10) Expect(err).NotTo(HaveOccurred()) Expect(len(data)).To(Equal(10)) Expect(string(data)).To(Equal("ABCDEFGHIJ")) data, err = proto.NewReader(strings.NewReader(strings.Repeat("x", 8192))).ReadN(6000) Expect(err).NotTo(HaveOccurred()) Expect(len(data)).To(Equal(6000)) }) It("should read lines", func() { p := proto.NewReader(strings.NewReader("$5\r\nhello\r\n")) data, err := p.ReadLine() Expect(err).NotTo(HaveOccurred()) Expect(string(data)).To(Equal("$5")) data, err = p.ReadLine() Expect(err).NotTo(HaveOccurred()) Expect(string(data)).To(Equal("hello")) }) }) func BenchmarkReader_ParseReply_Status(b *testing.B) { benchmarkParseReply(b, "+OK\r\n", nil, false) } func BenchmarkReader_ParseReply_Int(b *testing.B) { benchmarkParseReply(b, ":1\r\n", nil, false) } func BenchmarkReader_ParseReply_Error(b *testing.B) { benchmarkParseReply(b, "-Error message\r\n", nil, true) } func BenchmarkReader_ParseReply_String(b *testing.B) { benchmarkParseReply(b, "$5\r\nhello\r\n", nil, false) } func BenchmarkReader_ParseReply_Slice(b *testing.B) { benchmarkParseReply(b, "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", multiBulkParse, false) } func benchmarkParseReply(b *testing.B, reply string, m proto.MultiBulkParse, wanterr bool) { buf := new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.WriteString(reply) } p := proto.NewReader(buf) b.ResetTimer() for i := 0; i < b.N; i++ { _, err := p.ReadReply(m) if !wanterr && err != nil { b.Fatal(err) } } } func multiBulkParse(p *proto.Reader, n int64) (interface{}, error) { vv := make([]interface{}, 0, n) for i := int64(0); i < n; i++ { v, err := p.ReadReply(multiBulkParse) if err != nil { return nil, err } vv = append(vv, v) } return vv, nil } redis-6.9.2/internal/proto/scan.go000066400000000000000000000061441324526341500171020ustar00rootroot00000000000000package proto import ( "encoding" "fmt" "reflect" "github.com/go-redis/redis/internal/util" ) func Scan(b []byte, v interface{}) error { switch v := v.(type) { case nil: return fmt.Errorf("redis: Scan(nil)") case *string: *v = util.BytesToString(b) return nil case *[]byte: *v = b return nil case *int: var err error *v, err = util.Atoi(b) return err case *int8: n, err := util.ParseInt(b, 10, 8) if err != nil { return err } *v = int8(n) return nil case *int16: n, err := util.ParseInt(b, 10, 16) if err != nil { return err } *v = int16(n) return nil case *int32: n, err := util.ParseInt(b, 10, 32) if err != nil { return err } *v = int32(n) return nil case *int64: n, err := util.ParseInt(b, 10, 64) if err != nil { return err } *v = n return nil case *uint: n, err := util.ParseUint(b, 10, 64) if err != nil { return err } *v = uint(n) return nil case *uint8: n, err := util.ParseUint(b, 10, 8) if err != nil { return err } *v = uint8(n) return nil case *uint16: n, err := util.ParseUint(b, 10, 16) if err != nil { return err } *v = uint16(n) return nil case *uint32: n, err := util.ParseUint(b, 10, 32) if err != nil { return err } *v = uint32(n) return nil case *uint64: n, err := util.ParseUint(b, 10, 64) if err != nil { return err } *v = n return nil case *float32: n, err := util.ParseFloat(b, 32) if err != nil { return err } *v = float32(n) return err case *float64: var err error *v, err = util.ParseFloat(b, 64) return err case *bool: *v = len(b) == 1 && b[0] == '1' return nil case encoding.BinaryUnmarshaler: return v.UnmarshalBinary(b) default: return fmt.Errorf( "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) } } func ScanSlice(data []string, slice interface{}) error { v := reflect.ValueOf(slice) if !v.IsValid() { return fmt.Errorf("redis: ScanSlice(nil)") } if v.Kind() != reflect.Ptr { return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) } v = v.Elem() if v.Kind() != reflect.Slice { return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) } next := makeSliceNextElemFunc(v) for i, s := range data { elem := next() if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %s", i, s, err) return err } } return nil } func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { elemType := v.Type().Elem() if elemType.Kind() == reflect.Ptr { elemType = elemType.Elem() return func() reflect.Value { if v.Len() < v.Cap() { v.Set(v.Slice(0, v.Len()+1)) elem := v.Index(v.Len() - 1) if elem.IsNil() { elem.Set(reflect.New(elemType)) } return elem.Elem() } elem := reflect.New(elemType) v.Set(reflect.Append(v, elem)) return elem.Elem() } } zero := reflect.Zero(elemType) return func() reflect.Value { if v.Len() < v.Cap() { v.Set(v.Slice(0, v.Len()+1)) return v.Index(v.Len() - 1) } v.Set(reflect.Append(v, zero)) return v.Index(v.Len() - 1) } } redis-6.9.2/internal/proto/scan_test.go000066400000000000000000000017101324526341500201330ustar00rootroot00000000000000package proto import ( "encoding/json" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) type testScanSliceStruct struct { ID int Name string } func (s *testScanSliceStruct) MarshalBinary() ([]byte, error) { return json.Marshal(s) } func (s *testScanSliceStruct) UnmarshalBinary(b []byte) error { return json.Unmarshal(b, s) } var _ = Describe("ScanSlice", func() { data := []string{ `{"ID":-1,"Name":"Back Yu"}`, `{"ID":1,"Name":"szyhf"}`, } It("[]testScanSliceStruct", func() { var slice []testScanSliceStruct err := ScanSlice(data, &slice) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(Equal([]testScanSliceStruct{ {-1, "Back Yu"}, {1, "szyhf"}, })) }) It("var testContainer []*testScanSliceStruct", func() { var slice []*testScanSliceStruct err := ScanSlice(data, &slice) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(Equal([]*testScanSliceStruct{ {-1, "Back Yu"}, {1, "szyhf"}, })) }) }) redis-6.9.2/internal/proto/write_buffer.go000066400000000000000000000042731324526341500206420ustar00rootroot00000000000000package proto import ( "encoding" "fmt" "strconv" ) type WriteBuffer struct { b []byte } func NewWriteBuffer() *WriteBuffer { return &WriteBuffer{ b: make([]byte, 0, 4096), } } func (w *WriteBuffer) Len() int { return len(w.b) } func (w *WriteBuffer) Bytes() []byte { return w.b } func (w *WriteBuffer) Reset() { w.b = w.b[:0] } func (w *WriteBuffer) Append(args []interface{}) error { w.b = append(w.b, ArrayReply) w.b = strconv.AppendUint(w.b, uint64(len(args)), 10) w.b = append(w.b, '\r', '\n') for _, arg := range args { if err := w.append(arg); err != nil { return err } } return nil } func (w *WriteBuffer) append(val interface{}) error { switch v := val.(type) { case nil: w.AppendString("") case string: w.AppendString(v) case []byte: w.AppendBytes(v) case int: w.AppendString(formatInt(int64(v))) case int8: w.AppendString(formatInt(int64(v))) case int16: w.AppendString(formatInt(int64(v))) case int32: w.AppendString(formatInt(int64(v))) case int64: w.AppendString(formatInt(v)) case uint: w.AppendString(formatUint(uint64(v))) case uint8: w.AppendString(formatUint(uint64(v))) case uint16: w.AppendString(formatUint(uint64(v))) case uint32: w.AppendString(formatUint(uint64(v))) case uint64: w.AppendString(formatUint(v)) case float32: w.AppendString(formatFloat(float64(v))) case float64: w.AppendString(formatFloat(v)) case bool: if v { w.AppendString("1") } else { w.AppendString("0") } default: if bm, ok := val.(encoding.BinaryMarshaler); ok { bb, err := bm.MarshalBinary() if err != nil { return err } w.AppendBytes(bb) } else { return fmt.Errorf( "redis: can't marshal %T (consider implementing encoding.BinaryMarshaler)", val) } } return nil } func (w *WriteBuffer) AppendString(s string) { w.b = append(w.b, StringReply) w.b = strconv.AppendUint(w.b, uint64(len(s)), 10) w.b = append(w.b, '\r', '\n') w.b = append(w.b, s...) w.b = append(w.b, '\r', '\n') } func (w *WriteBuffer) AppendBytes(p []byte) { w.b = append(w.b, StringReply) w.b = strconv.AppendUint(w.b, uint64(len(p)), 10) w.b = append(w.b, '\r', '\n') w.b = append(w.b, p...) w.b = append(w.b, '\r', '\n') } redis-6.9.2/internal/proto/write_buffer_test.go000066400000000000000000000022551324526341500216770ustar00rootroot00000000000000package proto_test import ( "testing" "time" "github.com/go-redis/redis/internal/proto" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("WriteBuffer", func() { var buf *proto.WriteBuffer BeforeEach(func() { buf = proto.NewWriteBuffer() }) It("should reset", func() { buf.AppendString("string") Expect(buf.Len()).To(Equal(12)) buf.Reset() Expect(buf.Len()).To(Equal(0)) }) It("should append args", func() { err := buf.Append([]interface{}{ "string", 12, 34.56, []byte{'b', 'y', 't', 'e', 's'}, true, nil, }) Expect(err).NotTo(HaveOccurred()) Expect(buf.Bytes()).To(Equal([]byte("*6\r\n" + "$6\r\nstring\r\n" + "$2\r\n12\r\n" + "$5\r\n34.56\r\n" + "$5\r\nbytes\r\n" + "$1\r\n1\r\n" + "$0\r\n" + "\r\n"))) }) It("should append marshalable args", func() { err := buf.Append([]interface{}{time.Unix(1414141414, 0)}) Expect(err).NotTo(HaveOccurred()) Expect(buf.Len()).To(Equal(26)) }) }) func BenchmarkWriteBuffer_Append(b *testing.B) { buf := proto.NewWriteBuffer() args := []interface{}{"hello", "world", "foo", "bar"} for i := 0; i < b.N; i++ { buf.Append(args) buf.Reset() } } redis-6.9.2/internal/singleflight/000077500000000000000000000000001324526341500171365ustar00rootroot00000000000000redis-6.9.2/internal/singleflight/singleflight.go000066400000000000000000000032221324526341500221430ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package singleflight provides a duplicate function call suppression // mechanism. package singleflight import "sync" // call is an in-flight or completed Do call type call struct { wg sync.WaitGroup val interface{} err error } // Group represents a class of work and forms a namespace in which // units of work can be executed with duplicate suppression. type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Do executes and returns the results of the given function, making // sure that only one execution is in-flight for a given key at a // time. If a duplicate comes in, the duplicate caller waits for the // original to complete and receives the same results. func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { g.mu.Unlock() c.wg.Wait() return c.val, c.err } c := new(call) c.wg.Add(1) g.m[key] = c g.mu.Unlock() c.val, c.err = fn() c.wg.Done() g.mu.Lock() delete(g.m, key) g.mu.Unlock() return c.val, c.err } redis-6.9.2/internal/util.go000066400000000000000000000006531324526341500157670ustar00rootroot00000000000000package internal import "github.com/go-redis/redis/internal/util" func ToLower(s string) string { if isLower(s) { return s } b := make([]byte, len(s)) for i := range b { c := s[i] if c >= 'A' && c <= 'Z' { c += 'a' - 'A' } b[i] = c } return util.BytesToString(b) } func isLower(s string) bool { for i := 0; i < len(s); i++ { c := s[i] if c >= 'A' && c <= 'Z' { return false } } return true } redis-6.9.2/internal/util/000077500000000000000000000000001324526341500154345ustar00rootroot00000000000000redis-6.9.2/internal/util/safe.go000066400000000000000000000001351324526341500167000ustar00rootroot00000000000000// +build appengine package util func BytesToString(b []byte) string { return string(b) } redis-6.9.2/internal/util/strconv.go000066400000000000000000000007341324526341500174650ustar00rootroot00000000000000package util import "strconv" func Atoi(b []byte) (int, error) { return strconv.Atoi(BytesToString(b)) } func ParseInt(b []byte, base int, bitSize int) (int64, error) { return strconv.ParseInt(BytesToString(b), base, bitSize) } func ParseUint(b []byte, base int, bitSize int) (uint64, error) { return strconv.ParseUint(BytesToString(b), base, bitSize) } func ParseFloat(b []byte, bitSize int) (float64, error) { return strconv.ParseFloat(BytesToString(b), bitSize) } redis-6.9.2/internal/util/unsafe.go000066400000000000000000000002711324526341500172440ustar00rootroot00000000000000// +build !appengine package util import ( "unsafe" ) // BytesToString converts byte slice to string. func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } redis-6.9.2/iterator.go000066400000000000000000000026411324526341500150260ustar00rootroot00000000000000package redis import "sync" // ScanIterator is used to incrementally iterate over a collection of elements. // It's safe for concurrent use by multiple goroutines. type ScanIterator struct { mu sync.Mutex // protects Scanner and pos cmd *ScanCmd pos int } // Err returns the last iterator error, if any. func (it *ScanIterator) Err() error { it.mu.Lock() err := it.cmd.Err() it.mu.Unlock() return err } // Next advances the cursor and returns true if more values can be read. func (it *ScanIterator) Next() bool { it.mu.Lock() defer it.mu.Unlock() // Instantly return on errors. if it.cmd.Err() != nil { return false } // Advance cursor, check if we are still within range. if it.pos < len(it.cmd.page) { it.pos++ return true } for { // Return if there is no more data to fetch. if it.cmd.cursor == 0 { return false } // Fetch next page. if it.cmd._args[0] == "scan" { it.cmd._args[1] = it.cmd.cursor } else { it.cmd._args[2] = it.cmd.cursor } err := it.cmd.process(it.cmd) if err != nil { return false } it.pos = 1 // Redis can occasionally return empty page. if len(it.cmd.page) > 0 { return true } } } // Val returns the key/field at the current cursor position. func (it *ScanIterator) Val() string { var v string it.mu.Lock() if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) { v = it.cmd.page[it.pos-1] } it.mu.Unlock() return v } redis-6.9.2/iterator_test.go000066400000000000000000000062631324526341500160710ustar00rootroot00000000000000package redis_test import ( "fmt" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("ScanIterator", func() { var client *redis.Client var seed = func(n int) error { pipe := client.Pipeline() for i := 1; i <= n; i++ { pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err() } _, err := pipe.Exec() return err } var extraSeed = func(n int, m int) error { pipe := client.Pipeline() for i := 1; i <= m; i++ { pipe.Set(fmt.Sprintf("A%02d", i), "x", 0).Err() } for i := 1; i <= n; i++ { pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err() } _, err := pipe.Exec() return err } var hashKey = "K_HASHTEST" var hashSeed = func(n int) error { pipe := client.Pipeline() for i := 1; i <= n; i++ { pipe.HSet(hashKey, fmt.Sprintf("K%02d", i), "x").Err() } _, err := pipe.Exec() return err } BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("should scan across empty DBs", func() { iter := client.Scan(0, "", 10).Iterator() Expect(iter.Next()).To(BeFalse()) Expect(iter.Err()).NotTo(HaveOccurred()) }) It("should scan across one page", func() { Expect(seed(7)).NotTo(HaveOccurred()) var vals []string iter := client.Scan(0, "", 0).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(ConsistOf([]string{"K01", "K02", "K03", "K04", "K05", "K06", "K07"})) }) It("should scan across multiple pages", func() { Expect(seed(71)).NotTo(HaveOccurred()) var vals []string iter := client.Scan(0, "", 10).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(HaveLen(71)) Expect(vals).To(ContainElement("K01")) Expect(vals).To(ContainElement("K71")) }) It("should hscan across multiple pages", func() { Expect(hashSeed(71)).NotTo(HaveOccurred()) var vals []string iter := client.HScan(hashKey, 0, "", 10).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(HaveLen(71 * 2)) Expect(vals).To(ContainElement("K01")) Expect(vals).To(ContainElement("K71")) }) It("should scan to page borders", func() { Expect(seed(20)).NotTo(HaveOccurred()) var vals []string iter := client.Scan(0, "", 10).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(HaveLen(20)) }) It("should scan with match", func() { Expect(seed(33)).NotTo(HaveOccurred()) var vals []string iter := client.Scan(0, "K*2*", 10).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(HaveLen(13)) }) It("should scan with match across empty pages", func() { Expect(extraSeed(2, 10)).NotTo(HaveOccurred()) var vals []string iter := client.Scan(0, "K*", 1).Iterator() for iter.Next() { vals = append(vals, iter.Val()) } Expect(iter.Err()).NotTo(HaveOccurred()) Expect(vals).To(HaveLen(2)) }) }) redis-6.9.2/main_test.go000066400000000000000000000177151324526341500151700ustar00rootroot00000000000000package redis_test import ( "errors" "fmt" "net" "os" "os/exec" "path/filepath" "sync" "sync/atomic" "testing" "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) const ( redisPort = "6380" redisAddr = ":" + redisPort redisSecondaryPort = "6381" ) const ( ringShard1Port = "6390" ringShard2Port = "6391" ) const ( sentinelName = "mymaster" sentinelMasterPort = "8123" sentinelSlave1Port = "8124" sentinelSlave2Port = "8125" sentinelPort = "8126" ) var ( redisMain *redisProcess ringShard1, ringShard2 *redisProcess sentinelMaster, sentinelSlave1, sentinelSlave2, sentinel *redisProcess ) var cluster = &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), } var _ = BeforeSuite(func() { var err error redisMain, err = startRedis(redisPort) Expect(err).NotTo(HaveOccurred()) ringShard1, err = startRedis(ringShard1Port) Expect(err).NotTo(HaveOccurred()) ringShard2, err = startRedis(ringShard2Port) Expect(err).NotTo(HaveOccurred()) sentinelMaster, err = startRedis(sentinelMasterPort) Expect(err).NotTo(HaveOccurred()) sentinel, err = startSentinel(sentinelPort, sentinelName, sentinelMasterPort) Expect(err).NotTo(HaveOccurred()) sentinelSlave1, err = startRedis( sentinelSlave1Port, "--slaveof", "127.0.0.1", sentinelMasterPort) Expect(err).NotTo(HaveOccurred()) sentinelSlave2, err = startRedis( sentinelSlave2Port, "--slaveof", "127.0.0.1", sentinelMasterPort) Expect(err).NotTo(HaveOccurred()) Expect(startCluster(cluster)).NotTo(HaveOccurred()) }) var _ = AfterSuite(func() { Expect(redisMain.Close()).NotTo(HaveOccurred()) Expect(ringShard1.Close()).NotTo(HaveOccurred()) Expect(ringShard2.Close()).NotTo(HaveOccurred()) Expect(sentinel.Close()).NotTo(HaveOccurred()) Expect(sentinelSlave1.Close()).NotTo(HaveOccurred()) Expect(sentinelSlave2.Close()).NotTo(HaveOccurred()) Expect(sentinelMaster.Close()).NotTo(HaveOccurred()) Expect(stopCluster(cluster)).NotTo(HaveOccurred()) }) func TestGinkgoSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "go-redis") } //------------------------------------------------------------------------------ func redisOptions() *redis.Options { return &redis.Options{ Addr: redisAddr, DB: 15, DialTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, PoolSize: 10, PoolTimeout: 30 * time.Second, IdleTimeout: 500 * time.Millisecond, IdleCheckFrequency: 500 * time.Millisecond, } } func redisClusterOptions() *redis.ClusterOptions { return &redis.ClusterOptions{ DialTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, PoolSize: 10, PoolTimeout: 30 * time.Second, IdleTimeout: 500 * time.Millisecond, IdleCheckFrequency: 500 * time.Millisecond, } } func redisRingOptions() *redis.RingOptions { return &redis.RingOptions{ Addrs: map[string]string{ "ringShardOne": ":" + ringShard1Port, "ringShardTwo": ":" + ringShard2Port, }, DialTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, PoolSize: 10, PoolTimeout: 30 * time.Second, IdleTimeout: 500 * time.Millisecond, IdleCheckFrequency: 500 * time.Millisecond, } } func perform(n int, cbs ...func(int)) { var wg sync.WaitGroup for _, cb := range cbs { for i := 0; i < n; i++ { wg.Add(1) go func(cb func(int), i int) { defer GinkgoRecover() defer wg.Done() cb(i) }(cb, i) } } wg.Wait() } func eventually(fn func() error, timeout time.Duration) error { var exit int32 errCh := make(chan error) done := make(chan struct{}) go func() { defer GinkgoRecover() for atomic.LoadInt32(&exit) == 0 { err := fn() if err == nil { close(done) return } select { case errCh <- err: default: } time.Sleep(timeout / 100) } }() select { case <-done: return nil case <-time.After(timeout): atomic.StoreInt32(&exit, 1) select { case err := <-errCh: return err default: return fmt.Errorf("timeout after %s", timeout) } } } func execCmd(name string, args ...string) (*os.Process, error) { cmd := exec.Command(name, args...) if testing.Verbose() { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } return cmd.Process, cmd.Start() } func connectTo(port string) (*redis.Client, error) { client := redis.NewClient(&redis.Options{ Addr: ":" + port, }) err := eventually(func() error { return client.Ping().Err() }, 30*time.Second) if err != nil { return nil, err } return client, nil } type redisProcess struct { *os.Process *redis.Client } func (p *redisProcess) Close() error { if err := p.Kill(); err != nil { return err } err := eventually(func() error { if err := p.Client.Ping().Err(); err != nil { return nil } return errors.New("client is not shutdown") }, 10*time.Second) if err != nil { return err } p.Client.Close() return nil } var ( redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server")) redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis.conf")) ) func redisDir(port string) (string, error) { dir, err := filepath.Abs(filepath.Join("testdata", "instances", port)) if err != nil { return "", err } if err := os.RemoveAll(dir); err != nil { return "", err } if err := os.MkdirAll(dir, 0775); err != nil { return "", err } return dir, nil } func startRedis(port string, args ...string) (*redisProcess, error) { dir, err := redisDir(port) if err != nil { return nil, err } if err = exec.Command("cp", "-f", redisServerConf, dir).Run(); err != nil { return nil, err } baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir} process, err := execCmd(redisServerBin, append(baseArgs, args...)...) if err != nil { return nil, err } client, err := connectTo(port) if err != nil { process.Kill() return nil, err } return &redisProcess{process, client}, err } func startSentinel(port, masterName, masterPort string) (*redisProcess, error) { dir, err := redisDir(port) if err != nil { return nil, err } process, err := execCmd(redisServerBin, os.DevNull, "--sentinel", "--port", port, "--dir", dir) if err != nil { return nil, err } client, err := connectTo(port) if err != nil { process.Kill() return nil, err } for _, cmd := range []*redis.StatusCmd{ redis.NewStatusCmd("SENTINEL", "MONITOR", masterName, "127.0.0.1", masterPort, "1"), redis.NewStatusCmd("SENTINEL", "SET", masterName, "down-after-milliseconds", "500"), redis.NewStatusCmd("SENTINEL", "SET", masterName, "failover-timeout", "1000"), redis.NewStatusCmd("SENTINEL", "SET", masterName, "parallel-syncs", "1"), } { client.Process(cmd) if err := cmd.Err(); err != nil { process.Kill() return nil, err } } return &redisProcess{process, client}, nil } //------------------------------------------------------------------------------ type badConnError string func (e badConnError) Error() string { return string(e) } func (e badConnError) Timeout() bool { return false } func (e badConnError) Temporary() bool { return false } type badConn struct { net.TCPConn readDelay, writeDelay time.Duration readErr, writeErr error } var _ net.Conn = &badConn{} func (cn *badConn) Read([]byte) (int, error) { if cn.readDelay != 0 { time.Sleep(cn.readDelay) } if cn.readErr != nil { return 0, cn.readErr } return 0, badConnError("bad connection") } func (cn *badConn) Write([]byte) (int, error) { if cn.writeDelay != 0 { time.Sleep(cn.writeDelay) } if cn.writeErr != nil { return 0, cn.writeErr } return 0, badConnError("bad connection") } redis-6.9.2/options.go000066400000000000000000000113011324526341500146610ustar00rootroot00000000000000package redis import ( "crypto/tls" "errors" "fmt" "net" "net/url" "runtime" "strconv" "strings" "time" "github.com/go-redis/redis/internal/pool" ) type Options struct { // The network type, either tcp or unix. // Default is tcp. Network string // host:port address. Addr string // Dialer creates new network connection and has priority over // Network and Addr options. Dialer func() (net.Conn, error) // Hook that is called when new connection is established. OnConnect func(*Conn) error // Optional password. Must match the password specified in the // requirepass server configuration option. Password string // Database to be selected after connecting to the server. DB int // Maximum number of retries before giving up. // Default is to not retry failed commands. MaxRetries int // Minimum backoff between each retry. // Default is 8 milliseconds; -1 disables backoff. MinRetryBackoff time.Duration // Maximum backoff between each retry. // Default is 512 milliseconds; -1 disables backoff. MaxRetryBackoff time.Duration // Dial timeout for establishing new connections. // Default is 5 seconds. DialTimeout time.Duration // Timeout for socket reads. If reached, commands will fail // with a timeout instead of blocking. // Default is 3 seconds. ReadTimeout time.Duration // Timeout for socket writes. If reached, commands will fail // with a timeout instead of blocking. // Default is ReadTimeout. WriteTimeout time.Duration // Maximum number of socket connections. // Default is 10 connections per every CPU as reported by runtime.NumCPU. PoolSize int // Amount of time client waits for connection if all connections // are busy before returning an error. // Default is ReadTimeout + 1 second. PoolTimeout time.Duration // Amount of time after which client closes idle connections. // Should be less than server's timeout. // Default is 5 minutes. IdleTimeout time.Duration // Frequency of idle checks. // Default is 1 minute. // When minus value is set, then idle check is disabled. IdleCheckFrequency time.Duration // Enables read only queries on slave nodes. readOnly bool // TLS Config to use. When set TLS will be negotiated. TLSConfig *tls.Config } func (opt *Options) init() { if opt.Network == "" { opt.Network = "tcp" } if opt.Dialer == nil { opt.Dialer = func() (net.Conn, error) { conn, err := net.DialTimeout(opt.Network, opt.Addr, opt.DialTimeout) if opt.TLSConfig == nil || err != nil { return conn, err } t := tls.Client(conn, opt.TLSConfig) return t, t.Handshake() } } if opt.PoolSize == 0 { opt.PoolSize = 10 * runtime.NumCPU() } if opt.DialTimeout == 0 { opt.DialTimeout = 5 * time.Second } 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 } if opt.PoolTimeout == 0 { opt.PoolTimeout = opt.ReadTimeout + time.Second } if opt.IdleTimeout == 0 { opt.IdleTimeout = 5 * time.Minute } if opt.IdleCheckFrequency == 0 { opt.IdleCheckFrequency = time.Minute } 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 } } // ParseURL parses an URL into Options that can be used to connect to Redis. func ParseURL(redisURL string) (*Options, error) { o := &Options{Network: "tcp"} u, err := url.Parse(redisURL) if err != nil { return nil, err } if u.Scheme != "redis" && u.Scheme != "rediss" { return nil, errors.New("invalid redis URL scheme: " + u.Scheme) } if u.User != nil { if p, ok := u.User.Password(); ok { o.Password = p } } if len(u.Query()) > 0 { return nil, errors.New("no options supported") } h, p, err := net.SplitHostPort(u.Host) if err != nil { h = u.Host } if h == "" { h = "localhost" } if p == "" { p = "6379" } o.Addr = net.JoinHostPort(h, p) f := strings.FieldsFunc(u.Path, func(r rune) bool { return r == '/' }) switch len(f) { case 0: o.DB = 0 case 1: if o.DB, err = strconv.Atoi(f[0]); err != nil { return nil, fmt.Errorf("invalid redis database number: %q", f[0]) } default: return nil, errors.New("invalid redis URL path: " + u.Path) } if u.Scheme == "rediss" { o.TLSConfig = &tls.Config{ServerName: h} } return o, nil } func newConnPool(opt *Options) *pool.ConnPool { return pool.NewConnPool(&pool.Options{ Dialer: opt.Dialer, PoolSize: opt.PoolSize, PoolTimeout: opt.PoolTimeout, IdleTimeout: opt.IdleTimeout, IdleCheckFrequency: opt.IdleCheckFrequency, }) } redis-6.9.2/options_test.go000066400000000000000000000031711324526341500157260ustar00rootroot00000000000000// +build go1.7 package redis import ( "errors" "testing" ) func TestParseURL(t *testing.T) { cases := []struct { u string addr string db int tls bool err error }{ { "redis://localhost:123/1", "localhost:123", 1, false, nil, }, { "redis://localhost:123", "localhost:123", 0, false, nil, }, { "redis://localhost/1", "localhost:6379", 1, false, nil, }, { "redis://12345", "12345:6379", 0, false, nil, }, { "rediss://localhost:123", "localhost:123", 0, true, nil, }, { "redis://localhost/?abc=123", "", 0, false, errors.New("no options supported"), }, { "http://google.com", "", 0, false, errors.New("invalid redis URL scheme: http"), }, { "redis://localhost/1/2/3/4", "", 0, false, errors.New("invalid redis URL path: /1/2/3/4"), }, { "12345", "", 0, false, errors.New("invalid redis URL scheme: "), }, { "redis://localhost/iamadatabase", "", 0, false, errors.New(`invalid redis database number: "iamadatabase"`), }, } for _, c := range cases { t.Run(c.u, func(t *testing.T) { o, err := ParseURL(c.u) if c.err == nil && err != nil { t.Fatalf("unexpected error: %q", err) return } if c.err != nil && err != nil { if c.err.Error() != err.Error() { t.Fatalf("got %q, expected %q", err, c.err) } return } if o.Addr != c.addr { t.Errorf("got %q, want %q", o.Addr, c.addr) } if o.DB != c.db { t.Errorf("got %q, expected %q", o.DB, c.db) } if c.tls && o.TLSConfig == nil { t.Errorf("got nil TLSConfig, expected a TLSConfig") } }) } } redis-6.9.2/parser.go000066400000000000000000000172141324526341500144730ustar00rootroot00000000000000package redis import ( "fmt" "net" "strconv" "time" "github.com/go-redis/redis/internal/proto" ) // 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 []byte: vals = append(vals, string(v)) default: vals = append(vals, v) } } return vals, 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 } // 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.ReadStringReply() if err == Nil { ss = append(ss, "") } else if err != nil { return nil, err } else { ss = append(ss, s) } } return ss, 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.ReadStringReply() if err != nil { return nil, err } value, err := rd.ReadStringReply() if err != nil { return nil, err } m[key] = value } return m, 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.ReadStringReply() if err != nil { return nil, err } n, err := rd.ReadIntReply() if err != nil { return nil, err } m[key] = n } return m, 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.ReadStringReply() if err != nil { return nil, err } m[key] = struct{}{} } return m, 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.ReadStringReply() if err != nil { return nil, err } z.Score, err = rd.ReadFloatReply() if err != nil { return nil, err } } return zz, 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.ReadStringReply() if err != nil { return nil, err } port, err := rd.ReadIntReply() if err != nil { return nil, err } nodes[j].Addr = net.JoinHostPort(ip, strconv.FormatInt(port, 10)) if n == 3 { id, err := rd.ReadStringReply() if err != nil { return nil, err } nodes[j].Id = id } } slots[i] = ClusterSlot{ Start: int(start), End: int(end), Nodes: nodes, } } return slots, 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.ReadStringReply() 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 []byte: locs = append(locs, GeoLocation{ Name: string(vv), }) case *GeoLocation: locs = append(locs, *vv) default: return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) } } return locs, 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 } 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 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.ReadStringReply() 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 } // 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 } // 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 } redis-6.9.2/pipeline.go000066400000000000000000000037431324526341500150060ustar00rootroot00000000000000package redis import ( "sync" "github.com/go-redis/redis/internal/pool" ) type pipelineExecer func([]Cmder) error type Pipeliner interface { StatefulCmdable Process(cmd Cmder) error Close() error Discard() error Exec() ([]Cmder, error) } var _ Pipeliner = (*Pipeline)(nil) // Pipeline implements pipelining as described in // http://redis.io/topics/pipelining. It's safe for concurrent use // by multiple goroutines. type Pipeline struct { statefulCmdable exec pipelineExecer mu sync.Mutex cmds []Cmder closed bool } func (c *Pipeline) Process(cmd Cmder) error { c.mu.Lock() c.cmds = append(c.cmds, cmd) c.mu.Unlock() return nil } // Close closes the pipeline, releasing any open resources. func (c *Pipeline) Close() error { c.mu.Lock() c.discard() c.closed = true c.mu.Unlock() return nil } // Discard resets the pipeline and discards queued commands. func (c *Pipeline) Discard() error { c.mu.Lock() err := c.discard() c.mu.Unlock() return err } func (c *Pipeline) discard() error { if c.closed { return pool.ErrClosed } c.cmds = c.cmds[:0] return nil } // Exec executes all previously queued commands using one // client-server roundtrip. // // Exec always returns list of commands and error of the first failed // command if any. func (c *Pipeline) Exec() ([]Cmder, error) { c.mu.Lock() defer c.mu.Unlock() if c.closed { return nil, pool.ErrClosed } if len(c.cmds) == 0 { return nil, nil } cmds := c.cmds c.cmds = nil return cmds, c.exec(cmds) } func (c *Pipeline) pipelined(fn func(Pipeliner) error) ([]Cmder, error) { if err := fn(c); err != nil { return nil, err } cmds, err := c.Exec() _ = c.Close() return cmds, err } func (c *Pipeline) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.pipelined(fn) } func (c *Pipeline) Pipeline() Pipeliner { return c } func (c *Pipeline) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.pipelined(fn) } func (c *Pipeline) TxPipeline() Pipeliner { return c } redis-6.9.2/pipeline_test.go000066400000000000000000000032541324526341500160420ustar00rootroot00000000000000package redis_test import ( "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("pipelining", func() { var client *redis.Client var pipe *redis.Pipeline BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("supports block style", func() { var get *redis.StringCmd cmds, err := client.Pipelined(func(pipe redis.Pipeliner) error { get = pipe.Get("foo") return nil }) Expect(err).To(Equal(redis.Nil)) Expect(cmds).To(HaveLen(1)) Expect(cmds[0]).To(Equal(get)) Expect(get.Err()).To(Equal(redis.Nil)) Expect(get.Val()).To(Equal("")) }) assertPipeline := func() { It("returns no errors when there are no commands", func() { _, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) }) It("discards queued commands", func() { pipe.Get("key") pipe.Discard() cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(BeNil()) }) It("handles val/err", func() { err := client.Set("key", "value", 0).Err() Expect(err).NotTo(HaveOccurred()) get := pipe.Get("key") cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(1)) val, err := get.Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("value")) }) } Describe("Pipeline", func() { BeforeEach(func() { pipe = client.Pipeline().(*redis.Pipeline) }) assertPipeline() }) Describe("TxPipeline", func() { BeforeEach(func() { pipe = client.TxPipeline().(*redis.Pipeline) }) assertPipeline() }) }) redis-6.9.2/pool_test.go000066400000000000000000000066201324526341500152060ustar00rootroot00000000000000package redis_test import ( "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("pool", 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("respects max size", func() { perform(1000, func(id int) { val, err := client.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("PONG")) }) pool := client.Pool() Expect(pool.Len()).To(BeNumerically("<=", 10)) Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) Expect(pool.Len()).To(Equal(pool.FreeLen())) }) It("respects max size on multi", func() { perform(1000, func(id int) { var ping *redis.StatusCmd err := client.Watch(func(tx *redis.Tx) error { cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { ping = pipe.Ping() return nil }) Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(1)) return err }) Expect(err).NotTo(HaveOccurred()) Expect(ping.Err()).NotTo(HaveOccurred()) Expect(ping.Val()).To(Equal("PONG")) }) pool := client.Pool() Expect(pool.Len()).To(BeNumerically("<=", 10)) Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) Expect(pool.Len()).To(Equal(pool.FreeLen())) }) It("respects max size on pipelines", func() { perform(1000, func(id int) { pipe := client.Pipeline() ping := pipe.Ping() cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(1)) Expect(ping.Err()).NotTo(HaveOccurred()) Expect(ping.Val()).To(Equal("PONG")) Expect(pipe.Close()).NotTo(HaveOccurred()) }) pool := client.Pool() Expect(pool.Len()).To(BeNumerically("<=", 10)) Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) Expect(pool.Len()).To(Equal(pool.FreeLen())) }) It("removes broken connections", func() { cn, _, err := client.Pool().Get() Expect(err).NotTo(HaveOccurred()) cn.SetNetConn(&badConn{}) Expect(client.Pool().Put(cn)).NotTo(HaveOccurred()) err = client.Ping().Err() Expect(err).To(MatchError("bad connection")) val, err := client.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("PONG")) pool := client.Pool() Expect(pool.Len()).To(Equal(1)) Expect(pool.FreeLen()).To(Equal(1)) stats := pool.Stats() Expect(stats.Hits).To(Equal(uint32(2))) Expect(stats.Misses).To(Equal(uint32(2))) Expect(stats.Timeouts).To(Equal(uint32(0))) }) It("reuses connections", func() { for i := 0; i < 100; i++ { val, err := client.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("PONG")) } pool := client.Pool() Expect(pool.Len()).To(Equal(1)) Expect(pool.FreeLen()).To(Equal(1)) stats := pool.Stats() Expect(stats.Hits).To(Equal(uint32(100))) Expect(stats.Misses).To(Equal(uint32(1))) Expect(stats.Timeouts).To(Equal(uint32(0))) }) It("removes idle connections", func() { stats := client.PoolStats() Expect(stats).To(Equal(&redis.PoolStats{ Hits: 0, Misses: 1, Timeouts: 0, TotalConns: 1, FreeConns: 1, StaleConns: 0, })) time.Sleep(2 * time.Second) stats = client.PoolStats() Expect(stats).To(Equal(&redis.PoolStats{ Hits: 0, Misses: 1, Timeouts: 0, TotalConns: 0, FreeConns: 0, StaleConns: 1, })) }) }) redis-6.9.2/pubsub.go000066400000000000000000000210671324526341500145000ustar00rootroot00000000000000package redis import ( "fmt" "net" "sync" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/pool" ) // PubSub implements Pub/Sub commands as described in // http://redis.io/topics/pubsub. It's NOT safe for concurrent use by // multiple goroutines. // // PubSub automatically resubscribes to the channels and patterns // when Redis becomes unavailable. type PubSub struct { opt *Options newConn func([]string) (*pool.Conn, error) closeConn func(*pool.Conn) error mu sync.Mutex cn *pool.Conn channels []string patterns []string closed bool cmd *Cmd chOnce sync.Once ch chan *Message } func (c *PubSub) conn() (*pool.Conn, error) { c.mu.Lock() cn, err := c._conn(nil) c.mu.Unlock() return cn, err } func (c *PubSub) _conn(channels []string) (*pool.Conn, error) { if c.closed { return nil, pool.ErrClosed } if c.cn != nil { return c.cn, nil } cn, err := c.newConn(channels) if err != nil { return nil, err } if err := c.resubscribe(cn); err != nil { _ = c.closeConn(cn) return nil, err } c.cn = cn return cn, nil } func (c *PubSub) resubscribe(cn *pool.Conn) error { var firstErr error if len(c.channels) > 0 { if err := c._subscribe(cn, "subscribe", c.channels...); err != nil && firstErr == nil { firstErr = err } } if len(c.patterns) > 0 { if err := c._subscribe(cn, "psubscribe", c.patterns...); err != nil && firstErr == nil { firstErr = err } } return firstErr } func (c *PubSub) _subscribe(cn *pool.Conn, redisCmd string, channels ...string) error { args := make([]interface{}, 1+len(channels)) args[0] = redisCmd for i, channel := range channels { args[1+i] = channel } cmd := NewSliceCmd(args...) cn.SetWriteTimeout(c.opt.WriteTimeout) return writeCmd(cn, cmd) } func (c *PubSub) releaseConn(cn *pool.Conn, err error) { c.mu.Lock() c._releaseConn(cn, err) c.mu.Unlock() } func (c *PubSub) _releaseConn(cn *pool.Conn, err error) { if c.cn != cn { return } if internal.IsBadConn(err, true) { _ = c.closeTheCn() } } func (c *PubSub) closeTheCn() error { err := c.closeConn(c.cn) c.cn = nil return err } func (c *PubSub) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.closed { return pool.ErrClosed } c.closed = true if c.cn != nil { return c.closeTheCn() } return nil } // Subscribe the client to the specified channels. It returns // empty subscription if there are no channels. func (c *PubSub) Subscribe(channels ...string) error { c.mu.Lock() err := c.subscribe("subscribe", channels...) c.channels = appendIfNotExists(c.channels, channels...) c.mu.Unlock() return err } // PSubscribe the client to the given patterns. It returns // empty subscription if there are no patterns. func (c *PubSub) PSubscribe(patterns ...string) error { c.mu.Lock() err := c.subscribe("psubscribe", patterns...) c.patterns = appendIfNotExists(c.patterns, patterns...) c.mu.Unlock() return err } // Unsubscribe the client from the given channels, or from all of // them if none is given. func (c *PubSub) Unsubscribe(channels ...string) error { c.mu.Lock() err := c.subscribe("unsubscribe", channels...) c.channels = remove(c.channels, channels...) c.mu.Unlock() return err } // PUnsubscribe the client from the given patterns, or from all of // them if none is given. func (c *PubSub) PUnsubscribe(patterns ...string) error { c.mu.Lock() err := c.subscribe("punsubscribe", patterns...) c.patterns = remove(c.patterns, patterns...) c.mu.Unlock() return err } func (c *PubSub) subscribe(redisCmd string, channels ...string) error { cn, err := c._conn(channels) if err != nil { return err } err = c._subscribe(cn, redisCmd, channels...) c._releaseConn(cn, err) return err } func (c *PubSub) Ping(payload ...string) error { args := []interface{}{"ping"} if len(payload) == 1 { args = append(args, payload[0]) } cmd := NewCmd(args...) cn, err := c.conn() if err != nil { return err } cn.SetWriteTimeout(c.opt.WriteTimeout) err = writeCmd(cn, cmd) c.releaseConn(cn, err) return err } // Subscription received after a successful subscription to channel. type Subscription struct { // Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe". Kind string // Channel name we have subscribed to. Channel string // Number of channels we are currently subscribed to. Count int } func (m *Subscription) String() string { return fmt.Sprintf("%s: %s", m.Kind, m.Channel) } // Message received as result of a PUBLISH command issued by another client. type Message struct { Channel string Pattern string Payload string } func (m *Message) String() string { return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload) } // Pong received as result of a PING command issued by another client. type Pong struct { Payload string } func (p *Pong) String() string { if p.Payload != "" { return fmt.Sprintf("Pong<%s>", p.Payload) } return "Pong" } func (c *PubSub) newMessage(reply interface{}) (interface{}, error) { switch reply := reply.(type) { case string: return &Pong{ Payload: reply, }, nil case []interface{}: switch kind := reply[0].(string); kind { case "subscribe", "unsubscribe", "psubscribe", "punsubscribe": return &Subscription{ Kind: kind, Channel: reply[1].(string), Count: int(reply[2].(int64)), }, nil case "message": return &Message{ Channel: reply[1].(string), Payload: reply[2].(string), }, nil case "pmessage": return &Message{ Pattern: reply[1].(string), Channel: reply[2].(string), Payload: reply[3].(string), }, nil case "pong": return &Pong{ Payload: reply[1].(string), }, nil default: return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind) } default: return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply) } } // ReceiveTimeout acts like Receive but returns an error if message // is not received in time. This is low-level API and most clients // should use ReceiveMessage. func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) { if c.cmd == nil { c.cmd = NewCmd() } cn, err := c.conn() if err != nil { return nil, err } cn.SetReadTimeout(timeout) err = c.cmd.readReply(cn) c.releaseConn(cn, err) if err != nil { return nil, err } return c.newMessage(c.cmd.Val()) } // Receive returns a message as a Subscription, Message, Pong or error. // See PubSub example for details. This is low-level API and most clients // should use ReceiveMessage. func (c *PubSub) Receive() (interface{}, error) { return c.ReceiveTimeout(0) } // ReceiveMessage returns a Message or error ignoring Subscription or Pong // messages. It automatically reconnects to Redis Server and resubscribes // to channels in case of network errors. func (c *PubSub) ReceiveMessage() (*Message, error) { return c.receiveMessage(5 * time.Second) } func (c *PubSub) receiveMessage(timeout time.Duration) (*Message, error) { var errNum uint for { msgi, err := c.ReceiveTimeout(timeout) if err != nil { if !internal.IsNetworkError(err) { return nil, err } errNum++ if errNum < 3 { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { err := c.Ping() if err != nil { internal.Logf("PubSub.Ping failed: %s", err) } } } else { // 3 consequent errors - connection is broken or // Redis Server is down. // Sleep to not exceed max number of open connections. time.Sleep(time.Second) } continue } // Reset error number, because we received a message. errNum = 0 switch msg := msgi.(type) { case *Subscription: // Ignore. case *Pong: // Ignore. case *Message: return msg, nil default: return nil, fmt.Errorf("redis: unknown message: %T", msgi) } } } // Channel returns a Go channel for concurrently receiving messages. // The channel is closed with PubSub. Receive or ReceiveMessage APIs // can not be used after channel is created. func (c *PubSub) Channel() <-chan *Message { c.chOnce.Do(func() { c.ch = make(chan *Message, 100) go func() { for { msg, err := c.ReceiveMessage() if err != nil { if err == pool.ErrClosed { break } continue } c.ch <- msg } close(c.ch) }() }) return c.ch } 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.9.2/pubsub_test.go000066400000000000000000000260621324526341500155370ustar00rootroot00000000000000package redis_test import ( "io" "net" "sync" "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("PubSub", 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("should support pattern matching", func() { pubsub := client.PSubscribe("mychannel*") defer pubsub.Close() { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("psubscribe")) Expect(subscr.Channel).To(Equal("mychannel*")) Expect(subscr.Count).To(Equal(1)) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err.(net.Error).Timeout()).To(Equal(true)) Expect(msgi).To(BeNil()) } n, err := client.Publish("mychannel1", "hello").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) Expect(pubsub.PUnsubscribe("mychannel*")).NotTo(HaveOccurred()) { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Message) Expect(subscr.Channel).To(Equal("mychannel1")) Expect(subscr.Pattern).To(Equal("mychannel*")) Expect(subscr.Payload).To(Equal("hello")) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("punsubscribe")) Expect(subscr.Channel).To(Equal("mychannel*")) Expect(subscr.Count).To(Equal(0)) } stats := client.PoolStats() Expect(stats.Misses).To(Equal(uint32(2))) }) It("should pub/sub channels", func() { channels, err := client.PubSubChannels("mychannel*").Result() Expect(err).NotTo(HaveOccurred()) Expect(channels).To(BeEmpty()) pubsub := client.Subscribe("mychannel", "mychannel2") defer pubsub.Close() channels, err = client.PubSubChannels("mychannel*").Result() Expect(err).NotTo(HaveOccurred()) Expect(channels).To(ConsistOf([]string{"mychannel", "mychannel2"})) channels, err = client.PubSubChannels("").Result() Expect(err).NotTo(HaveOccurred()) Expect(channels).To(BeEmpty()) channels, err = client.PubSubChannels("*").Result() Expect(err).NotTo(HaveOccurred()) Expect(len(channels)).To(BeNumerically(">=", 2)) }) It("should return the numbers of subscribers", func() { pubsub := client.Subscribe("mychannel", "mychannel2") defer pubsub.Close() channels, err := client.PubSubNumSub("mychannel", "mychannel2", "mychannel3").Result() Expect(err).NotTo(HaveOccurred()) Expect(channels).To(Equal(map[string]int64{ "mychannel": 1, "mychannel2": 1, "mychannel3": 0, })) }) It("should return the numbers of subscribers by pattern", func() { num, err := client.PubSubNumPat().Result() Expect(err).NotTo(HaveOccurred()) Expect(num).To(Equal(int64(0))) pubsub := client.PSubscribe("*") defer pubsub.Close() num, err = client.PubSubNumPat().Result() Expect(err).NotTo(HaveOccurred()) Expect(num).To(Equal(int64(1))) }) It("should pub/sub", func() { pubsub := client.Subscribe("mychannel", "mychannel2") defer pubsub.Close() { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("subscribe")) Expect(subscr.Channel).To(Equal("mychannel")) Expect(subscr.Count).To(Equal(1)) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("subscribe")) Expect(subscr.Channel).To(Equal("mychannel2")) Expect(subscr.Count).To(Equal(2)) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err.(net.Error).Timeout()).To(Equal(true)) Expect(msgi).NotTo(HaveOccurred()) } n, err := client.Publish("mychannel", "hello").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) n, err = client.Publish("mychannel2", "hello2").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) Expect(pubsub.Unsubscribe("mychannel", "mychannel2")).NotTo(HaveOccurred()) { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) msg := msgi.(*redis.Message) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("hello")) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) msg := msgi.(*redis.Message) Expect(msg.Channel).To(Equal("mychannel2")) Expect(msg.Payload).To(Equal("hello2")) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("unsubscribe")) Expect(subscr.Channel).To(Equal("mychannel")) Expect(subscr.Count).To(Equal(1)) } { msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) subscr := msgi.(*redis.Subscription) Expect(subscr.Kind).To(Equal("unsubscribe")) Expect(subscr.Channel).To(Equal("mychannel2")) Expect(subscr.Count).To(Equal(0)) } stats := client.PoolStats() Expect(stats.Misses).To(Equal(uint32(2))) }) It("should ping/pong", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() _, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) err = pubsub.Ping("") Expect(err).NotTo(HaveOccurred()) msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) pong := msgi.(*redis.Pong) Expect(pong.Payload).To(Equal("")) }) It("should ping/pong with payload", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() _, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) err = pubsub.Ping("hello") Expect(err).NotTo(HaveOccurred()) msgi, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) pong := msgi.(*redis.Pong) Expect(pong.Payload).To(Equal("hello")) }) It("should multi-ReceiveMessage", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() subscr, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) Expect(subscr).To(Equal(&redis.Subscription{ Kind: "subscribe", Channel: "mychannel", Count: 1, })) err = client.Publish("mychannel", "hello").Err() Expect(err).NotTo(HaveOccurred()) err = client.Publish("mychannel", "world").Err() Expect(err).NotTo(HaveOccurred()) msg, err := pubsub.ReceiveMessage() Expect(err).NotTo(HaveOccurred()) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("hello")) msg, err = pubsub.ReceiveMessage() Expect(err).NotTo(HaveOccurred()) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("world")) }) It("should ReceiveMessage after timeout", func() { timeout := 100 * time.Millisecond pubsub := client.Subscribe("mychannel") defer pubsub.Close() subscr, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) Expect(subscr).To(Equal(&redis.Subscription{ Kind: "subscribe", Channel: "mychannel", Count: 1, })) done := make(chan bool, 1) go func() { defer GinkgoRecover() defer func() { done <- true }() time.Sleep(timeout + 100*time.Millisecond) n, err := client.Publish("mychannel", "hello").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) }() msg, err := pubsub.ReceiveMessageTimeout(timeout) Expect(err).NotTo(HaveOccurred()) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("hello")) Eventually(done).Should(Receive()) stats := client.PoolStats() Expect(stats.Hits).To(Equal(uint32(1))) Expect(stats.Misses).To(Equal(uint32(1))) }) It("returns an error when subscribe fails", func() { pubsub := client.Subscribe() defer pubsub.Close() pubsub.SetNetConn(&badConn{ readErr: io.EOF, writeErr: io.EOF, }) err := pubsub.Subscribe("mychannel") Expect(err).To(MatchError("EOF")) err = pubsub.Subscribe("mychannel") Expect(err).NotTo(HaveOccurred()) }) expectReceiveMessageOnError := func(pubsub *redis.PubSub) { pubsub.SetNetConn(&badConn{ readErr: io.EOF, writeErr: io.EOF, }) done := make(chan bool, 1) go func() { defer GinkgoRecover() defer func() { done <- true }() time.Sleep(100 * time.Millisecond) err := client.Publish("mychannel", "hello").Err() Expect(err).NotTo(HaveOccurred()) }() msg, err := pubsub.ReceiveMessage() Expect(err).NotTo(HaveOccurred()) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("hello")) Eventually(done).Should(Receive()) } It("Subscribe should reconnect on ReceiveMessage error", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() subscr, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) Expect(subscr).To(Equal(&redis.Subscription{ Kind: "subscribe", Channel: "mychannel", Count: 1, })) expectReceiveMessageOnError(pubsub) }) It("PSubscribe should reconnect on ReceiveMessage error", func() { pubsub := client.PSubscribe("mychannel") defer pubsub.Close() subscr, err := pubsub.ReceiveTimeout(time.Second) Expect(err).NotTo(HaveOccurred()) Expect(subscr).To(Equal(&redis.Subscription{ Kind: "psubscribe", Channel: "mychannel", Count: 1, })) expectReceiveMessageOnError(pubsub) }) It("should return on Close", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() var wg sync.WaitGroup wg.Add(1) go func() { defer GinkgoRecover() wg.Done() defer wg.Done() _, err := pubsub.ReceiveMessage() Expect(err).To(HaveOccurred()) Expect(err).To(SatisfyAny( MatchError("redis: client is closed"), MatchError("use of closed network connection"), // Go 1.4 )) }() wg.Wait() wg.Add(1) Expect(pubsub.Close()).NotTo(HaveOccurred()) wg.Wait() }) It("should ReceiveMessage without a subscription", func() { timeout := 100 * time.Millisecond pubsub := client.Subscribe() defer pubsub.Close() var wg sync.WaitGroup wg.Add(1) go func() { defer GinkgoRecover() defer wg.Done() time.Sleep(2 * timeout) err := pubsub.Subscribe("mychannel") Expect(err).NotTo(HaveOccurred()) time.Sleep(timeout) err = client.Publish("mychannel", "hello").Err() Expect(err).NotTo(HaveOccurred()) }() msg, err := pubsub.ReceiveMessageTimeout(timeout) Expect(err).NotTo(HaveOccurred()) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal("hello")) wg.Wait() }) It("handles big message payload", func() { pubsub := client.Subscribe("mychannel") defer pubsub.Close() ch := pubsub.Channel() bigVal := bigVal() err := client.Publish("mychannel", bigVal).Err() Expect(err).NotTo(HaveOccurred()) var msg *redis.Message Eventually(ch).Should(Receive(&msg)) Expect(msg.Channel).To(Equal("mychannel")) Expect(msg.Payload).To(Equal(string(bigVal))) }) }) redis-6.9.2/race_test.go000066400000000000000000000132761324526341500151540ustar00rootroot00000000000000package redis_test import ( "bytes" "fmt" "net" "strconv" "testing" "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("races", func() { var client *redis.Client var C, N int BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).To(BeNil()) C, N = 10, 1000 if testing.Short() { C = 4 N = 100 } }) AfterEach(func() { err := client.Close() Expect(err).NotTo(HaveOccurred()) }) It("should echo", func() { perform(C, func(id int) { for i := 0; i < N; i++ { msg := fmt.Sprintf("echo %d %d", id, i) echo, err := client.Echo(msg).Result() Expect(err).NotTo(HaveOccurred()) Expect(echo).To(Equal(msg)) } }) }) It("should incr", func() { key := "TestIncrFromGoroutines" perform(C, func(id int) { for i := 0; i < N; i++ { err := client.Incr(key).Err() Expect(err).NotTo(HaveOccurred()) } }) val, err := client.Get(key).Int64() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal(int64(C * N))) }) It("should handle many keys", func() { perform(C, func(id int) { for i := 0; i < N; i++ { err := client.Set( fmt.Sprintf("keys.key-%d-%d", id, i), fmt.Sprintf("hello-%d-%d", id, i), 0, ).Err() Expect(err).NotTo(HaveOccurred()) } }) keys := client.Keys("keys.*") Expect(keys.Err()).NotTo(HaveOccurred()) Expect(len(keys.Val())).To(Equal(C * N)) }) It("should handle many keys 2", func() { perform(C, func(id int) { keys := []string{"non-existent-key"} for i := 0; i < N; i++ { key := fmt.Sprintf("keys.key-%d", i) keys = append(keys, key) err := client.Set(key, fmt.Sprintf("hello-%d", i), 0).Err() Expect(err).NotTo(HaveOccurred()) } keys = append(keys, "non-existent-key") vals, err := client.MGet(keys...).Result() Expect(err).NotTo(HaveOccurred()) Expect(len(vals)).To(Equal(N + 2)) for i := 0; i < N; i++ { Expect(vals[i+1]).To(Equal(fmt.Sprintf("hello-%d", i))) } Expect(vals[0]).To(BeNil()) Expect(vals[N+1]).To(BeNil()) }) }) It("should handle big vals in Get", func() { C, N = 4, 100 bigVal := bigVal() err := client.Set("key", bigVal, 0).Err() Expect(err).NotTo(HaveOccurred()) // Reconnect to get new connection. Expect(client.Close()).To(BeNil()) client = redis.NewClient(redisOptions()) perform(C, func(id int) { for i := 0; i < N; i++ { got, err := client.Get("key").Bytes() Expect(err).NotTo(HaveOccurred()) Expect(got).To(Equal(bigVal)) } }) }) It("should handle big vals in Set", func() { C, N = 4, 100 bigVal := bigVal() perform(C, func(id int) { for i := 0; i < N; i++ { err := client.Set("key", bigVal, 0).Err() Expect(err).NotTo(HaveOccurred()) } }) }) It("should select db", func() { err := client.Set("db", 1, 0).Err() Expect(err).NotTo(HaveOccurred()) perform(C, func(id int) { opt := redisOptions() opt.DB = id client := redis.NewClient(opt) for i := 0; i < N; i++ { err := client.Set("db", id, 0).Err() Expect(err).NotTo(HaveOccurred()) n, err := client.Get("db").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(id))) } err := client.Close() Expect(err).NotTo(HaveOccurred()) }) n, err := client.Get("db").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(1))) }) It("should select DB with read timeout", func() { perform(C, func(id int) { opt := redisOptions() opt.DB = id opt.ReadTimeout = time.Nanosecond client := redis.NewClient(opt) perform(C, func(id int) { err := client.Ping().Err() Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) err := client.Close() Expect(err).NotTo(HaveOccurred()) }) }) It("should Watch/Unwatch", func() { err := client.Set("key", "0", 0).Err() Expect(err).NotTo(HaveOccurred()) perform(C, func(id int) { for i := 0; i < N; i++ { err := client.Watch(func(tx *redis.Tx) error { val, err := tx.Get("key").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).NotTo(Equal(redis.Nil)) num, err := strconv.ParseInt(val, 10, 64) Expect(err).NotTo(HaveOccurred()) cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Set("key", strconv.FormatInt(num+1, 10), 0) return nil }) Expect(cmds).To(HaveLen(1)) return err }, "key") if err == redis.TxFailedErr { i-- continue } Expect(err).NotTo(HaveOccurred()) } }) val, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal(int64(C * N))) }) It("should Pipeline", func() { perform(C, func(id int) { pipe := client.Pipeline() for i := 0; i < N; i++ { pipe.Echo(fmt.Sprint(i)) } cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(N)) for i := 0; i < N; i++ { Expect(cmds[i].(*redis.StringCmd).Val()).To(Equal(fmt.Sprint(i))) } }) }) It("should Pipeline", func() { pipe := client.Pipeline() perform(N, func(id int) { pipe.Incr("key") }) cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(N)) n, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(N))) }) It("should TxPipeline", func() { pipe := client.TxPipeline() perform(N, func(id int) { pipe.Incr("key") }) cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(N)) n, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(N))) }) }) func bigVal() []byte { return bytes.Repeat([]byte{'*'}, 1<<17) // 128kb } redis-6.9.2/redis.go000066400000000000000000000244341324526341500143070ustar00rootroot00000000000000package redis import ( "fmt" "log" "os" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/pool" "github.com/go-redis/redis/internal/proto" ) // Nil reply redis returned when key does not exist. const Nil = proto.Nil func init() { SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)) } func SetLogger(logger *log.Logger) { internal.Logger = logger } func (c *baseClient) init() { c.process = c.defaultProcess c.processPipeline = c.defaultProcessPipeline c.processTxPipeline = c.defaultProcessTxPipeline } func (c *baseClient) String() string { return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB) } func (c *baseClient) newConn() (*pool.Conn, error) { cn, err := c.connPool.NewConn() if err != nil { return nil, err } if !cn.Inited { if err := c.initConn(cn); err != nil { _ = c.connPool.CloseConn(cn) return nil, err } } return cn, nil } func (c *baseClient) getConn() (*pool.Conn, bool, error) { cn, isNew, err := c.connPool.Get() if err != nil { return nil, false, err } if !cn.Inited { if err := c.initConn(cn); err != nil { _ = c.connPool.Remove(cn) return nil, false, err } } return cn, isNew, nil } func (c *baseClient) releaseConn(cn *pool.Conn, err error) bool { if internal.IsBadConn(err, false) { _ = c.connPool.Remove(cn) return false } _ = c.connPool.Put(cn) return true } func (c *baseClient) initConn(cn *pool.Conn) error { cn.Inited = true if c.opt.Password == "" && c.opt.DB == 0 && !c.opt.readOnly && c.opt.OnConnect == nil { return nil } // Temp client to initialize connection. conn := &Conn{ baseClient: baseClient{ opt: c.opt, connPool: pool.NewSingleConnPool(cn), }, } conn.baseClient.init() conn.statefulCmdable.setProcessor(conn.Process) _, err := conn.Pipelined(func(pipe Pipeliner) error { if c.opt.Password != "" { pipe.Auth(c.opt.Password) } if c.opt.DB > 0 { pipe.Select(c.opt.DB) } if c.opt.readOnly { pipe.ReadOnly() } return nil }) if err != nil { return err } if c.opt.OnConnect != nil { return c.opt.OnConnect(conn) } return nil } // WrapProcess replaces the process func. It takes a function createWrapper // which is supplied by the user. createWrapper takes the old process func as // an input and returns the new wrapper process func. createWrapper should // use call the old process func within the new process func. func (c *baseClient) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) { c.process = fn(c.process) } func (c *baseClient) Process(cmd Cmder) error { return c.process(cmd) } func (c *baseClient) defaultProcess(cmd Cmder) error { for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } cn, _, err := c.getConn() if err != nil { cmd.setErr(err) if internal.IsRetryableError(err, true) { continue } return err } cn.SetWriteTimeout(c.opt.WriteTimeout) if err := writeCmd(cn, cmd); err != nil { c.releaseConn(cn, err) cmd.setErr(err) if internal.IsRetryableError(err, true) { continue } return err } cn.SetReadTimeout(c.cmdTimeout(cmd)) err = cmd.readReply(cn) c.releaseConn(cn, err) if err != nil && internal.IsRetryableError(err, cmd.readTimeout() == nil) { continue } return err } return cmd.Err() } func (c *baseClient) retryBackoff(attempt int) time.Duration { return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) } func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration { if timeout := cmd.readTimeout(); timeout != nil { return *timeout } return c.opt.ReadTimeout } // Close closes the client, releasing any open resources. // // It is rare to Close a Client, as the Client is meant to be // long-lived and shared between many goroutines. func (c *baseClient) Close() error { var firstErr error if c.onClose != nil { if err := c.onClose(); err != nil && firstErr == nil { firstErr = err } } if err := c.connPool.Close(); err != nil && firstErr == nil { firstErr = err } return firstErr } func (c *baseClient) getAddr() string { return c.opt.Addr } func (c *baseClient) WrapProcessPipeline( fn func(oldProcess func([]Cmder) error) func([]Cmder) error, ) { c.processPipeline = fn(c.processPipeline) c.processTxPipeline = fn(c.processTxPipeline) } func (c *baseClient) defaultProcessPipeline(cmds []Cmder) error { return c.generalProcessPipeline(cmds, c.pipelineProcessCmds) } func (c *baseClient) defaultProcessTxPipeline(cmds []Cmder) error { return c.generalProcessPipeline(cmds, c.txPipelineProcessCmds) } type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error) func (c *baseClient) generalProcessPipeline(cmds []Cmder, p pipelineProcessor) error { for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } cn, _, err := c.getConn() if err != nil { setCmdsErr(cmds, err) return err } canRetry, err := p(cn, cmds) if err == nil || internal.IsRedisError(err) { _ = c.connPool.Put(cn) break } _ = c.connPool.Remove(cn) if !canRetry || !internal.IsRetryableError(err, true) { break } } return firstCmdsErr(cmds) } func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { cn.SetWriteTimeout(c.opt.WriteTimeout) if err := writeCmd(cn, cmds...); err != nil { setCmdsErr(cmds, err) return true, err } // Set read timeout for all commands. cn.SetReadTimeout(c.opt.ReadTimeout) return true, pipelineReadCmds(cn, cmds) } func pipelineReadCmds(cn *pool.Conn, cmds []Cmder) error { for _, cmd := range cmds { err := cmd.readReply(cn) if err != nil && !internal.IsRedisError(err) { return err } } return nil } func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { cn.SetWriteTimeout(c.opt.WriteTimeout) if err := txPipelineWriteMulti(cn, cmds); err != nil { setCmdsErr(cmds, err) return true, err } // Set read timeout for all commands. cn.SetReadTimeout(c.opt.ReadTimeout) if err := c.txPipelineReadQueued(cn, cmds); err != nil { setCmdsErr(cmds, err) return false, err } return false, pipelineReadCmds(cn, cmds) } func txPipelineWriteMulti(cn *pool.Conn, cmds []Cmder) error { multiExec := make([]Cmder, 0, len(cmds)+2) multiExec = append(multiExec, NewStatusCmd("MULTI")) multiExec = append(multiExec, cmds...) multiExec = append(multiExec, NewSliceCmd("EXEC")) return writeCmd(cn, multiExec...) } func (c *baseClient) txPipelineReadQueued(cn *pool.Conn, cmds []Cmder) error { // Parse queued replies. var statusCmd StatusCmd if err := statusCmd.readReply(cn); err != nil { return err } for _ = range cmds { err := statusCmd.readReply(cn) if err != nil && !internal.IsRedisError(err) { return err } } // Parse number of replies. line, err := cn.Rd.ReadLine() if err != nil { if err == Nil { err = TxFailedErr } return err } switch line[0] { case proto.ErrorReply: return proto.ParseErrorReply(line) case proto.ArrayReply: // ok default: err := fmt.Errorf("redis: expected '*', but got line %q", line) return err } return nil } //------------------------------------------------------------------------------ // Client is a Redis client representing a pool of zero or more // underlying connections. It's safe for concurrent use by multiple // goroutines. type Client struct { baseClient cmdable } func newClient(opt *Options, pool pool.Pooler) *Client { c := Client{ baseClient: baseClient{ opt: opt, connPool: pool, }, } c.baseClient.init() c.cmdable.setProcessor(c.Process) return &c } // NewClient returns a client to the Redis Server specified by Options. func NewClient(opt *Options) *Client { opt.init() return newClient(opt, newConnPool(opt)) } func (c *Client) copy() *Client { c2 := new(Client) *c2 = *c c2.cmdable.setProcessor(c2.Process) return c2 } // Options returns read-only Options that were used to create the client. func (c *Client) Options() *Options { return c.opt } type PoolStats pool.Stats // PoolStats returns connection pool stats. func (c *Client) PoolStats() *PoolStats { stats := c.connPool.Stats() return (*PoolStats)(stats) } func (c *Client) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *Client) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } func (c *Client) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.TxPipeline().Pipelined(fn) } // TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. func (c *Client) TxPipeline() Pipeliner { pipe := Pipeline{ exec: c.processTxPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } func (c *Client) pubSub() *PubSub { return &PubSub{ opt: c.opt, newConn: func(channels []string) (*pool.Conn, error) { return c.newConn() }, closeConn: c.connPool.CloseConn, } } // Subscribe subscribes the client to the specified channels. // Channels can be omitted to create empty subscription. func (c *Client) Subscribe(channels ...string) *PubSub { pubsub := c.pubSub() 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 *Client) PSubscribe(channels ...string) *PubSub { pubsub := c.pubSub() if len(channels) > 0 { _ = pubsub.PSubscribe(channels...) } return pubsub } //------------------------------------------------------------------------------ // Conn is like Client, but its pool contains single connection. type Conn struct { baseClient statefulCmdable } func (c *Conn) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *Conn) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } func (c *Conn) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.TxPipeline().Pipelined(fn) } // TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. func (c *Conn) TxPipeline() Pipeliner { pipe := Pipeline{ exec: c.processTxPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } redis-6.9.2/redis_context.go000066400000000000000000000011621324526341500160440ustar00rootroot00000000000000// +build go1.7 package redis import ( "context" "github.com/go-redis/redis/internal/pool" ) type baseClient struct { connPool pool.Pooler opt *Options process func(Cmder) error processPipeline func([]Cmder) error processTxPipeline func([]Cmder) error onClose func() error // hook called when client is closed ctx context.Context } func (c *Client) Context() context.Context { if c.ctx != nil { return c.ctx } return context.Background() } func (c *Client) WithContext(ctx context.Context) *Client { if ctx == nil { panic("nil context") } c2 := c.copy() c2.ctx = ctx return c2 } redis-6.9.2/redis_no_context.go000066400000000000000000000005141324526341500165400ustar00rootroot00000000000000// +build !go1.7 package redis import ( "github.com/go-redis/redis/internal/pool" ) type baseClient struct { connPool pool.Pooler opt *Options process func(Cmder) error processPipeline func([]Cmder) error processTxPipeline func([]Cmder) error onClose func() error // hook called when client is closed } redis-6.9.2/redis_test.go000066400000000000000000000204141324526341500153400ustar00rootroot00000000000000package redis_test import ( "bytes" "net" "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Client", func() { var client *redis.Client BeforeEach(func() { client = redis.NewClient(redisOptions()) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { client.Close() }) It("should Stringer", func() { Expect(client.String()).To(Equal("Redis<:6380 db:15>")) }) It("should ping", func() { val, err := client.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("PONG")) }) It("should return pool stats", func() { Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{})) }) It("should support custom dialers", func() { custom := redis.NewClient(&redis.Options{ Addr: ":1234", Dialer: func() (net.Conn, error) { return net.Dial("tcp", redisAddr) }, }) val, err := custom.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("PONG")) Expect(custom.Close()).NotTo(HaveOccurred()) }) It("should close", func() { Expect(client.Close()).NotTo(HaveOccurred()) err := client.Ping().Err() Expect(err).To(MatchError("redis: client is closed")) }) It("should close pubsub without closing the client", func() { pubsub := client.Subscribe() Expect(pubsub.Close()).NotTo(HaveOccurred()) _, err := pubsub.Receive() Expect(err).To(MatchError("redis: client is closed")) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) It("should close Tx without closing the client", func() { err := client.Watch(func(tx *redis.Tx) error { _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) return err }) Expect(err).NotTo(HaveOccurred()) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) It("should close pipeline without closing the client", func() { pipeline := client.Pipeline() Expect(pipeline.Close()).NotTo(HaveOccurred()) pipeline.Ping() _, err := pipeline.Exec() Expect(err).To(MatchError("redis: client is closed")) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) It("should close pubsub when client is closed", func() { pubsub := client.Subscribe() Expect(client.Close()).NotTo(HaveOccurred()) _, err := pubsub.Receive() Expect(err).To(MatchError("redis: client is closed")) Expect(pubsub.Close()).NotTo(HaveOccurred()) }) It("should close pipeline when client is closed", func() { pipeline := client.Pipeline() Expect(client.Close()).NotTo(HaveOccurred()) Expect(pipeline.Close()).NotTo(HaveOccurred()) }) It("should select DB", func() { db2 := redis.NewClient(&redis.Options{ Addr: redisAddr, DB: 2, }) Expect(db2.FlushDB().Err()).NotTo(HaveOccurred()) Expect(db2.Get("db").Err()).To(Equal(redis.Nil)) Expect(db2.Set("db", 2, 0).Err()).NotTo(HaveOccurred()) n, err := db2.Get("db").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(2))) Expect(client.Get("db").Err()).To(Equal(redis.Nil)) Expect(db2.FlushDB().Err()).NotTo(HaveOccurred()) Expect(db2.Close()).NotTo(HaveOccurred()) }) It("processes custom commands", func() { cmd := redis.NewCmd("PING") client.Process(cmd) // Flush buffers. Expect(client.Echo("hello").Err()).NotTo(HaveOccurred()) Expect(cmd.Err()).NotTo(HaveOccurred()) Expect(cmd.Val()).To(Equal("PONG")) }) It("should retry command on network error", func() { Expect(client.Close()).NotTo(HaveOccurred()) client = redis.NewClient(&redis.Options{ Addr: redisAddr, MaxRetries: 1, }) // Put bad connection in the pool. cn, _, err := client.Pool().Get() Expect(err).NotTo(HaveOccurred()) cn.SetNetConn(&badConn{}) err = client.Pool().Put(cn) Expect(err).NotTo(HaveOccurred()) err = client.Ping().Err() Expect(err).NotTo(HaveOccurred()) }) It("should retry with backoff", func() { clientNoRetry := redis.NewClient(&redis.Options{ Addr: ":1234", MaxRetries: 0, }) defer clientNoRetry.Close() clientRetry := redis.NewClient(&redis.Options{ Addr: ":1234", MaxRetries: 5, MaxRetryBackoff: 128 * time.Millisecond, }) defer clientRetry.Close() startNoRetry := time.Now() err := clientNoRetry.Ping().Err() Expect(err).To(HaveOccurred()) elapseNoRetry := time.Since(startNoRetry) startRetry := time.Now() err = clientRetry.Ping().Err() Expect(err).To(HaveOccurred()) elapseRetry := time.Since(startRetry) Expect(elapseRetry).To(BeNumerically(">", elapseNoRetry, 10*time.Millisecond)) }) It("should update conn.UsedAt on read/write", func() { cn, _, err := client.Pool().Get() Expect(err).NotTo(HaveOccurred()) Expect(cn.UsedAt).NotTo(BeZero()) createdAt := cn.UsedAt() err = client.Pool().Put(cn) Expect(err).NotTo(HaveOccurred()) Expect(cn.UsedAt().Equal(createdAt)).To(BeTrue()) err = client.Ping().Err() Expect(err).NotTo(HaveOccurred()) cn, _, err = client.Pool().Get() Expect(err).NotTo(HaveOccurred()) Expect(cn).NotTo(BeNil()) Expect(cn.UsedAt().After(createdAt)).To(BeTrue()) }) It("should process command with special chars", func() { set := client.Set("key", "hello1\r\nhello2\r\n", 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("hello1\r\nhello2\r\n")) }) It("should handle big vals", func() { bigVal := bytes.Repeat([]byte{'*'}, 2e6) err := client.Set("key", bigVal, 0).Err() Expect(err).NotTo(HaveOccurred()) // Reconnect to get new connection. Expect(client.Close()).NotTo(HaveOccurred()) client = redis.NewClient(redisOptions()) got, err := client.Get("key").Bytes() Expect(err).NotTo(HaveOccurred()) Expect(got).To(Equal(bigVal)) }) It("should call WrapProcess", func() { var wrapperFnCalled bool client.WrapProcess(func(oldProcess func(redis.Cmder) error) func(redis.Cmder) error { return func(cmd redis.Cmder) error { wrapperFnCalled = true return oldProcess(cmd) } }) Expect(client.Ping().Err()).NotTo(HaveOccurred()) Expect(wrapperFnCalled).To(BeTrue()) }) }) var _ = Describe("Client timeout", func() { var opt *redis.Options var client *redis.Client AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) 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("Subscribe timeouts", func() { if opt.WriteTimeout == 0 { return } pubsub := client.Subscribe() defer pubsub.Close() err := pubsub.Subscribe("_") 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() }) 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 }) Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) }) } Context("read timeout", func() { BeforeEach(func() { opt = redisOptions() opt.ReadTimeout = time.Nanosecond opt.WriteTimeout = -1 client = redis.NewClient(opt) }) testTimeout() }) Context("write timeout", func() { BeforeEach(func() { opt = redisOptions() opt.ReadTimeout = -1 opt.WriteTimeout = time.Nanosecond client = redis.NewClient(opt) }) testTimeout() }) }) var _ = Describe("Client OnConnect", func() { var client *redis.Client BeforeEach(func() { opt := redisOptions() opt.DB = 0 opt.OnConnect = func(cn *redis.Conn) error { return cn.ClientSetName("on_connect").Err() } client = redis.NewClient(opt) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("calls OnConnect", func() { name, err := client.ClientGetName().Result() Expect(err).NotTo(HaveOccurred()) Expect(name).To(Equal("on_connect")) }) }) redis-6.9.2/result.go000066400000000000000000000073051324526341500145150ustar00rootroot00000000000000package redis import "time" // NewCmdResult returns a Cmd initalised with val and err for testing func NewCmdResult(val interface{}, err error) *Cmd { var cmd Cmd cmd.val = val cmd.setErr(err) return &cmd } // NewSliceResult returns a SliceCmd initalised with val and err for testing func NewSliceResult(val []interface{}, err error) *SliceCmd { var cmd SliceCmd cmd.val = val cmd.setErr(err) return &cmd } // NewStatusResult returns a StatusCmd initalised with val and err for testing func NewStatusResult(val string, err error) *StatusCmd { var cmd StatusCmd cmd.val = val cmd.setErr(err) return &cmd } // NewIntResult returns an IntCmd initalised with val and err for testing func NewIntResult(val int64, err error) *IntCmd { var cmd IntCmd cmd.val = val cmd.setErr(err) return &cmd } // NewDurationResult returns a DurationCmd initalised with val and err for testing func NewDurationResult(val time.Duration, err error) *DurationCmd { var cmd DurationCmd cmd.val = val cmd.setErr(err) return &cmd } // NewBoolResult returns a BoolCmd initalised with val and err for testing func NewBoolResult(val bool, err error) *BoolCmd { var cmd BoolCmd cmd.val = val cmd.setErr(err) return &cmd } // NewStringResult returns a StringCmd initalised with val and err for testing func NewStringResult(val string, err error) *StringCmd { var cmd StringCmd cmd.val = []byte(val) cmd.setErr(err) return &cmd } // NewFloatResult returns a FloatCmd initalised with val and err for testing func NewFloatResult(val float64, err error) *FloatCmd { var cmd FloatCmd cmd.val = val cmd.setErr(err) return &cmd } // NewStringSliceResult returns a StringSliceCmd initalised with val and err for testing func NewStringSliceResult(val []string, err error) *StringSliceCmd { var cmd StringSliceCmd cmd.val = val cmd.setErr(err) return &cmd } // NewBoolSliceResult returns a BoolSliceCmd initalised with val and err for testing func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd { var cmd BoolSliceCmd cmd.val = val cmd.setErr(err) return &cmd } // NewStringStringMapResult returns a StringStringMapCmd initalised with val and err for testing func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd { var cmd StringStringMapCmd cmd.val = val cmd.setErr(err) return &cmd } // NewStringIntMapCmdResult returns a StringIntMapCmd initalised with val and err for testing func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd { var cmd StringIntMapCmd cmd.val = val cmd.setErr(err) return &cmd } // NewZSliceCmdResult returns a ZSliceCmd initalised with val and err for testing func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd { var cmd ZSliceCmd cmd.val = val cmd.setErr(err) return &cmd } // NewScanCmdResult returns a ScanCmd initalised with val and err for testing func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd { var cmd ScanCmd cmd.page = keys cmd.cursor = cursor cmd.setErr(err) return &cmd } // NewClusterSlotsCmdResult returns a ClusterSlotsCmd initalised with val and err for testing func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd { var cmd ClusterSlotsCmd cmd.val = val cmd.setErr(err) return &cmd } // NewGeoLocationCmdResult returns a GeoLocationCmd initalised with val and err for testing func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd { var cmd GeoLocationCmd cmd.locations = val cmd.setErr(err) return &cmd } // NewCommandsInfoCmdResult returns a CommandsInfoCmd initalised with val and err for testing func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd { var cmd CommandsInfoCmd cmd.val = val cmd.setErr(err) return &cmd } redis-6.9.2/ring.go000066400000000000000000000260001324526341500141270ustar00rootroot00000000000000package redis import ( "errors" "fmt" "math/rand" "strconv" "sync" "sync/atomic" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/consistenthash" "github.com/go-redis/redis/internal/hashtag" "github.com/go-redis/redis/internal/pool" ) var errRingShardsDown = errors.New("redis: all ring shards are down") // RingOptions are used to configure a ring client and should be // passed to NewRing. type RingOptions struct { // Map of name => host:port addresses of ring shards. Addrs map[string]string // Frequency of PING commands sent to check shards availability. // Shard is considered down after 3 subsequent failed checks. HeartbeatFrequency time.Duration // Following options are copied from Options struct. OnConnect func(*Conn) error DB int Password string MaxRetries int MinRetryBackoff time.Duration MaxRetryBackoff time.Duration DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration PoolSize int PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration } func (opt *RingOptions) init() { if opt.HeartbeatFrequency == 0 { opt.HeartbeatFrequency = 500 * time.Millisecond } 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 *RingOptions) clientOptions() *Options { return &Options{ OnConnect: opt.OnConnect, DB: opt.DB, Password: opt.Password, DialTimeout: opt.DialTimeout, ReadTimeout: opt.ReadTimeout, WriteTimeout: opt.WriteTimeout, PoolSize: opt.PoolSize, PoolTimeout: opt.PoolTimeout, IdleTimeout: opt.IdleTimeout, IdleCheckFrequency: opt.IdleCheckFrequency, } } type ringShard struct { Client *Client down int32 } func (shard *ringShard) String() string { var state string if shard.IsUp() { state = "up" } else { state = "down" } return fmt.Sprintf("%s is %s", shard.Client, state) } func (shard *ringShard) IsDown() bool { const threshold = 3 return atomic.LoadInt32(&shard.down) >= threshold } func (shard *ringShard) IsUp() bool { return !shard.IsDown() } // Vote votes to set shard state and returns true if state was changed. func (shard *ringShard) Vote(up bool) bool { if up { changed := shard.IsDown() atomic.StoreInt32(&shard.down, 0) return changed } if shard.IsDown() { return false } atomic.AddInt32(&shard.down, 1) return shard.IsDown() } // Ring is a Redis client that uses constistent hashing to distribute // keys across multiple Redis servers (shards). It's safe for // concurrent use by multiple goroutines. // // Ring monitors the state of each shard and removes dead shards from // the ring. When shard comes online it is added back to the ring. This // gives you maximum availability and partition tolerance, but no // consistency between different shards or even clients. Each client // uses shards that are available to the client and does not do any // coordination when shard state is changed. // // Ring should be used when you need multiple Redis servers for caching // and can tolerate losing data when one of the servers dies. // Otherwise you should use Redis Cluster. type Ring struct { cmdable opt *RingOptions nreplicas int mu sync.RWMutex hash *consistenthash.Map shards map[string]*ringShard shardsList []*ringShard processPipeline func([]Cmder) error cmdsInfoOnce internal.Once cmdsInfo map[string]*CommandInfo closed bool } func NewRing(opt *RingOptions) *Ring { const nreplicas = 100 opt.init() ring := &Ring{ opt: opt, nreplicas: nreplicas, hash: consistenthash.New(nreplicas, nil), shards: make(map[string]*ringShard), } ring.processPipeline = ring.defaultProcessPipeline ring.cmdable.setProcessor(ring.Process) for name, addr := range opt.Addrs { clopt := opt.clientOptions() clopt.Addr = addr ring.addShard(name, NewClient(clopt)) } go ring.heartbeat() return ring } func (c *Ring) addShard(name string, cl *Client) { shard := &ringShard{Client: cl} c.mu.Lock() c.hash.Add(name) c.shards[name] = shard c.shardsList = append(c.shardsList, shard) c.mu.Unlock() } // Options returns read-only Options that were used to create the client. func (c *Ring) Options() *RingOptions { return c.opt } func (c *Ring) retryBackoff(attempt int) time.Duration { return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) } // PoolStats returns accumulated connection pool stats. func (c *Ring) PoolStats() *PoolStats { c.mu.RLock() shards := c.shardsList c.mu.RUnlock() var acc PoolStats for _, shard := range shards { s := shard.Client.connPool.Stats() acc.Hits += s.Hits acc.Misses += s.Misses acc.Timeouts += s.Timeouts acc.TotalConns += s.TotalConns acc.FreeConns += s.FreeConns } return &acc } // Subscribe subscribes the client to the specified channels. func (c *Ring) Subscribe(channels ...string) *PubSub { if len(channels) == 0 { panic("at least one channel is required") } shard, err := c.shardByKey(channels[0]) if err != nil { // TODO: return PubSub with sticky error panic(err) } return shard.Client.Subscribe(channels...) } // PSubscribe subscribes the client to the given patterns. func (c *Ring) PSubscribe(channels ...string) *PubSub { if len(channels) == 0 { panic("at least one channel is required") } shard, err := c.shardByKey(channels[0]) if err != nil { // TODO: return PubSub with sticky error panic(err) } return shard.Client.PSubscribe(channels...) } // ForEachShard concurrently calls the fn on each live shard in the ring. // It returns the first error if any. func (c *Ring) ForEachShard(fn func(client *Client) error) error { c.mu.RLock() shards := c.shardsList c.mu.RUnlock() var wg sync.WaitGroup errCh := make(chan error, 1) for _, shard := range shards { if shard.IsDown() { continue } wg.Add(1) go func(shard *ringShard) { defer wg.Done() err := fn(shard.Client) if err != nil { select { case errCh <- err: default: } } }(shard) } wg.Wait() select { case err := <-errCh: return err default: return nil } } func (c *Ring) cmdInfo(name string) *CommandInfo { err := c.cmdsInfoOnce.Do(func() error { c.mu.RLock() shards := c.shardsList c.mu.RUnlock() var firstErr error for _, shard := range shards { cmdsInfo, err := shard.Client.Command().Result() if err == nil { c.cmdsInfo = cmdsInfo return nil } if firstErr == nil { firstErr = err } } return firstErr }) if err != nil { return nil } if c.cmdsInfo == nil { return nil } info := c.cmdsInfo[name] if info == nil { internal.Logf("info for cmd=%s not found", name) } return info } func (c *Ring) shardByKey(key string) (*ringShard, error) { key = hashtag.Key(key) c.mu.RLock() if c.closed { c.mu.RUnlock() return nil, pool.ErrClosed } name := c.hash.Get(key) if name == "" { c.mu.RUnlock() return nil, errRingShardsDown } shard := c.shards[name] c.mu.RUnlock() return shard, nil } func (c *Ring) randomShard() (*ringShard, error) { return c.shardByKey(strconv.Itoa(rand.Int())) } func (c *Ring) shardByName(name string) (*ringShard, error) { if name == "" { return c.randomShard() } c.mu.RLock() shard := c.shards[name] c.mu.RUnlock() return shard, nil } func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) { cmdInfo := c.cmdInfo(cmd.Name()) pos := cmdFirstKeyPos(cmd, cmdInfo) if pos == 0 { return c.randomShard() } firstKey := cmd.stringArg(pos) return c.shardByKey(firstKey) } func (c *Ring) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) { c.ForEachShard(func(c *Client) error { c.WrapProcess(fn) return nil }) } func (c *Ring) Process(cmd Cmder) error { shard, err := c.cmdShard(cmd) if err != nil { cmd.setErr(err) return err } return shard.Client.Process(cmd) } // rebalance removes dead shards from the Ring. func (c *Ring) rebalance() { hash := consistenthash.New(c.nreplicas, nil) for name, shard := range c.shards { if shard.IsUp() { hash.Add(name) } } c.mu.Lock() c.hash = hash c.mu.Unlock() } // heartbeat monitors state of each shard in the ring. func (c *Ring) heartbeat() { ticker := time.NewTicker(c.opt.HeartbeatFrequency) defer ticker.Stop() for range ticker.C { var rebalance bool c.mu.RLock() if c.closed { c.mu.RUnlock() break } shards := c.shardsList c.mu.RUnlock() for _, shard := range shards { err := shard.Client.Ping().Err() if shard.Vote(err == nil || err == pool.ErrPoolTimeout) { internal.Logf("ring shard state changed: %s", shard) rebalance = true } } if rebalance { c.rebalance() } } } // Close closes the ring client, releasing any open resources. // // It is rare to Close a Ring, as the Ring is meant to be long-lived // and shared between many goroutines. func (c *Ring) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.closed { return nil } c.closed = true var firstErr error for _, shard := range c.shards { if err := shard.Client.Close(); err != nil && firstErr == nil { firstErr = err } } c.hash = nil c.shards = nil c.shardsList = nil return firstErr } func (c *Ring) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processPipeline, } pipe.cmdable.setProcessor(pipe.Process) return &pipe } func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *Ring) WrapProcessPipeline( fn func(oldProcess func([]Cmder) error) func([]Cmder) error, ) { c.processPipeline = fn(c.processPipeline) } func (c *Ring) defaultProcessPipeline(cmds []Cmder) error { cmdsMap := make(map[string][]Cmder) for _, cmd := range cmds { cmdInfo := c.cmdInfo(cmd.Name()) name := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo)) if name != "" { name = c.hash.Get(hashtag.Key(name)) } cmdsMap[name] = append(cmdsMap[name], cmd) } for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { if attempt > 0 { time.Sleep(c.retryBackoff(attempt)) } var failedCmdsMap map[string][]Cmder for name, cmds := range cmdsMap { shard, err := c.shardByName(name) if err != nil { setCmdsErr(cmds, err) continue } cn, _, err := shard.Client.getConn() if err != nil { setCmdsErr(cmds, err) continue } canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds) if err == nil || internal.IsRedisError(err) { _ = shard.Client.connPool.Put(cn) continue } _ = shard.Client.connPool.Remove(cn) if canRetry && internal.IsRetryableError(err, true) { if failedCmdsMap == nil { failedCmdsMap = make(map[string][]Cmder) } failedCmdsMap[name] = cmds } } if len(failedCmdsMap) == 0 { break } cmdsMap = failedCmdsMap } return firstCmdsErr(cmds) } func (c *Ring) TxPipeline() Pipeliner { panic("not implemented") } func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { panic("not implemented") } redis-6.9.2/ring_test.go000066400000000000000000000114021324526341500151660ustar00rootroot00000000000000package redis_test import ( "crypto/rand" "fmt" "time" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Redis Ring", func() { const heartbeat = 100 * time.Millisecond var ring *redis.Ring setRingKeys := func() { for i := 0; i < 100; i++ { err := ring.Set(fmt.Sprintf("key%d", i), "value", 0).Err() Expect(err).NotTo(HaveOccurred()) } } BeforeEach(func() { opt := redisRingOptions() opt.HeartbeatFrequency = heartbeat ring = redis.NewRing(opt) err := ring.ForEachShard(func(cl *redis.Client) error { return cl.FlushDB().Err() }) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(ring.Close()).NotTo(HaveOccurred()) }) It("distributes keys", func() { setRingKeys() // Both shards should have some keys now. Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) }) 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(ring, []string{key}, "value").Err() Expect(err).NotTo(HaveOccurred()) } Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) }) It("uses single shard when one of the shards is down", func() { // Stop ringShard2. Expect(ringShard2.Close()).NotTo(HaveOccurred()) // Ring needs 3 * heartbeat time to detect that node is down. // Give it more to be sure. time.Sleep(2 * 3 * heartbeat) setRingKeys() // RingShard1 should have all keys. Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=100")) // Start ringShard2. var err error ringShard2, err = startRedis(ringShard2Port) Expect(err).NotTo(HaveOccurred()) // Wait for ringShard2 to come up. Eventually(func() error { return ringShard2.Ping().Err() }, "1s").ShouldNot(HaveOccurred()) // Ring needs heartbeat time to detect that node is up. // Give it more to be sure. time.Sleep(heartbeat + heartbeat) setRingKeys() // RingShard2 should have its keys. Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) }) It("supports hash tags", func() { for i := 0; i < 100; i++ { err := ring.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err() Expect(err).NotTo(HaveOccurred()) } Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys=")) Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100")) }) Describe("pipeline", func() { It("distributes keys", func() { pipe := ring.Pipeline() for i := 0; i < 100; i++ { err := pipe.Set(fmt.Sprintf("key%d", i), "value", 0).Err() Expect(err).NotTo(HaveOccurred()) } cmds, err := pipe.Exec() Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(100)) Expect(pipe.Close()).NotTo(HaveOccurred()) for _, cmd := range cmds { Expect(cmd.Err()).NotTo(HaveOccurred()) Expect(cmd.(*redis.StatusCmd).Val()).To(Equal("OK")) } // Both shards should have some keys now. Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) }) It("is consistent with ring", func() { var keys []string for i := 0; i < 100; i++ { key := make([]byte, 64) _, err := rand.Read(key) Expect(err).NotTo(HaveOccurred()) keys = append(keys, string(key)) } _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { for _, key := range keys { pipe.Set(key, "value", 0).Err() } return nil }) Expect(err).NotTo(HaveOccurred()) for _, key := range keys { val, err := ring.Get(key).Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("value")) } }) It("supports hash tags", func() { _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { for i := 0; i < 100; i++ { pipe.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err() } return nil }) Expect(err).NotTo(HaveOccurred()) Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys=")) Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100")) }) }) }) var _ = Describe("empty Redis Ring", func() { var ring *redis.Ring BeforeEach(func() { ring = redis.NewRing(&redis.RingOptions{}) }) AfterEach(func() { Expect(ring.Close()).NotTo(HaveOccurred()) }) It("returns an error", func() { err := ring.Ping().Err() Expect(err).To(MatchError("redis: all ring shards are down")) }) It("pipeline returns an error", func() { _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) Expect(err).To(MatchError("redis: all ring shards are down")) }) }) redis-6.9.2/script.go000066400000000000000000000026061324526341500145020ustar00rootroot00000000000000package redis import ( "crypto/sha1" "encoding/hex" "io" "strings" ) type scripter interface { Eval(script string, keys []string, args ...interface{}) *Cmd EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd ScriptExists(scripts ...string) *BoolSliceCmd ScriptLoad(script string) *StringCmd } var _ scripter = (*Client)(nil) var _ scripter = (*Ring)(nil) var _ scripter = (*ClusterClient)(nil) type Script struct { src, hash string } func NewScript(src string) *Script { h := sha1.New() io.WriteString(h, src) return &Script{ src: src, hash: hex.EncodeToString(h.Sum(nil)), } } func (s *Script) Hash() string { return s.hash } func (s *Script) Load(c scripter) *StringCmd { return c.ScriptLoad(s.src) } func (s *Script) Exists(c scripter) *BoolSliceCmd { return c.ScriptExists(s.src) } func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd { return c.Eval(s.src, keys, args...) } func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd { return c.EvalSha(s.hash, keys, args...) } // Run optimistically uses EVALSHA to run the script. If script does not exist // it is retried using EVAL. func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd { r := s.EvalSha(c, keys, args...) if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { return s.Eval(c, keys, args...) } return r } redis-6.9.2/sentinel.go000066400000000000000000000163121324526341500150160ustar00rootroot00000000000000package redis import ( "errors" "net" "strings" "sync" "time" "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/pool" ) //------------------------------------------------------------------------------ // FailoverOptions are used to configure a failover client and should // be passed to NewFailoverClient. type FailoverOptions struct { // The master name. MasterName string // A seed list of host:port addresses of sentinel nodes. SentinelAddrs []string // Following options are copied from Options struct. OnConnect func(*Conn) error Password string DB int MaxRetries int DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration PoolSize int PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration } func (opt *FailoverOptions) options() *Options { return &Options{ Addr: "FailoverClient", OnConnect: opt.OnConnect, DB: opt.DB, Password: opt.Password, MaxRetries: opt.MaxRetries, DialTimeout: opt.DialTimeout, ReadTimeout: opt.ReadTimeout, WriteTimeout: opt.WriteTimeout, PoolSize: opt.PoolSize, PoolTimeout: opt.PoolTimeout, IdleTimeout: opt.IdleTimeout, IdleCheckFrequency: opt.IdleCheckFrequency, } } // NewFailoverClient returns a Redis client that uses Redis Sentinel // for automatic failover. It's safe for concurrent use by multiple // goroutines. func NewFailoverClient(failoverOpt *FailoverOptions) *Client { opt := failoverOpt.options() opt.init() failover := &sentinelFailover{ masterName: failoverOpt.MasterName, sentinelAddrs: failoverOpt.SentinelAddrs, opt: opt, } c := Client{ baseClient: baseClient{ opt: opt, connPool: failover.Pool(), onClose: func() error { return failover.Close() }, }, } c.baseClient.init() c.setProcessor(c.Process) return &c } //------------------------------------------------------------------------------ type sentinelClient struct { cmdable baseClient } func newSentinel(opt *Options) *sentinelClient { opt.init() c := sentinelClient{ baseClient: baseClient{ opt: opt, connPool: newConnPool(opt), }, } c.baseClient.init() c.cmdable.setProcessor(c.Process) return &c } func (c *sentinelClient) PubSub() *PubSub { return &PubSub{ opt: c.opt, newConn: func(channels []string) (*pool.Conn, error) { return c.newConn() }, closeConn: c.connPool.CloseConn, } } func (c *sentinelClient) GetMasterAddrByName(name string) *StringSliceCmd { cmd := NewStringSliceCmd("SENTINEL", "get-master-addr-by-name", name) c.Process(cmd) return cmd } func (c *sentinelClient) Sentinels(name string) *SliceCmd { cmd := NewSliceCmd("SENTINEL", "sentinels", name) c.Process(cmd) return cmd } type sentinelFailover struct { sentinelAddrs []string opt *Options pool *pool.ConnPool poolOnce sync.Once mu sync.RWMutex masterName string _masterAddr string sentinel *sentinelClient } func (d *sentinelFailover) Close() error { return d.resetSentinel() } func (d *sentinelFailover) Pool() *pool.ConnPool { d.poolOnce.Do(func() { d.opt.Dialer = d.dial d.pool = newConnPool(d.opt) }) return d.pool } func (d *sentinelFailover) dial() (net.Conn, error) { addr, err := d.MasterAddr() if err != nil { return nil, err } return net.DialTimeout("tcp", addr, d.opt.DialTimeout) } func (d *sentinelFailover) MasterAddr() (string, error) { d.mu.Lock() defer d.mu.Unlock() addr, err := d.masterAddr() if err != nil { return "", err } if d._masterAddr != addr { d.switchMaster(addr) } return addr, nil } func (d *sentinelFailover) masterAddr() (string, error) { // Try last working sentinel. if d.sentinel != nil { addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result() if err == nil { addr := net.JoinHostPort(addr[0], addr[1]) internal.Logf("sentinel: master=%q addr=%q", d.masterName, addr) return addr, nil } internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", d.masterName, err) d._resetSentinel() } for i, sentinelAddr := range d.sentinelAddrs { sentinel := newSentinel(&Options{ Addr: sentinelAddr, DialTimeout: d.opt.DialTimeout, ReadTimeout: d.opt.ReadTimeout, WriteTimeout: d.opt.WriteTimeout, PoolSize: d.opt.PoolSize, PoolTimeout: d.opt.PoolTimeout, IdleTimeout: d.opt.IdleTimeout, }) masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result() if err != nil { internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", d.masterName, err) sentinel.Close() continue } // Push working sentinel to the top. d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0] d.setSentinel(sentinel) addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) return addr, nil } return "", errors.New("redis: all sentinels are unreachable") } func (d *sentinelFailover) switchMaster(masterAddr string) { internal.Logf( "sentinel: new master=%q addr=%q", d.masterName, masterAddr, ) _ = d.Pool().Filter(func(cn *pool.Conn) bool { return cn.RemoteAddr().String() != masterAddr }) d._masterAddr = masterAddr } func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) { d.discoverSentinels(sentinel) d.sentinel = sentinel go d.listen(sentinel) } func (d *sentinelFailover) resetSentinel() error { var err error d.mu.Lock() if d.sentinel != nil { err = d._resetSentinel() } d.mu.Unlock() return err } func (d *sentinelFailover) _resetSentinel() error { err := d.sentinel.Close() d.sentinel = nil return err } func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) { sentinels, err := sentinel.Sentinels(d.masterName).Result() if err != nil { internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err) return } for _, sentinel := range sentinels { vals := sentinel.([]interface{}) for i := 0; i < len(vals); i += 2 { key := vals[i].(string) if key == "name" { sentinelAddr := vals[i+1].(string) if !contains(d.sentinelAddrs, sentinelAddr) { internal.Logf( "sentinel: discovered new sentinel=%q for master=%q", sentinelAddr, d.masterName, ) d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr) } } } } } func (d *sentinelFailover) listen(sentinel *sentinelClient) { var pubsub *PubSub for { if pubsub == nil { pubsub = sentinel.PubSub() if err := pubsub.Subscribe("+switch-master"); err != nil { internal.Logf("sentinel: Subscribe failed: %s", err) pubsub.Close() d.resetSentinel() return } } msg, err := pubsub.ReceiveMessage() if err != nil { if err != pool.ErrClosed { internal.Logf("sentinel: ReceiveMessage failed: %s", err) pubsub.Close() } d.resetSentinel() return } switch msg.Channel { case "+switch-master": parts := strings.Split(msg.Payload, " ") if parts[0] != d.masterName { internal.Logf("sentinel: ignore addr for master=%q", parts[0]) continue } addr := net.JoinHostPort(parts[3], parts[4]) d.mu.Lock() if d._masterAddr != addr { d.switchMaster(addr) } d.mu.Unlock() } } } func contains(slice []string, str string) bool { for _, s := range slice { if s == str { return true } } return false } redis-6.9.2/sentinel_test.go000066400000000000000000000045271324526341500160620ustar00rootroot00000000000000package redis_test import ( "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Sentinel", func() { var client *redis.Client BeforeEach(func() { client = redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: sentinelName, SentinelAddrs: []string{":" + sentinelPort}, }) Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(client.Close()).NotTo(HaveOccurred()) }) It("should facilitate failover", func() { // Set value on master. err := client.Set("foo", "master", 0).Err() Expect(err).NotTo(HaveOccurred()) // Verify. val, err := sentinelMaster.Get("foo").Result() Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("master")) // Create subscription. ch := client.Subscribe("foo").Channel() // Wait until replicated. Eventually(func() string { return sentinelSlave1.Get("foo").Val() }, "1s", "100ms").Should(Equal("master")) Eventually(func() string { return sentinelSlave2.Get("foo").Val() }, "1s", "100ms").Should(Equal("master")) // Wait until slaves are picked up by sentinel. Eventually(func() string { return sentinel.Info().Val() }, "10s", "100ms").Should(ContainSubstring("slaves=2")) // Kill master. sentinelMaster.Shutdown() Eventually(func() error { return sentinelMaster.Ping().Err() }, "5s", "100ms").Should(HaveOccurred()) // Wait for Redis sentinel to elect new master. Eventually(func() string { return sentinelSlave1.Info().Val() + sentinelSlave2.Info().Val() }, "30s", "1s").Should(ContainSubstring("role:master")) // Check that client picked up new master. Eventually(func() error { return client.Get("foo").Err() }, "5s", "100ms").ShouldNot(HaveOccurred()) // Publish message to check if subscription is renewed. err = client.Publish("foo", "hello").Err() Expect(err).NotTo(HaveOccurred()) var msg *redis.Message Eventually(ch).Should(Receive(&msg)) Expect(msg.Channel).To(Equal("foo")) Expect(msg.Payload).To(Equal("hello")) }) It("supports DB selection", func() { Expect(client.Close()).NotTo(HaveOccurred()) client = redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: sentinelName, SentinelAddrs: []string{":" + sentinelPort}, DB: 1, }) err := client.Ping().Err() Expect(err).NotTo(HaveOccurred()) }) }) redis-6.9.2/testdata/000077500000000000000000000000001324526341500144545ustar00rootroot00000000000000redis-6.9.2/testdata/redis.conf000066400000000000000000000002231324526341500164260ustar00rootroot00000000000000# Minimal redis.conf port 6379 daemonize no dir . save "" appendonly yes cluster-config-file nodes.conf cluster-node-timeout 30000 maxclients 1001redis-6.9.2/tx.go000066400000000000000000000050311324526341500136240ustar00rootroot00000000000000package redis import ( "github.com/go-redis/redis/internal/pool" "github.com/go-redis/redis/internal/proto" ) // TxFailedErr transaction redis failed. const TxFailedErr = proto.RedisError("redis: transaction failed") // Tx implements Redis transactions as described in // http://redis.io/topics/transactions. It's NOT safe for concurrent use // by multiple goroutines, because Exec resets list of watched keys. // If you don't need WATCH it is better to use Pipeline. type Tx struct { statefulCmdable baseClient } func (c *Client) newTx() *Tx { tx := Tx{ baseClient: baseClient{ opt: c.opt, connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true), }, } tx.baseClient.init() tx.statefulCmdable.setProcessor(tx.Process) return &tx } func (c *Client) Watch(fn func(*Tx) error, keys ...string) error { tx := c.newTx() if len(keys) > 0 { if err := tx.Watch(keys...).Err(); err != nil { _ = tx.Close() return err } } err := fn(tx) _ = tx.Close() return err } // Close closes the transaction, releasing any open resources. func (c *Tx) Close() error { _ = c.Unwatch().Err() return c.baseClient.Close() } // Watch marks the keys to be watched for conditional execution // of a transaction. func (c *Tx) Watch(keys ...string) *StatusCmd { args := make([]interface{}, 1+len(keys)) args[0] = "watch" for i, key := range keys { args[1+i] = key } cmd := NewStatusCmd(args...) c.Process(cmd) return cmd } // Unwatch flushes all the previously watched keys for a transaction. func (c *Tx) Unwatch(keys ...string) *StatusCmd { args := make([]interface{}, 1+len(keys)) args[0] = "unwatch" for i, key := range keys { args[1+i] = key } cmd := NewStatusCmd(args...) c.Process(cmd) return cmd } func (c *Tx) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processTxPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } // Pipelined executes commands queued in the fn in a transaction // and restores the connection state to normal. // // When using WATCH, EXEC will execute commands only if the watched keys // were not modified, allowing for a check-and-set mechanism. // // Exec always returns list of commands. If transaction fails // TxFailedErr is returned. Otherwise Exec returns error of the first // failed command or nil. func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipelined(fn) } func (c *Tx) TxPipeline() Pipeliner { return c.Pipeline() } redis-6.9.2/tx_test.go000066400000000000000000000064461324526341500146760ustar00rootroot00000000000000package redis_test import ( "strconv" "sync" "github.com/go-redis/redis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Tx", 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("should 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() n, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(100))) }) It("should discard", func() { err := client.Watch(func(tx *redis.Tx) error { cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Set("key1", "hello1", 0) pipe.Discard() pipe.Set("key2", "hello2", 0) return nil }) Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(1)) return err }, "key1", "key2") Expect(err).NotTo(HaveOccurred()) get := client.Get("key1") Expect(get.Err()).To(Equal(redis.Nil)) Expect(get.Val()).To(Equal("")) get = client.Get("key2") Expect(get.Err()).NotTo(HaveOccurred()) Expect(get.Val()).To(Equal("hello2")) }) It("returns no error when there are no commands", func() { err := client.Watch(func(tx *redis.Tx) error { _, err := tx.Pipelined(func(redis.Pipeliner) error { return nil }) return err }) Expect(err).NotTo(HaveOccurred()) v, err := client.Ping().Result() Expect(err).NotTo(HaveOccurred()) Expect(v).To(Equal("PONG")) }) It("should exec bulks", func() { const N = 20000 err := client.Watch(func(tx *redis.Tx) error { cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { for i := 0; i < N; i++ { pipe.Incr("key") } return nil }) Expect(err).NotTo(HaveOccurred()) Expect(len(cmds)).To(Equal(N)) for _, cmd := range cmds { Expect(cmd.Err()).NotTo(HaveOccurred()) } return err }) Expect(err).NotTo(HaveOccurred()) num, err := client.Get("key").Int64() Expect(err).NotTo(HaveOccurred()) Expect(num).To(Equal(int64(N))) }) It("should recover from bad connection", func() { // Put bad connection in the pool. cn, _, err := client.Pool().Get() Expect(err).NotTo(HaveOccurred()) cn.SetNetConn(&badConn{}) err = client.Pool().Put(cn) Expect(err).NotTo(HaveOccurred()) do := func() error { err := client.Watch(func(tx *redis.Tx) error { _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { pipe.Ping() return nil }) return err }) return err } err = do() Expect(err).To(MatchError("bad connection")) err = do() Expect(err).NotTo(HaveOccurred()) }) }) redis-6.9.2/universal.go000066400000000000000000000072741324526341500152140ustar00rootroot00000000000000package redis import "time" // UniversalOptions information is required by UniversalClient to establish // connections. type UniversalOptions struct { // Either a single address or a seed list of host:port addresses // of cluster/sentinel nodes. Addrs []string // The sentinel master name. // Only failover clients. MasterName string // Database to be selected after connecting to the server. // Only single-node and failover clients. DB int // Only cluster clients. // Enables read only queries on slave nodes. ReadOnly bool MaxRedirects int RouteByLatency bool // Common options MaxRetries int Password string DialTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration PoolSize int PoolTimeout time.Duration IdleTimeout time.Duration IdleCheckFrequency time.Duration } func (o *UniversalOptions) cluster() *ClusterOptions { if len(o.Addrs) == 0 { o.Addrs = []string{"127.0.0.1:6379"} } return &ClusterOptions{ Addrs: o.Addrs, MaxRedirects: o.MaxRedirects, RouteByLatency: o.RouteByLatency, ReadOnly: o.ReadOnly, MaxRetries: o.MaxRetries, Password: o.Password, DialTimeout: o.DialTimeout, ReadTimeout: o.ReadTimeout, WriteTimeout: o.WriteTimeout, PoolSize: o.PoolSize, PoolTimeout: o.PoolTimeout, IdleTimeout: o.IdleTimeout, IdleCheckFrequency: o.IdleCheckFrequency, } } func (o *UniversalOptions) failover() *FailoverOptions { if len(o.Addrs) == 0 { o.Addrs = []string{"127.0.0.1:26379"} } return &FailoverOptions{ SentinelAddrs: o.Addrs, MasterName: o.MasterName, DB: o.DB, MaxRetries: o.MaxRetries, Password: o.Password, DialTimeout: o.DialTimeout, ReadTimeout: o.ReadTimeout, WriteTimeout: o.WriteTimeout, PoolSize: o.PoolSize, PoolTimeout: o.PoolTimeout, IdleTimeout: o.IdleTimeout, IdleCheckFrequency: o.IdleCheckFrequency, } } func (o *UniversalOptions) simple() *Options { addr := "127.0.0.1:6379" if len(o.Addrs) > 0 { addr = o.Addrs[0] } return &Options{ Addr: addr, DB: o.DB, MaxRetries: o.MaxRetries, Password: o.Password, DialTimeout: o.DialTimeout, ReadTimeout: o.ReadTimeout, WriteTimeout: o.WriteTimeout, PoolSize: o.PoolSize, PoolTimeout: o.PoolTimeout, IdleTimeout: o.IdleTimeout, IdleCheckFrequency: o.IdleCheckFrequency, } } // -------------------------------------------------------------------- // UniversalClient is an abstract client which - based on the provided options - // can connect to either clusters, or sentinel-backed failover instances or simple // single-instance servers. This can be useful for testing cluster-specific // applications locally. type UniversalClient interface { Cmdable Process(cmd Cmder) error Subscribe(channels ...string) *PubSub PSubscribe(channels ...string) *PubSub Close() error } var _ UniversalClient = (*Client)(nil) var _ UniversalClient = (*ClusterClient)(nil) // NewUniversalClient returns a new multi client. The type of client returned depends // on the following three conditions: // // 1. if a MasterName is passed a sentinel-backed FailoverClient will be returned // 2. if the number of Addrs is two or more, a ClusterClient will be returned // 3. otherwise, a single-node redis Client will be returned. func NewUniversalClient(opts *UniversalOptions) UniversalClient { if opts.MasterName != "" { return NewFailoverClient(opts.failover()) } else if len(opts.Addrs) > 1 { return NewClusterClient(opts.cluster()) } return NewClient(opts.simple()) } redis-6.9.2/universal_test.go000066400000000000000000000016471324526341500162510ustar00rootroot00000000000000package redis_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/go-redis/redis" ) var _ = Describe("UniversalClient", func() { var client redis.UniversalClient AfterEach(func() { if client != nil { Expect(client.Close()).To(Succeed()) } }) It("should connect to failover servers", func() { client = redis.NewUniversalClient(&redis.UniversalOptions{ MasterName: sentinelName, Addrs: []string{":" + sentinelPort}, }) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) It("should connect to simple servers", func() { client = redis.NewUniversalClient(&redis.UniversalOptions{ Addrs: []string{redisAddr}, }) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) It("should connect to clusters", func() { client = redis.NewUniversalClient(&redis.UniversalOptions{ Addrs: cluster.addrs(), }) Expect(client.Ping().Err()).NotTo(HaveOccurred()) }) })