diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index e67657d076..34a305c4ad 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -467,8 +467,10 @@ LENGTH = 20 BATCH_LENGTH = 20 ; Connection string for redis queues this will store the redis connection string. CONN_STR = "addrs=127.0.0.1:6379 db=0" -; Provide the suffix of the default redis queue name - specific queues can be overriden within in their [queue.name] sections. +; Provides the suffix of the default redis/disk queue name - specific queues can be overriden within in their [queue.name] sections. QUEUE_NAME = "_queue" +; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overriden within in their [queue.name] sections. +SET_NAME = "_unique" ; If the queue cannot be created at startup - level queues may need a timeout at startup - wrap the queue: WRAP_IF_NECESSARY = true ; Attempt to create the wrapped queue at max diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index c63233fe1f..883f09ff9d 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -308,15 +308,13 @@ relation to port exhaustion. ## Queue (`queue` and `queue.*`) - `TYPE`: **persistable-channel**: General queue type, currently support: `persistable-channel`, `channel`, `level`, `redis`, `dummy` -- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for inidividual queues can be set in `queue.name` sections but will default to `DATADIR/`**`name`**. +- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`name`**. - `LENGTH`: **20**: Maximal queue size before channel queues block - `BATCH_LENGTH`: **20**: Batch data before passing to the handler -- `CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Connection string for the redis queue type. -- `QUEUE_NAME`: **_queue**: The suffix for default redis queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section. -- `SET_NAME`: **_unique**: The suffix that will added to the default redis -set name for unique queues. Individual queues will default to -**`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific -`queue.name` section. +- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. Options can be set using query params. Similarly LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value** +- `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section. +- `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to + **`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific `queue.name` section. - `WRAP_IF_NECESSARY`: **true**: Will wrap queues with a timeoutable queue if the selected queue is not ready to be created - (Only relevant for the level queue.) - `MAX_ATTEMPTS`: **10**: Maximum number of attempts to create the wrapped queue - `TIMEOUT`: **GRACEFUL_HAMMER_TIME + 30s**: Timeout the creation of the wrapped queue if it takes longer than this to create. @@ -459,7 +457,7 @@ set name for unique queues. Individual queues will default to - `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`. - `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only. - `HOST`: **\**: Connection string for `redis` and `memcache`. - - Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180` + - Redis: `redis://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` - Memcache: `127.0.0.1:9090;127.0.0.1:9091` - `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching. @@ -708,7 +706,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `QUEUE_TYPE`: **channel**: Task queue type, could be `channel` or `redis`. - `QUEUE_LENGTH`: **1000**: Task queue length, available only when `QUEUE_TYPE` is `channel`. -- `QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `addrs=127.0.0.1:6379 password=123 db=0`. +- `QUEUE_CONN_STR`: **redis://127.0.0.1:6379/0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `redis://123@127.0.0.1:6379/0`. ## Migrations (`migrations`) diff --git a/go.mod b/go.mod index 00a970c0b0..ac417ac896 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/go-enry/go-enry/v2 v2.5.2 github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git/v5 v5.1.0 - github.com/go-redis/redis v6.15.2+incompatible + github.com/go-redis/redis/v7 v7.4.0 github.com/go-sql-driver/mysql v1.5.0 github.com/go-swagger/go-swagger v0.25.0 github.com/go-testfixtures/testfixtures/v3 v3.4.0 @@ -88,6 +88,7 @@ require ( github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b // indirect github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd github.com/stretchr/testify v1.6.1 + github.com/syndtr/goleveldb v1.0.0 github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect github.com/tinylib/msgp v1.1.2 // indirect github.com/tstranex/u2f v1.0.0 diff --git a/go.sum b/go.sum index a9b6f6f011..7a6fa8aef8 100644 --- a/go.sum +++ b/go.sum @@ -342,6 +342,8 @@ github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNP github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -730,9 +732,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1014,6 +1020,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/modules/cache/cache.go b/modules/cache/cache.go index 859f4a4b47..60865d8335 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -13,7 +13,6 @@ import ( mc "gitea.com/macaron/cache" _ "gitea.com/macaron/cache/memcache" // memcache plugin for cache - _ "gitea.com/macaron/cache/redis" ) var ( diff --git a/vendor/gitea.com/macaron/cache/redis/redis.go b/modules/cache/cache_redis.go similarity index 63% rename from vendor/gitea.com/macaron/cache/redis/redis.go rename to modules/cache/cache_redis.go index 892ee28bdc..96e865a382 100644 --- a/vendor/gitea.com/macaron/cache/redis/redis.go +++ b/modules/cache/cache_redis.go @@ -1,35 +1,23 @@ -// Copyright 2013 Beego Authors -// Copyright 2014 The Macaron 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. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. package cache import ( "fmt" - "strings" "time" - "github.com/go-redis/redis" - "github.com/unknwon/com" - "gopkg.in/ini.v1" + "code.gitea.io/gitea/modules/nosql" "gitea.com/macaron/cache" + "github.com/go-redis/redis/v7" + "github.com/unknwon/com" ) // RedisCacher represents a redis cache adapter implementation. type RedisCacher struct { - c *redis.Client + c redis.UniversalClient prefix string hsetName string occupyMode bool @@ -112,7 +100,7 @@ func (c *RedisCacher) IsExist(key string) bool { // Flush deletes all cached data. func (c *RedisCacher) Flush() error { if c.occupyMode { - return c.c.FlushDb().Err() + return c.c.FlushDB().Err() } keys, err := c.c.HKeys(c.hsetName).Result() @@ -131,46 +119,20 @@ func (c *RedisCacher) StartAndGC(opts cache.Options) error { c.hsetName = "MacaronCache" c.occupyMode = opts.OccupyMode - cfg, err := ini.Load([]byte(strings.Replace(opts.AdapterConfig, ",", "\n", -1))) - if err != nil { - return err - } + uri := nosql.ToRedisURI(opts.AdapterConfig) - opt := &redis.Options{ - Network: "tcp", - } - for k, v := range cfg.Section("").KeysHash() { + c.c = nosql.GetManager().GetRedisClient(uri.String()) + + for k, v := range uri.Query() { switch k { - case "network": - opt.Network = v - case "addr": - opt.Addr = v - case "password": - opt.Password = v - case "db": - opt.DB = com.StrTo(v).MustInt() - case "pool_size": - opt.PoolSize = com.StrTo(v).MustInt() - case "idle_timeout": - opt.IdleTimeout, err = time.ParseDuration(v + "s") - if err != nil { - return fmt.Errorf("error parsing idle timeout: %v", err) - } case "hset_name": - c.hsetName = v + c.hsetName = v[0] case "prefix": - c.prefix = v - default: - return fmt.Errorf("session/redis: unsupported option '%s'", k) + c.prefix = v[0] } } - c.c = redis.NewClient(opt) - if err = c.c.Ping().Err(); err != nil { - return err - } - - return nil + return c.c.Ping().Err() } func init() { diff --git a/modules/nosql/leveldb.go b/modules/nosql/leveldb.go new file mode 100644 index 0000000000..5da2291e03 --- /dev/null +++ b/modules/nosql/leveldb.go @@ -0,0 +1,25 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import "net/url" + +// ToLevelDBURI converts old style connections to a LevelDBURI +// +// A LevelDBURI matches the pattern: +// +// leveldb://path[?[option=value]*] +// +// We have previously just provided the path but this prevent other options +func ToLevelDBURI(connection string) *url.URL { + uri, err := url.Parse(connection) + if err == nil && uri.Scheme == "leveldb" { + return uri + } + uri, _ = url.Parse("leveldb://common") + uri.Host = "" + uri.Path = connection + return uri +} diff --git a/modules/nosql/manager.go b/modules/nosql/manager.go new file mode 100644 index 0000000000..ad61d6d18c --- /dev/null +++ b/modules/nosql/manager.go @@ -0,0 +1,71 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import ( + "strconv" + "sync" + "time" + + "github.com/go-redis/redis/v7" + "github.com/syndtr/goleveldb/leveldb" +) + +var manager *Manager + +// Manager is the nosql connection manager +type Manager struct { + mutex sync.Mutex + + RedisConnections map[string]*redisClientHolder + LevelDBConnections map[string]*levelDBHolder +} + +type redisClientHolder struct { + redis.UniversalClient + name []string + count int64 +} + +func (r *redisClientHolder) Close() error { + return manager.CloseRedisClient(r.name[0]) +} + +type levelDBHolder struct { + name []string + count int64 + db *leveldb.DB +} + +func init() { + _ = GetManager() +} + +// GetManager returns a Manager and initializes one as singleton is there's none yet +func GetManager() *Manager { + if manager == nil { + manager = &Manager{ + RedisConnections: make(map[string]*redisClientHolder), + LevelDBConnections: make(map[string]*levelDBHolder), + } + } + return manager +} + +func valToTimeDuration(vs []string) (result time.Duration) { + var err error + for _, v := range vs { + result, err = time.ParseDuration(v) + if err != nil { + var val int + val, err = strconv.Atoi(v) + result = time.Duration(val) + } + if err == nil { + return + } + } + return +} diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go new file mode 100644 index 0000000000..769d5002d0 --- /dev/null +++ b/modules/nosql/manager_leveldb.go @@ -0,0 +1,151 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import ( + "path" + "strconv" + "strings" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +// CloseLevelDB closes a levelDB +func (m *Manager) CloseLevelDB(connection string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + db, ok := m.LevelDBConnections[connection] + if !ok { + connection = ToLevelDBURI(connection).String() + db, ok = m.LevelDBConnections[connection] + } + if !ok { + return nil + } + + db.count-- + if db.count > 0 { + return nil + } + + for _, name := range db.name { + delete(m.LevelDBConnections, name) + } + return db.db.Close() +} + +// GetLevelDB gets a levelDB for a particular connection +func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + db, ok := m.LevelDBConnections[connection] + if ok { + db.count++ + + return db.db, nil + } + dataDir := connection + uri := ToLevelDBURI(connection) + db = &levelDBHolder{ + name: []string{connection, uri.String()}, + } + + dataDir = path.Join(uri.Host, uri.Path) + opts := &opt.Options{} + for k, v := range uri.Query() { + switch replacer.Replace(strings.ToLower(k)) { + case "blockcachecapacity": + opts.BlockCacheCapacity, _ = strconv.Atoi(v[0]) + case "blockcacheevictremoved": + opts.BlockCacheEvictRemoved, _ = strconv.ParseBool(v[0]) + case "blockrestartinterval": + opts.BlockRestartInterval, _ = strconv.Atoi(v[0]) + case "blocksize": + opts.BlockSize, _ = strconv.Atoi(v[0]) + case "compactionexpandlimitfactor": + opts.CompactionExpandLimitFactor, _ = strconv.Atoi(v[0]) + case "compactiongpoverlapsfactor": + opts.CompactionGPOverlapsFactor, _ = strconv.Atoi(v[0]) + case "compactionl0trigger": + opts.CompactionL0Trigger, _ = strconv.Atoi(v[0]) + case "compactionsourcelimitfactor": + opts.CompactionSourceLimitFactor, _ = strconv.Atoi(v[0]) + case "compactiontablesize": + opts.CompactionTableSize, _ = strconv.Atoi(v[0]) + case "compactiontablesizemultiplier": + opts.CompactionTableSizeMultiplier, _ = strconv.ParseFloat(v[0], 64) + case "compactiontablesizemultiplierperlevel": + for _, val := range v { + f, _ := strconv.ParseFloat(val, 64) + opts.CompactionTableSizeMultiplierPerLevel = append(opts.CompactionTableSizeMultiplierPerLevel, f) + } + case "compactiontotalsize": + opts.CompactionTotalSize, _ = strconv.Atoi(v[0]) + case "compactiontotalsizemultiplier": + opts.CompactionTotalSizeMultiplier, _ = strconv.ParseFloat(v[0], 64) + case "compactiontotalsizemultiplierperlevel": + for _, val := range v { + f, _ := strconv.ParseFloat(val, 64) + opts.CompactionTotalSizeMultiplierPerLevel = append(opts.CompactionTotalSizeMultiplierPerLevel, f) + } + case "compression": + val, _ := strconv.Atoi(v[0]) + opts.Compression = opt.Compression(val) + case "disablebufferpool": + opts.DisableBufferPool, _ = strconv.ParseBool(v[0]) + case "disableblockcache": + opts.DisableBlockCache, _ = strconv.ParseBool(v[0]) + case "disablecompactionbackoff": + opts.DisableCompactionBackoff, _ = strconv.ParseBool(v[0]) + case "disablelargebatchtransaction": + opts.DisableLargeBatchTransaction, _ = strconv.ParseBool(v[0]) + case "errorifexist": + opts.ErrorIfExist, _ = strconv.ParseBool(v[0]) + case "errorifmissing": + opts.ErrorIfMissing, _ = strconv.ParseBool(v[0]) + case "iteratorsamplingrate": + opts.IteratorSamplingRate, _ = strconv.Atoi(v[0]) + case "nosync": + opts.NoSync, _ = strconv.ParseBool(v[0]) + case "nowritemerge": + opts.NoWriteMerge, _ = strconv.ParseBool(v[0]) + case "openfilescachecapacity": + opts.OpenFilesCacheCapacity, _ = strconv.Atoi(v[0]) + case "readonly": + opts.ReadOnly, _ = strconv.ParseBool(v[0]) + case "strict": + val, _ := strconv.Atoi(v[0]) + opts.Strict = opt.Strict(val) + case "writebuffer": + opts.WriteBuffer, _ = strconv.Atoi(v[0]) + case "writel0pausetrigger": + opts.WriteL0PauseTrigger, _ = strconv.Atoi(v[0]) + case "writel0slowdowntrigger": + opts.WriteL0SlowdownTrigger, _ = strconv.Atoi(v[0]) + case "clientname": + db.name = append(db.name, v[0]) + } + } + + var err error + db.db, err = leveldb.OpenFile(dataDir, opts) + if err != nil { + if !errors.IsCorrupted(err) { + return nil, err + } + db.db, err = leveldb.RecoverFile(dataDir, opts) + if err != nil { + return nil, err + } + } + + for _, name := range db.name { + m.LevelDBConnections[name] = db + } + db.count++ + return db.db, nil +} diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go new file mode 100644 index 0000000000..7792a90112 --- /dev/null +++ b/modules/nosql/manager_redis.go @@ -0,0 +1,205 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import ( + "crypto/tls" + "path" + "strconv" + "strings" + + "github.com/go-redis/redis/v7" +) + +var replacer = strings.NewReplacer("_", "", "-", "") + +// CloseRedisClient closes a redis client +func (m *Manager) CloseRedisClient(connection string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + client, ok := m.RedisConnections[connection] + if !ok { + connection = ToRedisURI(connection).String() + client, ok = m.RedisConnections[connection] + } + if !ok { + return nil + } + + client.count-- + if client.count > 0 { + return nil + } + + for _, name := range client.name { + delete(m.RedisConnections, name) + } + return client.UniversalClient.Close() +} + +// GetRedisClient gets a redis client for a particular connection +func (m *Manager) GetRedisClient(connection string) redis.UniversalClient { + m.mutex.Lock() + defer m.mutex.Unlock() + client, ok := m.RedisConnections[connection] + if ok { + client.count++ + return client + } + + uri := ToRedisURI(connection) + client, ok = m.RedisConnections[uri.String()] + if ok { + client.count++ + return client + } + client = &redisClientHolder{ + name: []string{connection, uri.String()}, + } + + opts := &redis.UniversalOptions{} + tlsConfig := &tls.Config{} + + // Handle username/password + if password, ok := uri.User.Password(); ok { + opts.Password = password + // Username does not appear to be handled by redis.Options + opts.Username = uri.User.Username() + } else if uri.User.Username() != "" { + // assume this is the password + opts.Password = uri.User.Username() + } + + // Now handle the uri query sets + for k, v := range uri.Query() { + switch replacer.Replace(strings.ToLower(k)) { + case "addr": + opts.Addrs = append(opts.Addrs, v...) + case "addrs": + opts.Addrs = append(opts.Addrs, strings.Split(v[0], ",")...) + case "username": + opts.Username = v[0] + case "password": + opts.Password = v[0] + case "database": + fallthrough + case "db": + opts.DB, _ = strconv.Atoi(v[0]) + case "maxretries": + opts.MaxRetries, _ = strconv.Atoi(v[0]) + case "minretrybackoff": + opts.MinRetryBackoff = valToTimeDuration(v) + case "maxretrybackoff": + opts.MaxRetryBackoff = valToTimeDuration(v) + case "timeout": + timeout := valToTimeDuration(v) + if timeout != 0 { + if opts.DialTimeout == 0 { + opts.DialTimeout = timeout + } + if opts.ReadTimeout == 0 { + opts.ReadTimeout = timeout + } + } + case "dialtimeout": + opts.DialTimeout = valToTimeDuration(v) + case "readtimeout": + opts.ReadTimeout = valToTimeDuration(v) + case "writetimeout": + opts.WriteTimeout = valToTimeDuration(v) + case "poolsize": + opts.PoolSize, _ = strconv.Atoi(v[0]) + case "minidleconns": + opts.MinIdleConns, _ = strconv.Atoi(v[0]) + case "pooltimeout": + opts.PoolTimeout = valToTimeDuration(v) + case "idletimeout": + opts.IdleTimeout = valToTimeDuration(v) + case "idlecheckfrequency": + opts.IdleCheckFrequency = valToTimeDuration(v) + case "maxredirects": + opts.MaxRedirects, _ = strconv.Atoi(v[0]) + case "readonly": + opts.ReadOnly, _ = strconv.ParseBool(v[0]) + case "routebylatency": + opts.RouteByLatency, _ = strconv.ParseBool(v[0]) + case "routerandomly": + opts.RouteRandomly, _ = strconv.ParseBool(v[0]) + case "sentinelmasterid": + fallthrough + case "mastername": + opts.MasterName = v[0] + case "skipverify": + fallthrough + case "insecureskipverify": + insecureSkipVerify, _ := strconv.ParseBool(v[0]) + tlsConfig.InsecureSkipVerify = insecureSkipVerify + case "clientname": + client.name = append(client.name, v[0]) + } + } + + switch uri.Scheme { + case "redis+sentinels": + fallthrough + case "rediss+sentinel": + opts.TLSConfig = tlsConfig + fallthrough + case "redis+sentinel": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + + client.UniversalClient = redis.NewFailoverClient(opts.Failover()) + case "redis+clusters": + fallthrough + case "rediss+cluster": + opts.TLSConfig = tlsConfig + fallthrough + case "redis+cluster": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + client.UniversalClient = redis.NewClusterClient(opts.Cluster()) + case "redis+socket": + simpleOpts := opts.Simple() + simpleOpts.Network = "unix" + simpleOpts.Addr = path.Join(uri.Host, uri.Path) + client.UniversalClient = redis.NewClient(simpleOpts) + case "rediss": + opts.TLSConfig = tlsConfig + fallthrough + case "redis": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + client.UniversalClient = redis.NewClient(opts.Simple()) + default: + return nil + } + + for _, name := range client.name { + m.RedisConnections[name] = client + } + + client.count++ + + return client +} diff --git a/modules/nosql/redis.go b/modules/nosql/redis.go new file mode 100644 index 0000000000..528f5fc802 --- /dev/null +++ b/modules/nosql/redis.go @@ -0,0 +1,102 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import ( + "net/url" + "strconv" + "strings" +) + +// The file contains common redis connection functions + +// ToRedisURI converts old style connections to a RedisURI +// +// A RedisURI matches the pattern: +// +// redis://[username:password@]host[:port][/database][?[option=value]*] +// rediss://[username:password@]host[:port][/database][?[option=value]*] +// redis+socket://[username:password@]path[/database][?[option=value]*] +// redis+sentinel://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*] +// redis+cluster://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*] +// +// We have previously used a URI like: +// addrs=127.0.0.1:6379 db=0 +// network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 +// +// We need to convert this old style to the new style +func ToRedisURI(connection string) *url.URL { + uri, err := url.Parse(connection) + if err == nil && strings.HasPrefix(uri.Scheme, "redis") { + // OK we're going to assume that this is a reasonable redis URI + return uri + } + + // Let's set a nice default + uri, _ = url.Parse("redis://127.0.0.1:6379/0") + network := "tcp" + query := uri.Query() + + // OK so there are two types: Space delimited and Comma delimited + // Let's assume that we have a space delimited string - as this is the most common + fields := strings.Fields(connection) + if len(fields) == 1 { + // It's a comma delimited string, then... + fields = strings.Split(connection, ",") + + } + for _, f := range fields { + items := strings.SplitN(f, "=", 2) + if len(items) < 2 { + continue + } + switch strings.ToLower(items[0]) { + case "network": + if items[1] == "unix" { + uri.Scheme = "redis+socket" + } + network = items[1] + case "addrs": + uri.Host = items[1] + // now we need to handle the clustering + if strings.Contains(items[1], ",") && network == "tcp" { + uri.Scheme = "redis+cluster" + } + case "addr": + uri.Host = items[1] + case "password": + uri.User = url.UserPassword(uri.User.Username(), items[1]) + case "username": + password, set := uri.User.Password() + if !set { + uri.User = url.User(items[1]) + } else { + uri.User = url.UserPassword(items[1], password) + } + case "db": + uri.Path = "/" + items[1] + case "idle_timeout": + _, err := strconv.Atoi(items[1]) + if err == nil { + query.Add("idle_timeout", items[1]+"s") + } else { + query.Add("idle_timeout", items[1]) + } + default: + // Other options become query params + query.Add(items[0], items[1]) + } + } + + // Finally we need to fix up the Host if we have a unix port + if uri.Scheme == "redis+socket" { + query.Set("db", uri.Path) + uri.Path = uri.Host + uri.Host = "" + } + uri.RawQuery = query.Encode() + + return uri +} diff --git a/modules/nosql/redis_test.go b/modules/nosql/redis_test.go new file mode 100644 index 0000000000..c70d236bdc --- /dev/null +++ b/modules/nosql/redis_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nosql + +import ( + "testing" +) + +func TestToRedisURI(t *testing.T) { + tests := []struct { + name string + connection string + want string + }{ + { + name: "old_default", + connection: "addrs=127.0.0.1:6379 db=0", + want: "redis://127.0.0.1:6379/0", + }, + { + name: "old_macaron_session_default", + connection: "network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180", + want: "redis://:macaron@127.0.0.1:6379/0?idle_timeout=180s&pool_size=100", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToRedisURI(tt.connection); got == nil || got.String() != tt.want { + t.Errorf(`ToRedisURI(%q) = %s, want %s`, tt.connection, got.String(), tt.want) + } + }) + } +} diff --git a/modules/queue/queue_disk.go b/modules/queue/queue_disk.go index ff0876488b..88b8c414c0 100644 --- a/modules/queue/queue_disk.go +++ b/modules/queue/queue_disk.go @@ -5,6 +5,8 @@ package queue import ( + "code.gitea.io/gitea/modules/nosql" + "gitea.com/lunny/levelqueue" ) @@ -14,7 +16,9 @@ const LevelQueueType Type = "level" // LevelQueueConfiguration is the configuration for a LevelQueue type LevelQueueConfiguration struct { ByteFIFOQueueConfiguration - DataDir string + DataDir string + ConnectionString string + QueueName string } // LevelQueue implements a disk library queue @@ -30,7 +34,11 @@ func NewLevelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) } config := configInterface.(LevelQueueConfiguration) - byteFIFO, err := NewLevelQueueByteFIFO(config.DataDir) + if len(config.ConnectionString) == 0 { + config.ConnectionString = config.DataDir + } + + byteFIFO, err := NewLevelQueueByteFIFO(config.ConnectionString, config.QueueName) if err != nil { return nil, err } @@ -51,18 +59,25 @@ var _ (ByteFIFO) = &LevelQueueByteFIFO{} // LevelQueueByteFIFO represents a ByteFIFO formed from a LevelQueue type LevelQueueByteFIFO struct { - internal *levelqueue.Queue + internal *levelqueue.Queue + connection string } // NewLevelQueueByteFIFO creates a ByteFIFO formed from a LevelQueue -func NewLevelQueueByteFIFO(dataDir string) (*LevelQueueByteFIFO, error) { - internal, err := levelqueue.Open(dataDir) +func NewLevelQueueByteFIFO(connection, prefix string) (*LevelQueueByteFIFO, error) { + db, err := nosql.GetManager().GetLevelDB(connection) + if err != nil { + return nil, err + } + + internal, err := levelqueue.NewQueue(db, []byte(prefix), false) if err != nil { return nil, err } return &LevelQueueByteFIFO{ - internal: internal, + connection: connection, + internal: internal, }, nil } @@ -87,7 +102,9 @@ func (fifo *LevelQueueByteFIFO) Pop() ([]byte, error) { // Close this fifo func (fifo *LevelQueueByteFIFO) Close() error { - return fifo.internal.Close() + err := fifo.internal.Close() + _ = nosql.GetManager().CloseLevelDB(fifo.connection) + return err } // Len returns the length of the fifo diff --git a/modules/queue/queue_redis.go b/modules/queue/queue_redis.go index 4e05ddd17e..04e7b5d252 100644 --- a/modules/queue/queue_redis.go +++ b/modules/queue/queue_redis.go @@ -5,12 +5,10 @@ package queue import ( - "errors" - "strings" - "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/nosql" - "github.com/go-redis/redis" + "github.com/go-redis/redis/v7" ) // RedisQueueType is the type for redis queue @@ -75,11 +73,8 @@ type RedisByteFIFO struct { // RedisByteFIFOConfiguration is the configuration for the RedisByteFIFO type RedisByteFIFOConfiguration struct { - Network string - Addresses string - Password string - DBIndex int - QueueName string + ConnectionString string + QueueName string } // NewRedisByteFIFO creates a ByteFIFO formed from a redisClient @@ -87,21 +82,7 @@ func NewRedisByteFIFO(config RedisByteFIFOConfiguration) (*RedisByteFIFO, error) fifo := &RedisByteFIFO{ queueName: config.QueueName, } - dbs := strings.Split(config.Addresses, ",") - if len(dbs) == 0 { - return nil, errors.New("no redis host specified") - } else if len(dbs) == 1 { - fifo.client = redis.NewClient(&redis.Options{ - Network: config.Network, - Addr: strings.TrimSpace(dbs[0]), // use default Addr - Password: config.Password, // no password set - DB: config.DBIndex, // use default DB - }) - } else { - fifo.client = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: dbs, - }) - } + fifo.client = nosql.GetManager().GetRedisClient(config.ConnectionString) if err := fifo.client.Ping().Err(); err != nil { return nil, err } diff --git a/modules/queue/unique_queue_disk.go b/modules/queue/unique_queue_disk.go index bfe7aeed83..dd6ac1a538 100644 --- a/modules/queue/unique_queue_disk.go +++ b/modules/queue/unique_queue_disk.go @@ -5,6 +5,8 @@ package queue import ( + "code.gitea.io/gitea/modules/nosql" + "gitea.com/lunny/levelqueue" ) @@ -14,7 +16,9 @@ const LevelUniqueQueueType Type = "unique-level" // LevelUniqueQueueConfiguration is the configuration for a LevelUniqueQueue type LevelUniqueQueueConfiguration struct { ByteFIFOQueueConfiguration - DataDir string + DataDir string + ConnectionString string + QueueName string } // LevelUniqueQueue implements a disk library queue @@ -34,7 +38,11 @@ func NewLevelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, } config := configInterface.(LevelUniqueQueueConfiguration) - byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.DataDir) + if len(config.ConnectionString) == 0 { + config.ConnectionString = config.DataDir + } + + byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.ConnectionString, config.QueueName) if err != nil { return nil, err } @@ -55,18 +63,25 @@ var _ (UniqueByteFIFO) = &LevelUniqueQueueByteFIFO{} // LevelUniqueQueueByteFIFO represents a ByteFIFO formed from a LevelUniqueQueue type LevelUniqueQueueByteFIFO struct { - internal *levelqueue.UniqueQueue + internal *levelqueue.UniqueQueue + connection string } // NewLevelUniqueQueueByteFIFO creates a new ByteFIFO formed from a LevelUniqueQueue -func NewLevelUniqueQueueByteFIFO(dataDir string) (*LevelUniqueQueueByteFIFO, error) { - internal, err := levelqueue.OpenUnique(dataDir) +func NewLevelUniqueQueueByteFIFO(connection, prefix string) (*LevelUniqueQueueByteFIFO, error) { + db, err := nosql.GetManager().GetLevelDB(connection) + if err != nil { + return nil, err + } + + internal, err := levelqueue.NewUniqueQueue(db, []byte(prefix), []byte(prefix+"-unique"), false) if err != nil { return nil, err } return &LevelUniqueQueueByteFIFO{ - internal: internal, + connection: connection, + internal: internal, }, nil } @@ -96,7 +111,9 @@ func (fifo *LevelUniqueQueueByteFIFO) Has(data []byte) (bool, error) { // Close this fifo func (fifo *LevelUniqueQueueByteFIFO) Close() error { - return fifo.internal.Close() + err := fifo.internal.Close() + _ = nosql.GetManager().CloseLevelDB(fifo.connection) + return err } func init() { diff --git a/modules/queue/unique_queue_redis.go b/modules/queue/unique_queue_redis.go index 9404369075..67efc66bc9 100644 --- a/modules/queue/unique_queue_redis.go +++ b/modules/queue/unique_queue_redis.go @@ -4,7 +4,7 @@ package queue -import "github.com/go-redis/redis" +import "github.com/go-redis/redis/v7" // RedisUniqueQueueType is the type for redis queue const RedisUniqueQueueType Type = "unique-redis" diff --git a/vendor/gitea.com/macaron/session/redis/redis.go b/modules/session/redis.go similarity index 82% rename from vendor/gitea.com/macaron/session/redis/redis.go rename to modules/session/redis.go index 5f242d6b37..c88ebd5769 100644 --- a/vendor/gitea.com/macaron/session/redis/redis.go +++ b/modules/session/redis.go @@ -1,5 +1,6 @@ // Copyright 2013 Beego Authors // Copyright 2014 The Macaron Authors +// Copyright 2020 The Gitea Authors. All rights reserved. // // 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 @@ -17,19 +18,18 @@ package session import ( "fmt" - "strings" "sync" "time" + "code.gitea.io/gitea/modules/nosql" + "gitea.com/macaron/session" - "github.com/go-redis/redis" - "github.com/unknwon/com" - "gopkg.in/ini.v1" + "github.com/go-redis/redis/v7" ) // RedisStore represents a redis session store implementation. type RedisStore struct { - c *redis.Client + c redis.UniversalClient prefix, sid string duration time.Duration lock sync.RWMutex @@ -37,7 +37,7 @@ type RedisStore struct { } // NewRedisStore creates and returns a redis session store. -func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { +func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { return &RedisStore{ c: c, prefix: prefix, @@ -104,7 +104,7 @@ func (s *RedisStore) Flush() error { // RedisProvider represents a redis session provider implementation. type RedisProvider struct { - c *redis.Client + c redis.UniversalClient duration time.Duration prefix string } @@ -117,39 +117,16 @@ func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { return err } - cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) - if err != nil { - return err - } + uri := nosql.ToRedisURI(configs) - opt := &redis.Options{ - Network: "tcp", - } - for k, v := range cfg.Section("").KeysHash() { + for k, v := range uri.Query() { switch k { - case "network": - opt.Network = v - case "addr": - opt.Addr = v - case "password": - opt.Password = v - case "db": - opt.DB = com.StrTo(v).MustInt() - case "pool_size": - opt.PoolSize = com.StrTo(v).MustInt() - case "idle_timeout": - opt.IdleTimeout, err = time.ParseDuration(v + "s") - if err != nil { - return fmt.Errorf("error parsing idle timeout: %v", err) - } case "prefix": - p.prefix = v - default: - return fmt.Errorf("session/redis: unsupported option '%s'", k) + p.prefix = v[0] } } - p.c = redis.NewClient(opt) + p.c = nosql.GetManager().GetRedisClient(uri.String()) return p.c.Ping().Err() } @@ -228,11 +205,11 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err // Count counts and returns number of sessions. func (p *RedisProvider) Count() int { - return int(p.c.DbSize().Val()) + return int(p.c.DBSize().Val()) } // GC calls GC to clean expired sessions. -func (_ *RedisProvider) GC() {} +func (*RedisProvider) GC() {} func init() { session.Register("redis", &RedisProvider{}) diff --git a/modules/session/virtual.go b/modules/session/virtual.go index c8e1e210cb..1139cfe89c 100644 --- a/modules/session/virtual.go +++ b/modules/session/virtual.go @@ -15,7 +15,6 @@ import ( mysql "gitea.com/macaron/session/mysql" nodb "gitea.com/macaron/session/nodb" postgres "gitea.com/macaron/session/postgres" - redis "gitea.com/macaron/session/redis" ) // VirtualSessionProvider represents a shadowed session provider implementation. @@ -40,7 +39,7 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error { case "file": o.provider = &session.FileProvider{} case "redis": - o.provider = &redis.RedisProvider{} + o.provider = &RedisProvider{} case "mysql": o.provider = &mysql.MysqlProvider{} case "postgres": diff --git a/vendor/gitea.com/macaron/cache/redis/redis.goconvey b/vendor/gitea.com/macaron/cache/redis/redis.goconvey deleted file mode 100644 index 8485e986e4..0000000000 --- a/vendor/gitea.com/macaron/cache/redis/redis.goconvey +++ /dev/null @@ -1 +0,0 @@ -ignore \ No newline at end of file diff --git a/vendor/gitea.com/macaron/session/redis/redis.goconvey b/vendor/gitea.com/macaron/session/redis/redis.goconvey deleted file mode 100644 index 8485e986e4..0000000000 --- a/vendor/gitea.com/macaron/session/redis/redis.goconvey +++ /dev/null @@ -1 +0,0 @@ -ignore \ No newline at end of file diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml deleted file mode 100644 index 6b110b4cbb..0000000000 --- a/vendor/github.com/go-redis/redis/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -language: go - -services: - - redis-server - -go: - - 1.9.x - - 1.10.x - - 1.11.x - - tip - -matrix: - allow_failures: - - go: tip - -install: - - go get github.com/onsi/ginkgo - - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/CHANGELOG.md b/vendor/github.com/go-redis/redis/CHANGELOG.md deleted file mode 100644 index 19645661a4..0000000000 --- a/vendor/github.com/go-redis/redis/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## Unreleased - -- Cluster and Ring pipelines process commands for each node in its own goroutine. - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/internal/error.go b/vendor/github.com/go-redis/redis/internal/error.go deleted file mode 100644 index 34f6bd4dc7..0000000000 --- a/vendor/github.com/go-redis/redis/internal/error.go +++ /dev/null @@ -1,89 +0,0 @@ -package internal - -import ( - "io" - "net" - "strings" - - "github.com/go-redis/redis/internal/proto" -) - -func IsRetryableError(err error, retryTimeout bool) bool { - if err == nil { - return false - } - if err == io.EOF { - return true - } - if netErr, ok := err.(net.Error); ok { - if netErr.Timeout() { - return retryTimeout - } - return true - } - s := err.Error() - if s == "ERR max number of clients reached" { - return true - } - if strings.HasPrefix(s, "LOADING ") { - return true - } - if strings.HasPrefix(s, "READONLY ") { - return true - } - if strings.HasPrefix(s, "CLUSTERDOWN ") { - return true - } - return false -} - -func IsRedisError(err error) bool { - _, ok := err.(proto.RedisError) - return ok -} - -func IsBadConn(err error, allowTimeout bool) bool { - if err == nil { - return false - } - if IsRedisError(err) { - // #790 - return IsReadOnlyError(err) - } - 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 ") -} - -func IsReadOnlyError(err error) bool { - return strings.HasPrefix(err.Error(), "READONLY ") -} diff --git a/vendor/github.com/go-redis/redis/internal/log.go b/vendor/github.com/go-redis/redis/internal/log.go deleted file mode 100644 index fd14222eee..0000000000 --- a/vendor/github.com/go-redis/redis/internal/log.go +++ /dev/null @@ -1,15 +0,0 @@ -package 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...)) -} diff --git a/vendor/github.com/go-redis/redis/internal/pool/conn.go b/vendor/github.com/go-redis/redis/internal/pool/conn.go deleted file mode 100644 index 1095bfe59b..0000000000 --- a/vendor/github.com/go-redis/redis/internal/pool/conn.go +++ /dev/null @@ -1,93 +0,0 @@ -package 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 - rdLocked bool - wr *proto.Writer - - InitedAt time.Time - pooled bool - usedAt atomic.Value -} - -func NewConn(netConn net.Conn) *Conn { - cn := &Conn{ - netConn: netConn, - } - cn.rd = proto.NewReader(netConn) - cn.wr = proto.NewWriter(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) - cn.wr.Reset(netConn) -} - -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) WithReader(timeout time.Duration, fn func(rd *proto.Reader) error) error { - _ = cn.setReadTimeout(timeout) - return fn(cn.rd) -} - -func (cn *Conn) WithWriter(timeout time.Duration, fn func(wr *proto.Writer) error) error { - _ = cn.setWriteTimeout(timeout) - - firstErr := fn(cn.wr) - err := cn.wr.Flush() - if err != nil && firstErr == nil { - firstErr = err - } - return firstErr -} - -func (cn *Conn) Close() error { - return cn.netConn.Close() -} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool_single.go b/vendor/github.com/go-redis/redis/internal/pool/pool_single.go deleted file mode 100644 index b35b78afbd..0000000000 --- a/vendor/github.com/go-redis/redis/internal/pool/pool_single.go +++ /dev/null @@ -1,53 +0,0 @@ -package 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, error) { - return p.cn, nil -} - -func (p *SingleConnPool) Put(cn *Conn) { - if p.cn != cn { - panic("p.cn != cn") - } -} - -func (p *SingleConnPool) Remove(cn *Conn) { - if p.cn != cn { - panic("p.cn != cn") - } -} - -func (p *SingleConnPool) Len() int { - return 1 -} - -func (p *SingleConnPool) IdleLen() int { - return 0 -} - -func (p *SingleConnPool) Stats() *Stats { - return nil -} - -func (p *SingleConnPool) Close() error { - return nil -} diff --git a/vendor/github.com/go-redis/redis/internal/util.go b/vendor/github.com/go-redis/redis/internal/util.go deleted file mode 100644 index ffd2353e0e..0000000000 --- a/vendor/github.com/go-redis/redis/internal/util.go +++ /dev/null @@ -1,29 +0,0 @@ -package 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 -} diff --git a/vendor/github.com/go-redis/redis/redis.go b/vendor/github.com/go-redis/redis/redis.go deleted file mode 100644 index aca30648f5..0000000000 --- a/vendor/github.com/go-redis/redis/redis.go +++ /dev/null @@ -1,580 +0,0 @@ -package redis - -import ( - "context" - "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 returns 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 -} - -type baseClient struct { - opt *Options - connPool pool.Pooler - limiter Limiter - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error - - onClose func() error // hook called when client is closed -} - -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.InitedAt.IsZero() { - if err := c.initConn(cn); err != nil { - _ = c.connPool.CloseConn(cn) - return nil, err - } - } - - return cn, nil -} - -func (c *baseClient) getConn() (*pool.Conn, error) { - if c.limiter != nil { - err := c.limiter.Allow() - if err != nil { - return nil, err - } - } - - cn, err := c._getConn() - if err != nil { - if c.limiter != nil { - c.limiter.ReportResult(err) - } - return nil, err - } - return cn, nil -} - -func (c *baseClient) _getConn() (*pool.Conn, error) { - cn, err := c.connPool.Get() - if err != nil { - return nil, err - } - - if cn.InitedAt.IsZero() { - err := c.initConn(cn) - if err != nil { - c.connPool.Remove(cn) - return nil, err - } - } - - return cn, nil -} - -func (c *baseClient) releaseConn(cn *pool.Conn, err error) { - if c.limiter != nil { - c.limiter.ReportResult(err) - } - - if internal.IsBadConn(err, false) { - c.connPool.Remove(cn) - } else { - c.connPool.Put(cn) - } -} - -func (c *baseClient) releaseConnStrict(cn *pool.Conn, err error) { - if c.limiter != nil { - c.limiter.ReportResult(err) - } - - if err == nil || internal.IsRedisError(err) { - c.connPool.Put(cn) - } else { - c.connPool.Remove(cn) - } -} - -func (c *baseClient) initConn(cn *pool.Conn) error { - cn.InitedAt = time.Now() - - if c.opt.Password == "" && - c.opt.DB == 0 && - !c.opt.readOnly && - c.opt.OnConnect == nil { - return nil - } - - conn := newConn(c.opt, cn) - _, 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 -} - -// Do creates a Cmd from the args and processes the cmd. -func (c *baseClient) Do(args ...interface{}) *Cmd { - cmd := NewCmd(args...) - _ = c.Process(cmd) - return cmd -} - -// WrapProcess wraps function that processes Redis commands. -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 - } - - err = cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmd) - }) - if err != nil { - c.releaseConn(cn, err) - cmd.setErr(err) - if internal.IsRetryableError(err, true) { - continue - } - return err - } - - err = cn.WithReader(c.cmdTimeout(cmd), func(rd *proto.Reader) error { - return cmd.readReply(rd) - }) - 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 { - t := *timeout - if t == 0 { - return 0 - } - return t + 10*time.Second - } - 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) - c.releaseConnStrict(cn, err) - - if !canRetry || !internal.IsRetryableError(err, true) { - break - } - } - return cmdsFirstErr(cmds) -} - -func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - return true, err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return pipelineReadCmds(rd, cmds) - }) - return true, err -} - -func pipelineReadCmds(rd *proto.Reader, cmds []Cmder) error { - for _, cmd := range cmds { - err := cmd.readReply(rd) - if err != nil && !internal.IsRedisError(err) { - return err - } - } - return nil -} - -func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - return true, err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := txPipelineReadQueued(rd, cmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) - }) - return false, err -} - -func txPipelineWriteMulti(wr *proto.Writer, 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(wr, multiExec...) -} - -func txPipelineReadQueued(rd *proto.Reader, cmds []Cmder) error { - // Parse queued replies. - var statusCmd StatusCmd - err := statusCmd.readReply(rd) - if err != nil { - return err - } - - for range cmds { - err = statusCmd.readReply(rd) - if err != nil && !internal.IsRedisError(err) { - return err - } - } - - // Parse number of replies. - line, err := rd.ReadLine() - if err != nil { - if err == Nil { - err = TxFailedErr - } - return err - } - - switch line[0] { - case proto.ErrorReply: - 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 - - ctx context.Context -} - -// NewClient returns a client to the Redis Server specified by Options. -func NewClient(opt *Options) *Client { - opt.init() - - c := Client{ - baseClient: baseClient{ - opt: opt, - connPool: newConnPool(opt), - }, - } - c.baseClient.init() - c.init() - - return &c -} - -func (c *Client) init() { - c.cmdable.setProcessor(c.Process) -} - -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.clone() - c2.ctx = ctx - return c2 -} - -func (c *Client) clone() *Client { - cp := *c - cp.init() - return &cp -} - -// Options returns read-only Options that were used to create the client. -func (c *Client) Options() *Options { - return c.opt -} - -func (c *Client) SetLimiter(l Limiter) *Client { - c.limiter = l - return c -} - -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 { - pubsub := &PubSub{ - opt: c.opt, - - newConn: func(channels []string) (*pool.Conn, error) { - return c.newConn() - }, - closeConn: c.connPool.CloseConn, - } - pubsub.init() - return pubsub -} - -// Subscribe subscribes the client to the specified channels. -// Channels can be omitted to create empty subscription. -// Note that this method does not wait on a response from Redis, so the -// subscription may not be active immediately. To force the connection to wait, -// you may call the Receive() method on the returned *PubSub like so: -// -// sub := client.Subscribe(queryResp) -// iface, err := sub.Receive() -// if err != nil { -// // handle error -// } -// -// // Should be *Subscription, but others are possible if other actions have been -// // taken on sub since it was created. -// switch iface.(type) { -// case *Subscription: -// // subscribe succeeded -// case *Message: -// // received first message -// case *Pong: -// // pong received -// default: -// // handle error -// } -// -// ch := sub.Channel() -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 newConn(opt *Options, cn *pool.Conn) *Conn { - c := Conn{ - baseClient: baseClient{ - opt: opt, - connPool: pool.NewSingleConnPool(cn), - }, - } - c.baseClient.init() - c.statefulCmdable.setProcessor(c.Process) - return &c -} - -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 -} diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/v7/.gitignore similarity index 100% rename from vendor/github.com/go-redis/redis/.gitignore rename to vendor/github.com/go-redis/redis/v7/.gitignore diff --git a/vendor/github.com/go-redis/redis/v7/.golangci.yml b/vendor/github.com/go-redis/redis/v7/.golangci.yml new file mode 100644 index 0000000000..912dab1ef3 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/.golangci.yml @@ -0,0 +1,15 @@ +run: + concurrency: 8 + deadline: 5m + tests: false +linters: + enable-all: true + disable: + - funlen + - gochecknoglobals + - gocognit + - goconst + - godox + - gosec + - maligned + - wsl diff --git a/vendor/github.com/go-redis/redis/v7/.travis.yml b/vendor/github.com/go-redis/redis/v7/.travis.yml new file mode 100644 index 0000000000..3f93932bc8 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/.travis.yml @@ -0,0 +1,22 @@ +dist: xenial +language: go + +services: + - redis-server + +go: + - 1.12.x + - 1.13.x + - tip + +matrix: + allow_failures: + - go: tip + +env: + - GO111MODULE=on + +go_import_path: github.com/go-redis/redis + +before_install: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 diff --git a/vendor/github.com/go-redis/redis/v7/CHANGELOG.md b/vendor/github.com/go-redis/redis/v7/CHANGELOG.md new file mode 100644 index 0000000000..bd4eccff24 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +## v7.2 + +- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users. + +## v7.1 + +- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer` interface. + +## v7 + +- *Important*. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a transactional pipeline. +- WrapProcess is replaced with more convenient AddHook that has access to context.Context. +- WithContext now can not be used to create a shallow copy of the client. +- New methods ProcessContext, DoContext, and ExecContext. +- Client respects Context.Deadline when setting net.Conn deadline. +- Client listens on Context.Done while waiting for a connection from the pool and returns an error when context context is cancelled. +- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow detecting reconnections. +- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse the time. +- `SetLimiter` is removed and added `Options.Limiter` instead. +- `HMSet` is deprecated as of Redis v4. + +## v6.15 + +- Cluster and Ring pipelines process commands for each node in its own goroutine. + +## 6.14 + +- Added Options.MinIdleConns. +- Added Options.MaxConnAge. +- PoolStats.FreeConns is renamed to PoolStats.IdleConns. +- Add Client.Do to simplify creating custom commands. +- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. +- Lower memory usage. + +## v6.13 + +- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. +- Cluster client was optimized to use much less memory when reloading cluster state. +- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. +- Dialer.KeepAlive is set to 5 minutes by default. + +## v6.12 + +- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/v7/LICENSE similarity index 100% rename from vendor/github.com/go-redis/redis/LICENSE rename to vendor/github.com/go-redis/redis/v7/LICENSE diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/v7/Makefile similarity index 59% rename from vendor/github.com/go-redis/redis/Makefile rename to vendor/github.com/go-redis/redis/v7/Makefile index fa3b4e004f..86609c6e07 100644 --- a/vendor/github.com/go-redis/redis/Makefile +++ b/vendor/github.com/go-redis/redis/v7/Makefile @@ -1,10 +1,9 @@ all: testdeps go test ./... go test ./... -short -race + go test ./... -run=NONE -bench=. -benchmem env GOOS=linux GOARCH=386 go test ./... - go vet - go get github.com/gordonklaus/ineffassign - ineffassign . + golangci-lint run testdeps: testdata/redis/src/redis-server @@ -15,8 +14,7 @@ bench: testdeps testdata/redis: mkdir -p $@ - wget -qO- https://github.com/antirez/redis/archive/5.0.tar.gz | tar xvz --strip-components=1 -C $@ + wget -qO- http://download.redis.io/redis-stable.tar.gz | tar xvz --strip-components=1 -C $@ testdata/redis/src/redis-server: testdata/redis - sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ threshold { @@ -558,10 +571,13 @@ func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { return node, nil } -func (c *clusterState) slotRandomNode(slot int) *clusterNode { +func (c *clusterState) slotRandomNode(slot int) (*clusterNode, error) { nodes := c.slotNodes(slot) + if len(nodes) == 0 { + return c.nodes.Random() + } n := rand.Intn(len(nodes)) - return nodes[n] + return nodes[n], nil } func (c *clusterState) slotNodes(slot int) []*clusterNode { @@ -639,22 +655,21 @@ func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { //------------------------------------------------------------------------------ +type clusterClient struct { + opt *ClusterOptions + nodes *clusterNodes + state *clusterStateHolder //nolint:structcheck + cmdsInfoCache *cmdsInfoCache //nolint:structcheck +} + // 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 { + *clusterClient cmdable - + hooks ctx context.Context - - opt *ClusterOptions - nodes *clusterNodes - state *clusterStateHolder - cmdsInfoCache *cmdsInfoCache - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error } // NewClusterClient returns a Redis Cluster client as described in @@ -663,17 +678,16 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient { opt.init() c := &ClusterClient{ - opt: opt, - nodes: newClusterNodes(opt), + clusterClient: &clusterClient{ + opt: opt, + nodes: newClusterNodes(opt), + }, + ctx: context.Background(), } c.state = newClusterStateHolder(c.loadState) c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) + c.cmdable = c.Process - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline - - c.init() if opt.IdleCheckFrequency > 0 { go c.reaper(opt.IdleCheckFrequency) } @@ -681,37 +695,19 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient { return c } -func (c *ClusterClient) init() { - c.cmdable.setProcessor(c.Process) -} - -// ReloadState reloads cluster state. If available it calls ClusterSlots func -// to get cluster slots information. -func (c *ClusterClient) ReloadState() error { - _, err := c.state.Reload() - return err -} - func (c *ClusterClient) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() + return c.ctx } func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { if ctx == nil { panic("nil context") } - c2 := c.copy() - c2.ctx = ctx - return c2 -} - -func (c *ClusterClient) copy() *ClusterClient { - cp := *c - cp.init() - return &cp + clone := *c + clone.cmdable = clone.Process + clone.hooks.lock() + clone.ctx = ctx + return &clone } // Options returns read-only Options that were used to create the client. @@ -719,164 +715,10 @@ func (c *ClusterClient) Options() *ClusterOptions { return c.opt } -func (c *ClusterClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.Get(addr) - if err != nil { - return nil, err - } - if node == nil { - continue - } - - info, err := node.Client.Command().Result() - if err == nil { - return info, nil - } - if firstErr == nil { - firstErr = err - } - } - return nil, firstErr -} - -func (c *ClusterClient) cmdInfo(name string) *CommandInfo { - cmdsInfo, err := c.cmdsInfoCache.Get() - if err != nil { - return nil - } - - info := cmdsInfo[name] - if info == nil { - internal.Logf("info for cmd=%s not found", name) - } - return info -} - -func cmdSlot(cmd Cmder, pos int) int { - if pos == 0 { - return hashtag.RandomSlot() - } - firstKey := cmd.stringArg(pos) - return hashtag.Slot(firstKey) -} - -func (c *ClusterClient) cmdSlot(cmd Cmder) int { - args := cmd.Args() - if args[0] == "cluster" && args[1] == "getkeysinslot" { - return args[2].(int) - } - - cmdInfo := c.cmdInfo(cmd.Name()) - return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) -} - -func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return 0, nil, err - } - - cmdInfo := c.cmdInfo(cmd.Name()) - slot := c.cmdSlot(cmd) - - if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { - if c.opt.RouteByLatency { - node, err := state.slotClosestNode(slot) - return slot, node, err - } - - if c.opt.RouteRandomly { - node := state.slotRandomNode(slot) - return slot, node, nil - } - - node, err := state.slotSlaveNode(slot) - return slot, node, err - } - - node, err := state.slotMasterNode(slot) - return slot, node, err -} - -func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return nil, err - } - - nodes := state.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { - if len(keys) == 0 { - return fmt.Errorf("redis: Watch requires at least one key") - } - - slot := hashtag.Slot(keys[0]) - for _, key := range keys[1:] { - if hashtag.Slot(key) != slot { - err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") - return err - } - } - - node, err := c.slotMasterNode(slot) - if err != nil { - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - err = node.Client.Watch(fn, keys...) - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - moved, ask, addr := internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - return err - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node, err = c.slotMasterNode(slot) - if err != nil { - return err - } - continue - } - - if internal.IsRetryableError(err, true) { - continue - } - - return err - } - +// ReloadState reloads cluster state. If available it calls ClusterSlots func +// to get cluster slots information. +func (c *ClusterClient) ReloadState() error { + _, err := c.state.Reload() return err } @@ -890,99 +732,111 @@ func (c *ClusterClient) Close() error { // Do creates a Cmd from the args and processes the cmd. func (c *ClusterClient) Do(args ...interface{}) *Cmd { + return c.DoContext(c.ctx, args...) +} + +func (c *ClusterClient) DoContext(ctx context.Context, args ...interface{}) *Cmd { cmd := NewCmd(args...) - c.Process(cmd) + _ = c.ProcessContext(ctx, cmd) return cmd } -func (c *ClusterClient) WrapProcess( - fn func(oldProcess func(Cmder) error) func(Cmder) error, -) { - c.process = fn(c.process) -} - func (c *ClusterClient) Process(cmd Cmder) error { - return c.process(cmd) + return c.ProcessContext(c.ctx, cmd) } -func (c *ClusterClient) defaultProcess(cmd Cmder) error { +func (c *ClusterClient) ProcessContext(ctx context.Context, cmd Cmder) error { + return c.hooks.process(ctx, cmd, c.process) +} + +func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error { + err := c._process(ctx, cmd) + if err != nil { + cmd.SetErr(err) + return err + } + return nil +} + +func (c *ClusterClient) _process(ctx context.Context, cmd Cmder) error { + cmdInfo := c.cmdInfo(cmd.Name()) + slot := c.cmdSlot(cmd) + var node *clusterNode var ask bool + var lastErr error for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } } if node == nil { var err error - _, node, err = c.cmdSlotAndNode(cmd) + node, err = c.cmdNode(cmdInfo, slot) if err != nil { - cmd.setErr(err) - break + return err } } - var err error if ask { pipe := node.Client.Pipeline() - _ = pipe.Process(NewCmd("ASKING")) + _ = pipe.Process(NewCmd("asking")) _ = pipe.Process(cmd) - _, err = pipe.Exec() + _, lastErr = pipe.ExecContext(ctx) _ = pipe.Close() ask = false } else { - err = node.Client.Process(cmd) + lastErr = node.Client.ProcessContext(ctx, cmd) } // If there is no error - we are done. - if err == nil { - break + if lastErr == nil { + return nil } - if err != Nil { + if lastErr != Nil { c.state.LazyReload() } + if lastErr == pool.ErrClosed || isReadOnlyError(lastErr) { + node = nil + continue + } // If slave is loading - pick another node. - if c.opt.ReadOnly && internal.IsLoadingError(err) { - node.MarkAsLoading() + if c.opt.ReadOnly && isLoadingError(lastErr) { + node.MarkAsFailing() node = nil continue } var moved bool var addr string - moved, ask, addr = internal.IsMovedError(err) + moved, ask, addr = isMovedError(lastErr) if moved || ask { - node, err = c.nodes.GetOrCreate(addr) + var err error + node, err = c.nodes.Get(addr) if err != nil { - break + return err } continue } - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node = nil - continue - } - - if internal.IsRetryableError(err, true) { + if isRetryableError(lastErr, cmd.readTimeout() == nil) { // First retry the same node. if attempt == 0 { continue } - // Second try random node. - node, err = c.nodes.Random() - if err != nil { - break - } + // Second try another node. + node.MarkAsFailing() + node = nil continue } - break + return lastErr } - - return cmd.Err() + return lastErr } // ForEachMaster concurrently calls the fn on each master node in the cluster. @@ -995,6 +849,7 @@ func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + for _, master := range state.Masters { wg.Add(1) go func(node *clusterNode) { @@ -1008,6 +863,7 @@ func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { } }(master) } + wg.Wait() select { @@ -1028,6 +884,7 @@ func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + for _, slave := range state.Slaves { wg.Add(1) go func(node *clusterNode) { @@ -1041,6 +898,7 @@ func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { } }(slave) } + wg.Wait() select { @@ -1061,6 +919,7 @@ func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + worker := func(node *clusterNode) { defer wg.Done() err := fn(node.Client) @@ -1082,6 +941,7 @@ func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { } wg.Wait() + select { case err := <-errCh: return err @@ -1140,7 +1000,7 @@ func (c *ClusterClient) loadState() (*clusterState, error) { var firstErr error for _, addr := range addrs { - node, err := c.nodes.GetOrCreate(addr) + node, err := c.nodes.Get(addr) if err != nil { if firstErr == nil { firstErr = err @@ -1176,7 +1036,7 @@ func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { for _, node := range nodes { _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() if err != nil { - internal.Logf("ReapStaleConns failed: %s", err) + internal.Logger.Printf("ReapStaleConns failed: %s", err) } } } @@ -1184,9 +1044,10 @@ func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { func (c *ClusterClient) Pipeline() Pipeliner { pipe := Pipeline{ + ctx: c.ctx, exec: c.processPipeline, } - pipe.statefulCmdable.setProcessor(pipe.Process) + pipe.init() return &pipe } @@ -1194,15 +1055,13 @@ 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) processPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processPipeline(ctx, cmds, c._processPipeline) } -func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { +func (c *ClusterClient) _processPipeline(ctx context.Context, cmds []Cmder) error { cmdsMap := newCmdsMap() - err := c.mapCmdsByNode(cmds, cmdsMap) + err := c.mapCmdsByNode(cmdsMap, cmds) if err != nil { setCmdsErr(cmds, err) return err @@ -1210,7 +1069,10 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } } failedCmds := newCmdsMap() @@ -1221,18 +1083,17 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { go func(node *clusterNode, cmds []Cmder) { defer wg.Done() - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } + err := c._processPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { return } - - err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } + } else { + setCmdsErr(cmds, err) + } }(node, cmds) } @@ -1246,40 +1107,31 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { return cmdsFirstErr(cmds) } -type cmdsMap struct { - mu sync.Mutex - m map[*clusterNode][]Cmder -} - -func newCmdsMap() *cmdsMap { - return &cmdsMap{ - m: make(map[*clusterNode][]Cmder), - } -} - -func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error { +func (c *ClusterClient) mapCmdsByNode(cmdsMap *cmdsMap, cmds []Cmder) error { state, err := c.state.Get() if err != nil { - setCmdsErr(cmds, err) return err } - cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) - for _, cmd := range cmds { - var node *clusterNode - var err error - if cmdsAreReadOnly { - _, node, err = c.cmdSlotAndNode(cmd) - } else { + if c.opt.ReadOnly && c.cmdsAreReadOnly(cmds) { + for _, cmd := range cmds { slot := c.cmdSlot(cmd) - node, err = state.slotMasterNode(slot) + node, err := c.slotReadOnlyNode(state, slot) + if err != nil { + return err + } + cmdsMap.Add(node, cmd) } + return nil + } + + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + node, err := state.slotMasterNode(slot) if err != nil { return err } - cmdsMap.mu.Lock() - cmdsMap.m[node] = append(cmdsMap.m[node], cmd) - cmdsMap.mu.Unlock() + cmdsMap.Add(node, cmd) } return nil } @@ -1294,94 +1146,83 @@ func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { return true } -func (c *ClusterClient) pipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, +func (c *ClusterClient) _processPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, ) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } + return node.Client.hooks.processPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return c.pipelineReadCmds(node, rd, cmds, failedCmds) + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + return c.pipelineReadCmds(node, rd, cmds, failedCmds) + }) + }) }) - return err } func (c *ClusterClient) pipelineReadCmds( node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, ) error { - var firstErr error for _, cmd := range cmds { err := cmd.readReply(rd) if err == nil { continue } - if c.checkMovedErr(cmd, err, failedCmds) { continue } - if internal.IsRedisError(err) { + if c.opt.ReadOnly && isLoadingError(err) { + node.MarkAsFailing() + return err + } + if isRedisError(err) { continue } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - if firstErr == nil { - firstErr = err - } + return err } - return firstErr + return nil } func (c *ClusterClient) checkMovedErr( cmd Cmder, err error, failedCmds *cmdsMap, ) bool { - moved, ask, addr := internal.IsMovedError(err) + moved, ask, addr := isMovedError(err) + if !moved && !ask { + return false + } + + node, err := c.nodes.Get(addr) + if err != nil { + return false + } if moved { c.state.LazyReload() - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() + failedCmds.Add(node, cmd) return true } if ask { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd) - failedCmds.mu.Unlock() + failedCmds.Add(node, NewCmd("asking"), cmd) return true } - return false + panic("not reached") } // TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. func (c *ClusterClient) TxPipeline() Pipeliner { pipe := Pipeline{ + ctx: c.ctx, exec: c.processTxPipeline, } - pipe.statefulCmdable.setProcessor(pipe.Process) + pipe.init() return &pipe } @@ -1389,9 +1230,14 @@ func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.TxPipeline().Pipelined(fn) } -func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { +func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processPipeline(ctx, cmds, c._processTxPipeline) +} + +func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) error { state, err := c.state.Get() if err != nil { + setCmdsErr(cmds, err) return err } @@ -1402,11 +1248,14 @@ func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { setCmdsErr(cmds, err) continue } - cmdsMap := map[*clusterNode][]Cmder{node: cmds} + cmdsMap := map[*clusterNode][]Cmder{node: cmds} for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } } failedCmds := newCmdsMap() @@ -1417,18 +1266,17 @@ func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { go func(node *clusterNode, cmds []Cmder) { defer wg.Done() - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } + err := c._processTxPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { return } - - err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } + } else { + setCmdsErr(cmds, err) + } }(node, cmds) } @@ -1452,50 +1300,51 @@ func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { return cmdsMap } -func (c *ClusterClient) txPipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, +func (c *ClusterClient) _processTxPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, ) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } + return node.Client.hooks.processTxPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := c.txPipelineReadQueued(rd, cmds, failedCmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + statusCmd := cmds[0].(*StatusCmd) + // Trim multi and exec. + cmds = cmds[1 : len(cmds)-1] + + err := c.txPipelineReadQueued(rd, statusCmd, cmds, failedCmds) + if err != nil { + moved, ask, addr := isMovedError(err) + if moved || ask { + return c.cmdsMoved(cmds, moved, ask, addr, failedCmds) + } + return err + } + + return pipelineReadCmds(rd, cmds) + }) + }) }) - return err } func (c *ClusterClient) txPipelineReadQueued( - rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, + rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder, failedCmds *cmdsMap, ) error { // Parse queued replies. - var statusCmd StatusCmd if err := statusCmd.readReply(rd); err != nil { return err } for _, cmd := range cmds { err := statusCmd.readReply(rd) - if err == nil { + if err == nil || c.checkMovedErr(cmd, err, failedCmds) || isRedisError(err) { continue } - - if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { - continue - } - return err } @@ -1510,23 +1359,106 @@ func (c *ClusterClient) txPipelineReadQueued( switch line[0] { case proto.ErrorReply: - err := proto.ParseErrorReply(line) - for _, cmd := range cmds { - if !c.checkMovedErr(cmd, err, failedCmds) { - break - } - } - return err + return proto.ParseErrorReply(line) case proto.ArrayReply: // ok default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err + return fmt.Errorf("redis: expected '*', but got line %q", line) } return nil } +func (c *ClusterClient) cmdsMoved( + cmds []Cmder, moved, ask bool, addr string, failedCmds *cmdsMap, +) error { + node, err := c.nodes.Get(addr) + if err != nil { + return err + } + + if moved { + c.state.LazyReload() + for _, cmd := range cmds { + failedCmds.Add(node, cmd) + } + return nil + } + + if ask { + for _, cmd := range cmds { + failedCmds.Add(node, NewCmd("asking"), cmd) + } + return nil + } + + return nil +} + +func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { + return c.WatchContext(c.ctx, fn, keys...) +} + +func (c *ClusterClient) WatchContext(ctx context.Context, fn func(*Tx) error, keys ...string) error { + if len(keys) == 0 { + return fmt.Errorf("redis: Watch requires at least one key") + } + + slot := hashtag.Slot(keys[0]) + for _, key := range keys[1:] { + if hashtag.Slot(key) != slot { + err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") + return err + } + } + + node, err := c.slotMasterNode(slot) + if err != nil { + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } + } + + err = node.Client.WatchContext(ctx, fn, keys...) + if err == nil { + break + } + if err != Nil { + c.state.LazyReload() + } + + moved, ask, addr := isMovedError(err) + if moved || ask { + node, err = c.nodes.Get(addr) + if err != nil { + return err + } + continue + } + + if err == pool.ErrClosed || isReadOnlyError(err) { + node, err = c.slotMasterNode(slot) + if err != nil { + return err + } + continue + } + + if isRetryableError(err, true) { + continue + } + + return err + } + + return err +} + func (c *ClusterClient) pubSub() *PubSub { var node *clusterNode pubsub := &PubSub{ @@ -1537,16 +1469,21 @@ func (c *ClusterClient) pubSub() *PubSub { panic("node != nil") } - slot := hashtag.Slot(channels[0]) - var err error - node, err = c.slotMasterNode(slot) + if len(channels) > 0 { + slot := hashtag.Slot(channels[0]) + node, err = c.slotMasterNode(slot) + } else { + node, err = c.nodes.Random() + } if err != nil { return nil, err } - cn, err := node.Client.newConn() + cn, err := node.Client.newConn(context.TODO()) if err != nil { + node = nil + return nil, err } @@ -1583,6 +1520,98 @@ func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { return pubsub } +func (c *ClusterClient) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { + addrs, err := c.nodes.Addrs() + if err != nil { + return nil, err + } + + var firstErr error + for _, addr := range addrs { + node, err := c.nodes.Get(addr) + if err != nil { + return nil, err + } + if node == nil { + continue + } + + info, err := node.Client.Command().Result() + if err == nil { + return info, nil + } + if firstErr == nil { + firstErr = err + } + } + return nil, firstErr +} + +func (c *ClusterClient) cmdInfo(name string) *CommandInfo { + cmdsInfo, err := c.cmdsInfoCache.Get() + if err != nil { + return nil + } + + info := cmdsInfo[name] + if info == nil { + internal.Logger.Printf("info for cmd=%s not found", name) + } + return info +} + +func (c *ClusterClient) cmdSlot(cmd Cmder) int { + args := cmd.Args() + if args[0] == "cluster" && args[1] == "getkeysinslot" { + return args[2].(int) + } + + cmdInfo := c.cmdInfo(cmd.Name()) + return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) +} + +func cmdSlot(cmd Cmder, pos int) int { + if pos == 0 { + return hashtag.RandomSlot() + } + firstKey := cmd.stringArg(pos) + return hashtag.Slot(firstKey) +} + +func (c *ClusterClient) cmdNode(cmdInfo *CommandInfo, slot int) (*clusterNode, error) { + state, err := c.state.Get() + if err != nil { + return nil, err + } + + if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { + return c.slotReadOnlyNode(state, slot) + } + return state.slotMasterNode(slot) +} + +func (c *clusterClient) slotReadOnlyNode(state *clusterState, slot int) (*clusterNode, error) { + if c.opt.RouteByLatency { + return state.slotClosestNode(slot) + } + if c.opt.RouteRandomly { + return state.slotRandomNode(slot) + } + return state.slotSlaveNode(slot) +} + +func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { + state, err := c.state.Get() + if err != nil { + return nil, err + } + return state.slotMasterNode(slot) +} + func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { for _, n := range nodes { if n == node { @@ -1619,3 +1648,22 @@ func remove(ss []string, es ...string) []string { } return ss } + +//------------------------------------------------------------------------------ + +type cmdsMap struct { + mu sync.Mutex + m map[*clusterNode][]Cmder +} + +func newCmdsMap() *cmdsMap { + return &cmdsMap{ + m: make(map[*clusterNode][]Cmder), + } +} + +func (m *cmdsMap) Add(node *clusterNode, cmds ...Cmder) { + m.mu.Lock() + m.m[node] = append(m.m[node], cmds...) + m.mu.Unlock() +} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/v7/cluster_commands.go similarity index 95% rename from vendor/github.com/go-redis/redis/cluster_commands.go rename to vendor/github.com/go-redis/redis/v7/cluster_commands.go index dff62c902d..c9b9b9de24 100644 --- a/vendor/github.com/go-redis/redis/cluster_commands.go +++ b/vendor/github.com/go-redis/redis/v7/cluster_commands.go @@ -14,7 +14,7 @@ func (c *ClusterClient) DBSize() *IntCmd { return nil }) if err != nil { - cmd.setErr(err) + cmd.SetErr(err) return cmd } cmd.val = size diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/v7/command.go similarity index 66% rename from vendor/github.com/go-redis/redis/command.go rename to vendor/github.com/go-redis/redis/v7/command.go index cb4f94b122..dd7fe4a91e 100644 --- a/vendor/github.com/go-redis/redis/command.go +++ b/vendor/github.com/go-redis/redis/v7/command.go @@ -7,27 +7,28 @@ import ( "strings" "time" - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/proto" + "github.com/go-redis/redis/v7/internal" + "github.com/go-redis/redis/v7/internal/proto" + "github.com/go-redis/redis/v7/internal/util" ) type Cmder interface { Name() string Args() []interface{} + String() string stringArg(int) string - readReply(rd *proto.Reader) error - setErr(error) - readTimeout() *time.Duration + readReply(rd *proto.Reader) error + SetErr(error) Err() error } func setCmdsErr(cmds []Cmder, e error) { for _, cmd := range cmds { if cmd.Err() == nil { - cmd.setErr(e) + cmd.SetErr(e) } } } @@ -41,18 +42,21 @@ func cmdsFirstErr(cmds []Cmder) error { return nil } -func writeCmd(wr *proto.Writer, cmds ...Cmder) error { +func writeCmds(wr *proto.Writer, cmds []Cmder) error { for _, cmd := range cmds { - err := wr.WriteArgs(cmd.Args()) - if err != nil { + if err := writeCmd(wr, cmd); err != nil { return err } } return nil } +func writeCmd(wr *proto.Writer, cmd Cmder) error { + return wr.WriteArgs(cmd.Args()) +} + func cmdString(cmd Cmder, val interface{}) string { - var ss []string + ss := make([]string, 0, len(cmd.Args())) for _, arg := range cmd.Args() { ss = append(ss, fmt.Sprint(arg)) } @@ -69,7 +73,6 @@ func cmdString(cmd Cmder, val interface{}) string { } } return s - } func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { @@ -92,38 +95,40 @@ func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { //------------------------------------------------------------------------------ type baseCmd struct { - _args []interface{} - err error + args []interface{} + err error _readTimeout *time.Duration } var _ Cmder = (*Cmd)(nil) -func (cmd *baseCmd) Err() error { - return cmd.err +func (cmd *baseCmd) Name() string { + if len(cmd.args) == 0 { + return "" + } + // Cmd name must be lower cased. + return internal.ToLower(cmd.stringArg(0)) } func (cmd *baseCmd) Args() []interface{} { - return cmd._args + return cmd.args } func (cmd *baseCmd) stringArg(pos int) string { - if pos < 0 || pos >= len(cmd._args) { + if pos < 0 || pos >= len(cmd.args) { return "" } - s, _ := cmd._args[pos].(string) + 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) SetErr(e error) { + cmd.err = e +} + +func (cmd *baseCmd) Err() error { + return cmd.err } func (cmd *baseCmd) readTimeout() *time.Duration { @@ -134,10 +139,6 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) { cmd._readTimeout = &d } -func (cmd *baseCmd) setErr(e error) { - cmd.err = e -} - //------------------------------------------------------------------------------ type Cmd struct { @@ -148,10 +149,14 @@ type Cmd struct { func NewCmd(args ...interface{}) *Cmd { return &Cmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } +func (cmd *Cmd) String() string { + return cmdString(cmd, cmd.val) +} + func (cmd *Cmd) Val() interface{} { return cmd.val } @@ -160,7 +165,7 @@ func (cmd *Cmd) Result() (interface{}, error) { return cmd.val, cmd.err } -func (cmd *Cmd) String() (string, error) { +func (cmd *Cmd) Text() (string, error) { if cmd.err != nil { return "", cmd.err } @@ -218,6 +223,25 @@ func (cmd *Cmd) Uint64() (uint64, error) { } } +func (cmd *Cmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + switch val := cmd.val.(type) { + case int64: + return float32(val), nil + case string: + f, err := strconv.ParseFloat(val, 32) + if err != nil { + return 0, err + } + return float32(f), nil + default: + err := fmt.Errorf("redis: unexpected type=%T for Float32", val) + return 0, err + } +} + func (cmd *Cmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err @@ -255,27 +279,21 @@ func (cmd *Cmd) readReply(rd *proto.Reader) error { // Implements proto.MultiBulkParse func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { + vals := make([]interface{}, n) + for i := 0; i < len(vals); i++ { v, err := rd.ReadReply(sliceParser) if err != nil { if err == Nil { - vals = append(vals, nil) + vals[i] = nil continue } if err, ok := err.(proto.RedisError); ok { - vals = append(vals, err) + vals[i] = err continue } return nil, err } - - switch v := v.(type) { - case string: - vals = append(vals, v) - default: - vals = append(vals, v) - } + vals[i] = v } return vals, nil } @@ -292,7 +310,7 @@ var _ Cmder = (*SliceCmd)(nil) func NewSliceCmd(args ...interface{}) *SliceCmd { return &SliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -330,7 +348,7 @@ var _ Cmder = (*StatusCmd)(nil) func NewStatusCmd(args ...interface{}) *StatusCmd { return &StatusCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -363,7 +381,7 @@ var _ Cmder = (*IntCmd)(nil) func NewIntCmd(args ...interface{}) *IntCmd { return &IntCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -375,6 +393,10 @@ func (cmd *IntCmd) Result() (int64, error) { return cmd.val, cmd.err } +func (cmd *IntCmd) Uint64() (uint64, error) { + return uint64(cmd.val), cmd.err +} + func (cmd *IntCmd) String() string { return cmdString(cmd, cmd.val) } @@ -386,6 +408,49 @@ func (cmd *IntCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ +type IntSliceCmd struct { + baseCmd + + val []int64 +} + +var _ Cmder = (*IntSliceCmd)(nil) + +func NewIntSliceCmd(args ...interface{}) *IntSliceCmd { + return &IntSliceCmd{ + baseCmd: baseCmd{args: args}, + } +} + +func (cmd *IntSliceCmd) Val() []int64 { + return cmd.val +} + +func (cmd *IntSliceCmd) Result() ([]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error { + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]int64, n) + for i := 0; i < len(cmd.val); i++ { + num, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = num + } + return nil, nil + }) + return cmd.err +} + +//------------------------------------------------------------------------------ + type DurationCmd struct { baseCmd @@ -397,7 +462,7 @@ var _ Cmder = (*DurationCmd)(nil) func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { return &DurationCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, precision: precision, } } @@ -420,7 +485,14 @@ func (cmd *DurationCmd) readReply(rd *proto.Reader) error { if cmd.err != nil { return cmd.err } - cmd.val = time.Duration(n) * cmd.precision + switch n { + // -2 if the key does not exist + // -1 if the key exists but has no associated expire + case -2, -1: + cmd.val = time.Duration(n) + default: + cmd.val = time.Duration(n) * cmd.precision + } return nil } @@ -436,7 +508,7 @@ var _ Cmder = (*TimeCmd)(nil) func NewTimeCmd(args ...interface{}) *TimeCmd { return &TimeCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -453,32 +525,25 @@ func (cmd *TimeCmd) String() string { } func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(timeParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(time.Time) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d elements, expected 2", n) + } -// 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 + } - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } + microsec, 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 + cmd.val = time.Unix(sec, microsec*1000) + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -493,7 +558,7 @@ var _ Cmder = (*BoolCmd)(nil) func NewBoolCmd(args ...interface{}) *BoolCmd { return &BoolCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -514,7 +579,6 @@ func (cmd *BoolCmd) readReply(rd *proto.Reader) error { v, cmd.err = rd.ReadReply(nil) // `SET key value NX` returns nil when key already exists. But // `SETNX key value` returns bool (0/1). So convert nil to bool. - // TODO: is this okay? if cmd.err == Nil { cmd.val = false cmd.err = nil @@ -548,7 +612,7 @@ var _ Cmder = (*StringCmd)(nil) func NewStringCmd(args ...interface{}) *StringCmd { return &StringCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -561,7 +625,7 @@ func (cmd *StringCmd) Result() (string, error) { } func (cmd *StringCmd) Bytes() ([]byte, error) { - return []byte(cmd.val), cmd.err + return util.StringToBytes(cmd.val), cmd.err } func (cmd *StringCmd) Int() (int, error) { @@ -585,6 +649,17 @@ func (cmd *StringCmd) Uint64() (uint64, error) { return strconv.ParseUint(cmd.Val(), 10, 64) } +func (cmd *StringCmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + f, err := strconv.ParseFloat(cmd.Val(), 32) + if err != nil { + return 0, err + } + return float32(f), nil +} + func (cmd *StringCmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err @@ -592,6 +667,13 @@ func (cmd *StringCmd) Float64() (float64, error) { return strconv.ParseFloat(cmd.Val(), 64) } +func (cmd *StringCmd) Time() (time.Time, error) { + if cmd.err != nil { + return time.Time{}, cmd.err + } + return time.Parse(time.RFC3339Nano, cmd.Val()) +} + func (cmd *StringCmd) Scan(val interface{}) error { if cmd.err != nil { return cmd.err @@ -620,7 +702,7 @@ var _ Cmder = (*FloatCmd)(nil) func NewFloatCmd(args ...interface{}) *FloatCmd { return &FloatCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -653,7 +735,7 @@ var _ Cmder = (*StringSliceCmd)(nil) func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { return &StringSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -674,29 +756,21 @@ func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { } func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ss := make([]string, 0, n) - for i := int64(0); i < n; i++ { - s, err := rd.ReadString() - if err == Nil { - ss = append(ss, "") - } else if err != nil { - return nil, err - } else { - ss = append(ss, s) + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]string, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return nil, err + default: + cmd.val[i] = s + } } - } - return ss, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -711,7 +785,7 @@ var _ Cmder = (*BoolSliceCmd)(nil) func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { return &BoolSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -728,26 +802,18 @@ func (cmd *BoolSliceCmd) String() string { } func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(boolSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]bool) - return nil -} - -// Implements proto.MultiBulkParse -func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - bools := make([]bool, 0, n) - for i := int64(0); i < n; i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]bool, n) + for i := 0; i < len(cmd.val); i++ { + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = n == 1 } - bools = append(bools, n == 1) - } - return bools, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -762,7 +828,7 @@ var _ Cmder = (*StringStringMapCmd)(nil) func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { return &StringStringMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -779,32 +845,24 @@ func (cmd *StringStringMapCmd) String() string { } func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStringMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]string) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]string, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } -// Implements proto.MultiBulkParse -func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err + value, err := rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val[key] = value } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -819,7 +877,7 @@ var _ Cmder = (*StringIntMapCmd)(nil) func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { return &StringIntMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -836,32 +894,24 @@ func (cmd *StringIntMapCmd) String() string { } func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringIntMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]int64) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]int64, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } -// Implements proto.MultiBulkParse -func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + cmd.val[key] = n } - - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - m[key] = n - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -876,7 +926,7 @@ var _ Cmder = (*StringStructMapCmd)(nil) func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { return &StringStructMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -893,27 +943,18 @@ func (cmd *StringStructMapCmd) String() string { } func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStructMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]struct{}) - return nil -} - -// Implements proto.MultiBulkParse -func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]struct{}, n) + for i := int64(0); i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + cmd.val[key] = struct{}{} } - - m[key] = struct{}{} - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -933,7 +974,7 @@ var _ Cmder = (*XMessageSliceCmd)(nil) func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { return &XMessageSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -961,23 +1002,30 @@ func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { // Implements proto.MultiBulkParse func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - msgs := make([]XMessage, 0, n) - for i := int64(0); i < n; i++ { + msgs := make([]XMessage, n) + for i := 0; i < len(msgs); i++ { + i := i _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { id, err := rd.ReadString() if err != nil { return nil, err } + var values map[string]interface{} + v, err := rd.ReadArrayReply(stringInterfaceMapParser) if err != nil { - return nil, err + if err != proto.Nil { + return nil, err + } + } else { + values = v.(map[string]interface{}) } - msgs = append(msgs, XMessage{ + msgs[i] = XMessage{ ID: id, - Values: v.(map[string]interface{}), - }) + Values: values, + } return nil, nil }) if err != nil { @@ -1023,7 +1071,7 @@ var _ Cmder = (*XStreamSliceCmd)(nil) func NewXStreamSliceCmd(args ...interface{}) *XStreamSliceCmd { return &XStreamSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1040,45 +1088,38 @@ func (cmd *XStreamSliceCmd) String() string { } func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XStream) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XStream, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } -// Implements proto.MultiBulkParse -func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XStream, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } + stream, err := rd.ReadString() + if err != nil { + return nil, err + } - stream, err := rd.ReadString() - if err != nil { - return nil, err - } + v, err := rd.ReadArrayReply(xMessageSliceParser) + if err != nil { + return nil, err + } - v, err := rd.ReadArrayReply(xMessageSliceParser) - if err != nil { - return nil, err - } - - ret = append(ret, XStream{ - Stream: stream, - Messages: v.([]XMessage), + cmd.val[i] = XStream{ + Stream: stream, + Messages: v.([]XMessage), + } + return nil, nil }) - return nil, nil - }) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } - } - return ret, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1099,7 +1140,7 @@ var _ Cmder = (*XPendingCmd)(nil) func NewXPendingCmd(args ...interface{}) *XPendingCmd { return &XPendingCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1116,81 +1157,74 @@ func (cmd *XPendingCmd) String() string { } func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.(*XPending) - return nil -} - -func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - count, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - pending := &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if pending.Consumers == nil { - pending.Consumers = make(map[string]int64) - } - pending.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) } + + count, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + lower, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + higher, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = &XPending{ + Count: count, + Lower: lower, + Higher: higher, + } + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + for i := int64(0); i < n; i++ { + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + + consumerName, err := rd.ReadString() + if err != nil { + return nil, err + } + + consumerPending, err := rd.ReadInt() + if err != nil { + return nil, err + } + + if cmd.val.Consumers == nil { + cmd.val.Consumers = make(map[string]int64) + } + cmd.val.Consumers[consumerName] = consumerPending + + return nil, nil + }) + if err != nil { + return nil, err + } + } + return nil, nil + }) + if err != nil && err != Nil { + return nil, err + } + return nil, nil }) - if err != nil && err != Nil { - return nil, err - } - - return pending, nil + return cmd.err } //------------------------------------------------------------------------------ type XPendingExt struct { - Id string + ID string Consumer string Idle time.Duration RetryCount int64 @@ -1205,7 +1239,7 @@ var _ Cmder = (*XPendingExtCmd)(nil) func NewXPendingExtCmd(args ...interface{}) *XPendingExtCmd { return &XPendingExtCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1222,62 +1256,143 @@ func (cmd *XPendingExtCmd) String() string { } func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.([]XPendingExt) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XPendingExt, 0, n) + for i := int64(0); i < n; i++ { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) + } -func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } + id, err := rd.ReadString() + if err != nil { + return nil, err + } - id, err := rd.ReadString() + consumer, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + idle, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + retryCount, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = append(cmd.val, XPendingExt{ + ID: id, + Consumer: consumer, + Idle: time.Duration(idle) * time.Millisecond, + RetryCount: retryCount, + }) + return nil, nil + }) if err != nil { return nil, err } + } + return nil, nil + }) + return cmd.err +} - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err +//------------------------------------------------------------------------------ + +type XInfoGroupsCmd struct { + baseCmd + val []XInfoGroups +} + +type XInfoGroups struct { + Name string + Consumers int64 + Pending int64 + LastDeliveredID string +} + +var _ Cmder = (*XInfoGroupsCmd)(nil) + +func NewXInfoGroupsCmd(stream string) *XInfoGroupsCmd { + return &XInfoGroupsCmd{ + baseCmd: baseCmd{args: []interface{}{"xinfo", "groups", stream}}, + } +} + +func (cmd *XInfoGroupsCmd) Val() []XInfoGroups { + return cmd.val +} + +func (cmd *XInfoGroupsCmd) Result() ([]XInfoGroups, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoGroupsCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error { + _, cmd.err = rd.ReadArrayReply( + func(rd *proto.Reader, n int64) (interface{}, error) { + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(xGroupInfoParser) + if err != nil { + return nil, err + } + cmd.val = append(cmd.val, v.(XInfoGroups)) } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - ret = append(ret, XPendingExt{ - Id: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) return nil, nil }) + return nil +} + +func xGroupInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + if n != 8 { + return nil, fmt.Errorf("redis: got %d elements in XINFO GROUPS reply,"+ + "wanted 8", n) + } + var ( + err error + grp XInfoGroups + key string + val string + ) + + for i := 0; i < 4; i++ { + key, err = rd.ReadString() + if err != nil { + return nil, err + } + val, err = rd.ReadString() + if err != nil { + return nil, err + } + switch key { + case "name": + grp.Name = val + case "consumers": + grp.Consumers, err = strconv.ParseInt(val, 0, 64) + case "pending": + grp.Pending, err = strconv.ParseInt(val, 0, 64) + case "last-delivered-id": + grp.LastDeliveredID = val + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO GROUPS reply", key) + } if err != nil { return nil, err } } - return ret, nil + return grp, err } //------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - type ZSliceCmd struct { baseCmd @@ -1288,7 +1403,7 @@ var _ Cmder = (*ZSliceCmd)(nil) func NewZSliceCmd(args ...interface{}) *ZSliceCmd { return &ZSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1305,34 +1420,27 @@ func (cmd *ZSliceCmd) String() string { } func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]Z) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]Z, n/2) + for i := 0; i < len(cmd.val); i++ { + member, err := rd.ReadString() + if err != nil { + return nil, err + } -// 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 + score, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } - z := &zz[i/2] - - z.Member, err = rd.ReadString() - if err != nil { - return nil, err + cmd.val[i] = Z{ + Member: member, + Score: score, + } } - - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - return zz, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1340,22 +1448,22 @@ func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { type ZWithKeyCmd struct { baseCmd - val ZWithKey + val *ZWithKey } var _ Cmder = (*ZWithKeyCmd)(nil) func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { return &ZWithKeyCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } -func (cmd *ZWithKeyCmd) Val() ZWithKey { +func (cmd *ZWithKeyCmd) Val() *ZWithKey { return cmd.val } -func (cmd *ZWithKeyCmd) Result() (ZWithKey, error) { +func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) { return cmd.Val(), cmd.Err() } @@ -1364,37 +1472,32 @@ func (cmd *ZWithKeyCmd) String() string { } func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zWithKeyParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(ZWithKey) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 3 { + return nil, fmt.Errorf("got %d elements, expected 3", n) + } -// Implements proto.MultiBulkParse -func zWithKeyParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 3 { - return nil, fmt.Errorf("got %d elements, expected 3", n) - } + cmd.val = &ZWithKey{} + var err error - var z ZWithKey - var err error + cmd.val.Key, err = rd.ReadString() + if err != nil { + return nil, err + } - z.Key, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - return z, nil + cmd.val.Member, err = rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val.Score, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1412,7 +1515,7 @@ var _ Cmder = (*ScanCmd)(nil) func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { return &ScanCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, process: process, } } @@ -1444,7 +1547,7 @@ func (cmd *ScanCmd) Iterator() *ScanIterator { //------------------------------------------------------------------------------ type ClusterNode struct { - Id string + ID string Addr string } @@ -1464,7 +1567,7 @@ var _ Cmder = (*ClusterSlotsCmd)(nil) func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { return &ClusterSlotsCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1481,77 +1584,69 @@ func (cmd *ClusterSlotsCmd) String() string { } func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]ClusterSlot) - return nil -} - -// Implements proto.MultiBulkParse -func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { - slots := make([]ClusterSlot, n) - for i := 0; i < len(slots); i++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err - } - - start, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - end, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]ClusterSlot, n) + for i := 0; i < len(cmd.val); i++ { 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) + if n < 2 { + err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) return nil, err } - ip, err := rd.ReadString() + start, err := rd.ReadIntReply() if err != nil { return nil, err } - port, err := rd.ReadString() + end, err := rd.ReadIntReply() if err != nil { return nil, err } - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n == 3 { - id, err := rd.ReadString() + nodes := make([]ClusterNode, n-2) + for j := 0; j < len(nodes); j++ { + n, err := rd.ReadArrayLen() if err != nil { return nil, err } - nodes[j].Id = id + if n != 2 && n != 3 { + err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) + return nil, err + } + + ip, err := rd.ReadString() + if err != nil { + return nil, err + } + + port, err := rd.ReadString() + if err != nil { + return nil, err + } + + nodes[j].Addr = net.JoinHostPort(ip, port) + + if n == 3 { + id, err := rd.ReadString() + if err != nil { + return nil, err + } + nodes[j].ID = id + } + } + + cmd.val[i] = ClusterSlot{ + Start: int(start), + End: int(end), + Nodes: nodes, } } - - slots[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, - } - } - return slots, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1588,6 +1683,13 @@ type GeoLocationCmd struct { var _ Cmder = (*GeoLocationCmd)(nil) func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { + return &GeoLocationCmd{ + baseCmd: baseCmd{args: geoLocationArgs(q, args...)}, + q: q, + } +} + +func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { args = append(args, q.Radius) if q.Unit != "" { args = append(args, q.Unit) @@ -1617,10 +1719,7 @@ func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { args = append(args, "storedist") args = append(args, q.StoreDist) } - return &GeoLocationCmd{ - baseCmd: baseCmd{_args: args}, - q: q, - } + return args } func (cmd *GeoLocationCmd) Val() []GeoLocation { @@ -1645,6 +1744,30 @@ func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { return nil } +func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + locs := make([]GeoLocation, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(newGeoLocationParser(q)) + if err != nil { + return nil, err + } + switch vv := v.(type) { + case string: + locs = append(locs, GeoLocation{ + Name: vv, + }) + case *GeoLocation: + //TODO: avoid copying + locs = append(locs, *vv) + default: + return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) + } + } + return locs, nil + } +} + func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { return func(rd *proto.Reader, n int64) (interface{}, error) { var loc GeoLocation @@ -1689,29 +1812,6 @@ func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { } } -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - //------------------------------------------------------------------------------ type GeoPos struct { @@ -1721,19 +1821,19 @@ type GeoPos struct { type GeoPosCmd struct { baseCmd - positions []*GeoPos + val []*GeoPos } var _ Cmder = (*GeoPosCmd)(nil) func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { return &GeoPosCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } func (cmd *GeoPosCmd) Val() []*GeoPos { - return cmd.positions + return cmd.val } func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { @@ -1741,55 +1841,42 @@ func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { } func (cmd *GeoPosCmd) String() string { - return cmdString(cmd, cmd.positions) + return cmdString(cmd, cmd.val) } func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.positions = v.([]*GeoPos) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]*GeoPos, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + longitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } -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 + latitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + cmd.val[i] = &GeoPos{ + Longitude: longitude, + Latitude: latitude, + } + return nil, nil + }) + if err != nil { + if err == Nil { + cmd.val[i] = nil + continue + } + return nil, err } - return nil, err } - switch v := v.(type) { - case *GeoPos: - positions = append(positions, v) - default: - return nil, fmt.Errorf("got %T, expected *GeoPos", v) - } - } - return positions, nil -} - -func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { - var pos GeoPos - var err error - - pos.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - pos.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return &pos, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1798,6 +1885,7 @@ type CommandInfo struct { Name string Arity int8 Flags []string + ACLFlags []string FirstKeyPos int8 LastKeyPos int8 StepCount int8 @@ -1814,7 +1902,7 @@ var _ Cmder = (*CommandsInfoCmd)(nil) func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { return &CommandsInfoCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1831,38 +1919,35 @@ func (cmd *CommandsInfoCmd) String() string { } func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]*CommandInfo) - return nil -} - -// Implements proto.MultiBulkParse -func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = 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) + cmd.val[vv.Name] = vv } - vv := v.(*CommandInfo) - m[vv.Name] = vv - - } - return m, nil + return nil, nil + }) + return cmd.err } func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + const numArgRedis5 = 6 + const numArgRedis6 = 7 + + switch n { + case numArgRedis5, numArgRedis6: + // continue + default: + return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 7", n) + } + var cmd CommandInfo var err error - if n != 6 { - return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) - } - cmd.Name, err = rd.ReadString() if err != nil { return nil, err @@ -1874,11 +1959,23 @@ func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { } cmd.Arity = int8(arity) - flags, err := rd.ReadReply(stringSliceParser) + _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.Flags = make([]string, n) + for i := 0; i < len(cmd.Flags); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.Flags[i] = "" + case err != nil: + return nil, err + default: + cmd.Flags[i] = s + } + } + return nil, nil + }) if err != nil { return nil, err } - cmd.Flags = flags.([]string) firstKeyPos, err := rd.ReadIntReply() if err != nil { @@ -1905,6 +2002,28 @@ func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { } } + if n == numArgRedis5 { + return &cmd, nil + } + + _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.ACLFlags = make([]string, n) + for i := 0; i < len(cmd.ACLFlags); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.ACLFlags[i] = "" + case err != nil: + return nil, err + default: + cmd.ACLFlags[i] = s + } + } + return nil, nil + }) + if err != nil { + return nil, err + } + return &cmd, nil } @@ -1929,6 +2048,15 @@ func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { if err != nil { return err } + + // Extensions have cmd names in upper case. Convert them to lower case. + for k, v := range cmds { + lower := internal.ToLower(k) + if lower != k { + cmds[lower] = v + } + } + c.cmds = cmds return nil }) diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/v7/commands.go similarity index 63% rename from vendor/github.com/go-redis/redis/commands.go rename to vendor/github.com/go-redis/redis/v7/commands.go index 653e4abe96..da5ceda13e 100644 --- a/vendor/github.com/go-redis/redis/commands.go +++ b/vendor/github.com/go-redis/redis/v7/commands.go @@ -5,7 +5,7 @@ import ( "io" "time" - "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/v7/internal" ) func usePrecise(dur time.Duration) bool { @@ -14,7 +14,7 @@ func usePrecise(dur time.Duration) bool { func formatMs(dur time.Duration) int64 { if dur > 0 && dur < time.Millisecond { - internal.Logf( + internal.Logger.Printf( "specified duration is %s, but minimal supported value is %s", dur, time.Millisecond, ) @@ -24,7 +24,7 @@ func formatMs(dur time.Duration) int64 { func formatSec(dur time.Duration) int64 { if dur > 0 && dur < time.Second { - internal.Logf( + internal.Logger.Printf( "specified duration is %s, but minimal supported value is %s", dur, time.Second, ) @@ -34,17 +34,21 @@ func formatSec(dur time.Duration) int64 { func appendArgs(dst, src []interface{}) []interface{} { if len(src) == 1 { - if ss, ok := src[0].([]string); ok { - for _, s := range ss { + switch v := src[0].(type) { + case []string: + for _, s := range v { dst = append(dst, s) } return dst + case map[string]interface{}: + for k, v := range v { + dst = append(dst, k, v) + } + return dst } } - for _, v := range src { - dst = append(dst, v) - } + dst = append(dst, src...) return dst } @@ -67,8 +71,8 @@ type Cmdable interface { 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 + Migrate(host, port, key string, db int, timeout time.Duration) *StatusCmd + Move(key string, db int) *BoolCmd ObjectRefCount(key string) *IntCmd ObjectEncoding(key string) *StringCmd ObjectIdleTime(key string) *DurationCmd @@ -98,6 +102,7 @@ type Cmdable interface { BitOpXor(destKey string, keys ...string) *IntCmd BitOpNot(destKey string, key string) *IntCmd BitPos(key string, bit int64, pos ...int64) *IntCmd + BitField(key string, args ...interface{}) *IntSliceCmd Decr(key string) *IntCmd DecrBy(key string, decrement int64) *IntCmd Get(key string) *StringCmd @@ -108,8 +113,8 @@ type Cmdable interface { IncrBy(key string, value int64) *IntCmd IncrByFloat(key string, value float64) *FloatCmd MGet(keys ...string) *SliceCmd - MSet(pairs ...interface{}) *StatusCmd - MSetNX(pairs ...interface{}) *BoolCmd + MSet(values ...interface{}) *StatusCmd + MSetNX(values ...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 @@ -125,8 +130,8 @@ type Cmdable interface { 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 + HSet(key string, values ...interface{}) *IntCmd + HMSet(key string, values ...interface{}) *BoolCmd HSetNX(key, field string, value interface{}) *BoolCmd HVals(key string) *StringSliceCmd BLPop(timeout time.Duration, keys ...string) *StringSliceCmd @@ -139,7 +144,7 @@ type Cmdable interface { LLen(key string) *IntCmd LPop(key string) *StringCmd LPush(key string, values ...interface{}) *IntCmd - LPushX(key string, value interface{}) *IntCmd + LPushX(key string, values ...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 @@ -147,7 +152,7 @@ type Cmdable interface { RPop(key string) *StringCmd RPopLPush(source, destination string) *StringCmd RPush(key string, values ...interface{}) *IntCmd - RPushX(key string, value interface{}) *IntCmd + RPushX(key string, values ...interface{}) *IntCmd SAdd(key string, members ...interface{}) *IntCmd SCard(key string) *IntCmd SDiff(keys ...string) *StringSliceCmd @@ -187,29 +192,30 @@ type Cmdable interface { XClaimJustID(a *XClaimArgs) *StringSliceCmd XTrim(key string, maxLen int64) *IntCmd XTrimApprox(key string, maxLen int64) *IntCmd + XInfoGroups(key string) *XInfoGroupsCmd BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd - ZAdd(key string, members ...Z) *IntCmd - ZAddNX(key string, members ...Z) *IntCmd - ZAddXX(key string, members ...Z) *IntCmd - ZAddCh(key string, members ...Z) *IntCmd - ZAddNXCh(key string, members ...Z) *IntCmd - ZAddXXCh(key string, members ...Z) *IntCmd - ZIncr(key string, member Z) *FloatCmd - ZIncrNX(key string, member Z) *FloatCmd - ZIncrXX(key string, member Z) *FloatCmd + 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 + ZInterStore(destination string, store *ZStore) *IntCmd ZPopMax(key string, count ...int64) *ZSliceCmd ZPopMin(key string, count ...int64) *ZSliceCmd ZRange(key string, start, stop int64) *StringSliceCmd ZRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd + 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 @@ -217,12 +223,12 @@ type Cmdable interface { 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 + 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 + ZUnionStore(dest string, store *ZStore) *IntCmd PFAdd(key string, els ...interface{}) *IntCmd PFCount(keys ...string) *IntCmd PFMerge(dest string, keys ...string) *StatusCmd @@ -283,9 +289,9 @@ type Cmdable interface { 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 + GeoRadiusStore(key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusByMemberStore(key, member string, query *GeoRadiusQuery) *IntCmd GeoDist(key string, member1, member2, unit string) *FloatCmd GeoHash(key string, members ...string) *StringSliceCmd ReadOnly() *StatusCmd @@ -296,6 +302,7 @@ type Cmdable interface { type StatefulCmdable interface { Cmdable Auth(password string) *StatusCmd + AuthACL(username, password string) *StatusCmd Select(index int) *StatusCmd SwapDB(index1, index2 int) *StatusCmd ClientSetName(name string) *BoolCmd @@ -306,132 +313,127 @@ var _ Cmdable = (*Tx)(nil) var _ Cmdable = (*Ring)(nil) var _ Cmdable = (*ClusterClient)(nil) -type cmdable struct { - process func(cmd Cmder) error -} +type cmdable 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) -} +type statefulCmdable func(cmd Cmder) error //------------------------------------------------------------------------------ -func (c *statefulCmdable) Auth(password string) *StatusCmd { +func (c statefulCmdable) Auth(password string) *StatusCmd { cmd := NewStatusCmd("auth", password) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Echo(message interface{}) *StringCmd { +// Perform an AUTH command, using the given user and pass. +// Should be used to authenticate the current connection with one of the connections defined in the ACL list +// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system. +func (c statefulCmdable) AuthACL(username, password string) *StatusCmd { + cmd := NewStatusCmd("auth", username, password) + _ = c(cmd) + return cmd +} + +func (c cmdable) Echo(message interface{}) *StringCmd { cmd := NewStringCmd("echo", message) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Ping() *StatusCmd { +func (c cmdable) Ping() *StatusCmd { cmd := NewStatusCmd("ping") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { +func (c cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Quit() *StatusCmd { +func (c cmdable) Quit() *StatusCmd { panic("not implemented") } -func (c *statefulCmdable) Select(index int) *StatusCmd { +func (c statefulCmdable) Select(index int) *StatusCmd { cmd := NewStatusCmd("select", index) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { +func (c statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { cmd := NewStatusCmd("swapdb", index1, index2) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) Command() *CommandsInfoCmd { +func (c cmdable) Command() *CommandsInfoCmd { cmd := NewCommandsInfoCmd("command") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Del(keys ...string) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) Unlink(keys ...string) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) Dump(key string) *StringCmd { +func (c cmdable) Dump(key string) *StringCmd { cmd := NewStringCmd("dump", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Exists(keys ...string) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { +func (c cmdable) Expire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("expire", key, formatSec(expiration)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { +func (c cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd("expireat", key, tm.Unix()) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Keys(pattern string) *StringSliceCmd { +func (c cmdable) Keys(pattern string) *StringSliceCmd { cmd := NewStringSliceCmd("keys", pattern) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { +func (c cmdable) Migrate(host, port, key string, db int, timeout time.Duration) *StatusCmd { cmd := NewStatusCmd( "migrate", host, @@ -441,92 +443,92 @@ func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duratio formatMs(timeout), ) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Move(key string, db int64) *BoolCmd { +func (c cmdable) Move(key string, db int) *BoolCmd { cmd := NewBoolCmd("move", key, db) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectRefCount(key string) *IntCmd { +func (c cmdable) ObjectRefCount(key string) *IntCmd { cmd := NewIntCmd("object", "refcount", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectEncoding(key string) *StringCmd { +func (c cmdable) ObjectEncoding(key string) *StringCmd { cmd := NewStringCmd("object", "encoding", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { +func (c cmdable) ObjectIdleTime(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "object", "idletime", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Persist(key string) *BoolCmd { +func (c cmdable) Persist(key string) *BoolCmd { cmd := NewBoolCmd("persist", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { +func (c cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { +func (c cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd( "pexpireat", key, tm.UnixNano()/int64(time.Millisecond), ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PTTL(key string) *DurationCmd { +func (c cmdable) PTTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Millisecond, "pttl", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RandomKey() *StringCmd { +func (c cmdable) RandomKey() *StringCmd { cmd := NewStringCmd("randomkey") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Rename(key, newkey string) *StatusCmd { +func (c cmdable) Rename(key, newkey string) *StatusCmd { cmd := NewStatusCmd("rename", key, newkey) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { +func (c cmdable) RenameNX(key, newkey string) *BoolCmd { cmd := NewBoolCmd("renamenx", key, newkey) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { +func (c cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, formatMs(ttl), value, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { +func (c cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, @@ -534,7 +536,7 @@ func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *S value, "replace", ) - c.process(cmd) + _ = c(cmd) return cmd } @@ -566,52 +568,52 @@ func (sort *Sort) args(key string) []interface{} { return args } -func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { +func (c cmdable) Sort(key string, sort *Sort) *StringSliceCmd { cmd := NewStringSliceCmd(sort.args(key)...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { +func (c cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { cmd := NewSliceCmd(sort.args(key)...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Touch(keys ...string) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) TTL(key string) *DurationCmd { +func (c cmdable) TTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "ttl", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Type(key string) *StatusCmd { +func (c cmdable) Type(key string) *StatusCmd { cmd := NewStatusCmd("type", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { +func (c cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"scan", cursor} if match != "" { args = append(args, "match", match) @@ -619,12 +621,12 @@ func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { +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) @@ -632,12 +634,12 @@ func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { +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) @@ -645,12 +647,12 @@ func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { +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) @@ -658,16 +660,16 @@ func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) Append(key, value string) *IntCmd { +func (c cmdable) Append(key, value string) *IntCmd { cmd := NewIntCmd("append", key, value) - c.process(cmd) + _ = c(cmd) return cmd } @@ -675,7 +677,7 @@ type BitCount struct { Start, End int64 } -func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { +func (c cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { args := []interface{}{"bitcount", key} if bitCount != nil { args = append( @@ -685,11 +687,11 @@ func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { ) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { +func (c cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { args := make([]interface{}, 3+len(keys)) args[0] = "bitop" args[1] = op @@ -698,27 +700,27 @@ func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { args[3+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { return c.bitOp("and", destKey, keys...) } -func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { return c.bitOp("or", destKey, keys...) } -func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { return c.bitOp("xor", destKey, keys...) } -func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { +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 { +func (c cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { args := make([]interface{}, 3+len(pos)) args[0] = "bitpos" args[1] = key @@ -734,91 +736,109 @@ func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { panic("too many arguments") } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Decr(key string) *IntCmd { +func (c cmdable) BitField(key string, args ...interface{}) *IntSliceCmd { + a := make([]interface{}, 0, 2+len(args)) + a = append(a, "bitfield") + a = append(a, key) + a = append(a, args...) + cmd := NewIntSliceCmd(a...) + _ = c(cmd) + return cmd +} + +func (c cmdable) Decr(key string) *IntCmd { cmd := NewIntCmd("decr", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { +func (c cmdable) DecrBy(key string, decrement int64) *IntCmd { cmd := NewIntCmd("decrby", key, decrement) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `GET key` command. It returns redis.Nil error when key does not exist. -func (c *cmdable) Get(key string) *StringCmd { +func (c cmdable) Get(key string) *StringCmd { cmd := NewStringCmd("get", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetBit(key string, offset int64) *IntCmd { +func (c cmdable) GetBit(key string, offset int64) *IntCmd { cmd := NewIntCmd("getbit", key, offset) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { +func (c cmdable) GetRange(key string, start, end int64) *StringCmd { cmd := NewStringCmd("getrange", key, start, end) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { +func (c cmdable) GetSet(key string, value interface{}) *StringCmd { cmd := NewStringCmd("getset", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Incr(key string) *IntCmd { +func (c cmdable) Incr(key string) *IntCmd { cmd := NewIntCmd("incr", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) IncrBy(key string, value int64) *IntCmd { +func (c cmdable) IncrBy(key string, value int64) *IntCmd { cmd := NewIntCmd("incrby", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { +func (c cmdable) IncrByFloat(key string, value float64) *FloatCmd { cmd := NewFloatCmd("incrbyfloat", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) MGet(keys ...string) *SliceCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { - args := make([]interface{}, 1, 1+len(pairs)) +// MSet is like Set but accepts multiple values: +// - MSet("key1", "value1", "key2", "value2") +// - MSet([]string{"key1", "value1", "key2", "value2"}) +// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSet(values ...interface{}) *StatusCmd { + args := make([]interface{}, 1, 1+len(values)) args[0] = "mset" - args = appendArgs(args, pairs) + args = appendArgs(args, values) cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { - args := make([]interface{}, 1, 1+len(pairs)) +// MSetNX is like SetNX but accepts multiple values: +// - MSetNX("key1", "value1", "key2", "value2") +// - MSetNX([]string{"key1", "value1", "key2", "value2"}) +// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSetNX(values ...interface{}) *BoolCmd { + args := make([]interface{}, 1, 1+len(values)) args[0] = "msetnx" - args = appendArgs(args, pairs) + args = appendArgs(args, values) cmd := NewBoolCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -826,8 +846,8 @@ func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { // // 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) +func (c cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { + args := make([]interface{}, 3, 5) args[0] = "set" args[1] = key args[2] = value @@ -839,25 +859,25 @@ func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) * } } cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { +func (c cmdable) SetBit(key string, offset int64, value int) *IntCmd { cmd := NewIntCmd( "setbit", key, offset, value, ) - c.process(cmd) + _ = c(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 { +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. @@ -869,14 +889,14 @@ func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") } } - c.process(cmd) + _ = c(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 { +func (c cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd if expiration == 0 { cmd = NewBoolCmd("set", key, value, "xx") @@ -887,25 +907,25 @@ func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") } } - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { +func (c cmdable) SetRange(key string, offset int64, value string) *IntCmd { cmd := NewIntCmd("setrange", key, offset, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) StrLen(key string) *IntCmd { +func (c cmdable) StrLen(key string) *IntCmd { cmd := NewIntCmd("strlen", key) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) HDel(key string, fields ...string) *IntCmd { +func (c cmdable) HDel(key string, fields ...string) *IntCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hdel" args[1] = key @@ -913,53 +933,55 @@ func (c *cmdable) HDel(key string, fields ...string) *IntCmd { args[2+i] = field } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HExists(key, field string) *BoolCmd { +func (c cmdable) HExists(key, field string) *BoolCmd { cmd := NewBoolCmd("hexists", key, field) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HGet(key, field string) *StringCmd { +func (c cmdable) HGet(key, field string) *StringCmd { cmd := NewStringCmd("hget", key, field) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HGetAll(key string) *StringStringMapCmd { +func (c cmdable) HGetAll(key string) *StringStringMapCmd { cmd := NewStringStringMapCmd("hgetall", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { +func (c cmdable) HIncrBy(key, field string, incr int64) *IntCmd { cmd := NewIntCmd("hincrby", key, field, incr) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { +func (c cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { cmd := NewFloatCmd("hincrbyfloat", key, field, incr) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HKeys(key string) *StringSliceCmd { +func (c cmdable) HKeys(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hkeys", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HLen(key string) *IntCmd { +func (c cmdable) HLen(key string) *IntCmd { cmd := NewIntCmd("hlen", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { +// HMGet returns the values for the specified fields in the hash stored at key. +// It returns an interface{} to distinguish between empty string and nil value. +func (c cmdable) HMGet(key string, fields ...string) *SliceCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hmget" args[1] = key @@ -967,46 +989,52 @@ func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { args[2+i] = field } cmd := NewSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { - args := make([]interface{}, 2+len(fields)*2) +// HSet accepts values in following formats: +// - HMSet("myhash", "key1", "value1", "key2", "value2") +// - HMSet("myhash", []string{"key1", "value1", "key2", "value2"}) +// - HMSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) +// +// Note that it requires Redis v4 for multiple field/value pairs support. +func (c cmdable) HSet(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "hset" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) + return cmd +} + +// HMSet is a deprecated version of HSet left for compatibility with Redis 3. +func (c cmdable) HMSet(key string, values ...interface{}) *BoolCmd { + args := make([]interface{}, 2, 2+len(values)) 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) + args = appendArgs(args, values) + cmd := NewBoolCmd(args...) + _ = c(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 { +func (c cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { cmd := NewBoolCmd("hsetnx", key, field, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HVals(key string) *StringSliceCmd { +func (c cmdable) HVals(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hvals", key) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { +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 { @@ -1015,11 +1043,11 @@ func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { args[len(args)-1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { +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 { @@ -1028,11 +1056,11 @@ func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { args[len(keys)+1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { +func (c cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { cmd := NewStringCmd( "brpoplpush", source, @@ -1040,154 +1068,162 @@ func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) formatSec(timeout), ) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LIndex(key string, index int64) *StringCmd { +func (c cmdable) LIndex(key string, index int64) *StringCmd { cmd := NewStringCmd("lindex", key, index) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, op, pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "before", pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "after", pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LLen(key string) *IntCmd { +func (c cmdable) LLen(key string) *IntCmd { cmd := NewIntCmd("llen", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPop(key string) *StringCmd { +func (c cmdable) LPop(key string) *StringCmd { cmd := NewStringCmd("lpop", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { +func (c cmdable) LPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "lpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("lpushx", key, value) - c.process(cmd) +func (c cmdable) LPushX(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "lpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) return cmd } -func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { +func (c cmdable) LRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd( "lrange", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { +func (c cmdable) LRem(key string, count int64, value interface{}) *IntCmd { cmd := NewIntCmd("lrem", key, count, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { +func (c cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { cmd := NewStatusCmd("lset", key, index, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { +func (c cmdable) LTrim(key string, start, stop int64) *StatusCmd { cmd := NewStatusCmd( "ltrim", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPop(key string) *StringCmd { +func (c cmdable) RPop(key string) *StringCmd { cmd := NewStringCmd("rpop", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPopLPush(source, destination string) *StringCmd { +func (c cmdable) RPopLPush(source, destination string) *StringCmd { cmd := NewStringCmd("rpoplpush", source, destination) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { +func (c cmdable) RPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "rpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("rpushx", key, value) - c.process(cmd) +func (c cmdable) RPushX(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "rpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { +func (c cmdable) SAdd(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "sadd" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SCard(key string) *IntCmd { +func (c cmdable) SCard(key string) *IntCmd { cmd := NewIntCmd("scard", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SDiffStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sdiffstore" args[1] = destination @@ -1195,22 +1231,22 @@ func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SInter(keys ...string) *StringSliceCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SInterStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sinterstore" args[1] = destination @@ -1218,86 +1254,86 @@ func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { +func (c cmdable) SIsMember(key string, member interface{}) *BoolCmd { cmd := NewBoolCmd("sismember", key, member) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SMEMBERS key` command output as a slice -func (c *cmdable) SMembers(key string) *StringSliceCmd { +func (c cmdable) SMembers(key string) *StringSliceCmd { cmd := NewStringSliceCmd("smembers", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SMEMBERS key` command output as a map -func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { +func (c cmdable) SMembersMap(key string) *StringStructMapCmd { cmd := NewStringStructMapCmd("smembers", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { +func (c cmdable) SMove(source, destination string, member interface{}) *BoolCmd { cmd := NewBoolCmd("smove", source, destination, member) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SPOP key` command. -func (c *cmdable) SPop(key string) *StringCmd { +func (c cmdable) SPop(key string) *StringCmd { cmd := NewStringCmd("spop", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SPOP key count` command. -func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { +func (c cmdable) SPopN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("spop", key, count) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SRANDMEMBER key` command. -func (c *cmdable) SRandMember(key string) *StringCmd { +func (c cmdable) SRandMember(key string) *StringCmd { cmd := NewStringCmd("srandmember", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SRANDMEMBER key count` command. -func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { +func (c cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("srandmember", key, count) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { +func (c cmdable) SRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "srem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SUnionStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sunionstore" args[1] = destination @@ -1305,7 +1341,7 @@ func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1319,7 +1355,7 @@ type XAddArgs struct { Values map[string]interface{} } -func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { +func (c cmdable) XAdd(a *XAddArgs) *StringCmd { args := make([]interface{}, 0, 6+len(a.Values)*2) args = append(args, "xadd") args = append(args, a.Stream) @@ -1339,57 +1375,57 @@ func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { } cmd := NewStringCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XDel(stream string, ids ...string) *IntCmd { +func (c cmdable) XDel(stream string, ids ...string) *IntCmd { args := []interface{}{"xdel", stream} for _, id := range ids { args = append(args, id) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XLen(stream string) *IntCmd { +func (c cmdable) XLen(stream string) *IntCmd { cmd := NewIntCmd("xlen", stream) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { +func (c cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { +func (c cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { +func (c cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { +func (c cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) - c.process(cmd) + _ = c(cmd) return cmd } type XReadArgs struct { - Streams []string + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 Count int64 Block time.Duration } -func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { +func (c cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 5+len(a.Streams)) args = append(args, "xread") if a.Count > 0 { @@ -1400,6 +1436,7 @@ func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { args = append(args, "block") args = append(args, int64(a.Block/time.Millisecond)) } + args = append(args, "streams") for _, s := range a.Streams { args = append(args, s) @@ -1409,58 +1446,57 @@ func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { if a.Block >= 0 { cmd.setReadTimeout(a.Block) } - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { +func (c cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { return c.XRead(&XReadArgs{ Streams: streams, Block: -1, }) } -func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { +func (c cmdable) XGroupCreate(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "create", stream, group, start) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { +func (c cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { +func (c cmdable) XGroupSetID(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "setid", stream, group, start) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { +func (c cmdable) XGroupDestroy(stream, group string) *IntCmd { cmd := NewIntCmd("xgroup", "destroy", stream, group) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { +func (c cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) - c.process(cmd) + _ = c(cmd) return cmd } type XReadGroupArgs struct { Group string Consumer string - // List of streams and ids. - Streams []string - Count int64 - Block time.Duration - NoAck bool + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 + Count int64 + Block time.Duration + NoAck bool } -func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { +func (c cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 8+len(a.Streams)) args = append(args, "xreadgroup", "group", a.Group, a.Consumer) if a.Count > 0 { @@ -1481,23 +1517,23 @@ func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { if a.Block >= 0 { cmd.setReadTimeout(a.Block) } - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { +func (c cmdable) XAck(stream, group string, ids ...string) *IntCmd { args := []interface{}{"xack", stream, group} for _, id := range ids { args = append(args, id) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XPending(stream, group string) *XPendingCmd { +func (c cmdable) XPending(stream, group string) *XPendingCmd { cmd := NewXPendingCmd("xpending", stream, group) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1510,14 +1546,14 @@ type XPendingExtArgs struct { Consumer string } -func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { +func (c cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { args := make([]interface{}, 0, 7) args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) if a.Consumer != "" { args = append(args, a.Consumer) } cmd := NewXPendingExtCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1529,18 +1565,18 @@ type XClaimArgs struct { Messages []string } -func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { +func (c cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { args := xClaimArgs(a) cmd := NewXMessageSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { +func (c cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { args := xClaimArgs(a) args = append(args, "justid") cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1557,15 +1593,21 @@ func xClaimArgs(a *XClaimArgs) []interface{} { return args } -func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { +func (c cmdable) XTrim(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { +func (c cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) - c.process(cmd) + _ = c(cmd) + return cmd +} + +func (c cmdable) XInfoGroups(key string) *XInfoGroupsCmd { + cmd := NewXInfoGroupsCmd(key) + _ = c(cmd) return cmd } @@ -1585,13 +1627,14 @@ type ZWithKey struct { // ZStore is used as an arg to ZInterStore and ZUnionStore. type ZStore struct { + Keys []string Weights []float64 // Can be SUM, MIN or MAX. Aggregate string } // Redis `BZPOPMAX key [key ...] timeout` command. -func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { +func (c cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "bzpopmax" for i, key := range keys { @@ -1600,12 +1643,12 @@ func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { args[len(args)-1] = formatSec(timeout) cmd := NewZWithKeyCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `BZPOPMIN key [key ...] timeout` command. -func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { +func (c cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "bzpopmin" for i, key := range keys { @@ -1614,22 +1657,22 @@ func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { args[len(args)-1] = formatSec(timeout) cmd := NewZWithKeyCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { +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) + _ = c(cmd) return cmd } // Redis `ZADD key score member [score member ...]` command. -func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { +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 @@ -1637,7 +1680,7 @@ func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { } // Redis `ZADD key NX score member [score member ...]` command. -func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { +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" @@ -1645,7 +1688,7 @@ func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { } // Redis `ZADD key XX score member [score member ...]` command. -func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { +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" @@ -1653,7 +1696,7 @@ func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { } // Redis `ZADD key CH score member [score member ...]` command. -func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { +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" @@ -1661,7 +1704,7 @@ func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { } // Redis `ZADD key NX CH score member [score member ...]` command. -func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { +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" @@ -1669,25 +1712,25 @@ func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { } // Redis `ZADD key XX CH score member [score member ...]` command. -func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { +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 { +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) + _ = c(cmd) return cmd } // Redis `ZADD key INCR score member` command. -func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { +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" @@ -1695,7 +1738,7 @@ func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { } // Redis `ZADD key NX INCR score member` command. -func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { +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" @@ -1703,43 +1746,43 @@ func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { } // Redis `ZADD key XX INCR score member` command. -func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { +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 { +func (c cmdable) ZCard(key string) *IntCmd { cmd := NewIntCmd("zcard", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZCount(key, min, max string) *IntCmd { +func (c cmdable) ZCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zcount", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { +func (c cmdable) ZLexCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zlexcount", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { +func (c cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { cmd := NewFloatCmd("zincrby", key, increment, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) +func (c cmdable) ZInterStore(destination string, store *ZStore) *IntCmd { + args := make([]interface{}, 3+len(store.Keys)) args[0] = "zinterstore" args[1] = destination - args[2] = len(keys) - for i, key := range keys { + args[2] = len(store.Keys) + for i, key := range store.Keys { args[3+i] = key } if len(store.Weights) > 0 { @@ -1752,11 +1795,11 @@ func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { +func (c cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { args := []interface{}{ "zpopmax", key, @@ -1772,11 +1815,11 @@ func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { +func (c cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { args := []interface{}{ "zpopmin", key, @@ -1792,11 +1835,11 @@ func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { +func (c cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { args := []interface{}{ "zrange", key, @@ -1807,17 +1850,17 @@ func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *String args = append(args, "withscores") } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { +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 { +func (c cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") - c.process(cmd) + _ = c(cmd) return cmd } @@ -1826,7 +1869,7 @@ type ZRangeBy struct { Offset, Count int64 } -func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { +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") @@ -1840,19 +1883,19 @@ func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *Str ) } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { +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 { +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 { +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( @@ -1863,62 +1906,62 @@ func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { ) } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRank(key, member string) *IntCmd { +func (c cmdable) ZRank(key, member string) *IntCmd { cmd := NewIntCmd("zrank", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { +func (c cmdable) ZRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "zrem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { +func (c cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { cmd := NewIntCmd( "zremrangebyrank", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { +func (c cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebyscore", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { +func (c cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebylex", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { +func (c cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd("zrevrange", key, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { +func (c cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { +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( @@ -1929,19 +1972,19 @@ func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { ) } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRevRangeByScore(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebyscore", key, opt) } -func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRevRangeByLex(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebylex", key, opt) } -func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { +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( @@ -1952,28 +1995,28 @@ func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCm ) } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRank(key, member string) *IntCmd { +func (c cmdable) ZRevRank(key, member string) *IntCmd { cmd := NewIntCmd("zrevrank", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZScore(key, member string) *FloatCmd { +func (c cmdable) ZScore(key, member string) *FloatCmd { cmd := NewFloatCmd("zscore", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) +func (c cmdable) ZUnionStore(dest string, store *ZStore) *IntCmd { + args := make([]interface{}, 3+len(store.Keys)) args[0] = "zunionstore" args[1] = dest - args[2] = len(keys) - for i, key := range keys { + args[2] = len(store.Keys) + for i, key := range store.Keys { args[3+i] = key } if len(store.Weights) > 0 { @@ -1986,34 +2029,34 @@ func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { +func (c cmdable) PFAdd(key string, els ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(els)) args[0] = "pfadd" args[1] = key args = appendArgs(args, els) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PFCount(keys ...string) *IntCmd { +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) + _ = c(cmd) return cmd } -func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { +func (c cmdable) PFMerge(dest string, keys ...string) *StatusCmd { args := make([]interface{}, 2+len(keys)) args[0] = "pfmerge" args[1] = dest @@ -2021,33 +2064,33 @@ func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { args[2+i] = key } cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) BgRewriteAOF() *StatusCmd { +func (c cmdable) BgRewriteAOF() *StatusCmd { cmd := NewStatusCmd("bgrewriteaof") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BgSave() *StatusCmd { +func (c cmdable) BgSave() *StatusCmd { cmd := NewStatusCmd("bgsave") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ClientKill(ipPort string) *StatusCmd { +func (c cmdable) ClientKill(ipPort string) *StatusCmd { cmd := NewStatusCmd("client", "kill", ipPort) - c.process(cmd) + _ = c(cmd) return cmd } // ClientKillByFilter is new style synx, while the ClientKill is old // CLIENT KILL