diff --git a/v2/commands.go b/v2/commands.go new file mode 100644 index 0000000..db363b0 --- /dev/null +++ b/v2/commands.go @@ -0,0 +1,1155 @@ +package redis + +import ( + "strconv" +) + +func formatFloat(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +//------------------------------------------------------------------------------ + +func (c *Client) Auth(password string) *StatusReq { + req := NewStatusReq("AUTH", password) + c.Process(req) + return req +} + +func (c *Client) Echo(message string) *StringReq { + req := NewStringReq("ECHO", message) + c.Process(req) + return req +} + +func (c *Client) Ping() *StatusReq { + req := NewStatusReq("PING") + c.Process(req) + return req +} + +func (c *Client) Quit() *StatusReq { + panic("not implemented") +} + +func (c *Client) Select(index int64) *StatusReq { + req := NewStatusReq("SELECT", strconv.FormatInt(index, 10)) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) Del(keys ...string) *IntReq { + args := append([]string{"DEL"}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) Dump(key string) *StringReq { + req := NewStringReq("DUMP", key) + c.Process(req) + return req +} + +func (c *Client) Exists(key string) *BoolReq { + req := NewBoolReq("EXISTS", key) + c.Process(req) + return req +} + +func (c *Client) Expire(key string, seconds int64) *BoolReq { + req := NewBoolReq("EXPIRE", key, strconv.FormatInt(seconds, 10)) + c.Process(req) + return req +} + +func (c *Client) ExpireAt(key string, timestamp int64) *BoolReq { + req := NewBoolReq("EXPIREAT", key, strconv.FormatInt(timestamp, 10)) + c.Process(req) + return req +} + +func (c *Client) Keys(pattern string) *StringSliceReq { + req := NewStringSliceReq("KEYS", pattern) + c.Process(req) + return req +} + +func (c *Client) Migrate(host, port, key string, db, timeout int64) *StatusReq { + req := NewStatusReq( + "MIGRATE", + host, + port, + key, + strconv.FormatInt(db, 10), + strconv.FormatInt(timeout, 10), + ) + c.Process(req) + return req +} + +func (c *Client) Move(key string, db int64) *BoolReq { + req := NewBoolReq("MOVE", key, strconv.FormatInt(db, 10)) + c.Process(req) + return req +} + +func (c *Client) ObjectRefCount(keys ...string) *IntReq { + args := append([]string{"OBJECT", "REFCOUNT"}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) ObjectEncoding(keys ...string) *StringReq { + args := append([]string{"OBJECT", "ENCODING"}, keys...) + req := NewStringReq(args...) + c.Process(req) + return req +} + +func (c *Client) ObjectIdleTime(keys ...string) *IntReq { + args := append([]string{"OBJECT", "IDLETIME"}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) Persist(key string) *BoolReq { + req := NewBoolReq("PERSIST", key) + c.Process(req) + return req +} + +func (c *Client) PExpire(key string, milliseconds int64) *BoolReq { + req := NewBoolReq("PEXPIRE", key, strconv.FormatInt(milliseconds, 10)) + c.Process(req) + return req +} + +func (c *Client) PExpireAt(key string, milliseconds int64) *BoolReq { + req := NewBoolReq("PEXPIREAT", key, strconv.FormatInt(milliseconds, 10)) + c.Process(req) + return req +} + +func (c *Client) PTTL(key string) *IntReq { + req := NewIntReq("PTTL", key) + c.Process(req) + return req +} + +func (c *Client) RandomKey() *StringReq { + req := NewStringReq("RANDOMKEY") + c.Process(req) + return req +} + +func (c *Client) Rename(key, newkey string) *StatusReq { + req := NewStatusReq("RENAME", key, newkey) + c.Process(req) + return req +} + +func (c *Client) RenameNX(key, newkey string) *BoolReq { + req := NewBoolReq("RENAMENX", key, newkey) + c.Process(req) + return req +} + +func (c *Client) Restore(key string, ttl int64, value string) *StatusReq { + req := NewStatusReq( + "RESTORE", + key, + strconv.FormatInt(ttl, 10), + value, + ) + c.Process(req) + return req +} + +type Sort struct { + By string + Offset, Count float64 + Get []string + Order string + IsAlpha bool + Store string +} + +func (c *Client) Sort(key string, sort Sort) *StringSliceReq { + args := []string{"SORT", key} + if sort.By != "" { + args = append(args, sort.By) + } + if sort.Offset != 0 || sort.Count != 0 { + args = append(args, "LIMIT", formatFloat(sort.Offset), formatFloat(sort.Count)) + } + for _, get := range sort.Get { + args = append(args, "GET", get) + } + if sort.Order != "" { + args = append(args, sort.Order) + } + if sort.IsAlpha { + args = append(args, "ALPHA") + } + if sort.Store != "" { + args = append(args, "STORE", sort.Store) + } + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) TTL(key string) *IntReq { + req := NewIntReq("TTL", key) + c.Process(req) + return req +} + +func (c *Client) Type(key string) *StatusReq { + req := NewStatusReq("TYPE", key) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) Append(key, value string) *IntReq { + req := NewIntReq("APPEND", key, value) + c.Process(req) + return req +} + +type BitCount struct { + Start, End int64 +} + +func (c *Client) BitCount(key string, bitCount *BitCount) *IntReq { + args := []string{"BITCOUNT", key} + if bitCount != nil { + args = append( + args, + strconv.FormatInt(bitCount.Start, 10), + strconv.FormatInt(bitCount.End, 10), + ) + } + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) bitOp(op, destKey string, keys ...string) *IntReq { + args := []string{"BITOP", op, destKey} + args = append(args, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) BitOpAnd(destKey string, keys ...string) *IntReq { + return c.bitOp("AND", destKey, keys...) +} + +func (c *Client) BitOpOr(destKey string, keys ...string) *IntReq { + return c.bitOp("OR", destKey, keys...) +} + +func (c *Client) BitOpXor(destKey string, keys ...string) *IntReq { + return c.bitOp("XOR", destKey, keys...) +} + +func (c *Client) BitOpNot(destKey string, key string) *IntReq { + return c.bitOp("NOT", destKey, key) +} + +func (c *Client) Decr(key string) *IntReq { + req := NewIntReq("DECR", key) + c.Process(req) + return req +} + +func (c *Client) DecrBy(key string, decrement int64) *IntReq { + req := NewIntReq("DECRBY", key, strconv.FormatInt(decrement, 10)) + c.Process(req) + return req +} + +func (c *Client) Get(key string) *StringReq { + req := NewStringReq("GET", key) + c.Process(req) + return req +} + +func (c *Client) GetBit(key string, offset int64) *IntReq { + req := NewIntReq("GETBIT", key, strconv.FormatInt(offset, 10)) + c.Process(req) + return req +} + +func (c *Client) GetRange(key string, start, end int64) *StringReq { + req := NewStringReq( + "GETRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(end, 10), + ) + c.Process(req) + return req +} + +func (c *Client) GetSet(key, value string) *StringReq { + req := NewStringReq("GETSET", key, value) + c.Process(req) + return req +} + +func (c *Client) Incr(key string) *IntReq { + req := NewIntReq("INCR", key) + c.Process(req) + return req +} + +func (c *Client) IncrBy(key string, value int64) *IntReq { + req := NewIntReq("INCRBY", key, strconv.FormatInt(value, 10)) + c.Process(req) + return req +} + +func (c *Client) IncrByFloat(key string, value float64) *FloatReq { + req := NewFloatReq("INCRBYFLOAT", key, formatFloat(value)) + c.Process(req) + return req +} + +func (c *Client) MGet(keys ...string) *IfaceSliceReq { + args := append([]string{"MGET"}, keys...) + req := NewIfaceSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) MSet(pairs ...string) *StatusReq { + args := append([]string{"MSET"}, pairs...) + req := NewStatusReq(args...) + c.Process(req) + return req +} + +func (c *Client) MSetNX(pairs ...string) *BoolReq { + args := append([]string{"MSETNX"}, pairs...) + req := NewBoolReq(args...) + c.Process(req) + return req +} + +func (c *Client) PSetEx(key string, milliseconds int64, value string) *StatusReq { + req := NewStatusReq( + "PSETEX", + key, + strconv.FormatInt(milliseconds, 10), + value, + ) + c.Process(req) + return req +} + +func (c *Client) Set(key, value string) *StatusReq { + req := NewStatusReq("SET", key, value) + c.Process(req) + return req +} + +func (c *Client) SetBit(key string, offset int64, value int) *IntReq { + req := NewIntReq( + "SETBIT", + key, + strconv.FormatInt(offset, 10), + strconv.FormatInt(int64(value), 10), + ) + c.Process(req) + return req +} + +func (c *Client) SetEx(key string, seconds int64, value string) *StatusReq { + req := NewStatusReq("SETEX", key, strconv.FormatInt(seconds, 10), value) + c.Process(req) + return req +} + +func (c *Client) SetNX(key, value string) *BoolReq { + req := NewBoolReq("SETNX", key, value) + c.Process(req) + return req +} + +func (c *Client) SetRange(key string, offset int64, value string) *IntReq { + req := NewIntReq("SETRANGE", key, strconv.FormatInt(offset, 10), value) + c.Process(req) + return req +} + +func (c *Client) StrLen(key string) *IntReq { + req := NewIntReq("STRLEN", key) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) HDel(key string, fields ...string) *IntReq { + args := append([]string{"HDEL", key}, fields...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) HExists(key, field string) *BoolReq { + req := NewBoolReq("HEXISTS", key, field) + c.Process(req) + return req +} + +func (c *Client) HGet(key, field string) *StringReq { + req := NewStringReq("HGET", key, field) + c.Process(req) + return req +} + +func (c *Client) HGetAll(key string) *StringSliceReq { + req := NewStringSliceReq("HGETALL", key) + c.Process(req) + return req +} + +func (c *Client) HGetAllMap(key string) *StringStringMapReq { + req := NewStringStringMapReq("HGETALL", key) + c.Process(req) + return req +} + +func (c *Client) HIncrBy(key, field string, incr int64) *IntReq { + req := NewIntReq("HINCRBY", key, field, strconv.FormatInt(incr, 10)) + c.Process(req) + return req +} + +func (c *Client) HIncrByFloat(key, field string, incr float64) *FloatReq { + req := NewFloatReq("HINCRBYFLOAT", key, field, formatFloat(incr)) + c.Process(req) + return req +} + +func (c *Client) HKeys(key string) *StringSliceReq { + req := NewStringSliceReq("HKEYS", key) + c.Process(req) + return req +} + +func (c *Client) HLen(key string) *IntReq { + req := NewIntReq("HLEN", key) + c.Process(req) + return req +} + +func (c *Client) HMGet(key string, fields ...string) *IfaceSliceReq { + args := append([]string{"HMGET", key}, fields...) + req := NewIfaceSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) HMSet(key, field, value string, pairs ...string) *StatusReq { + args := append([]string{"HMSET", key, field, value}, pairs...) + req := NewStatusReq(args...) + c.Process(req) + return req +} + +func (c *Client) HSet(key, field, value string) *BoolReq { + req := NewBoolReq("HSET", key, field, value) + c.Process(req) + return req +} + +func (c *Client) HSetNX(key, field, value string) *BoolReq { + req := NewBoolReq("HSETNX", key, field, value) + c.Process(req) + return req +} + +func (c *Client) HVals(key string) *StringSliceReq { + req := NewStringSliceReq("HVALS", key) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) BLPop(timeout int64, keys ...string) *StringSliceReq { + args := append([]string{"BLPOP"}, keys...) + args = append(args, strconv.FormatInt(timeout, 10)) + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) BRPop(timeout int64, keys ...string) *StringSliceReq { + args := append([]string{"BRPOP"}, keys...) + args = append(args, strconv.FormatInt(timeout, 10)) + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) BRPopLPush(source, destination string, timeout int64) *StringReq { + req := NewStringReq( + "BRPOPLPUSH", + source, + destination, + strconv.FormatInt(timeout, 10), + ) + c.Process(req) + return req +} + +func (c *Client) LIndex(key string, index int64) *StringReq { + req := NewStringReq("LINDEX", key, strconv.FormatInt(index, 10)) + c.Process(req) + return req +} + +func (c *Client) LInsert(key, op, pivot, value string) *IntReq { + req := NewIntReq("LINSERT", key, op, pivot, value) + c.Process(req) + return req +} + +func (c *Client) LLen(key string) *IntReq { + req := NewIntReq("LLEN", key) + c.Process(req) + return req +} + +func (c *Client) LPop(key string) *StringReq { + req := NewStringReq("LPOP", key) + c.Process(req) + return req +} + +func (c *Client) LPush(key string, values ...string) *IntReq { + args := append([]string{"LPUSH", key}, values...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) LPushX(key, value string) *IntReq { + req := NewIntReq("LPUSHX", key, value) + c.Process(req) + return req +} + +func (c *Client) LRange(key string, start, stop int64) *StringSliceReq { + req := NewStringSliceReq( + "LRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + ) + c.Process(req) + return req +} + +func (c *Client) LRem(key string, count int64, value string) *IntReq { + req := NewIntReq("LREM", key, strconv.FormatInt(count, 10), value) + c.Process(req) + return req +} + +func (c *Client) LSet(key string, index int64, value string) *StatusReq { + req := NewStatusReq("LSET", key, strconv.FormatInt(index, 10), value) + c.Process(req) + return req +} + +func (c *Client) LTrim(key string, start, stop int64) *StatusReq { + req := NewStatusReq( + "LTRIM", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + ) + c.Process(req) + return req +} + +func (c *Client) RPop(key string) *StringReq { + req := NewStringReq("RPOP", key) + c.Process(req) + return req +} + +func (c *Client) RPopLPush(source, destination string) *StringReq { + req := NewStringReq("RPOPLPUSH", source, destination) + c.Process(req) + return req +} + +func (c *Client) RPush(key string, values ...string) *IntReq { + args := append([]string{"RPUSH", key}, values...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) RPushX(key string, value string) *IntReq { + req := NewIntReq("RPUSHX", key, value) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) SAdd(key string, members ...string) *IntReq { + args := append([]string{"SADD", key}, members...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) SCard(key string) *IntReq { + req := NewIntReq("SCARD", key) + c.Process(req) + return req +} + +func (c *Client) SDiff(keys ...string) *StringSliceReq { + args := append([]string{"SDIFF"}, keys...) + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) SDiffStore(destination string, keys ...string) *IntReq { + args := append([]string{"SDIFFSTORE", destination}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) SInter(keys ...string) *StringSliceReq { + args := append([]string{"SINTER"}, keys...) + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) SInterStore(destination string, keys ...string) *IntReq { + args := append([]string{"SINTERSTORE", destination}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) SIsMember(key, member string) *BoolReq { + req := NewBoolReq("SISMEMBER", key, member) + c.Process(req) + return req +} + +func (c *Client) SMembers(key string) *StringSliceReq { + req := NewStringSliceReq("SMEMBERS", key) + c.Process(req) + return req +} + +func (c *Client) SMove(source, destination, member string) *BoolReq { + req := NewBoolReq("SMOVE", source, destination, member) + c.Process(req) + return req +} + +func (c *Client) SPop(key string) *StringReq { + req := NewStringReq("SPOP", key) + c.Process(req) + return req +} + +func (c *Client) SRandMember(key string) *StringReq { + req := NewStringReq("SRANDMEMBER", key) + c.Process(req) + return req +} + +func (c *Client) SRem(key string, members ...string) *IntReq { + args := append([]string{"SREM", key}, members...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) SUnion(keys ...string) *StringSliceReq { + args := append([]string{"SUNION"}, keys...) + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) SUnionStore(destination string, keys ...string) *IntReq { + args := append([]string{"SUNIONSTORE", destination}, keys...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +type Z struct { + Score float64 + Member string +} + +type ZStore struct { + Weights []int64 + Aggregate string +} + +func (c *Client) ZAdd(key string, members ...Z) *IntReq { + args := []string{"ZADD", key} + for _, m := range members { + args = append(args, formatFloat(m.Score), m.Member) + } + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZCard(key string) *IntReq { + req := NewIntReq("ZCARD", key) + c.Process(req) + return req +} + +func (c *Client) ZCount(key, min, max string) *IntReq { + req := NewIntReq("ZCOUNT", key, min, max) + c.Process(req) + return req +} + +func (c *Client) ZIncrBy(key string, increment float64, member string) *FloatReq { + req := NewFloatReq("ZINCRBY", key, formatFloat(increment), member) + c.Process(req) + return req +} + +func (c *Client) ZInterStore( + destination string, + store ZStore, + keys ...string, +) *IntReq { + args := []string{"ZINTERSTORE", destination, strconv.FormatInt(int64(len(keys)), 10)} + args = append(args, keys...) + if len(store.Weights) > 0 { + args = append(args, "WEIGHTS") + for _, weight := range store.Weights { + args = append(args, strconv.FormatInt(weight, 10)) + } + } + if store.Aggregate != "" { + args = append(args, "AGGREGATE", store.Aggregate) + } + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) zRange(key string, start, stop int64, withScores bool) *StringSliceReq { + args := []string{ + "ZRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + } + if withScores { + args = append(args, "WITHSCORES") + } + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRange(key string, start, stop int64) *StringSliceReq { + return c.zRange(key, start, stop, false) +} + +func (c *Client) ZRangeWithScores(key string, start, stop int64) *StringSliceReq { + return c.zRange(key, start, stop, true) +} + +func (c *Client) ZRangeWithScoresMap(key string, start, stop int64) *StringFloatMapReq { + args := []string{ + "ZRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + "WITHSCORES", + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + +func (c *Client) zRangeByScore( + key string, + min, max string, + withScores bool, + offset, count int64, +) *StringSliceReq { + args := []string{"ZRANGEBYSCORE", key, min, max} + if withScores { + args = append(args, "WITHSCORES") + } + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRangeByScore(key string, min, max string, offset, count int64) *StringSliceReq { + return c.zRangeByScore(key, min, max, false, offset, count) +} + +func (c *Client) ZRangeByScoreWithScores(key, min, max string, offset, count int64) *StringSliceReq { + return c.zRangeByScore(key, min, max, true, offset, count) +} + +func (c *Client) ZRangeByScoreWithScoresMap( + key string, min, max string, offset, count int64) *StringFloatMapReq { + args := []string{"ZRANGEBYSCORE", key, min, max, "WITHSCORES"} + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRank(key, member string) *IntReq { + req := NewIntReq("ZRANK", key, member) + c.Process(req) + return req +} + +func (c *Client) ZRem(key string, members ...string) *IntReq { + args := append([]string{"ZREM", key}, members...) + req := NewIntReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRemRangeByRank(key string, start, stop int64) *IntReq { + req := NewIntReq( + "ZREMRANGEBYRANK", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + ) + c.Process(req) + return req +} + +func (c *Client) ZRemRangeByScore(key, min, max string) *IntReq { + req := NewIntReq("ZREMRANGEBYSCORE", key, min, max) + c.Process(req) + return req +} + +func (c *Client) zRevRange(key, start, stop string, withScores bool) *StringSliceReq { + args := []string{"ZREVRANGE", key, start, stop} + if withScores { + args = append(args, "WITHSCORES") + } + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRevRange(key, start, stop string) *StringSliceReq { + return c.zRevRange(key, start, stop, false) +} + +func (c *Client) ZRevRangeWithScores(key, start, stop string) *StringSliceReq { + return c.zRevRange(key, start, stop, true) +} + +func (c *Client) ZRevRangeWithScoresMap(key, start, stop string) *StringFloatMapReq { + args := []string{"ZREVRANGE", key, start, stop, "WITHSCORES"} + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + +func (c *Client) zRevRangeByScore(key, start, stop string, withScores bool, offset, count int64) *StringSliceReq { + args := []string{"ZREVRANGEBYSCORE", key, start, stop} + if withScores { + args = append(args, "WITHSCORES") + } + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRevRangeByScore(key, start, stop string, offset, count int64) *StringSliceReq { + return c.zRevRangeByScore(key, start, stop, false, offset, count) +} + +func (c *Client) ZRevRangeByScoreWithScores(key, start, stop string, offset, count int64) *StringSliceReq { + return c.zRevRangeByScore(key, start, stop, false, offset, count) +} + +func (c *Client) ZRevRangeByScoreWithScoresMap( + key, start, stop string, offset, count int64) *StringFloatMapReq { + args := []string{"ZREVRANGEBYSCORE", key, start, stop, "WITHSCORES"} + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + +func (c *Client) ZRevRank(key, member string) *IntReq { + req := NewIntReq("ZREVRANK", key, member) + c.Process(req) + return req +} + +func (c *Client) ZScore(key, member string) *FloatReq { + req := NewFloatReq("ZSCORE", key, member) + c.Process(req) + return req +} + +func (c *Client) ZUnionStore( + destination string, + store ZStore, + keys ...string, +) *IntReq { + args := []string{"ZUNIONSTORE", destination, strconv.FormatInt(int64(len(keys)), 10)} + args = append(args, keys...) + if len(store.Weights) > 0 { + args = append(args, "WEIGHTS") + for _, weight := range store.Weights { + args = append(args, strconv.FormatInt(weight, 10)) + } + } + if store.Aggregate != "" { + args = append(args, "AGGREGATE", store.Aggregate) + } + req := NewIntReq(args...) + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) BgRewriteAOF() *StatusReq { + req := NewStatusReq("BGREWRITEAOF") + c.Process(req) + return req +} + +func (c *Client) BgSave() *StatusReq { + req := NewStatusReq("BGSAVE") + c.Process(req) + return req +} + +func (c *Client) ClientKill(ipPort string) *StatusReq { + req := NewStatusReq("CLIENT", "KILL", ipPort) + c.Process(req) + return req +} + +func (c *Client) ClientList() *StringReq { + req := NewStringReq("CLIENT", "LIST") + c.Process(req) + return req +} + +func (c *Client) ConfigGet(parameter string) *IfaceSliceReq { + req := NewIfaceSliceReq("CONFIG", "GET", parameter) + c.Process(req) + return req +} + +func (c *Client) ConfigResetStat() *StatusReq { + req := NewStatusReq("CONFIG", "RESETSTAT") + c.Process(req) + return req +} + +func (c *Client) ConfigSet(parameter, value string) *StatusReq { + req := NewStatusReq("CONFIG", "SET", parameter, value) + c.Process(req) + return req +} + +func (c *Client) DbSize() *IntReq { + req := NewIntReq("DBSIZE") + c.Process(req) + return req +} + +func (c *Client) FlushAll() *StatusReq { + req := NewStatusReq("FLUSHALL") + c.Process(req) + return req +} + +func (c *Client) FlushDb() *StatusReq { + req := NewStatusReq("FLUSHDB") + c.Process(req) + return req +} + +func (c *Client) Info() *StringReq { + req := NewStringReq("INFO") + c.Process(req) + return req +} + +func (c *Client) LastSave() *IntReq { + req := NewIntReq("LASTSAVE") + c.Process(req) + return req +} + +func (c *Client) Monitor() { + panic("not implemented") +} + +func (c *Client) Save() *StatusReq { + req := NewStatusReq("SAVE") + c.Process(req) + return req +} + +func (c *Client) shutdown(modifier string) *StatusReq { + var args []string + if modifier == "" { + args = []string{"SHUTDOWN"} + } else { + args = []string{"SHUTDOWN", modifier} + } + req := NewStatusReq(args...) + c.Process(req) + c.Close() + return req +} + +func (c *Client) Shutdown() *StatusReq { + return c.shutdown("") +} + +func (c *Client) ShutdownSave() *StatusReq { + return c.shutdown("SAVE") +} + +func (c *Client) ShutdownNoSave() *StatusReq { + return c.shutdown("NOSAVE") +} + +func (c *Client) SlaveOf(host, port string) *StatusReq { + req := NewStatusReq("SLAVEOF", host, port) + c.Process(req) + return req +} + +func (c *Client) SlowLog() { + panic("not implemented") +} + +func (c *Client) Sync() { + panic("not implemented") +} + +func (c *Client) Time() *StringSliceReq { + req := NewStringSliceReq("TIME") + c.Process(req) + return req +} + +//------------------------------------------------------------------------------ + +func (c *Client) Eval(script string, keys []string, args []string) *IfaceReq { + reqArgs := []string{"EVAL", script, strconv.FormatInt(int64(len(keys)), 10)} + reqArgs = append(reqArgs, keys...) + reqArgs = append(reqArgs, args...) + req := NewIfaceReq(reqArgs...) + c.Process(req) + return req +} + +func (c *Client) EvalSha(sha1 string, keys []string, args []string) *IfaceReq { + reqArgs := []string{"EVALSHA", sha1, strconv.FormatInt(int64(len(keys)), 10)} + reqArgs = append(reqArgs, keys...) + reqArgs = append(reqArgs, args...) + req := NewIfaceReq(reqArgs...) + c.Process(req) + return req +} + +func (c *Client) ScriptExists(scripts ...string) *BoolSliceReq { + args := append([]string{"SCRIPT", "EXISTS"}, scripts...) + req := NewBoolSliceReq(args...) + c.Process(req) + return req +} + +func (c *Client) ScriptFlush() *StatusReq { + req := NewStatusReq("SCRIPT", "FLUSH") + c.Process(req) + return req +} + +func (c *Client) ScriptKill() *StatusReq { + req := NewStatusReq("SCRIPT", "KILL") + c.Process(req) + return req +} + +func (c *Client) ScriptLoad(script string) *StringReq { + req := NewStringReq("SCRIPT", "LOAD", script) + c.Process(req) + return req +} diff --git a/v2/doc.go b/v2/doc.go new file mode 100644 index 0000000..ea9ac6c --- /dev/null +++ b/v2/doc.go @@ -0,0 +1,133 @@ +/* +Package github.com/vmihailenco/redis implements a Redis client. + +Let's start with connecting to Redis using TCP: + + password := "" // no password set + db := int64(-1) // use default DB + client := redis.NewTCPClient("localhost:6379", password, db) + defer client.Close() + + ping := client.Ping() + fmt.Println(ping.Err(), ping.Val()) + // Output: PONG + +or using Unix socket: + + client := redis.NewUnixClient("/tmp/redis.sock", "", -1) + defer client.Close() + + ping := client.Ping() + fmt.Println(ping.Err(), ping.Val()) + // Output: PONG + +Then we can start sending commands: + + set := client.Set("foo", "bar") + fmt.Println(set.Err(), set.Val()) + + get := client.Get("foo") + fmt.Println(get.Err(), get.Val()) + + // Output: OK + // bar + +We can also pipeline two commands together: + + var set *redis.StatusReq + var get *redis.StringReq + reqs, err := client.Pipelined(func(c *redis.PipelineClient) { + set = c.Set("key1", "hello1") + get = c.Get("key2") + }) + fmt.Println(err, reqs) + fmt.Println(set) + fmt.Println(get) + // Output: [SET key1 hello1: OK GET key2: (nil)] + // SET key1 hello1: OK + // GET key2: (nil) + +or: + + var set *redis.StatusReq + var get *redis.StringReq + reqs, err := client.Pipelined(func(c *redis.PipelineClient) { + set = c.Set("key1", "hello1") + get = c.Get("key2") + }) + fmt.Println(err, reqs) + fmt.Println(set) + fmt.Println(get) + // Output: [SET key1 hello1 GET key2] + // SET key1 hello1 + // GET key2 + +We can also send several commands in transaction: + + func transaction(multi *redis.MultiClient) ([]redis.Req, error) { + get := multi.Get("key") + if err := get.Err(); err != nil && err != redis.Nil { + return nil, err + } + + val, _ := strconv.ParseInt(get.Val(), 10, 64) + + reqs, err := multi.Exec(func() { + multi.Set("key", strconv.FormatInt(val+1, 10)) + }) + // Transaction failed. Repeat. + if err == redis.Nil { + return transaction(multi) + } + return reqs, err + } + + multi, err := client.MultiClient() + _ = err + defer multi.Close() + + watch := multi.Watch("key") + _ = watch.Err() + + reqs, err := transaction(multi) + fmt.Println(err, reqs) + + // Output: [SET key 1: OK] + +To subscribe to the channel: + + pubsub, err := client.PubSubClient() + defer pubsub.Close() + + ch, err := pubsub.Subscribe("mychannel") + _ = err + + subscribeMsg := <-ch + fmt.Println(subscribeMsg.Err, subscribeMsg.Name) + + pub := client.Publish("mychannel", "hello") + _ = pub.Err() + + msg := <-ch + fmt.Println(msg.Err, msg.Message) + + // Output: subscribe + // hello + +You can also write custom commands: + + func Get(client *redis.Client, key string) *redis.StringReq { + req := redis.NewStringReq("GET", key) + client.Process(req) + return req + } + + get := Get(client, "key_does_not_exist") + fmt.Println(get.Err(), get.Val()) + // Output: (nil) + +Client uses connection pool to send commands. You can change maximum number of connections with: + + client.ConnPool.(*redis.MultiConnPool).MaxCap = 1 +*/ +package redis diff --git a/v2/example_test.go b/v2/example_test.go new file mode 100644 index 0000000..f998c1b --- /dev/null +++ b/v2/example_test.go @@ -0,0 +1,135 @@ +package redis_test + +import ( + "fmt" + "strconv" + + "github.com/vmihailenco/redis/v2" +) + +func ExampleTCPClient() { + password := "" // no password set + db := int64(-1) // use default DB + client := redis.NewTCPClient("localhost:6379", password, db) + defer client.Close() + + ping := client.Ping() + fmt.Println(ping.Err(), ping.Val()) + // Output: PONG +} + +func ExampleUnixClient() { + client := redis.NewUnixClient("/tmp/redis.sock", "", -1) + defer client.Close() + + ping := client.Ping() + fmt.Println(ping.Err(), ping.Val()) + // Output: PONG +} + +func ExampleSetGet() { + client := redis.NewTCPClient(":6379", "", -1) + defer client.Close() + + set := client.Set("foo", "bar") + fmt.Println(set.Err(), set.Val()) + + get := client.Get("foo") + fmt.Println(get.Err(), get.Val()) + + // Output: OK + // bar +} + +func ExamplePipeline() { + client := redis.NewTCPClient(":6379", "", -1) + defer client.Close() + + var set *redis.StatusReq + var get *redis.StringReq + reqs, err := client.Pipelined(func(c *redis.PipelineClient) { + set = c.Set("key1", "hello1") + get = c.Get("key2") + }) + fmt.Println(err, reqs) + fmt.Println(set) + fmt.Println(get) + // Output: [SET key1 hello1: OK GET key2: (nil)] + // SET key1 hello1: OK + // GET key2: (nil) +} + +func transaction(multi *redis.MultiClient) ([]redis.Req, error) { + get := multi.Get("key") + if err := get.Err(); err != nil && err != redis.Nil { + return nil, err + } + + val, _ := strconv.ParseInt(get.Val(), 10, 64) + + reqs, err := multi.Exec(func() { + multi.Set("key", strconv.FormatInt(val+1, 10)) + }) + // Transaction failed. Repeat. + if err == redis.Nil { + return transaction(multi) + } + return reqs, err +} + +func ExampleTransaction() { + client := redis.NewTCPClient(":6379", "", -1) + defer client.Close() + + client.Del("key") + + multi, err := client.MultiClient() + _ = err + defer multi.Close() + + watch := multi.Watch("key") + _ = watch.Err() + + reqs, err := transaction(multi) + fmt.Println(err, reqs) + + // Output: [SET key 1: OK] +} + +func ExamplePubSub() { + client := redis.NewTCPClient(":6379", "", -1) + defer client.Close() + + pubsub, err := client.PubSubClient() + defer pubsub.Close() + + ch, err := pubsub.Subscribe("mychannel") + _ = err + + subscribeMsg := <-ch + fmt.Println(subscribeMsg.Err, subscribeMsg.Name) + + pub := client.Publish("mychannel", "hello") + _ = pub.Err() + + msg := <-ch + fmt.Println(msg.Err, msg.Message) + + // Output: subscribe + // hello +} + +func Get(client *redis.Client, key string) *redis.StringReq { + req := redis.NewStringReq("GET", key) + client.Process(req) + return req +} + +func ExampleCustomCommand() { + client := redis.NewTCPClient(":6379", "", -1) + defer client.Close() + + get := Get(client, "key_does_not_exist") + fmt.Println(get.Err(), get.Val()) + // Output: (nil) +} diff --git a/v2/multi.go b/v2/multi.go new file mode 100644 index 0000000..86a1567 --- /dev/null +++ b/v2/multi.go @@ -0,0 +1,134 @@ +package redis + +import ( + "fmt" + "sync" +) + +type MultiClient struct { + *Client + execMtx sync.Mutex +} + +func (c *Client) MultiClient() (*MultiClient, error) { + return &MultiClient{ + Client: &Client{ + baseClient: &baseClient{ + connPool: newSingleConnPool(c.connPool, nil, true), + + password: c.password, + db: c.db, + }, + }, + }, nil +} + +func (c *MultiClient) Close() error { + c.Unwatch() + return c.Client.Close() +} + +func (c *MultiClient) Watch(keys ...string) *StatusReq { + args := append([]string{"WATCH"}, keys...) + req := NewStatusReq(args...) + c.Process(req) + return req +} + +func (c *MultiClient) Unwatch(keys ...string) *StatusReq { + args := append([]string{"UNWATCH"}, keys...) + req := NewStatusReq(args...) + c.Process(req) + return req +} + +func (c *MultiClient) Discard() { + c.reqsMtx.Lock() + if c.reqs == nil { + panic("Discard can be used only inside Exec") + } + c.reqs = c.reqs[:1] + c.reqsMtx.Unlock() +} + +func (c *MultiClient) Exec(do func()) ([]Req, error) { + c.reqsMtx.Lock() + c.reqs = []Req{NewStatusReq("MULTI")} + c.reqsMtx.Unlock() + + do() + + c.queue(NewIfaceSliceReq("EXEC")) + + c.reqsMtx.Lock() + reqs := c.reqs + c.reqs = nil + c.reqsMtx.Unlock() + + if len(reqs) == 2 { + return []Req{}, nil + } + + cn, err := c.conn() + if err != nil { + return nil, err + } + + // Synchronize writes and reads to the connection using mutex. + c.execMtx.Lock() + err = c.execReqs(reqs, cn) + c.execMtx.Unlock() + if err != nil { + c.removeConn(cn) + return nil, err + } + + c.putConn(cn) + return reqs[1 : len(reqs)-1], nil +} + +func (c *MultiClient) execReqs(reqs []Req, cn *conn) error { + err := c.writeReq(cn, reqs...) + if err != nil { + return err + } + + statusReq := NewStatusReq() + + // Omit last request (EXEC). + reqsLen := len(reqs) - 1 + + // Parse queued replies. + for i := 0; i < reqsLen; i++ { + _, err = statusReq.ParseReply(cn.Rd) + if err != nil { + return err + } + } + + // Parse number of replies. + line, err := readLine(cn.Rd) + if err != nil { + return err + } + if line[0] != '*' { + return fmt.Errorf("Expected '*', but got line %q", line) + } + if len(line) == 3 && line[1] == '-' && line[2] == '1' { + return Nil + } + + // Parse replies. + // Loop starts from 1 to omit first request (MULTI). + for i := 1; i < reqsLen; i++ { + req := reqs[i] + val, err := req.ParseReply(cn.Rd) + if err != nil { + req.SetErr(err) + } else { + req.SetVal(val) + } + } + + return nil +} diff --git a/v2/parser.go b/v2/parser.go new file mode 100644 index 0000000..c04312e --- /dev/null +++ b/v2/parser.go @@ -0,0 +1,304 @@ +package redis + +import ( + "errors" + "fmt" + "strconv" + + "github.com/vmihailenco/bufio" +) + +type replyType int + +const ( + ifaceSlice replyType = iota + stringSlice + boolSlice + stringStringMap + stringFloatMap +) + +// Represents Redis nil reply. +var Nil = errors.New("(nil)") + +var ( + errReaderTooSmall = errors.New("redis: reader is too small") + errValNotSet = errors.New("redis: value is not set") + errInvalidReplyType = errors.New("redis: invalid reply type") +) + +//------------------------------------------------------------------------------ + +type parserError struct { + err error +} + +func (e *parserError) Error() string { + return e.err.Error() +} + +//------------------------------------------------------------------------------ + +func appendReq(buf []byte, args []string) []byte { + buf = append(buf, '*') + buf = strconv.AppendUint(buf, uint64(len(args)), 10) + buf = append(buf, '\r', '\n') + for _, arg := range args { + buf = append(buf, '$') + buf = strconv.AppendUint(buf, uint64(len(arg)), 10) + buf = append(buf, '\r', '\n') + buf = append(buf, arg...) + buf = append(buf, '\r', '\n') + } + return buf +} + +//------------------------------------------------------------------------------ + +type reader interface { + ReadLine() ([]byte, bool, error) + Read([]byte) (int, error) + ReadN(n int) ([]byte, error) + Buffered() int + Peek(int) ([]byte, error) +} + +func readLine(rd reader) ([]byte, error) { + line, isPrefix, err := rd.ReadLine() + if err != nil { + return line, err + } + if isPrefix { + return line, errReaderTooSmall + } + return line, nil +} + +func readN(rd reader, n int) ([]byte, error) { + b, err := rd.ReadN(n) + if err == bufio.ErrBufferFull { + newB := make([]byte, n) + r := copy(newB, b) + b = newB + + for { + nn, err := rd.Read(b[r:]) + r += nn + if r >= n { + // Ignore error if we read enough. + break + } + if err != nil { + return nil, err + } + } + } else if err != nil { + return nil, err + } + return b, nil +} + +//------------------------------------------------------------------------------ + +func ParseReq(rd reader) ([]string, error) { + line, err := readLine(rd) + if err != nil { + return nil, err + } + + if line[0] != '*' { + return []string{string(line)}, nil + } + numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64) + if err != nil { + return nil, err + } + + args := make([]string, 0, numReplies) + for i := int64(0); i < numReplies; i++ { + line, err = readLine(rd) + if err != nil { + return nil, err + } + if line[0] != '$' { + return nil, fmt.Errorf("redis: expected '$', but got %q", line) + } + + argLen, err := strconv.ParseInt(string(line[1:]), 10, 32) + if err != nil { + return nil, err + } + + arg, err := readN(rd, int(argLen)+2) + if err != nil { + return nil, err + } + args = append(args, string(arg[:argLen])) + } + return args, nil +} + +//------------------------------------------------------------------------------ + +func parseReply(rd reader) (interface{}, error) { + return _parseReply(rd, ifaceSlice) +} + +func parseStringSliceReply(rd reader) (interface{}, error) { + return _parseReply(rd, stringSlice) +} + +func parseBoolSliceReply(rd reader) (interface{}, error) { + return _parseReply(rd, boolSlice) +} + +func parseStringStringMapReply(rd reader) (interface{}, error) { + return _parseReply(rd, stringStringMap) +} + +func parseStringFloatMapReply(rd reader) (interface{}, error) { + return _parseReply(rd, stringFloatMap) +} + +func _parseReply(rd reader, typ replyType) (interface{}, error) { + line, err := readLine(rd) + if err != nil { + return 0, &parserError{err} + } + + switch line[0] { + case '-': + return nil, errors.New(string(line[1:])) + case '+': + return string(line[1:]), nil + case ':': + v, err := strconv.ParseInt(string(line[1:]), 10, 64) + if err != nil { + return 0, &parserError{err} + } + return v, nil + case '$': + if len(line) == 3 && line[1] == '-' && line[2] == '1' { + return "", Nil + } + + replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32) + if err != nil { + return "", &parserError{err} + } + replyLen := int(replyLenInt32) + 2 + + line, err = readN(rd, replyLen) + if err != nil { + return "", &parserError{err} + } + return string(line[:len(line)-2]), nil + case '*': + if len(line) == 3 && line[1] == '-' && line[2] == '1' { + return nil, Nil + } + + repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64) + if err != nil { + return nil, &parserError{err} + } + + switch typ { + case stringSlice: + vals := make([]string, 0, repliesNum) + for i := int64(0); i < repliesNum; i++ { + vi, err := parseReply(rd) + if err != nil { + return nil, err + } + if v, ok := vi.(string); ok { + vals = append(vals, v) + } else { + return nil, errInvalidReplyType + } + } + return vals, nil + case boolSlice: + vals := make([]bool, 0, repliesNum) + for i := int64(0); i < repliesNum; i++ { + vi, err := parseReply(rd) + if err != nil { + return nil, err + } + if v, ok := vi.(int64); ok { + vals = append(vals, v == 1) + } else { + return nil, errInvalidReplyType + } + } + return vals, nil + case stringStringMap: // TODO: Consider handling Nil values. + m := make(map[string]string, repliesNum/2) + for i := int64(0); i < repliesNum; i += 2 { + keyI, err := parseReply(rd) + if err != nil { + return nil, err + } + key, ok := keyI.(string) + if !ok { + return nil, errInvalidReplyType + } + + valueI, err := parseReply(rd) + if err != nil { + return nil, err + } + value, ok := valueI.(string) + if !ok { + return nil, errInvalidReplyType + } + + m[key] = value + } + return m, nil + case stringFloatMap: // TODO: Consider handling Nil values. + m := make(map[string]float64, repliesNum/2) + for i := int64(0); i < repliesNum; i += 2 { + keyI, err := parseReply(rd) + if err != nil { + return nil, err + } + key, ok := keyI.(string) + if !ok { + return nil, errInvalidReplyType + } + + valueI, err := parseReply(rd) + if err != nil { + return nil, err + } + valueS, ok := valueI.(string) + if !ok { + return nil, errInvalidReplyType + } + value, err := strconv.ParseFloat(valueS, 64) + if err != nil { + return nil, &parserError{err} + } + + m[key] = value + } + return m, nil + default: + vals := make([]interface{}, 0, repliesNum) + for i := int64(0); i < repliesNum; i++ { + v, err := parseReply(rd) + if err == Nil { + vals = append(vals, nil) + } else if err != nil { + return nil, err + } else { + vals = append(vals, v) + } + } + return vals, nil + } + default: + return nil, &parserError{fmt.Errorf("redis: can't parse %q", line)} + } +} diff --git a/v2/parser_test.go b/v2/parser_test.go new file mode 100644 index 0000000..0dee8e4 --- /dev/null +++ b/v2/parser_test.go @@ -0,0 +1,23 @@ +package redis_test + +import ( + "bytes" + + "github.com/vmihailenco/bufio" + . "launchpad.net/gocheck" + + "github.com/vmihailenco/redis/v2" +) + +type ParserTest struct{} + +var _ = Suite(&ParserTest{}) + +func (t *ParserTest) TestParseReq(c *C) { + buf := bytes.NewBufferString("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nhello\r\n") + rd := bufio.NewReaderSize(buf, 1024) + + args, err := redis.ParseReq(rd) + c.Check(err, IsNil) + c.Check(args, DeepEquals, []string{"SET", "key", "hello"}) +} diff --git a/v2/pipeline.go b/v2/pipeline.go new file mode 100644 index 0000000..f9b00f7 --- /dev/null +++ b/v2/pipeline.go @@ -0,0 +1,87 @@ +package redis + +type PipelineClient struct { + *Client +} + +func (c *Client) PipelineClient() (*PipelineClient, error) { + return &PipelineClient{ + Client: &Client{ + baseClient: &baseClient{ + connPool: c.connPool, + + password: c.password, + db: c.db, + + reqs: make([]Req, 0), + }, + }, + }, nil +} + +func (c *Client) Pipelined(do func(*PipelineClient)) ([]Req, error) { + pc, err := c.PipelineClient() + if err != nil { + return nil, err + } + defer pc.Close() + + do(pc) + + return pc.RunQueued() +} + +func (c *PipelineClient) Close() error { + return nil +} + +func (c *PipelineClient) DiscardQueued() { + c.reqsMtx.Lock() + c.reqs = c.reqs[:0] + c.reqsMtx.Unlock() +} + +func (c *PipelineClient) RunQueued() ([]Req, error) { + c.reqsMtx.Lock() + reqs := c.reqs + c.reqs = make([]Req, 0) + c.reqsMtx.Unlock() + + if len(reqs) == 0 { + return []Req{}, nil + } + + conn, err := c.conn() + if err != nil { + return nil, err + } + + err = c.runReqs(reqs, conn) + if err != nil { + c.removeConn(conn) + return nil, err + } + + c.putConn(conn) + return reqs, nil +} + +func (c *PipelineClient) runReqs(reqs []Req, cn *conn) error { + err := c.writeReq(cn, reqs...) + if err != nil { + return err + } + + reqsLen := len(reqs) + for i := 0; i < reqsLen; i++ { + req := reqs[i] + val, err := req.ParseReply(cn.Rd) + if err != nil { + req.SetErr(err) + } else { + req.SetVal(val) + } + } + + return nil +} diff --git a/v2/pool.go b/v2/pool.go new file mode 100644 index 0000000..22d33db --- /dev/null +++ b/v2/pool.go @@ -0,0 +1,257 @@ +package redis + +import ( + "container/list" + "net" + "sync" + "time" + + "github.com/vmihailenco/bufio" +) + +type pool interface { + Get() (*conn, bool, error) + Put(*conn) error + Remove(*conn) error + Len() int + Close() error +} + +//------------------------------------------------------------------------------ + +type conn struct { + Cn net.Conn + Rd reader + + UsedAt time.Time + + readTimeout, writeTimeout time.Duration +} + +func newConn(netcn net.Conn, readTimeout, writeTimeout time.Duration) *conn { + cn := &conn{ + Cn: netcn, + + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } + cn.Rd = bufio.NewReaderSize(netcn, 1024) + return cn +} + +func (cn *conn) Read(b []byte) (int, error) { + if cn.readTimeout > 0 { + cn.Cn.SetReadDeadline(time.Now().Add(cn.readTimeout)) + } + return cn.Cn.Read(b) +} + +func (cn *conn) Write(b []byte) (int, error) { + if cn.writeTimeout > 0 { + cn.Cn.SetWriteDeadline(time.Now().Add(cn.writeTimeout)) + } + return cn.Cn.Write(b) +} + +//------------------------------------------------------------------------------ + +type connPool struct { + dial func() (net.Conn, error) + close func(net.Conn) error + + cond *sync.Cond + conns *list.List + + readTimeout, writeTimeout time.Duration + + size, maxSize int + idleTimeout time.Duration +} + +func newConnPool( + dial func() (net.Conn, error), + close func(net.Conn) error, + maxSize int, + readTimeout, writeTimeout, idleTimeout time.Duration, +) *connPool { + return &connPool{ + dial: dial, + close: close, + + cond: sync.NewCond(&sync.Mutex{}), + conns: list.New(), + + maxSize: maxSize, + + readTimeout: readTimeout, + writeTimeout: writeTimeout, + idleTimeout: idleTimeout, + } +} + +func (p *connPool) Get() (*conn, bool, error) { + defer p.cond.L.Unlock() + p.cond.L.Lock() + + for p.conns.Len() == 0 && p.size >= p.maxSize { + p.cond.Wait() + } + + if p.idleTimeout > 0 { + for e := p.conns.Front(); e != nil; e = e.Next() { + cn := e.Value.(*conn) + if time.Since(cn.UsedAt) > p.idleTimeout { + p.conns.Remove(e) + } + } + } + + if p.conns.Len() == 0 { + rw, err := p.dial() + if err != nil { + return nil, false, err + } + + p.size++ + return newConn(rw, p.readTimeout, p.writeTimeout), true, nil + } + + elem := p.conns.Front() + p.conns.Remove(elem) + return elem.Value.(*conn), false, nil +} + +func (p *connPool) Put(cn *conn) error { + p.cond.L.Lock() + cn.UsedAt = time.Now() + p.conns.PushFront(cn) + p.cond.Signal() + p.cond.L.Unlock() + return nil +} + +func (p *connPool) Remove(cn *conn) error { + var err error + if cn != nil { + err = p.closeConn(cn) + } + p.cond.L.Lock() + p.size-- + p.cond.Signal() + p.cond.L.Unlock() + return err +} + +func (p *connPool) Len() int { + return p.conns.Len() +} + +func (p *connPool) Size() int { + return p.size +} + +func (p *connPool) Close() error { + defer p.cond.L.Unlock() + p.cond.L.Lock() + + for e := p.conns.Front(); e != nil; e = e.Next() { + if err := p.closeConn(e.Value.(*conn)); err != nil { + return err + } + } + p.conns.Init() + p.size = 0 + + return nil +} + +func (p *connPool) closeConn(cn *conn) error { + if p.close != nil { + return p.close(cn.Cn) + } else { + return cn.Cn.Close() + } +} + +//------------------------------------------------------------------------------ + +type singleConnPool struct { + pool pool + + l sync.RWMutex + cn *conn + reusable bool +} + +func newSingleConnPool(pool pool, cn *conn, reusable bool) *singleConnPool { + return &singleConnPool{ + pool: pool, + cn: cn, + reusable: reusable, + } +} + +func (p *singleConnPool) Get() (*conn, bool, error) { + p.l.RLock() + if p.cn != nil { + p.l.RUnlock() + return p.cn, false, nil + } + p.l.RUnlock() + + defer p.l.Unlock() + p.l.Lock() + + cn, isNew, err := p.pool.Get() + if err != nil { + return nil, false, err + } + p.cn = cn + + return cn, isNew, nil +} + +func (p *singleConnPool) Put(cn *conn) error { + defer p.l.Unlock() + p.l.Lock() + if p.cn != cn { + panic("p.cn != cn") + } + return nil +} + +func (p *singleConnPool) Remove(cn *conn) error { + defer p.l.Unlock() + p.l.Lock() + if p.cn != cn { + panic("p.cn != cn") + } + p.cn = nil + return nil +} + +func (p *singleConnPool) Len() int { + defer p.l.Unlock() + p.l.Lock() + if p.cn == nil { + return 0 + } + return 1 +} + +func (p *singleConnPool) Close() error { + defer p.l.Unlock() + p.l.Lock() + + var err error + if p.cn != nil { + if p.reusable { + err = p.pool.Put(p.cn) + } else { + err = p.pool.Remove(p.cn) + } + } + p.cn = nil + + return err +} diff --git a/v2/pubsub.go b/v2/pubsub.go new file mode 100644 index 0000000..8481024 --- /dev/null +++ b/v2/pubsub.go @@ -0,0 +1,128 @@ +package redis + +import ( + "fmt" + "sync" +) + +type PubSubClient struct { + *baseClient + + ch chan *Message + once sync.Once +} + +func (c *Client) PubSubClient() (*PubSubClient, error) { + return &PubSubClient{ + baseClient: &baseClient{ + connPool: newSingleConnPool(c.connPool, nil, false), + + password: c.password, + db: c.db, + }, + + ch: make(chan *Message), + }, nil +} + +func (c *Client) Publish(channel, message string) *IntReq { + req := NewIntReq("PUBLISH", channel, message) + c.Process(req) + return req +} + +type Message struct { + Name, Channel, ChannelPattern, Message string + Number int64 + + Err error +} + +func (c *PubSubClient) consumeMessages(cn *conn) { + req := NewIfaceSliceReq() + + for { + msg := &Message{} + + replyIface, err := req.ParseReply(cn.Rd) + if err != nil { + msg.Err = err + c.ch <- msg + return + } + reply, ok := replyIface.([]interface{}) + if !ok { + msg.Err = fmt.Errorf("redis: unexpected reply type %T", replyIface) + c.ch <- msg + return + } + + msgName := reply[0].(string) + switch msgName { + case "subscribe", "unsubscribe", "psubscribe", "punsubscribe": + msg.Name = msgName + msg.Channel = reply[1].(string) + msg.Number = reply[2].(int64) + case "message": + msg.Name = msgName + msg.Channel = reply[1].(string) + msg.Message = reply[2].(string) + case "pmessage": + msg.Name = msgName + msg.ChannelPattern = reply[1].(string) + msg.Channel = reply[2].(string) + msg.Message = reply[3].(string) + default: + msg.Err = fmt.Errorf("redis: unsupported message name: %q", msgName) + } + c.ch <- msg + } +} + +func (c *PubSubClient) subscribe(cmd string, channels ...string) (chan *Message, error) { + args := append([]string{cmd}, channels...) + req := NewIfaceSliceReq(args...) + + cn, err := c.conn() + if err != nil { + return nil, err + } + + if err := c.writeReq(cn, req); err != nil { + return nil, err + } + + c.once.Do(func() { + go c.consumeMessages(cn) + }) + + return c.ch, nil +} + +func (c *PubSubClient) Subscribe(channels ...string) (chan *Message, error) { + return c.subscribe("SUBSCRIBE", channels...) +} + +func (c *PubSubClient) PSubscribe(patterns ...string) (chan *Message, error) { + return c.subscribe("PSUBSCRIBE", patterns...) +} + +func (c *PubSubClient) unsubscribe(cmd string, channels ...string) error { + args := append([]string{cmd}, channels...) + req := NewIfaceSliceReq(args...) + + cn, err := c.conn() + if err != nil { + return err + } + + return c.writeReq(cn, req) +} + +func (c *PubSubClient) Unsubscribe(channels ...string) error { + return c.unsubscribe("UNSUBSCRIBE", channels...) +} + +func (c *PubSubClient) PUnsubscribe(patterns ...string) error { + return c.unsubscribe("PUNSUBSCRIBE", patterns...) +} diff --git a/v2/redis.go b/v2/redis.go new file mode 100644 index 0000000..ee25c9d --- /dev/null +++ b/v2/redis.go @@ -0,0 +1,226 @@ +package redis + +import ( + "crypto/tls" + "log" + "net" + "os" + "sync" + "time" +) + +// Package logger. +var Logger = log.New(os.Stdout, "redis: ", log.Ldate|log.Ltime) + +//------------------------------------------------------------------------------ + +type baseClient struct { + connPool pool + + password string + db int64 + + reqs []Req + reqsMtx sync.Mutex +} + +func (c *baseClient) writeReq(cn *conn, reqs ...Req) error { + buf := make([]byte, 0, 1000) + for _, req := range reqs { + buf = appendReq(buf, req.Args()) + } + + _, err := cn.Write(buf) + return err +} + +func (c *baseClient) conn() (*conn, error) { + cn, isNew, err := c.connPool.Get() + if err != nil { + return nil, err + } + + if isNew && (c.password != "" || c.db > 0) { + if err = c.init(cn, c.password, c.db); err != nil { + c.removeConn(cn) + return nil, err + } + } + + return cn, nil +} + +func (c *baseClient) init(cn *conn, password string, db int64) error { + // Client is not closed on purpose. + client := &Client{ + baseClient: &baseClient{ + connPool: newSingleConnPool(c.connPool, cn, false), + }, + } + + if password != "" { + auth := client.Auth(password) + if auth.Err() != nil { + return auth.Err() + } + } + + if db > 0 { + sel := client.Select(db) + if sel.Err() != nil { + return sel.Err() + } + } + + return nil +} + +func (c *baseClient) removeConn(cn *conn) { + if err := c.connPool.Remove(cn); err != nil { + Logger.Printf("connPool.Remove error: %v", err) + } +} + +func (c *baseClient) putConn(cn *conn) { + if err := c.connPool.Put(cn); err != nil { + Logger.Printf("connPool.Add error: %v", err) + } +} + +func (c *baseClient) Process(req Req) { + if c.reqs == nil { + c.run(req) + } else { + c.queue(req) + } +} + +func (c *baseClient) run(req Req) { + cn, err := c.conn() + if err != nil { + req.SetErr(err) + return + } + + err = c.writeReq(cn, req) + if err != nil { + c.removeConn(cn) + req.SetErr(err) + return + } + + val, err := req.ParseReply(cn.Rd) + if err != nil { + if _, ok := err.(*parserError); ok { + c.removeConn(cn) + } else { + c.putConn(cn) + } + req.SetErr(err) + return + } + + c.putConn(cn) + req.SetVal(val) +} + +// Queues request to be executed later. +func (c *baseClient) queue(req Req) { + c.reqsMtx.Lock() + c.reqs = append(c.reqs, req) + c.reqsMtx.Unlock() +} + +func (c *baseClient) Close() error { + return c.connPool.Close() +} + +//------------------------------------------------------------------------------ + +type ClientFactory struct { + Dial func() (net.Conn, error) + Close func(net.Conn) error + + Password string + DB int64 + + PoolSize int + + ReadTimeout, WriteTimeout, IdleTimeout time.Duration +} + +func (f *ClientFactory) New() *Client { + return &Client{ + baseClient: &baseClient{ + password: f.Password, + db: f.DB, + + connPool: newConnPool( + f.Dial, f.getClose(), f.getPoolSize(), + f.ReadTimeout, f.WriteTimeout, f.IdleTimeout, + ), + }, + } +} + +func (f *ClientFactory) getClose() func(net.Conn) error { + if f.Close == nil { + return func(conn net.Conn) error { + return conn.Close() + } + } + return f.Close +} + +func (f *ClientFactory) getPoolSize() int { + if f.PoolSize == 0 { + return 10 + } + return f.PoolSize +} + +//------------------------------------------------------------------------------ + +type Client struct { + *baseClient +} + +func NewTCPClient(addr string, password string, db int64) *Client { + dial := func() (net.Conn, error) { + return net.DialTimeout("tcp", addr, 3*time.Second) + } + return (&ClientFactory{ + Dial: dial, + + Password: password, + DB: db, + }).New() +} + +func NewTLSClient(addr string, tlsConfig *tls.Config, password string, db int64) *Client { + dial := func() (net.Conn, error) { + conn, err := net.DialTimeout("tcp", addr, 3*time.Second) + if err != nil { + return nil, err + } + return tls.Client(conn, tlsConfig), nil + } + return (&ClientFactory{ + Dial: dial, + + Password: password, + DB: db, + }).New() +} + +func NewUnixClient(addr string, password string, db int64) *Client { + dial := func() (net.Conn, error) { + return net.DialTimeout("unix", addr, 3*time.Second) + } + return (&ClientFactory{ + Dial: dial, + + Password: password, + DB: db, + }).New() +} diff --git a/v2/redis_test.go b/v2/redis_test.go new file mode 100644 index 0000000..12260ca --- /dev/null +++ b/v2/redis_test.go @@ -0,0 +1,2953 @@ +package redis_test + +import ( + "bytes" + "fmt" + "io" + "net" + "sort" + "strconv" + "sync" + "testing" + "time" + + "github.com/vmihailenco/redis/v2" + + . "launchpad.net/gocheck" +) + +const redisAddr = ":6379" + +//------------------------------------------------------------------------------ + +func sortStrings(slice []string) []string { + sort.Strings(slice) + return slice +} + +//------------------------------------------------------------------------------ + +type RedisShutdownTest struct { + client *redis.Client +} + +var _ = Suite(&RedisShutdownTest{}) + +func (t *RedisShutdownTest) SetUpTest(c *C) { + t.client = redis.NewTCPClient(redisAddr, "", -1) +} + +func (t *RedisShutdownTest) TestShutdown(c *C) { + c.Skip("shutdowns server") + + shutdown := t.client.Shutdown() + c.Check(shutdown.Err(), Equals, io.EOF) + c.Check(shutdown.Val(), Equals, "") + + ping := t.client.Ping() + c.Check(ping.Err(), ErrorMatches, "dial tcp :[0-9]+: connection refused") + c.Check(ping.Val(), Equals, "") +} + +//------------------------------------------------------------------------------ + +type RedisConnectorTest struct{} + +var _ = Suite(&RedisConnectorTest{}) + +func (t *RedisConnectorTest) TestTCPConnector(c *C) { + client := redis.NewTCPClient(":6379", "", -1) + ping := client.Ping() + c.Check(ping.Err(), IsNil) + c.Check(ping.Val(), Equals, "PONG") +} + +func (t *RedisConnectorTest) TestUnixConnector(c *C) { + client := redis.NewUnixClient("/tmp/redis.sock", "", -1) + ping := client.Ping() + c.Check(ping.Err(), IsNil) + c.Check(ping.Val(), Equals, "PONG") +} + +//------------------------------------------------------------------------------ + +type RedisConnPoolTest struct { + dialedConns, closedConns int64 + + client *redis.Client +} + +var _ = Suite(&RedisConnPoolTest{}) + +func (t *RedisConnPoolTest) SetUpTest(c *C) { + if t.client == nil { + dial := func() (net.Conn, error) { + t.dialedConns++ + return net.Dial("tcp", redisAddr) + } + close := func(conn net.Conn) error { + t.closedConns++ + return nil + } + + t.client = (&redis.ClientFactory{ + Dial: dial, + Close: close, + }).New() + } +} + +func (t *RedisConnPoolTest) TearDownTest(c *C) { + t.resetRedis(c) + t.resetClient(c) +} + +func (t *RedisConnPoolTest) resetRedis(c *C) { + // This is much faster than Flushall. + c.Assert(t.client.Select(1).Err(), IsNil) + c.Assert(t.client.FlushDb().Err(), IsNil) + c.Assert(t.client.Select(0).Err(), IsNil) + c.Assert(t.client.FlushDb().Err(), IsNil) +} + +func (t *RedisConnPoolTest) resetClient(c *C) { + t.client.Close() + c.Check(t.closedConns, Equals, t.dialedConns) + t.dialedConns, t.closedConns = 0, 0 +} + +func (t *RedisConnPoolTest) TestConnPoolMaxSize(c *C) { + wg := &sync.WaitGroup{} + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + ping := t.client.Ping() + c.Assert(ping.Err(), IsNil) + c.Assert(ping.Val(), Equals, "PONG") + wg.Done() + }() + } + wg.Wait() + + c.Assert(t.client.Close(), IsNil) + c.Assert(t.dialedConns, Equals, int64(10)) + c.Assert(t.closedConns, Equals, int64(10)) +} + +func (t *RedisConnPoolTest) TestConnPoolMaxSizeOnPipelineClient(c *C) { + wg := &sync.WaitGroup{} + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + + ping := pipeline.Ping() + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 1) + c.Assert(ping.Err(), IsNil) + c.Assert(ping.Val(), Equals, "PONG") + + c.Assert(pipeline.Close(), IsNil) + + wg.Done() + }() + } + wg.Wait() + + c.Assert(t.client.Close(), IsNil) + c.Assert(t.dialedConns, Equals, int64(10)) + c.Assert(t.closedConns, Equals, int64(10)) +} + +func (t *RedisConnPoolTest) TestConnPoolMaxSizeOnMultiClient(c *C) { + wg := &sync.WaitGroup{} + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + + var ping *redis.StatusReq + reqs, err := multi.Exec(func() { + ping = multi.Ping() + }) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 1) + c.Assert(ping.Err(), IsNil) + c.Assert(ping.Val(), Equals, "PONG") + + c.Assert(multi.Close(), IsNil) + + wg.Done() + }() + } + wg.Wait() + + c.Assert(t.client.Close(), IsNil) + c.Assert(t.dialedConns, Equals, int64(10)) + c.Assert(t.closedConns, Equals, int64(10)) +} + +func (t *RedisConnPoolTest) TestConnPoolMaxSizeOnPubSubClient(c *C) { + wg := &sync.WaitGroup{} + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + pubsub, err := t.client.PubSubClient() + c.Assert(err, IsNil) + + _, err = pubsub.Subscribe() + c.Assert(err, IsNil) + + c.Assert(pubsub.Close(), IsNil) + + wg.Done() + }() + } + wg.Wait() + + c.Assert(t.client.Close(), IsNil) + c.Assert(t.dialedConns, Equals, int64(1000)) + c.Assert(t.closedConns, Equals, int64(1000)) +} + +//------------------------------------------------------------------------------ + +type RedisTest struct { + client *redis.Client +} + +var _ = Suite(&RedisTest{}) + +func Test(t *testing.T) { TestingT(t) } + +func (t *RedisTest) SetUpTest(c *C) { + if t.client == nil { + t.client = redis.NewTCPClient(":6379", "", -1) + } +} + +func (t *RedisTest) TearDownTest(c *C) { + t.resetRedis(c) +} + +func (t *RedisTest) resetRedis(c *C) { + // This is much faster than Flushall. + c.Assert(t.client.Select(1).Err(), IsNil) + c.Assert(t.client.FlushDb().Err(), IsNil) + c.Assert(t.client.Select(0).Err(), IsNil) + c.Assert(t.client.FlushDb().Err(), IsNil) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestReqStringMethod(c *C) { + set := t.client.Set("foo", "bar") + c.Assert(set.String(), Equals, "SET foo bar: OK") + + get := t.client.Get("foo") + c.Assert(get.String(), Equals, "GET foo: bar") +} + +func (t *RedisTest) TestReqStringMethodError(c *C) { + get2 := t.client.Get("key_does_not_exists") + c.Assert(get2.String(), Equals, "GET key_does_not_exists: (nil)") +} + +func (t *RedisTest) TestRunWithouthCheckingErrVal(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") + + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") +} + +func (t *RedisTest) TestGetSpecChars(c *C) { + set := t.client.Set("key", "hello1\r\nhello2\r\n") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello1\r\nhello2\r\n") +} + +func (t *RedisTest) TestGetBigVal(c *C) { + val := string(bytes.Repeat([]byte{'*'}, 1<<16)) + + set := t.client.Set("key", val) + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, val) +} + +func (t *RedisTest) TestManyKeys(c *C) { + for i := 0; i < 100000; i++ { + t.client.Set("keys.key"+strconv.Itoa(i), "hello"+strconv.Itoa(i)) + } + keys := t.client.Keys("keys.*") + c.Assert(keys.Err(), IsNil) + c.Assert(keys.Val(), HasLen, 100000) +} + +func (t *RedisTest) TestManyKeys2(c *C) { + keys := []string{"non-existent-key"} + for i := 0; i < 100000; i++ { + key := "keys.key" + strconv.Itoa(i) + t.client.Set(key, "hello"+strconv.Itoa(i)) + keys = append(keys, key) + } + keys = append(keys, "non-existent-key") + + mget := t.client.MGet(keys...) + c.Assert(mget.Err(), IsNil) + c.Assert(mget.Val(), HasLen, 100002) + vals := mget.Val() + for i := 0; i < 100000; i++ { + c.Assert(vals[i+1], Equals, "hello"+strconv.Itoa(i)) + } + c.Assert(vals[0], Equals, nil) + c.Assert(vals[100001], Equals, nil) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestConnPoolRemovesBrokenConn(c *C) { + c.Skip("fix me") + + conn, err := net.Dial("tcp", redisAddr) + c.Assert(err, IsNil) + c.Assert(conn.Close(), IsNil) + + client := redis.NewTCPClient(redisAddr, "", -1) + defer func() { + c.Assert(client.Close(), IsNil) + }() + + // c.Assert(client.ConnPool.Add(redis.NewConn(conn)), IsNil) + + ping := client.Ping() + c.Assert(ping.Err().Error(), Equals, "use of closed network connection") + c.Assert(ping.Val(), Equals, "") + + ping = client.Ping() + c.Assert(ping.Err(), IsNil) + c.Assert(ping.Val(), Equals, "PONG") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestAuth(c *C) { + auth := t.client.Auth("password") + c.Assert(auth.Err(), ErrorMatches, "ERR Client sent AUTH, but no password is set") + c.Assert(auth.Val(), Equals, "") +} + +func (t *RedisTest) TestEcho(c *C) { + echo := t.client.Echo("hello") + c.Assert(echo.Err(), IsNil) + c.Assert(echo.Val(), Equals, "hello") +} + +func (t *RedisTest) TestPing(c *C) { + ping := t.client.Ping() + c.Assert(ping.Err(), IsNil) + c.Assert(ping.Val(), Equals, "PONG") +} + +func (t *RedisTest) TestSelect(c *C) { + sel := t.client.Select(1) + c.Assert(sel.Err(), IsNil) + c.Assert(sel.Val(), Equals, "OK") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestCmdKeysDel(c *C) { + set := t.client.Set("key1", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + set = t.client.Set("key2", "World") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + del := t.client.Del("key1", "key2", "key3") + c.Assert(del.Err(), IsNil) + c.Assert(del.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestCmdKeysDump(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + dump := t.client.Dump("key") + c.Assert(dump.Err(), IsNil) + c.Assert(dump.Val(), Equals, "\x00\x05hello\x06\x00\xf5\x9f\xb7\xf6\x90a\x1c\x99") +} + +func (t *RedisTest) TestCmdKeysExists(c *C) { + set := t.client.Set("key1", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + exists := t.client.Exists("key1") + c.Assert(exists.Err(), IsNil) + c.Assert(exists.Val(), Equals, true) + + exists = t.client.Exists("key2") + c.Assert(exists.Err(), IsNil) + c.Assert(exists.Val(), Equals, false) +} + +func (t *RedisTest) TestCmdKeysExpire(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + expire := t.client.Expire("key", 10) + c.Assert(expire.Err(), IsNil) + c.Assert(expire.Val(), Equals, true) + + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(10)) + + set = t.client.Set("key", "Hello World") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + ttl = t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(-1)) +} + +func (t *RedisTest) TestCmdKeysExpireAt(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + exists := t.client.Exists("key") + c.Assert(exists.Err(), IsNil) + c.Assert(exists.Val(), Equals, true) + + expireAt := t.client.ExpireAt("key", 1293840000) + c.Assert(expireAt.Err(), IsNil) + c.Assert(expireAt.Val(), Equals, true) + + exists = t.client.Exists("key") + c.Assert(exists.Err(), IsNil) + c.Assert(exists.Val(), Equals, false) +} + +func (t *RedisTest) TestCmdKeysKeys(c *C) { + mset := t.client.MSet("one", "1", "two", "2", "three", "3", "four", "4") + c.Assert(mset.Err(), IsNil) + c.Assert(mset.Val(), Equals, "OK") + + keys := t.client.Keys("*o*") + c.Assert(keys.Err(), IsNil) + c.Assert(sortStrings(keys.Val()), DeepEquals, []string{"four", "one", "two"}) + + keys = t.client.Keys("t??") + c.Assert(keys.Err(), IsNil) + c.Assert(keys.Val(), DeepEquals, []string{"two"}) + + keys = t.client.Keys("*") + c.Assert(keys.Err(), IsNil) + c.Assert( + sortStrings(keys.Val()), + DeepEquals, + []string{"four", "one", "three", "two"}, + ) +} + +func (t *RedisTest) TestCmdKeysMigrate(c *C) { + migrate := t.client.Migrate("localhost", "6380", "key", 0, 0) + c.Assert(migrate.Err(), IsNil) + c.Assert(migrate.Val(), Equals, "NOKEY") + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + migrate = t.client.Migrate("localhost", "6380", "key", 0, 0) + c.Assert(migrate.Err(), ErrorMatches, "IOERR error or timeout writing to target instance") + c.Assert(migrate.Val(), Equals, "") +} + +func (t *RedisTest) TestCmdKeysMove(c *C) { + move := t.client.Move("key", 1) + c.Assert(move.Err(), IsNil) + c.Assert(move.Val(), Equals, false) + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + move = t.client.Move("key", 1) + c.Assert(move.Err(), IsNil) + c.Assert(move.Val(), Equals, true) + + get := t.client.Get("key") + c.Assert(get.Err(), Equals, redis.Nil) + c.Assert(get.Val(), Equals, "") + + sel := t.client.Select(1) + c.Assert(sel.Err(), IsNil) + c.Assert(sel.Val(), Equals, "OK") + + get = t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdKeysObject(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + refCount := t.client.ObjectRefCount("key") + c.Assert(refCount.Err(), IsNil) + c.Assert(refCount.Val(), Equals, int64(1)) + + enc := t.client.ObjectEncoding("key") + c.Assert(enc.Err(), IsNil) + c.Assert(enc.Val(), Equals, "raw") + + idleTime := t.client.ObjectIdleTime("key") + c.Assert(idleTime.Err(), IsNil) + c.Assert(idleTime.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestCmdKeysPersist(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + expire := t.client.Expire("key", 10) + c.Assert(expire.Err(), IsNil) + c.Assert(expire.Val(), Equals, true) + + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(10)) + + persist := t.client.Persist("key") + c.Assert(persist.Err(), IsNil) + c.Assert(persist.Val(), Equals, true) + + ttl = t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(-1)) +} + +func (t *RedisTest) TestCmdKeysPExpire(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + pexpire := t.client.PExpire("key", 1900) + c.Assert(pexpire.Err(), IsNil) + c.Assert(pexpire.Val(), Equals, true) + + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(2)) + + pttl := t.client.PTTL("key") + c.Assert(pttl.Err(), IsNil) + c.Assert(pttl.Val() > 1800 && pttl.Val() <= 1900, Equals, true) +} + +func (t *RedisTest) TestCmdKeysPExpireAt(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + pExpireAt := t.client.PExpireAt("key", 1555555555005) + c.Assert(pExpireAt.Err(), IsNil) + c.Assert(pExpireAt.Val(), Equals, true) + + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Not(Equals), int64(0)) + + pttl := t.client.PTTL("key") + c.Assert(pttl.Err(), IsNil) + c.Assert(pttl.Val(), Not(Equals), int64(0)) +} + +func (t *RedisTest) TestCmdKeysPTTL(c *C) { + set := t.client.Set("key", "Hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + expire := t.client.Expire("key", 1) + c.Assert(expire.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + pttl := t.client.PTTL("key") + c.Assert(pttl.Err(), IsNil) + c.Assert(pttl.Val() > 900 && pttl.Val() <= 1000, Equals, true) +} + +func (t *RedisTest) TestCmdKeysRandomKey(c *C) { + randomKey := t.client.RandomKey() + c.Assert(randomKey.Err(), Equals, redis.Nil) + c.Assert(randomKey.Val(), Equals, "") + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + randomKey = t.client.RandomKey() + c.Assert(randomKey.Err(), IsNil) + c.Assert(randomKey.Val(), Equals, "key") +} + +func (t *RedisTest) TestCmdKeysRename(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + status := t.client.Rename("key", "key1") + c.Assert(status.Err(), IsNil) + c.Assert(status.Val(), Equals, "OK") + + get := t.client.Get("key1") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdKeysRenameNX(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + renameNX := t.client.RenameNX("key", "key1") + c.Assert(renameNX.Err(), IsNil) + c.Assert(renameNX.Val(), Equals, true) + + get := t.client.Get("key1") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdKeysRestore(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + dump := t.client.Dump("key") + c.Assert(dump.Err(), IsNil) + + del := t.client.Del("key") + c.Assert(del.Err(), IsNil) + + restore := t.client.Restore("key", 0, dump.Val()) + c.Assert(restore.Err(), IsNil) + c.Assert(restore.Val(), Equals, "OK") + + type_ := t.client.Type("key") + c.Assert(type_.Err(), IsNil) + c.Assert(type_.Val(), Equals, "string") + + lRange := t.client.Get("key") + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdKeysSort(c *C) { + lPush := t.client.LPush("list", "1") + c.Assert(lPush.Err(), IsNil) + c.Assert(lPush.Val(), Equals, int64(1)) + lPush = t.client.LPush("list", "3") + c.Assert(lPush.Err(), IsNil) + c.Assert(lPush.Val(), Equals, int64(2)) + lPush = t.client.LPush("list", "2") + c.Assert(lPush.Err(), IsNil) + c.Assert(lPush.Val(), Equals, int64(3)) + + sort := t.client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}) + c.Assert(sort.Err(), IsNil) + c.Assert(sort.Val(), DeepEquals, []string{"1", "2"}) +} + +func (t *RedisTest) TestCmdKeysTTL(c *C) { + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(-1)) + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + expire := t.client.Expire("key", 60) + c.Assert(expire.Err(), IsNil) + c.Assert(expire.Val(), Equals, true) + + ttl = t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(60)) +} + +func (t *RedisTest) TestCmdKeysType(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + type_ := t.client.Type("key") + c.Assert(type_.Err(), IsNil) + c.Assert(type_.Val(), Equals, "string") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestStringsAppend(c *C) { + exists := t.client.Exists("key") + c.Assert(exists.Err(), IsNil) + c.Assert(exists.Val(), Equals, false) + + append := t.client.Append("key", "Hello") + c.Assert(append.Err(), IsNil) + c.Assert(append.Val(), Equals, int64(5)) + + append = t.client.Append("key", " World") + c.Assert(append.Err(), IsNil) + c.Assert(append.Val(), Equals, int64(11)) + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "Hello World") +} + +func (t *RedisTest) TestStringsBitCount(c *C) { + set := t.client.Set("key", "foobar") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + bitCount := t.client.BitCount("key", nil) + c.Assert(bitCount.Err(), IsNil) + c.Assert(bitCount.Val(), Equals, int64(26)) + + bitCount = t.client.BitCount("key", &redis.BitCount{0, 0}) + c.Assert(bitCount.Err(), IsNil) + c.Assert(bitCount.Val(), Equals, int64(4)) + + bitCount = t.client.BitCount("key", &redis.BitCount{1, 1}) + c.Assert(bitCount.Err(), IsNil) + c.Assert(bitCount.Val(), Equals, int64(6)) +} + +func (t *RedisTest) TestStringsBitOpAnd(c *C) { + set := t.client.Set("key1", "1") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + set = t.client.Set("key2", "0") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + bitOpAnd := t.client.BitOpAnd("dest", "key1", "key2") + c.Assert(bitOpAnd.Err(), IsNil) + c.Assert(bitOpAnd.Val(), Equals, int64(1)) + + get := t.client.Get("dest") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "0") +} + +func (t *RedisTest) TestStringsBitOpOr(c *C) { + set := t.client.Set("key1", "1") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + set = t.client.Set("key2", "0") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + bitOpOr := t.client.BitOpOr("dest", "key1", "key2") + c.Assert(bitOpOr.Err(), IsNil) + c.Assert(bitOpOr.Val(), Equals, int64(1)) + + get := t.client.Get("dest") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "1") +} + +func (t *RedisTest) TestStringsBitOpXor(c *C) { + set := t.client.Set("key1", "\xff") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + set = t.client.Set("key2", "\x0f") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + bitOpXor := t.client.BitOpXor("dest", "key1", "key2") + c.Assert(bitOpXor.Err(), IsNil) + c.Assert(bitOpXor.Val(), Equals, int64(1)) + + get := t.client.Get("dest") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "\xf0") +} + +func (t *RedisTest) TestStringsBitOpNot(c *C) { + set := t.client.Set("key1", "\x00") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + bitOpNot := t.client.BitOpNot("dest", "key1") + c.Assert(bitOpNot.Err(), IsNil) + c.Assert(bitOpNot.Val(), Equals, int64(1)) + + get := t.client.Get("dest") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "\xff") +} + +func (t *RedisTest) TestStringsDecr(c *C) { + set := t.client.Set("key", "10") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + decr := t.client.Decr("key") + c.Assert(decr.Err(), IsNil) + c.Assert(decr.Val(), Equals, int64(9)) + + set = t.client.Set("key", "234293482390480948029348230948") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + decr = t.client.Decr("key") + c.Assert(decr.Err(), ErrorMatches, "ERR value is not an integer or out of range") + c.Assert(decr.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestStringsDecrBy(c *C) { + set := t.client.Set("key", "10") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + decrBy := t.client.DecrBy("key", 5) + c.Assert(decrBy.Err(), IsNil) + c.Assert(decrBy.Val(), Equals, int64(5)) +} + +func (t *RedisTest) TestStringsGet(c *C) { + get := t.client.Get("_") + c.Assert(get.Err(), Equals, redis.Nil) + c.Assert(get.Val(), Equals, "") + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + get = t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestStringsGetBit(c *C) { + setBit := t.client.SetBit("key", 7, 1) + c.Assert(setBit.Err(), IsNil) + c.Assert(setBit.Val(), Equals, int64(0)) + + getBit := t.client.GetBit("key", 0) + c.Assert(getBit.Err(), IsNil) + c.Assert(getBit.Val(), Equals, int64(0)) + + getBit = t.client.GetBit("key", 7) + c.Assert(getBit.Err(), IsNil) + c.Assert(getBit.Val(), Equals, int64(1)) + + getBit = t.client.GetBit("key", 100) + c.Assert(getBit.Err(), IsNil) + c.Assert(getBit.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestStringsGetRange(c *C) { + set := t.client.Set("key", "This is a string") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + getRange := t.client.GetRange("key", 0, 3) + c.Assert(getRange.Err(), IsNil) + c.Assert(getRange.Val(), Equals, "This") + + getRange = t.client.GetRange("key", -3, -1) + c.Assert(getRange.Err(), IsNil) + c.Assert(getRange.Val(), Equals, "ing") + + getRange = t.client.GetRange("key", 0, -1) + c.Assert(getRange.Err(), IsNil) + c.Assert(getRange.Val(), Equals, "This is a string") + + getRange = t.client.GetRange("key", 10, 100) + c.Assert(getRange.Err(), IsNil) + c.Assert(getRange.Val(), Equals, "string") +} + +func (t *RedisTest) TestStringsGetSet(c *C) { + incr := t.client.Incr("key") + c.Assert(incr.Err(), IsNil) + c.Assert(incr.Val(), Equals, int64(1)) + + getSet := t.client.GetSet("key", "0") + c.Assert(getSet.Err(), IsNil) + c.Assert(getSet.Val(), Equals, "1") + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "0") +} + +func (t *RedisTest) TestStringsIncr(c *C) { + set := t.client.Set("key", "10") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + incr := t.client.Incr("key") + c.Assert(incr.Err(), IsNil) + c.Assert(incr.Val(), Equals, int64(11)) + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "11") +} + +func (t *RedisTest) TestStringsIncrBy(c *C) { + set := t.client.Set("key", "10") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + incrBy := t.client.IncrBy("key", 5) + c.Assert(incrBy.Err(), IsNil) + c.Assert(incrBy.Val(), Equals, int64(15)) +} + +func (t *RedisTest) TestIncrByFloat(c *C) { + set := t.client.Set("key", "10.50") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + incrByFloat := t.client.IncrByFloat("key", 0.1) + c.Assert(incrByFloat.Err(), IsNil) + c.Assert(incrByFloat.Val(), Equals, 10.6) + + set = t.client.Set("key", "5.0e3") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + incrByFloat = t.client.IncrByFloat("key", 2.0e2) + c.Assert(incrByFloat.Err(), IsNil) + c.Assert(incrByFloat.Val(), Equals, float64(5200)) +} + +func (t *RedisTest) TestIncrByFloatOverflow(c *C) { + incrByFloat := t.client.IncrByFloat("key", 996945661) + c.Assert(incrByFloat.Err(), IsNil) + c.Assert(incrByFloat.Val(), Equals, float64(996945661)) +} + +func (t *RedisTest) TestStringsMSetMGet(c *C) { + mSet := t.client.MSet("key1", "hello1", "key2", "hello2") + c.Assert(mSet.Err(), IsNil) + c.Assert(mSet.Val(), Equals, "OK") + + mGet := t.client.MGet("key1", "key2", "_") + c.Assert(mGet.Err(), IsNil) + c.Assert(mGet.Val(), DeepEquals, []interface{}{"hello1", "hello2", nil}) +} + +func (t *RedisTest) TestStringsMSetNX(c *C) { + mSetNX := t.client.MSetNX("key1", "hello1", "key2", "hello2") + c.Assert(mSetNX.Err(), IsNil) + c.Assert(mSetNX.Val(), Equals, true) + + mSetNX = t.client.MSetNX("key2", "hello1", "key3", "hello2") + c.Assert(mSetNX.Err(), IsNil) + c.Assert(mSetNX.Val(), Equals, false) +} + +func (t *RedisTest) TestStringsPSetEx(c *C) { + pSetEx := t.client.PSetEx("key", 1000, "hello") + c.Assert(pSetEx.Err(), IsNil) + c.Assert(pSetEx.Val(), Equals, "OK") + + pttl := t.client.PTTL("key") + c.Assert(pttl.Err(), IsNil) + c.Assert(pttl.Val() > 900 && pttl.Val() <= 1000, Equals, true) + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestStringsSetGet(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestStringsSetEx(c *C) { + setEx := t.client.SetEx("key", 10, "hello") + c.Assert(setEx.Err(), IsNil) + c.Assert(setEx.Val(), Equals, "OK") + + ttl := t.client.TTL("key") + c.Assert(ttl.Err(), IsNil) + c.Assert(ttl.Val(), Equals, int64(10)) +} + +func (t *RedisTest) TestStringsSetNX(c *C) { + setNX := t.client.SetNX("key", "hello") + c.Assert(setNX.Err(), IsNil) + c.Assert(setNX.Val(), Equals, true) + + setNX = t.client.SetNX("key", "hello2") + c.Assert(setNX.Err(), IsNil) + c.Assert(setNX.Val(), Equals, false) + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestStringsSetRange(c *C) { + set := t.client.Set("key", "Hello World") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + range_ := t.client.SetRange("key", 6, "Redis") + c.Assert(range_.Err(), IsNil) + c.Assert(range_.Val(), Equals, int64(11)) + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "Hello Redis") +} + +func (t *RedisTest) TestStringsStrLen(c *C) { + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + strLen := t.client.StrLen("key") + c.Assert(strLen.Err(), IsNil) + c.Assert(strLen.Val(), Equals, int64(5)) + + strLen = t.client.StrLen("_") + c.Assert(strLen.Err(), IsNil) + c.Assert(strLen.Val(), Equals, int64(0)) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestCmdHDel(c *C) { + hSet := t.client.HSet("hash", "key", "hello") + c.Assert(hSet.Err(), IsNil) + + hDel := t.client.HDel("hash", "key") + c.Assert(hDel.Err(), IsNil) + c.Assert(hDel.Val(), Equals, int64(1)) + + hDel = t.client.HDel("hash", "key") + c.Assert(hDel.Err(), IsNil) + c.Assert(hDel.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestCmdHExists(c *C) { + hSet := t.client.HSet("hash", "key", "hello") + c.Assert(hSet.Err(), IsNil) + + hExists := t.client.HExists("hash", "key") + c.Assert(hExists.Err(), IsNil) + c.Assert(hExists.Val(), Equals, true) + + hExists = t.client.HExists("hash", "key1") + c.Assert(hExists.Err(), IsNil) + c.Assert(hExists.Val(), Equals, false) +} + +func (t *RedisTest) TestCmdHGet(c *C) { + hSet := t.client.HSet("hash", "key", "hello") + c.Assert(hSet.Err(), IsNil) + + hGet := t.client.HGet("hash", "key") + c.Assert(hGet.Err(), IsNil) + c.Assert(hGet.Val(), Equals, "hello") + + hGet = t.client.HGet("hash", "key1") + c.Assert(hGet.Err(), Equals, redis.Nil) + c.Assert(hGet.Val(), Equals, "") +} + +func (t *RedisTest) TestCmdHGetAll(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hGetAll := t.client.HGetAll("hash") + c.Assert(hGetAll.Err(), IsNil) + c.Assert(hGetAll.Val(), DeepEquals, []string{"key1", "hello1", "key2", "hello2"}) +} + +func (t *RedisTest) TestCmdHGetAllMap(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hGetAll := t.client.HGetAllMap("hash") + c.Assert(hGetAll.Err(), IsNil) + c.Assert(hGetAll.Val(), DeepEquals, map[string]string{"key1": "hello1", "key2": "hello2"}) +} + +func (t *RedisTest) TestCmdHIncrBy(c *C) { + hSet := t.client.HSet("hash", "key", "5") + c.Assert(hSet.Err(), IsNil) + + hIncrBy := t.client.HIncrBy("hash", "key", 1) + c.Assert(hIncrBy.Err(), IsNil) + c.Assert(hIncrBy.Val(), Equals, int64(6)) + + hIncrBy = t.client.HIncrBy("hash", "key", -1) + c.Assert(hIncrBy.Err(), IsNil) + c.Assert(hIncrBy.Val(), Equals, int64(5)) + + hIncrBy = t.client.HIncrBy("hash", "key", -10) + c.Assert(hIncrBy.Err(), IsNil) + c.Assert(hIncrBy.Val(), Equals, int64(-5)) +} + +func (t *RedisTest) TestCmdHIncrByFloat(c *C) { + hSet := t.client.HSet("hash", "field", "10.50") + c.Assert(hSet.Err(), IsNil) + c.Assert(hSet.Val(), Equals, true) + + hIncrByFloat := t.client.HIncrByFloat("hash", "field", 0.1) + c.Assert(hIncrByFloat.Err(), IsNil) + c.Assert(hIncrByFloat.Val(), Equals, 10.6) + + hSet = t.client.HSet("hash", "field", "5.0e3") + c.Assert(hSet.Err(), IsNil) + c.Assert(hSet.Val(), Equals, false) + + hIncrByFloat = t.client.HIncrByFloat("hash", "field", 2.0e2) + c.Assert(hIncrByFloat.Err(), IsNil) + c.Assert(hIncrByFloat.Val(), Equals, float64(5200)) +} + +func (t *RedisTest) TestCmdHKeys(c *C) { + hkeys := t.client.HKeys("hash") + c.Assert(hkeys.Err(), IsNil) + c.Assert(hkeys.Val(), DeepEquals, []string{}) + + hset := t.client.HSet("hash", "key1", "hello1") + c.Assert(hset.Err(), IsNil) + hset = t.client.HSet("hash", "key2", "hello2") + c.Assert(hset.Err(), IsNil) + + hkeys = t.client.HKeys("hash") + c.Assert(hkeys.Err(), IsNil) + c.Assert(hkeys.Val(), DeepEquals, []string{"key1", "key2"}) +} + +func (t *RedisTest) TestCmdHLen(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hLen := t.client.HLen("hash") + c.Assert(hLen.Err(), IsNil) + c.Assert(hLen.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestCmdHMGet(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hMGet := t.client.HMGet("hash", "key1", "key2", "_") + c.Assert(hMGet.Err(), IsNil) + c.Assert(hMGet.Val(), DeepEquals, []interface{}{"hello1", "hello2", nil}) +} + +func (t *RedisTest) TestCmdHMSet(c *C) { + hMSet := t.client.HMSet("hash", "key1", "hello1", "key2", "hello2") + c.Assert(hMSet.Err(), IsNil) + c.Assert(hMSet.Val(), Equals, "OK") + + hGet := t.client.HGet("hash", "key1") + c.Assert(hGet.Err(), IsNil) + c.Assert(hGet.Val(), Equals, "hello1") + + hGet = t.client.HGet("hash", "key2") + c.Assert(hGet.Err(), IsNil) + c.Assert(hGet.Val(), Equals, "hello2") +} + +func (t *RedisTest) TestCmdHSet(c *C) { + hSet := t.client.HSet("hash", "key", "hello") + c.Assert(hSet.Err(), IsNil) + c.Assert(hSet.Val(), Equals, true) + + hGet := t.client.HGet("hash", "key") + c.Assert(hGet.Err(), IsNil) + c.Assert(hGet.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdHSetNX(c *C) { + hSetNX := t.client.HSetNX("hash", "key", "hello") + c.Assert(hSetNX.Err(), IsNil) + c.Assert(hSetNX.Val(), Equals, true) + + hSetNX = t.client.HSetNX("hash", "key", "hello") + c.Assert(hSetNX.Err(), IsNil) + c.Assert(hSetNX.Val(), Equals, false) + + hGet := t.client.HGet("hash", "key") + c.Assert(hGet.Err(), IsNil) + c.Assert(hGet.Val(), Equals, "hello") +} + +func (t *RedisTest) TestCmdHVals(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hVals := t.client.HVals("hash") + c.Assert(hVals.Err(), IsNil) + c.Assert(hVals.Val(), DeepEquals, []string{"hello1", "hello2"}) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestCmdListsBLPop(c *C) { + rPush := t.client.RPush("list1", "a", "b", "c") + c.Assert(rPush.Err(), IsNil) + + bLPop := t.client.BLPop(0, "list1", "list2") + c.Assert(bLPop.Err(), IsNil) + c.Assert(bLPop.Val(), DeepEquals, []string{"list1", "a"}) +} + +func (t *RedisTest) TestCmdListsBLPopBlocks(c *C) { + started := make(chan bool) + done := make(chan bool) + go func() { + started <- true + bLPop := t.client.BLPop(0, "list") + c.Assert(bLPop.Err(), IsNil) + c.Assert(bLPop.Val(), DeepEquals, []string{"list", "a"}) + done <- true + }() + <-started + + select { + case <-done: + c.Error("BLPop is not blocked") + case <-time.After(time.Second): + // ok + } + + rPush := t.client.RPush("list", "a") + c.Assert(rPush.Err(), IsNil) + + select { + case <-done: + // ok + case <-time.After(time.Second): + c.Error("BLPop is still blocked") + // ok + } +} + +func (t *RedisTest) TestCmdListsBLPopTimeout(c *C) { + bLPop := t.client.BLPop(1, "list1") + c.Assert(bLPop.Err(), Equals, redis.Nil) + c.Assert(bLPop.Val(), IsNil) +} + +func (t *RedisTest) TestCmdListsBRPop(c *C) { + rPush := t.client.RPush("list1", "a", "b", "c") + c.Assert(rPush.Err(), IsNil) + + bRPop := t.client.BRPop(0, "list1", "list2") + c.Assert(bRPop.Err(), IsNil) + c.Assert(bRPop.Val(), DeepEquals, []string{"list1", "c"}) +} + +func (t *RedisTest) TestCmdListsBRPopBlocks(c *C) { + started := make(chan bool) + done := make(chan bool) + go func() { + started <- true + bRPop := t.client.BRPop(0, "list") + c.Assert(bRPop.Err(), IsNil) + c.Assert(bRPop.Val(), DeepEquals, []string{"list", "a"}) + done <- true + }() + <-started + + select { + case <-done: + c.Error("BRPop is not blocked") + case <-time.After(time.Second): + // ok + } + + rPush := t.client.RPush("list", "a") + c.Assert(rPush.Err(), IsNil) + + select { + case <-done: + // ok + case <-time.After(time.Second): + c.Error("BRPop is still blocked") + // ok + } +} + +func (t *RedisTest) TestCmdListsBRPopLPush(c *C) { + rPush := t.client.RPush("list1", "a", "b", "c") + c.Assert(rPush.Err(), IsNil) + + bRPopLPush := t.client.BRPopLPush("list1", "list2", 0) + c.Assert(bRPopLPush.Err(), IsNil) + c.Assert(bRPopLPush.Val(), Equals, "c") +} + +func (t *RedisTest) TestCmdListsLIndex(c *C) { + lPush := t.client.LPush("list", "World") + c.Assert(lPush.Err(), IsNil) + lPush = t.client.LPush("list", "Hello") + c.Assert(lPush.Err(), IsNil) + + lIndex := t.client.LIndex("list", 0) + c.Assert(lIndex.Err(), IsNil) + c.Assert(lIndex.Val(), Equals, "Hello") + + lIndex = t.client.LIndex("list", -1) + c.Assert(lIndex.Err(), IsNil) + c.Assert(lIndex.Val(), Equals, "World") + + lIndex = t.client.LIndex("list", 3) + c.Assert(lIndex.Err(), Equals, redis.Nil) + c.Assert(lIndex.Val(), Equals, "") +} + +func (t *RedisTest) TestCmdListsLInsert(c *C) { + rPush := t.client.RPush("list", "Hello") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "World") + c.Assert(rPush.Err(), IsNil) + + lInsert := t.client.LInsert("list", "BEFORE", "World", "There") + c.Assert(lInsert.Err(), IsNil) + c.Assert(lInsert.Val(), Equals, int64(3)) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"Hello", "There", "World"}) +} + +func (t *RedisTest) TestCmdListsLLen(c *C) { + lPush := t.client.LPush("list", "World") + c.Assert(lPush.Err(), IsNil) + lPush = t.client.LPush("list", "Hello") + c.Assert(lPush.Err(), IsNil) + + lLen := t.client.LLen("list") + c.Assert(lLen.Err(), IsNil) + c.Assert(lLen.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestCmdListsLPop(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + lPop := t.client.LPop("list") + c.Assert(lPop.Err(), IsNil) + c.Assert(lPop.Val(), Equals, "one") + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"two", "three"}) +} + +func (t *RedisTest) TestCmdListsLPush(c *C) { + lPush := t.client.LPush("list", "World") + c.Assert(lPush.Err(), IsNil) + lPush = t.client.LPush("list", "Hello") + c.Assert(lPush.Err(), IsNil) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"Hello", "World"}) +} + +func (t *RedisTest) TestCmdListsLPushX(c *C) { + lPush := t.client.LPush("list", "World") + c.Assert(lPush.Err(), IsNil) + + lPushX := t.client.LPushX("list", "Hello") + c.Assert(lPushX.Err(), IsNil) + c.Assert(lPushX.Val(), Equals, int64(2)) + + lPushX = t.client.LPushX("list2", "Hello") + c.Assert(lPushX.Err(), IsNil) + c.Assert(lPushX.Val(), Equals, int64(0)) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"Hello", "World"}) + + lRange = t.client.LRange("list2", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{}) +} + +func (t *RedisTest) TestCmdListsLRange(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + lRange := t.client.LRange("list", 0, 0) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"one"}) + + lRange = t.client.LRange("list", -3, 2) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"one", "two", "three"}) + + lRange = t.client.LRange("list", -100, 100) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"one", "two", "three"}) + + lRange = t.client.LRange("list", 5, 10) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{}) +} + +func (t *RedisTest) TestCmdListsLRem(c *C) { + rPush := t.client.RPush("list", "hello") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "hello") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "key") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "hello") + c.Assert(rPush.Err(), IsNil) + + lRem := t.client.LRem("list", -2, "hello") + c.Assert(lRem.Err(), IsNil) + c.Assert(lRem.Val(), Equals, int64(2)) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"hello", "key"}) +} + +func (t *RedisTest) TestCmdListsLSet(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + lSet := t.client.LSet("list", 0, "four") + c.Assert(lSet.Err(), IsNil) + c.Assert(lSet.Val(), Equals, "OK") + + lSet = t.client.LSet("list", -2, "five") + c.Assert(lSet.Err(), IsNil) + c.Assert(lSet.Val(), Equals, "OK") + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"four", "five", "three"}) +} + +func (t *RedisTest) TestCmdListsLTrim(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + lTrim := t.client.LTrim("list", 1, -1) + c.Assert(lTrim.Err(), IsNil) + c.Assert(lTrim.Val(), Equals, "OK") + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"two", "three"}) +} + +func (t *RedisTest) TestCmdListsRPop(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + rPop := t.client.RPop("list") + c.Assert(rPop.Err(), IsNil) + c.Assert(rPop.Val(), Equals, "three") + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"one", "two"}) +} + +func (t *RedisTest) TestCmdListsRPopLPush(c *C) { + rPush := t.client.RPush("list", "one") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "two") + c.Assert(rPush.Err(), IsNil) + rPush = t.client.RPush("list", "three") + c.Assert(rPush.Err(), IsNil) + + rPopLPush := t.client.RPopLPush("list", "list2") + c.Assert(rPopLPush.Err(), IsNil) + c.Assert(rPopLPush.Val(), Equals, "three") + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"one", "two"}) + + lRange = t.client.LRange("list2", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"three"}) +} + +func (t *RedisTest) TestCmdListsRPush(c *C) { + rPush := t.client.RPush("list", "Hello") + c.Assert(rPush.Err(), IsNil) + c.Assert(rPush.Val(), Equals, int64(1)) + + rPush = t.client.RPush("list", "World") + c.Assert(rPush.Err(), IsNil) + c.Assert(rPush.Val(), Equals, int64(2)) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"Hello", "World"}) +} + +func (t *RedisTest) TestCmdListsRPushX(c *C) { + rPush := t.client.RPush("list", "Hello") + c.Assert(rPush.Err(), IsNil) + c.Assert(rPush.Val(), Equals, int64(1)) + + rPushX := t.client.RPushX("list", "World") + c.Assert(rPushX.Err(), IsNil) + c.Assert(rPushX.Val(), Equals, int64(2)) + + rPushX = t.client.RPushX("list2", "World") + c.Assert(rPushX.Err(), IsNil) + c.Assert(rPushX.Val(), Equals, int64(0)) + + lRange := t.client.LRange("list", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{"Hello", "World"}) + + lRange = t.client.LRange("list2", 0, -1) + c.Assert(lRange.Err(), IsNil) + c.Assert(lRange.Val(), DeepEquals, []string{}) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestSAdd(c *C) { + sAdd := t.client.SAdd("set", "Hello") + c.Assert(sAdd.Err(), IsNil) + c.Assert(sAdd.Val(), Equals, int64(1)) + + sAdd = t.client.SAdd("set", "World") + c.Assert(sAdd.Err(), IsNil) + c.Assert(sAdd.Val(), Equals, int64(1)) + + sAdd = t.client.SAdd("set", "World") + c.Assert(sAdd.Err(), IsNil) + c.Assert(sAdd.Val(), Equals, int64(0)) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sortStrings(sMembers.Val()), DeepEquals, []string{"Hello", "World"}) +} + +func (t *RedisTest) TestSCard(c *C) { + sAdd := t.client.SAdd("set", "Hello") + c.Assert(sAdd.Err(), IsNil) + c.Assert(sAdd.Val(), Equals, int64(1)) + + sAdd = t.client.SAdd("set", "World") + c.Assert(sAdd.Err(), IsNil) + c.Assert(sAdd.Val(), Equals, int64(1)) + + sCard := t.client.SCard("set") + c.Assert(sCard.Err(), IsNil) + c.Assert(sCard.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestSDiff(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sDiff := t.client.SDiff("set1", "set2") + c.Assert(sDiff.Err(), IsNil) + c.Assert(sortStrings(sDiff.Val()), DeepEquals, []string{"a", "b"}) +} + +func (t *RedisTest) TestSDiffStore(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sDiffStore := t.client.SDiffStore("set", "set1", "set2") + c.Assert(sDiffStore.Err(), IsNil) + c.Assert(sDiffStore.Val(), Equals, int64(2)) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sortStrings(sMembers.Val()), DeepEquals, []string{"a", "b"}) +} + +func (t *RedisTest) TestSInter(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sInter := t.client.SInter("set1", "set2") + c.Assert(sInter.Err(), IsNil) + c.Assert(sInter.Val(), DeepEquals, []string{"c"}) +} + +func (t *RedisTest) TestSInterStore(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sInterStore := t.client.SInterStore("set", "set1", "set2") + c.Assert(sInterStore.Err(), IsNil) + c.Assert(sInterStore.Val(), Equals, int64(1)) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sMembers.Val(), DeepEquals, []string{"c"}) +} + +func (t *RedisTest) TestIsMember(c *C) { + sAdd := t.client.SAdd("set", "one") + c.Assert(sAdd.Err(), IsNil) + + sIsMember := t.client.SIsMember("set", "one") + c.Assert(sIsMember.Err(), IsNil) + c.Assert(sIsMember.Val(), Equals, true) + + sIsMember = t.client.SIsMember("set", "two") + c.Assert(sIsMember.Err(), IsNil) + c.Assert(sIsMember.Val(), Equals, false) +} + +func (t *RedisTest) TestSMembers(c *C) { + sAdd := t.client.SAdd("set", "Hello") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "World") + c.Assert(sAdd.Err(), IsNil) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sortStrings(sMembers.Val()), DeepEquals, []string{"Hello", "World"}) +} + +func (t *RedisTest) TestSMove(c *C) { + sAdd := t.client.SAdd("set1", "one") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "two") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "three") + c.Assert(sAdd.Err(), IsNil) + + sMove := t.client.SMove("set1", "set2", "two") + c.Assert(sMove.Err(), IsNil) + c.Assert(sMove.Val(), Equals, true) + + sMembers := t.client.SMembers("set1") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sMembers.Val(), DeepEquals, []string{"one"}) + + sMembers = t.client.SMembers("set2") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sortStrings(sMembers.Val()), DeepEquals, []string{"three", "two"}) +} + +func (t *RedisTest) TestSPop(c *C) { + sAdd := t.client.SAdd("set", "one") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "two") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "three") + c.Assert(sAdd.Err(), IsNil) + + sPop := t.client.SPop("set") + c.Assert(sPop.Err(), IsNil) + c.Assert(sPop.Val(), Not(Equals), "") + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sMembers.Val(), HasLen, 2) +} + +func (t *RedisTest) TestSRandMember(c *C) { + sAdd := t.client.SAdd("set", "one") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "two") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "three") + c.Assert(sAdd.Err(), IsNil) + + sRandMember := t.client.SRandMember("set") + c.Assert(sRandMember.Err(), IsNil) + c.Assert(sRandMember.Val(), Not(Equals), "") + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sMembers.Val(), HasLen, 3) +} + +func (t *RedisTest) TestSRem(c *C) { + sAdd := t.client.SAdd("set", "one") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "two") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set", "three") + c.Assert(sAdd.Err(), IsNil) + + sRem := t.client.SRem("set", "one") + c.Assert(sRem.Err(), IsNil) + c.Assert(sRem.Val(), Equals, int64(1)) + + sRem = t.client.SRem("set", "four") + c.Assert(sRem.Err(), IsNil) + c.Assert(sRem.Val(), Equals, int64(0)) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert( + sortStrings(sMembers.Val()), + DeepEquals, + []string{"three", "two"}, + ) +} + +func (t *RedisTest) TestSUnion(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sUnion := t.client.SUnion("set1", "set2") + c.Assert(sUnion.Err(), IsNil) + c.Assert(sUnion.Val(), HasLen, 5) +} + +func (t *RedisTest) TestSUnionStore(c *C) { + sAdd := t.client.SAdd("set1", "a") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "b") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set1", "c") + c.Assert(sAdd.Err(), IsNil) + + sAdd = t.client.SAdd("set2", "c") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "d") + c.Assert(sAdd.Err(), IsNil) + sAdd = t.client.SAdd("set2", "e") + c.Assert(sAdd.Err(), IsNil) + + sUnionStore := t.client.SUnionStore("set", "set1", "set2") + c.Assert(sUnionStore.Err(), IsNil) + c.Assert(sUnionStore.Val(), Equals, int64(5)) + + sMembers := t.client.SMembers("set") + c.Assert(sMembers.Err(), IsNil) + c.Assert(sMembers.Val(), HasLen, 5) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestZAdd(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + c.Assert(zAdd.Val(), Equals, int64(1)) + + zAdd = t.client.ZAdd("zset", redis.Z{1, "uno"}) + c.Assert(zAdd.Err(), IsNil) + c.Assert(zAdd.Val(), Equals, int64(1)) + + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + c.Assert(zAdd.Val(), Equals, int64(1)) + + zAdd = t.client.ZAdd("zset", redis.Z{3, "two"}) + c.Assert(zAdd.Err(), IsNil) + c.Assert(zAdd.Val(), Equals, int64(0)) + + zRange := t.client.ZRangeWithScores("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"one", "1", "uno", "1", "two", "3"}) +} + +func (t *RedisTest) TestZCard(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + + zCard := t.client.ZCard("zset") + c.Assert(zCard.Err(), IsNil) + c.Assert(zCard.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestZCount(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zCount := t.client.ZCount("zset", "-inf", "+inf") + c.Assert(zCount.Err(), IsNil) + c.Assert(zCount.Val(), Equals, int64(3)) + + zCount = t.client.ZCount("zset", "(1", "3") + c.Assert(zCount.Err(), IsNil) + c.Assert(zCount.Val(), Equals, int64(2)) +} + +func (t *RedisTest) TestZIncrBy(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + + zIncrBy := t.client.ZIncrBy("zset", 2, "one") + c.Assert(zIncrBy.Err(), IsNil) + c.Assert(zIncrBy.Val(), Equals, float64(3)) + + zRange := t.client.ZRangeWithScores("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"two", "2", "one", "3"}) +} + +func (t *RedisTest) TestZInterStore(c *C) { + zAdd := t.client.ZAdd("zset1", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset1", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + + zAdd = t.client.ZAdd("zset2", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset2", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset3", redis.Z{3, "two"}) + c.Assert(zAdd.Err(), IsNil) + + zInterStore := t.client.ZInterStore( + "out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2") + c.Assert(zInterStore.Err(), IsNil) + c.Assert(zInterStore.Val(), Equals, int64(2)) + + zRange := t.client.ZRangeWithScores("out", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"one", "5", "two", "10"}) +} + +func (t *RedisTest) TestZRange(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRange := t.client.ZRange("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"one", "two", "three"}) + + zRange = t.client.ZRange("zset", 2, 3) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"three"}) + + zRange = t.client.ZRange("zset", -2, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"two", "three"}) +} + +func (t *RedisTest) TestZRangeWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRange := t.client.ZRangeWithScoresMap("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2, "three": 3}) + + zRange = t.client.ZRangeWithScoresMap("zset", 2, 3) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"three": 3}) + + zRange = t.client.ZRangeWithScoresMap("zset", -2, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"two": 2, "three": 3}) +} + +func (t *RedisTest) TestZRangeByScore(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRangeByScore := t.client.ZRangeByScore("zset", "-inf", "+inf", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, []string{"one", "two", "three"}) + + zRangeByScore = t.client.ZRangeByScore("zset", "1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, []string{"one", "two"}) + + zRangeByScore = t.client.ZRangeByScore("zset", "(1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, []string{"two"}) + + zRangeByScore = t.client.ZRangeByScore("zset", "(1", "(2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, []string{}) +} + +func (t *RedisTest) TestZRangeByScoreWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRangeByScore := t.client.ZRangeByScoreWithScoresMap("zset", "-inf", "+inf", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2, "three": 3}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "(1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"two": 2}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "(1", "(2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{}) +} + +func (t *RedisTest) TestZRank(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRank := t.client.ZRank("zset", "three") + c.Assert(zRank.Err(), IsNil) + c.Assert(zRank.Val(), Equals, int64(2)) + + zRank = t.client.ZRank("zset", "four") + c.Assert(zRank.Err(), Equals, redis.Nil) + c.Assert(zRank.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestZRem(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRem := t.client.ZRem("zset", "two") + c.Assert(zRem.Err(), IsNil) + c.Assert(zRem.Val(), Equals, int64(1)) + + zRange := t.client.ZRangeWithScores("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"one", "1", "three", "3"}) +} + +func (t *RedisTest) TestZRemRangeByRank(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRemRangeByRank := t.client.ZRemRangeByRank("zset", 0, 1) + c.Assert(zRemRangeByRank.Err(), IsNil) + c.Assert(zRemRangeByRank.Val(), Equals, int64(2)) + + zRange := t.client.ZRangeWithScores("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"three", "3"}) +} + +func (t *RedisTest) TestZRemRangeByScore(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRemRangeByScore := t.client.ZRemRangeByScore("zset", "-inf", "(2") + c.Assert(zRemRangeByScore.Err(), IsNil) + c.Assert(zRemRangeByScore.Val(), Equals, int64(1)) + + zRange := t.client.ZRangeWithScores("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"two", "2", "three", "3"}) +} + +func (t *RedisTest) TestZRevRange(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRange := t.client.ZRevRange("zset", "0", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, []string{"three", "two", "one"}) + + zRevRange = t.client.ZRevRange("zset", "2", "3") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, []string{"one"}) + + zRevRange = t.client.ZRevRange("zset", "-2", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, []string{"two", "one"}) +} + +func (t *RedisTest) TestZRevRangeWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRange := t.client.ZRevRangeWithScoresMap("zset", "0", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"three": 3, "two": 2, "one": 1}) + + zRevRange = t.client.ZRevRangeWithScoresMap("zset", "2", "3") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"one": 1}) + + zRevRange = t.client.ZRevRangeWithScoresMap("zset", "-2", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"two": 2, "one": 1}) +} + +func (t *RedisTest) TestZRevRangeByScore(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRangeByScore := t.client.ZRevRangeByScore("zset", "+inf", "-inf", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, []string{"three", "two", "one"}) + + zRevRangeByScore = t.client.ZRevRangeByScore("zset", "2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, []string{"two"}) + + zRevRangeByScore = t.client.ZRevRangeByScore("zset", "(2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, []string{}) +} + +func (t *RedisTest) TestZRevRangeByScoreWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRangeByScore := t.client.ZRevRangeByScoreWithScoresMap("zset", "+inf", "-inf", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{"three": 3, "two": 2, "one": 1}) + + zRevRangeByScore = t.client.ZRevRangeByScoreWithScoresMap("zset", "2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{"two": 2}) + + zRevRangeByScore = t.client.ZRevRangeByScoreWithScoresMap("zset", "(2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{}) +} + +func (t *RedisTest) TestZRevRank(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRank := t.client.ZRevRank("zset", "one") + c.Assert(zRevRank.Err(), IsNil) + c.Assert(zRevRank.Val(), Equals, int64(2)) + + zRevRank = t.client.ZRevRank("zset", "four") + c.Assert(zRevRank.Err(), Equals, redis.Nil) + c.Assert(zRevRank.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestZScore(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1.001, "one"}) + c.Assert(zAdd.Err(), IsNil) + + zScore := t.client.ZScore("zset", "one") + c.Assert(zScore.Err(), IsNil) + c.Assert(zScore.Val(), Equals, float64(1.001)) +} + +func (t *RedisTest) TestZUnionStore(c *C) { + zAdd := t.client.ZAdd("zset1", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset1", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + + zAdd = t.client.ZAdd("zset2", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset2", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset2", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zUnionStore := t.client.ZUnionStore( + "out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2") + c.Assert(zUnionStore.Err(), IsNil) + c.Assert(zUnionStore.Val(), Equals, int64(3)) + + zRange := t.client.ZRangeWithScores("out", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, []string{"one", "5", "three", "9", "two", "10"}) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestPatternPubSub(c *C) { + pubsub, err := t.client.PubSubClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pubsub.Close(), IsNil) + }() + + ch, err := pubsub.PSubscribe("mychannel*") + c.Assert(err, IsNil) + c.Assert(ch, Not(IsNil)) + + pub := t.client.Publish("mychannel1", "hello") + c.Assert(pub.Err(), IsNil) + c.Assert(pub.Val(), Equals, int64(1)) + + err = pubsub.PUnsubscribe("mychannel*") + c.Assert(err, IsNil) + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "psubscribe") + c.Assert(msg.Channel, Equals, "mychannel*") + c.Assert(msg.Number, Equals, int64(1)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "pmessage") + c.Assert(msg.ChannelPattern, Equals, "mychannel*") + c.Assert(msg.Channel, Equals, "mychannel1") + c.Assert(msg.Message, Equals, "hello") + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "punsubscribe") + c.Assert(msg.Channel, Equals, "mychannel*") + c.Assert(msg.Number, Equals, int64(0)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } +} + +func (t *RedisTest) TestPubSub(c *C) { + pubsub, err := t.client.PubSubClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pubsub.Close(), IsNil) + }() + + ch, err := pubsub.Subscribe("mychannel") + c.Assert(err, IsNil) + c.Assert(ch, Not(IsNil)) + + ch2, err := pubsub.Subscribe("mychannel2") + c.Assert(err, IsNil) + c.Assert(ch2, Equals, ch) + + pub := t.client.Publish("mychannel", "hello") + c.Assert(pub.Err(), IsNil) + c.Assert(pub.Val(), Equals, int64(1)) + + pub = t.client.Publish("mychannel2", "hello2") + c.Assert(pub.Err(), IsNil) + c.Assert(pub.Val(), Equals, int64(1)) + + err = pubsub.Unsubscribe("mychannel") + c.Assert(err, IsNil) + + err = pubsub.Unsubscribe("mychannel2") + c.Assert(err, IsNil) + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "subscribe") + c.Assert(msg.Channel, Equals, "mychannel") + c.Assert(msg.Number, Equals, int64(1)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "subscribe") + c.Assert(msg.Channel, Equals, "mychannel2") + c.Assert(msg.Number, Equals, int64(2)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "message") + c.Assert(msg.Channel, Equals, "mychannel") + c.Assert(msg.Message, Equals, "hello") + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "message") + c.Assert(msg.Channel, Equals, "mychannel2") + c.Assert(msg.Message, Equals, "hello2") + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "unsubscribe") + c.Assert(msg.Channel, Equals, "mychannel") + c.Assert(msg.Number, Equals, int64(1)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } + + select { + case msg := <-ch: + c.Assert(msg.Err, Equals, nil) + c.Assert(msg.Name, Equals, "unsubscribe") + c.Assert(msg.Channel, Equals, "mychannel2") + c.Assert(msg.Number, Equals, int64(0)) + case <-time.After(time.Second): + c.Error("Channel is empty.") + } +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestPipeline(c *C) { + set := t.client.Set("key2", "hello2") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + set = pipeline.Set("key1", "hello1") + get := pipeline.Get("key2") + incr := pipeline.Incr("key3") + getNil := pipeline.Get("key4") + + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 4) + + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello2") + + c.Assert(incr.Err(), IsNil) + c.Assert(incr.Val(), Equals, int64(1)) + + c.Assert(getNil.Err(), Equals, redis.Nil) + c.Assert(getNil.Val(), Equals, "") +} + +func (t *RedisTest) TestPipelineDiscardQueued(c *C) { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + pipeline.Get("key") + pipeline.DiscardQueued() + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 0) +} + +func (t *RedisTest) TestPipelineFunc(c *C) { + var get *redis.StringReq + reqs, err := t.client.Pipelined(func(c *redis.PipelineClient) { + get = c.Get("foo") + }) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 1) + c.Assert(get.Err(), Equals, redis.Nil) + c.Assert(get.Val(), Equals, "") +} + +func (t *RedisTest) TestPipelineErrValNotSet(c *C) { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + get := pipeline.Get("key") + c.Assert(get.Err(), ErrorMatches, "redis: value is not set") +} + +func (t *RedisTest) TestPipelineRunQueuedOnEmptyQueue(c *C) { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 0) +} + +func (t *RedisTest) TestPipelineIncrFromGoroutines(c *C) { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + wg := &sync.WaitGroup{} + for i := int64(0); i < 20000; i++ { + wg.Add(1) + go func() { + pipeline.Incr("TestIncrPipeliningFromGoroutinesKey") + wg.Done() + }() + } + wg.Wait() + + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 20000) + for _, req := range reqs { + if req.Err() != nil { + c.Errorf("got %v, expected nil", req.Err()) + } + } + + get := t.client.Get("TestIncrPipeliningFromGoroutinesKey") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "20000") +} + +func (t *RedisTest) TestPipelineEchoFromGoroutines(c *C) { + pipeline, err := t.client.PipelineClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(pipeline.Close(), IsNil) + }() + + wg := &sync.WaitGroup{} + for i := int64(0); i < 1000; i += 2 { + wg.Add(1) + go func() { + msg1 := "echo" + strconv.FormatInt(i, 10) + msg2 := "echo" + strconv.FormatInt(i+1, 10) + + echo1 := pipeline.Echo(msg1) + echo2 := pipeline.Echo(msg2) + + reqs, err := pipeline.RunQueued() + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 2) + + c.Assert(echo1.Err(), IsNil) + c.Assert(echo1.Val(), Equals, msg1) + + c.Assert(echo2.Err(), IsNil) + c.Assert(echo2.Val(), Equals, msg2) + + wg.Done() + }() + } + wg.Wait() +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestMultiExec(c *C) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + var ( + set *redis.StatusReq + get *redis.StringReq + ) + reqs, err := multi.Exec(func() { + set = multi.Set("key", "hello") + get = multi.Get("key") + }) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 2) + + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello") +} + +func (t *RedisTest) TestMultiExecDiscard(c *C) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + reqs, err := multi.Exec(func() { + multi.Set("key1", "hello1") + multi.Discard() + multi.Set("key2", "hello2") + }) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 1) + + get := t.client.Get("key1") + c.Assert(get.Err(), Equals, redis.Nil) + c.Assert(get.Val(), Equals, "") + + get = t.client.Get("key2") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "hello2") +} + +func (t *RedisTest) TestMultiExecEmpty(c *C) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + reqs, err := multi.Exec(func() {}) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 0) + + ping := multi.Ping() + c.Check(ping.Err(), IsNil) + c.Check(ping.Val(), Equals, "PONG") +} + +func (t *RedisTest) TestMultiExecOnEmptyQueue(c *C) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + reqs, err := multi.Exec(func() {}) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 0) +} + +func (t *RedisTest) TestMultiExecIncrTransaction(c *C) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + reqs, err := multi.Exec(func() { + for i := int64(0); i < 20000; i++ { + multi.Incr("TestIncrTransactionKey") + } + }) + c.Assert(err, IsNil) + c.Assert(reqs, HasLen, 20000) + for _, req := range reqs { + if req.Err() != nil { + c.Errorf("got %v, expected nil", req.Err()) + } + } + + get := t.client.Get("TestIncrTransactionKey") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "20000") +} + +func (t *RedisTest) transactionalIncr(c *C) ([]redis.Req, error) { + multi, err := t.client.MultiClient() + c.Assert(err, IsNil) + defer func() { + c.Assert(multi.Close(), IsNil) + }() + + watch := multi.Watch("key") + c.Assert(watch.Err(), IsNil) + c.Assert(watch.Val(), Equals, "OK") + + get := multi.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Not(Equals), redis.Nil) + + v, err := strconv.ParseInt(get.Val(), 10, 64) + c.Assert(err, IsNil) + + reqs, err := multi.Exec(func() { + multi.Set("key", strconv.FormatInt(v+1, 10)) + }) + if err == redis.Nil { + return t.transactionalIncr(c) + } + return reqs, err +} + +func (t *RedisTest) TestWatchUnwatch(c *C) { + set := t.client.Set("key", "0") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + wg := &sync.WaitGroup{} + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + reqs, err := t.transactionalIncr(c) + c.Assert(reqs, HasLen, 1) + c.Assert(err, IsNil) + c.Assert(reqs[0].Err(), IsNil) + wg.Done() + }() + } + wg.Wait() + + get := t.client.Get("key") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "1000") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestSyncEchoFromGoroutines(c *C) { + wg := &sync.WaitGroup{} + for i := int64(0); i < 1000; i++ { + wg.Add(1) + go func() { + msg := "echo" + strconv.FormatInt(i, 10) + echo := t.client.Echo(msg) + c.Assert(echo.Err(), IsNil) + c.Assert(echo.Val(), Equals, msg) + wg.Done() + }() + } + wg.Wait() +} + +func (t *RedisTest) TestIncrFromGoroutines(c *C) { + wg := &sync.WaitGroup{} + for i := int64(0); i < 20000; i++ { + wg.Add(1) + go func() { + incr := t.client.Incr("TestIncrFromGoroutinesKey") + c.Assert(incr.Err(), IsNil) + wg.Done() + }() + } + wg.Wait() + + get := t.client.Get("TestIncrFromGoroutinesKey") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "20000") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestCmdBgRewriteAOF(c *C) { + r := t.client.BgRewriteAOF() + c.Assert(r.Err(), IsNil) + c.Assert(r.Val(), Equals, "Background append only file rewriting started") +} + +func (t *RedisTest) TestCmdBgSave(c *C) { + // workaround for "ERR Can't BGSAVE while AOF log rewriting is in progress" + time.Sleep(time.Second) + + r := t.client.BgSave() + c.Assert(r.Err(), IsNil) + c.Assert(r.Val(), Equals, "Background saving started") +} + +func (t *RedisTest) TestCmdClientKill(c *C) { + r := t.client.ClientKill("1.1.1.1:1111") + c.Assert(r.Err(), ErrorMatches, "ERR No such client") + c.Assert(r.Val(), Equals, "") +} + +func (t *RedisTest) TestCmdConfigGet(c *C) { + r := t.client.ConfigGet("*") + c.Assert(r.Err(), IsNil) + c.Assert(len(r.Val()) > 0, Equals, true) +} + +func (t *RedisTest) TestCmdConfigResetStat(c *C) { + r := t.client.ConfigResetStat() + c.Assert(r.Err(), IsNil) + c.Assert(r.Val(), Equals, "OK") +} + +func (t *RedisTest) TestCmdConfigSet(c *C) { + configGet := t.client.ConfigGet("maxmemory") + c.Assert(configGet.Err(), IsNil) + c.Assert(configGet.Val(), HasLen, 2) + c.Assert(configGet.Val()[0], Equals, "maxmemory") + + configSet := t.client.ConfigSet("maxmemory", configGet.Val()[1].(string)) + c.Assert(configSet.Err(), IsNil) + c.Assert(configSet.Val(), Equals, "OK") +} + +func (t *RedisTest) TestCmdDbSize(c *C) { + dbSize := t.client.DbSize() + c.Assert(dbSize.Err(), IsNil) + c.Assert(dbSize.Val(), Equals, int64(0)) +} + +func (t *RedisTest) TestCmdFlushAll(c *C) { + // TODO +} + +func (t *RedisTest) TestCmdFlushDb(c *C) { + // TODO +} + +func (t *RedisTest) TestCmdInfo(c *C) { + info := t.client.Info() + c.Assert(info.Err(), IsNil) + c.Assert(info.Val(), Not(Equals), "") +} + +func (t *RedisTest) TestCmdLastSave(c *C) { + lastSave := t.client.LastSave() + c.Assert(lastSave.Err(), IsNil) + c.Assert(lastSave.Val(), Not(Equals), 0) +} + +func (t *RedisTest) TestCmdSave(c *C) { + save := t.client.Save() + c.Assert(save.Err(), IsNil) + c.Assert(save.Val(), Equals, "OK") +} + +func (t *RedisTest) TestSlaveOf(c *C) { + slaveOf := t.client.SlaveOf("localhost", "8888") + c.Assert(slaveOf.Err(), IsNil) + c.Assert(slaveOf.Val(), Equals, "OK") + + slaveOf = t.client.SlaveOf("NO", "ONE") + c.Assert(slaveOf.Err(), IsNil) + c.Assert(slaveOf.Val(), Equals, "OK") +} + +func (t *RedisTest) TestTime(c *C) { + time := t.client.Time() + c.Assert(time.Err(), IsNil) + c.Assert(time.Val(), HasLen, 2) +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) TestScriptingEval(c *C) { + eval := t.client.Eval( + "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", + []string{"key1", "key2"}, + []string{"first", "second"}, + ) + c.Assert(eval.Err(), IsNil) + c.Assert(eval.Val(), DeepEquals, []interface{}{"key1", "key2", "first", "second"}) + + eval = t.client.Eval( + "return redis.call('set',KEYS[1],'bar')", + []string{"foo"}, + []string{}, + ) + c.Assert(eval.Err(), IsNil) + c.Assert(eval.Val(), Equals, "OK") + + eval = t.client.Eval("return 10", []string{}, []string{}) + c.Assert(eval.Err(), IsNil) + c.Assert(eval.Val(), Equals, int64(10)) + + eval = t.client.Eval("return {1,2,{3,'Hello World!'}}", []string{}, []string{}) + c.Assert(eval.Err(), IsNil) + // DeepEquals can't compare nested slices. + c.Assert( + fmt.Sprintf("%#v", eval.Val()), + Equals, + `[]interface {}{1, 2, []interface {}{3, "Hello World!"}}`, + ) +} + +func (t *RedisTest) TestScriptingEvalSha(c *C) { + set := t.client.Set("foo", "bar") + c.Assert(set.Err(), IsNil) + c.Assert(set.Val(), Equals, "OK") + + eval := t.client.Eval("return redis.call('get','foo')", []string{}, []string{}) + c.Assert(eval.Err(), IsNil) + c.Assert(eval.Val(), Equals, "bar") + + evalSha := t.client.EvalSha("6b1bf486c81ceb7edf3c093f4c48582e38c0e791", []string{}, []string{}) + c.Assert(evalSha.Err(), IsNil) + c.Assert(evalSha.Val(), Equals, "bar") + + evalSha = t.client.EvalSha("ffffffffffffffffffffffffffffffffffffffff", []string{}, []string{}) + c.Assert(evalSha.Err(), ErrorMatches, "NOSCRIPT No matching script. Please use EVAL.") + c.Assert(evalSha.Val(), Equals, nil) +} + +func (t *RedisTest) TestScriptingScriptExists(c *C) { + scriptLoad := t.client.ScriptLoad("return 1") + c.Assert(scriptLoad.Err(), IsNil) + c.Assert(scriptLoad.Val(), Equals, "e0e1f9fabfc9d4800c877a703b823ac0578ff8db") + + scriptExists := t.client.ScriptExists( + "e0e1f9fabfc9d4800c877a703b823ac0578ff8db", + "ffffffffffffffffffffffffffffffffffffffff", + ) + c.Assert(scriptExists.Err(), IsNil) + c.Assert(scriptExists.Val(), DeepEquals, []bool{true, false}) +} + +func (t *RedisTest) TestScriptingScriptFlush(c *C) { + scriptFlush := t.client.ScriptFlush() + c.Assert(scriptFlush.Err(), IsNil) + c.Assert(scriptFlush.Val(), Equals, "OK") +} + +func (t *RedisTest) TestScriptingScriptKill(c *C) { + scriptKill := t.client.ScriptKill() + c.Assert(scriptKill.Err(), ErrorMatches, ".*No scripts in execution right now.") + c.Assert(scriptKill.Val(), Equals, "") +} + +func (t *RedisTest) TestScriptingScriptLoad(c *C) { + scriptLoad := t.client.ScriptLoad("return redis.call('get','foo')") + c.Assert(scriptLoad.Err(), IsNil) + c.Assert(scriptLoad.Val(), Equals, "6b1bf486c81ceb7edf3c093f4c48582e38c0e791") +} + +//------------------------------------------------------------------------------ + +func (t *RedisTest) BenchmarkRedisPing(c *C) { + for i := 0; i < c.N; i++ { + if err := t.client.Ping().Err(); err != nil { + panic(err) + } + } +} + +func (t *RedisTest) BenchmarkRedisSet(c *C) { + for i := 0; i < c.N; i++ { + if err := t.client.Set("key", "hello").Err(); err != nil { + panic(err) + } + } +} + +func (t *RedisTest) BenchmarkRedisGetNil(c *C) { + for i := 0; i < c.N; i++ { + if err := t.client.Get("key").Err(); err != redis.Nil { + panic(err) + } + } +} + +func (t *RedisTest) BenchmarkRedisGet(c *C) { + c.StopTimer() + + set := t.client.Set("key", "hello") + c.Assert(set.Err(), IsNil) + + c.StartTimer() + + for i := 0; i < c.N; i++ { + if err := t.client.Get("key").Err(); err != nil { + panic(err) + } + } +} + +func (t *RedisTest) BenchmarkRedisMGet(c *C) { + c.StopTimer() + + mSet := t.client.MSet("key1", "hello1", "key2", "hello2") + c.Assert(mSet.Err(), IsNil) + + c.StartTimer() + + for i := 0; i < c.N; i++ { + if err := t.client.MGet("key1", "key2").Err(); err != nil { + panic(err) + } + } +} diff --git a/v2/req.go b/v2/req.go new file mode 100644 index 0000000..180bfa4 --- /dev/null +++ b/v2/req.go @@ -0,0 +1,315 @@ +package redis + +import ( + "fmt" + "strconv" + "strings" +) + +type Req interface { + Args() []string + ParseReply(reader) (interface{}, error) + SetErr(error) + Err() error + SetVal(interface{}) + IfaceVal() interface{} +} + +//------------------------------------------------------------------------------ + +type BaseReq struct { + args []string + + val interface{} + err error +} + +func NewBaseReq(args ...string) *BaseReq { + return &BaseReq{ + args: args, + } +} + +func (r *BaseReq) Args() []string { + return r.args +} + +func (r *BaseReq) SetErr(err error) { + if err == nil { + panic("non-nil value expected") + } + r.err = err +} + +func (r *BaseReq) Err() error { + if r.err != nil { + return r.err + } + if r.val == nil { + return errValNotSet + } + return nil +} + +func (r *BaseReq) SetVal(val interface{}) { + if val == nil { + panic("non-nil value expected") + } + r.val = val +} + +func (r *BaseReq) IfaceVal() interface{} { + return r.val +} + +func (r *BaseReq) ParseReply(rd reader) (interface{}, error) { + return parseReply(rd) +} + +func (r *BaseReq) String() string { + args := strings.Join(r.args, " ") + if r.err != nil { + return args + ": " + r.err.Error() + } else if r.val != nil { + return args + ": " + fmt.Sprint(r.val) + } + return args +} + +//------------------------------------------------------------------------------ + +type IfaceReq struct { + *BaseReq +} + +func NewIfaceReq(args ...string) *IfaceReq { + return &IfaceReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *IfaceReq) Val() interface{} { + return r.val +} + +//------------------------------------------------------------------------------ + +type StatusReq struct { + *BaseReq +} + +func NewStatusReq(args ...string) *StatusReq { + return &StatusReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *StatusReq) Val() string { + if r.val == nil { + return "" + } + return r.val.(string) +} + +//------------------------------------------------------------------------------ + +type IntReq struct { + *BaseReq +} + +func NewIntReq(args ...string) *IntReq { + return &IntReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *IntReq) Val() int64 { + if r.val == nil { + return 0 + } + return r.val.(int64) +} + +//------------------------------------------------------------------------------ + +type BoolReq struct { + *BaseReq +} + +func NewBoolReq(args ...string) *BoolReq { + return &BoolReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *BoolReq) ParseReply(rd reader) (interface{}, error) { + v, err := parseReply(rd) + if err != nil { + return nil, err + } + return v.(int64) == 1, nil +} + +func (r *BoolReq) Val() bool { + if r.val == nil { + return false + } + return r.val.(bool) +} + +//------------------------------------------------------------------------------ + +type StringReq struct { + *BaseReq +} + +func NewStringReq(args ...string) *StringReq { + return &StringReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *StringReq) Val() string { + if r.val == nil { + return "" + } + return r.val.(string) +} + +//------------------------------------------------------------------------------ + +type FloatReq struct { + *BaseReq +} + +func NewFloatReq(args ...string) *FloatReq { + return &FloatReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *FloatReq) ParseReply(rd reader) (interface{}, error) { + v, err := parseReply(rd) + if err != nil { + return nil, err + } + return strconv.ParseFloat(v.(string), 64) +} + +func (r *FloatReq) Val() float64 { + if r.val == nil { + return 0 + } + return r.val.(float64) +} + +//------------------------------------------------------------------------------ + +type IfaceSliceReq struct { + *BaseReq +} + +func NewIfaceSliceReq(args ...string) *IfaceSliceReq { + return &IfaceSliceReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *IfaceSliceReq) Val() []interface{} { + if r.val == nil { + return nil + } + return r.val.([]interface{}) +} + +//------------------------------------------------------------------------------ + +type StringSliceReq struct { + *BaseReq +} + +func NewStringSliceReq(args ...string) *StringSliceReq { + return &StringSliceReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *StringSliceReq) ParseReply(rd reader) (interface{}, error) { + return parseStringSliceReply(rd) +} + +func (r *StringSliceReq) Val() []string { + if r.val == nil { + return nil + } + return r.val.([]string) +} + +//------------------------------------------------------------------------------ + +type BoolSliceReq struct { + *BaseReq +} + +func NewBoolSliceReq(args ...string) *BoolSliceReq { + return &BoolSliceReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *BoolSliceReq) ParseReply(rd reader) (interface{}, error) { + return parseBoolSliceReply(rd) +} + +func (r *BoolSliceReq) Val() []bool { + if r.val == nil { + return nil + } + return r.val.([]bool) +} + +//------------------------------------------------------------------------------ + +type StringStringMapReq struct { + *BaseReq +} + +func NewStringStringMapReq(args ...string) *StringStringMapReq { + return &StringStringMapReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *StringStringMapReq) ParseReply(rd reader) (interface{}, error) { + return parseStringStringMapReply(rd) +} + +func (r *StringStringMapReq) Val() map[string]string { + if r.val == nil { + return nil + } + return r.val.(map[string]string) +} + +//------------------------------------------------------------------------------ + +type StringFloatMapReq struct { + *BaseReq +} + +func NewStringFloatMapReq(args ...string) *StringFloatMapReq { + return &StringFloatMapReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *StringFloatMapReq) ParseReply(rd reader) (interface{}, error) { + return parseStringFloatMapReply(rd) +} + +func (r *StringFloatMapReq) Val() map[string]float64 { + if r.val == nil { + return nil + } + return r.val.(map[string]float64) +} diff --git a/v2/req_test.go b/v2/req_test.go new file mode 100644 index 0000000..d7588a3 --- /dev/null +++ b/v2/req_test.go @@ -0,0 +1,93 @@ +package redis_test + +import ( + "github.com/vmihailenco/bufio" + . "launchpad.net/gocheck" + + "github.com/vmihailenco/redis" +) + +//------------------------------------------------------------------------------ + +type LineReader struct { + line []byte +} + +func NewLineReader(line []byte) *LineReader { + return &LineReader{line: line} +} + +func (r *LineReader) Read(buf []byte) (int, error) { + return copy(buf, r.line), nil +} + +//------------------------------------------------------------------------------ + +type RequestTest struct{} + +var _ = Suite(&RequestTest{}) + +//------------------------------------------------------------------------------ + +func (t *RequestTest) SetUpTest(c *C) {} + +func (t *RequestTest) TearDownTest(c *C) {} + +//------------------------------------------------------------------------------ + +func (t *RequestTest) benchmarkReq(c *C, reqString string, req redis.Req, checker Checker, expected interface{}) { + c.StopTimer() + + lineReader := NewLineReader([]byte(reqString)) + rd := bufio.NewReaderSize(lineReader, 1024) + + for i := 0; i < 10; i++ { + vIface, err := req.ParseReply(rd) + c.Check(err, IsNil) + c.Check(vIface, checker, expected) + req.SetVal(vIface) + } + + c.StartTimer() + + for i := 0; i < c.N; i++ { + v, _ := req.ParseReply(rd) + req.SetVal(v) + } +} + +func (t *RequestTest) BenchmarkStatusReq(c *C) { + t.benchmarkReq(c, "+OK\r\n", redis.NewStatusReq(), Equals, "OK") +} + +func (t *RequestTest) BenchmarkIntReq(c *C) { + t.benchmarkReq(c, ":1\r\n", redis.NewIntReq(), Equals, int64(1)) +} + +func (t *RequestTest) BenchmarkStringReq(c *C) { + t.benchmarkReq(c, "$5\r\nhello\r\n", redis.NewStringReq(), Equals, "hello") +} + +func (t *RequestTest) BenchmarkFloatReq(c *C) { + t.benchmarkReq(c, "$5\r\n1.111\r\n", redis.NewFloatReq(), Equals, 1.111) +} + +func (t *RequestTest) BenchmarkStringSliceReq(c *C) { + t.benchmarkReq( + c, + "*2\r\n$5\r\nhello\r\n$5\r\nhello\r\n", + redis.NewStringSliceReq(), + DeepEquals, + []string{"hello", "hello"}, + ) +} + +func (t *RequestTest) BenchmarkIfaceSliceReq(c *C) { + t.benchmarkReq( + c, + "*2\r\n$5\r\nhello\r\n$5\r\nhello\r\n", + redis.NewIfaceSliceReq(), + DeepEquals, + []interface{}{"hello", "hello"}, + ) +} diff --git a/v2/script.go b/v2/script.go new file mode 100644 index 0000000..6284a23 --- /dev/null +++ b/v2/script.go @@ -0,0 +1,45 @@ +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +type Script struct { + src, hash string +} + +func NewScript(src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{ + src: src, + hash: hex.EncodeToString(h.Sum(nil)), + } +} + +func (s *Script) Load(c *Client) *StringReq { + return c.ScriptLoad(s.src) +} + +func (s *Script) Exists(c *Client) *BoolSliceReq { + return c.ScriptExists(s.src) +} + +func (s *Script) Eval(c *Client, keys []string, args []string) *IfaceReq { + return c.Eval(s.src, keys, args) +} + +func (s *Script) EvalSha(c *Client, keys []string, args []string) *IfaceReq { + return c.EvalSha(s.hash, keys, args) +} + +func (s *Script) Run(c *Client, keys []string, args []string) *IfaceReq { + r := s.EvalSha(c, keys, args) + if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { + return s.Eval(c, keys, args) + } + return r +}