lib/redis.rb in redis-2.2.2 vs lib/redis.rb in redis-3.0.0.rc1
- old
+ new
@@ -1,44 +1,34 @@
require "monitor"
+require "redis/errors"
class Redis
- class ProtocolError < RuntimeError
- def initialize(reply_type)
- super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
- Got '#{reply_type}' as initial reply byte.
- If you're running in a multi-threaded environment, make sure you
- pass the :thread_safe option when initializing the connection.
- If you're in a forking environment, such as Unicorn, you need to
- connect to Redis after forking.
- EOS
- end
- end
- module DisableThreadSafety
- def synchronize
- yield
- end
- end
-
def self.deprecate(message, trace = caller[0])
$stderr.puts "\n#{message} (in #{trace})"
end
attr :client
def self.connect(options = {})
options = options.dup
- require "uri"
+ url = options.delete(:url) || ENV["REDIS_URL"]
+ if url
+ require "uri"
- url = URI(options.delete(:url) || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0")
+ uri = URI(url)
- options[:host] ||= url.host
- options[:port] ||= url.port
- options[:password] ||= url.password
- options[:db] ||= url.path[1..-1].to_i
+ # Require the URL to have at least a host
+ raise ArgumentError, "invalid url" unless uri.host
+ options[:host] ||= uri.host
+ options[:port] ||= uri.port
+ options[:password] ||= uri.password
+ options[:db] ||= uri.path[1..-1].to_i
+ end
+
new(options)
end
def self.current
Thread.current[:redis] ||= Redis.connect
@@ -52,594 +42,1094 @@
def initialize(options = {})
@client = Client.new(options)
if options[:thread_safe] == false
- # Override #synchronize
- extend DisableThreadSafety
+ @synchronizer = lambda { |&block| block.call }
else
- # Monitor#initialize
- super()
+ @synchronizer = lambda { |&block| mon_synchronize { block.call } }
+ super() # Monitor#initialize
end
end
+ def synchronize
+ @synchronizer.call { yield }
+ end
+
# Run code without the client reconnecting
def without_reconnect(&block)
synchronize do
@client.without_reconnect(&block)
end
end
# Authenticate to the server.
+ #
+ # @param [String] password must match the password specified in the
+ # `requirepass` directive in the configuration file
+ # @return [String] `OK`
def auth(password)
synchronize do
@client.call [:auth, password]
end
end
# Change the selected database for the current connection.
+ #
+ # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
+ # @return [String] `OK`
def select(db)
synchronize do
@client.db = db
@client.call [:select, db]
end
end
# Get information and statistics about the server.
+ #
+ # @param [String, Symbol] cmd e.g. "commandstats"
+ # @return [Hash<String, String>]
def info(cmd = nil)
synchronize do
- reply = @client.call [:info, cmd].compact
+ @client.call [:info, cmd].compact do |reply|
+ if reply.kind_of?(String)
+ reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
- if reply.kind_of?(String)
- reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
-
- if cmd && cmd.to_s == "commandstats"
- # Extract nested hashes for INFO COMMANDSTATS
- reply = Hash[reply.map do |k, v|
- [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
- end]
+ if cmd && cmd.to_s == "commandstats"
+ # Extract nested hashes for INFO COMMANDSTATS
+ reply = Hash[reply.map do |k, v|
+ [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
+ end]
+ end
end
- end
- reply
+ reply
+ end
end
end
+ # Get or set server configuration parameters.
+ #
+ # @param [String] action e.g. `get`, `set`, `resetstat`
+ # @return [String, Hash] string reply, or hash when retrieving more than one
+ # property with `CONFIG GET`
def config(action, *args)
synchronize do
- reply = @client.call [:config, action, *args]
-
- if reply.kind_of?(Array) && action == :get
- Hash[*reply]
- else
- reply
+ @client.call [:config, action, *args] do |reply|
+ if reply.kind_of?(Array) && action == :get
+ Hash[*reply]
+ else
+ reply
+ end
end
end
end
# Remove all keys from the current database.
+ #
+ # @return [String] `OK`
def flushdb
synchronize do
@client.call [:flushdb]
end
end
# Remove all keys from all databases.
+ #
+ # @return [String] `OK`
def flushall
synchronize do
@client.call [:flushall]
end
end
# Synchronously save the dataset to disk.
+ #
+ # @return [String]
def save
synchronize do
@client.call [:save]
end
end
# Asynchronously save the dataset to disk.
+ #
+ # @return [String] `OK`
def bgsave
synchronize do
@client.call [:bgsave]
end
end
# Asynchronously rewrite the append-only file.
+ #
+ # @return [String] `OK`
def bgrewriteaof
synchronize do
@client.call [:bgrewriteaof]
end
end
# Get the value of a key.
+ #
+ # @param [String] key
+ # @return [String]
def get(key)
synchronize do
@client.call [:get, key]
end
end
+ alias :[] :get
+
# Returns the bit value at offset in the string value stored at key.
+ #
+ # @param [String] key
+ # @param [Fixnum] offset bit offset
+ # @return [Fixnum] `0` or `1`
def getbit(key, offset)
synchronize do
@client.call [:getbit, key, offset]
end
end
# Get a substring of the string stored at a key.
+ #
+ # @param [String] key
+ # @param [Fixnum] start zero-based start offset
+ # @param [Fixnum] stop zero-based end offset. Use -1 for representing
+ # the end of the string
+ # @return [Fixnum] `0` or `1`
def getrange(key, start, stop)
synchronize do
@client.call [:getrange, key, start, stop]
end
end
# Set the string value of a key and return its old value.
+ #
+ # @param [String] key
+ # @param [String] value value to replace the current value with
+ # @return [String] the old value stored in the key, or `nil` if the key
+ # did not exist
def getset(key, value)
synchronize do
@client.call [:getset, key, value]
end
end
# Get the values of all the given keys.
- def mget(*keys)
+ #
+ # @param [Array<String>] keys
+ # @return [Array<String>]
+ def mget(*keys, &blk)
synchronize do
- @client.call [:mget, *keys]
+ @client.call [:mget, *keys], &blk
end
end
# Append a value to a key.
+ #
+ # @param [String] key
+ # @param [String] value value to append
+ # @return [Fixnum] length of the string after appending
def append(key, value)
synchronize do
@client.call [:append, key, value]
end
end
- def substr(key, start, stop)
- synchronize do
- @client.call [:substr, key, start, stop]
- end
- end
-
# Get the length of the value stored in a key.
+ #
+ # @param [String] key
+ # @return [Fixnum] the length of the value stored in the key, or 0
+ # if the key does not exist
def strlen(key)
synchronize do
@client.call [:strlen, key]
end
end
# Get all the fields and values in a hash.
+ #
+ # @param [String] key
+ # @return [Hash<String, String>]
def hgetall(key)
synchronize do
- reply = @client.call [:hgetall, key]
-
- if reply.kind_of?(Array)
- Hash[*reply]
- else
- reply
+ @client.call [:hgetall, key] do |reply|
+ if reply.kind_of?(Array)
+ hash = Hash.new
+ reply.each_slice(2) do |field, value|
+ hash[field] = value
+ end
+ hash
+ else
+ reply
+ end
end
end
end
# Get the value of a hash field.
+ #
+ # @param [String] key
+ # @param [String] field
+ # @return [String]
def hget(key, field)
synchronize do
@client.call [:hget, key, field]
end
end
- # Delete a hash field.
+ # Delete one or more hash fields.
+ #
+ # @param [String] key
+ # @param [String, Array<String>] field
+ # @return [Fixnum] the number of fields that were removed from the hash
def hdel(key, field)
synchronize do
@client.call [:hdel, key, field]
end
end
# Get all the fields in a hash.
+ #
+ # @param [String] key
+ # @return [Array<String>]
def hkeys(key)
synchronize do
@client.call [:hkeys, key]
end
end
# Find all keys matching the given pattern.
+ #
+ # @param [String] pattern
+ # @return [Array<String>]
def keys(pattern = "*")
synchronize do
- reply = @client.call [:keys, pattern]
-
- if reply.kind_of?(String)
- reply.split(" ")
- else
- reply
+ @client.call [:keys, pattern] do |reply|
+ if reply.kind_of?(String)
+ reply.split(" ")
+ else
+ reply
+ end
end
end
end
# Return a random key from the keyspace.
+ #
+ # @return [String]
def randomkey
synchronize do
@client.call [:randomkey]
end
end
# Echo the given string.
+ #
+ # @param [String] value
+ # @return [String]
def echo(value)
synchronize do
@client.call [:echo, value]
end
end
# Ping the server.
+ #
+ # @return [String] `PONG`
def ping
synchronize do
@client.call [:ping]
end
end
# Get the UNIX time stamp of the last successful save to disk.
+ #
+ # @return [Fixnum]
def lastsave
synchronize do
@client.call [:lastsave]
end
end
# Return the number of keys in the selected database.
+ #
+ # @return [Fixnum]
def dbsize
synchronize do
@client.call [:dbsize]
end
end
# Determine if a key exists.
+ #
+ # @param [String] key
+ # @return [Boolean]
def exists(key)
synchronize do
- _bool @client.call [:exists, key]
+ @client.call [:exists, key], &_boolify
end
end
# Get the length of a list.
+ #
+ # @param [String] key
+ # @return [Fixnum]
def llen(key)
synchronize do
@client.call [:llen, key]
end
end
# Get a range of elements from a list.
+ #
+ # @param [String] key
+ # @param [Fixnum] start start index
+ # @param [Fixnum] stop stop index
+ # @return [Array<String>]
def lrange(key, start, stop)
synchronize do
@client.call [:lrange, key, start, stop]
end
end
# Trim a list to the specified range.
+ #
+ # @param [String] key
+ # @param [Fixnum] start start index
+ # @param [Fixnum] stop stop index
+ # @return [String] `OK`
def ltrim(key, start, stop)
synchronize do
@client.call [:ltrim, key, start, stop]
end
end
# Get an element from a list by its index.
+ #
+ # @param [String] key
+ # @param [Fixnum] index
+ # @return [String]
def lindex(key, index)
synchronize do
@client.call [:lindex, key, index]
end
end
# Insert an element before or after another element in a list.
+ #
+ # @param [String] key
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
+ # @param [String] pivot reference element
+ # @param [String] value
+ # @return [Fixnum] length of the list after the insert operation, or `-1`
+ # when the element `pivot` was not found
def linsert(key, where, pivot, value)
synchronize do
@client.call [:linsert, key, where, pivot, value]
end
end
# Set the value of an element in a list by its index.
+ #
+ # @param [String] key
+ # @param [Fixnum] index
+ # @param [String] value
+ # @return [String] `OK`
def lset(key, index, value)
synchronize do
@client.call [:lset, key, index, value]
end
end
# Remove elements from a list.
+ #
+ # @param [String] key
+ # @param [Fixnum] count number of elements to remove. Use a positive
+ # value to remove the first `count` occurrences of `value`. A negative
+ # value to remove the last `count` occurrences of `value`. Or zero, to
+ # remove all occurrences of `value` from the list.
+ # @param [String] value
+ # @return [Fixnum] the number of removed elements
def lrem(key, count, value)
synchronize do
@client.call [:lrem, key, count, value]
end
end
- # Append a value to a list.
+ # Append one or more values to a list, creating the list if it doesn't exist
+ #
+ # @param [String] key
+ # @param [String] value
+ # @return [Fixnum] the length of the list after the push operation
def rpush(key, value)
synchronize do
@client.call [:rpush, key, value]
end
end
# Append a value to a list, only if the list exists.
+ #
+ # @param [String] key
+ # @param [String] value
+ # @return [Fixnum] the length of the list after the push operation
def rpushx(key, value)
synchronize do
@client.call [:rpushx, key, value]
end
end
- # Prepend a value to a list.
+ # Prepend one or more values to a list, creating the list if it doesn't exist
+ #
+ # @param [String] key
+ # @param [String] value
+ # @return [Fixnum] the length of the list after the push operation
def lpush(key, value)
synchronize do
@client.call [:lpush, key, value]
end
end
# Prepend a value to a list, only if the list exists.
+ #
+ # @param [String] key
+ # @param [String] value
+ # @return [Fixnum] the length of the list after the push operation
def lpushx(key, value)
synchronize do
@client.call [:lpushx, key, value]
end
end
# Remove and get the last element in a list.
+ #
+ # @param [String] key
+ # @return [String]
def rpop(key)
synchronize do
@client.call [:rpop, key]
end
end
# Remove and get the first element in a list, or block until one is available.
+ #
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
+ # followed by a `Fixnum` timeout value
+ # @return [nil, Array<String>] tuple of list that was popped from and element
+ # that was popped, or nil when the blocking operation timed out
def blpop(*args)
synchronize do
- @client.call_without_timeout(:blpop, *args)
+ @client.call_without_timeout [:blpop, *args]
end
end
# Remove and get the last element in a list, or block until one is available.
+ #
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
+ # followed by a `Fixnum` timeout value
+ # @return [nil, Array<String>] tuple of list that was popped from and element
+ # that was popped, or nil when the blocking operation timed out
def brpop(*args)
synchronize do
- @client.call_without_timeout(:brpop, *args)
+ @client.call_without_timeout [:brpop, *args]
end
end
# Pop a value from a list, push it to another list and return it; or block
# until one is available.
+ #
+ # @param [String] source source key
+ # @param [String] destination destination key
+ # @param [Fixnum] timeout
+ # @return [nil, String] the element, or nil when the blocking operation timed out
def brpoplpush(source, destination, timeout)
synchronize do
- @client.call_without_timeout(:brpoplpush, source, destination, timeout)
+ @client.call_without_timeout [:brpoplpush, source, destination, timeout]
end
end
# Remove the last element in a list, append it to another list and return it.
+ #
+ # @param [String] source source key
+ # @param [String] destination destination key
+ # @return [nil, String] the element, or nil when the source key does not exist
def rpoplpush(source, destination)
synchronize do
@client.call [:rpoplpush, source, destination]
end
end
# Remove and get the first element in a list.
+ #
+ # @param [String] key
+ # @return [String]
def lpop(key)
synchronize do
@client.call [:lpop, key]
end
end
+ # Interact with the slowlog (get, len, reset)
+ #
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
+ # @param [Fixnum] length maximum number of entries to return
+ # @return [Array<String>, Fixnum, String] depends on subcommand
+ def slowlog(subcommand, length=nil)
+ synchronize do
+ args = [:slowlog, subcommand]
+ args << length if length
+ @client.call args
+ end
+ end
+
# Get all the members in a set.
+ #
+ # @param [String] key
+ # @return [Array<String>]
def smembers(key)
synchronize do
@client.call [:smembers, key]
end
end
# Determine if a given value is a member of a set.
+ #
+ # @param [String] key
+ # @param [String] member
+ # @return [Boolean]
def sismember(key, member)
synchronize do
- _bool @client.call [:sismember, key, member]
+ @client.call [:sismember, key, member], &_boolify
end
end
- # Add a member to a set.
- def sadd(key, value)
+ # Add one or more members to a set.
+ #
+ # @param [String] key
+ # @param [String, Array<String>] member one member, or array of members
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
+ # holding whether or not adding the member succeeded, or `Fixnum` when an
+ # array of members is specified, holding the number of members that were
+ # successfully added
+ def sadd(key, member)
synchronize do
- _bool @client.call [:sadd, key, value]
+ @client.call [:sadd, key, member] do |reply|
+ if member.is_a? Array
+ # Variadic: return integer
+ reply
+ else
+ # Single argument: return boolean
+ _boolify.call(reply)
+ end
+ end
end
end
- # Remove a member from a set.
- def srem(key, value)
+ # Remove one or more members from a set.
+ #
+ # @param [String] key
+ # @param [String, Array<String>] member one member, or array of members
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
+ # holding whether or not removing the member succeeded, or `Fixnum` when an
+ # array of members is specified, holding the number of members that were
+ # successfully removed
+ def srem(key, member)
synchronize do
- _bool @client.call [:srem, key, value]
+ @client.call [:srem, key, member] do |reply|
+ if member.is_a? Array
+ # Variadic: return integer
+ reply
+ else
+ # Single argument: return boolean
+ _boolify.call(reply)
+ end
+ end
end
end
# Move a member from one set to another.
+ #
+ # @param [String] source source key
+ # @param [String] destination destination key
+ # @param [String] member member to move from `source` to `destination`
+ # @return [Boolean]
def smove(source, destination, member)
synchronize do
- _bool @client.call [:smove, source, destination, member]
+ @client.call [:smove, source, destination, member], &_boolify
end
end
# Remove and return a random member from a set.
+ #
+ # @param [String] key
+ # @return [String]
def spop(key)
synchronize do
@client.call [:spop, key]
end
end
# Get the number of members in a set.
+ #
+ # @param [String] key
+ # @return [Fixnum]
def scard(key)
synchronize do
@client.call [:scard, key]
end
end
# Intersect multiple sets.
+ #
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
+ # @return [Array<String>] members in the intersection
def sinter(*keys)
synchronize do
@client.call [:sinter, *keys]
end
end
# Intersect multiple sets and store the resulting set in a key.
+ #
+ # @param [String] destination destination key
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
+ # @return [Fixnum] number of elements in the resulting set
def sinterstore(destination, *keys)
synchronize do
@client.call [:sinterstore, destination, *keys]
end
end
# Add multiple sets.
+ #
+ # @param [String, Array<String>] keys keys pointing to sets to unify
+ # @return [Array<String>] members in the union
def sunion(*keys)
synchronize do
@client.call [:sunion, *keys]
end
end
# Add multiple sets and store the resulting set in a key.
+ #
+ # @param [String] destination destination key
+ # @param [String, Array<String>] keys keys pointing to sets to unify
+ # @return [Fixnum] number of elements in the resulting set
def sunionstore(destination, *keys)
synchronize do
@client.call [:sunionstore, destination, *keys]
end
end
# Subtract multiple sets.
+ #
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
+ # @return [Array<String>] members in the difference
def sdiff(*keys)
synchronize do
@client.call [:sdiff, *keys]
end
end
# Subtract multiple sets and store the resulting set in a key.
+ #
+ # @param [String] destination destination key
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
+ # @return [Fixnum] number of elements in the resulting set
def sdiffstore(destination, *keys)
synchronize do
@client.call [:sdiffstore, destination, *keys]
end
end
# Get a random member from a set.
+ #
+ # @param [String] key
+ # @return [String]
def srandmember(key)
synchronize do
@client.call [:srandmember, key]
end
end
- # Add a member to a sorted set, or update its score if it already exists.
- def zadd(key, score, member)
+ # Add one or more members to a sorted set, or update the score for members
+ # that already exist.
+ #
+ # @example Add a single `(score, member)` pair to a sorted set
+ # redis.zadd("zset", 32.0, "member")
+ # @example Add an array of `(score, member)` pairs to a sorted set
+ # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
+ #
+ # @param [String] key
+ # @param [(Float, String), Array<(Float,String)>] args
+ # - a single `(score, member)` pair
+ # - an array of `(score, member)` pairs
+ #
+ # @return [Boolean, Fixnum]
+ # - `Boolean` when a single pair is specified, holding whether or not it was
+ # **added** to the sorted set
+ # - `Fixnum` when an array of pairs is specified, holding the number of
+ # pairs that were **added** to the sorted set
+ def zadd(key, *args)
synchronize do
- _bool @client.call [:zadd, key, score, member]
+ if args.size == 1 && args[0].is_a?(Array)
+ # Variadic: return integer
+ @client.call [:zadd, key] + args[0]
+ elsif args.size == 2
+ # Single pair: return boolean
+ @client.call [:zadd, key, args[0], args[1]], &_boolify
+ else
+ raise ArgumentError, "wrong number of arguments"
+ end
end
end
+ # Remove one or more members from a sorted set.
+ #
+ # @example Remove a single member from a sorted set
+ # redis.zrem("zset", "a")
+ # @example Remove an array of members from a sorted set
+ # redis.zrem("zset", ["a", "b"])
+ #
+ # @param [String] key
+ # @param [String, Array<String>] member
+ # - a single member
+ # - an array of members
+ #
+ # @return [Boolean, Fixnum]
+ # - `Boolean` when a single member is specified, holding whether or not it
+ # was removed from the sorted set
+ # - `Fixnum` when an array of pairs is specified, holding the number of
+ # members that were removed to the sorted set
+ def zrem(key, member)
+ synchronize do
+ @client.call [:zrem, key, member] do |reply|
+ if member.is_a? Array
+ # Variadic: return integer
+ reply
+ else
+ # Single argument: return boolean
+ _boolify.call(reply)
+ end
+ end
+ end
+ end
+
# Determine the index of a member in a sorted set.
+ #
+ # @param [String] key
+ # @param [String] member
+ # @return [Fixnum]
def zrank(key, member)
synchronize do
@client.call [:zrank, key, member]
end
end
# Determine the index of a member in a sorted set, with scores ordered from
# high to low.
+ #
+ # @param [String] key
+ # @param [String] member
+ # @return [Fixnum]
def zrevrank(key, member)
synchronize do
@client.call [:zrevrank, key, member]
end
end
# Increment the score of a member in a sorted set.
+ #
+ # @example
+ # redis.zincrby("zset", 32.0, "a")
+ # # => 64.0
+ #
+ # @param [String] key
+ # @param [Float] increment
+ # @param [String] member
+ # @return [Float] score of the member after incrementing it
def zincrby(key, increment, member)
synchronize do
- @client.call [:zincrby, key, increment, member]
+ @client.call [:zincrby, key, increment, member] do |reply|
+ Float(reply) if reply
+ end
end
end
# Get the number of members in a sorted set.
+ #
+ # @example
+ # redis.zcard("zset")
+ # # => 4
+ #
+ # @param [String] key
+ # @return [Fixnum]
def zcard(key)
synchronize do
@client.call [:zcard, key]
end
end
# Return a range of members in a sorted set, by index.
+ #
+ # @example Retrieve all members from a sorted set
+ # redis.zrange("zset", 0, -1)
+ # # => ["a", "b"]
+ # @example Retrieve all members and their scores from a sorted set
+ # redis.zrange("zset", 0, -1, :with_scores => true)
+ # # => [["a", 32.0], ["b", 64.0]]
+ #
+ # @param [String] key
+ # @param [Fixnum] start start index
+ # @param [Fixnum] stop stop index
+ # @param [Hash] options
+ # - `:with_scores => true`: include scores in output
+ #
+ # @return [Array<String>, Array<(String, Float)>]
+ # - when `:with_scores` is not specified, an array of members
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
def zrange(key, start, stop, options = {})
- command = CommandOptions.new(options) do |c|
- c.bool :withscores
- c.bool :with_scores
- end
+ args = []
+ with_scores = options[:with_scores] || options[:withscores]
+ args << "WITHSCORES" if with_scores
+
synchronize do
- @client.call [:zrange, key, start, stop, *command.to_a]
+ @client.call [:zrange, key, start, stop, *args] do |reply|
+ if with_scores
+ if reply
+ reply.each_slice(2).map do |member, score|
+ [member, Float(score)]
+ end
+ end
+ else
+ reply
+ end
+ end
end
end
- # Return a range of members in a sorted set, by score.
- def zrangebyscore(key, min, max, options = {})
- command = CommandOptions.new(options) do |c|
- c.splat :limit
- c.bool :withscores
- c.bool :with_scores
- end
+ # Return a range of members in a sorted set, by index, with scores ordered
+ # from high to low.
+ #
+ # @example Retrieve all members from a sorted set
+ # redis.zrevrange("zset", 0, -1)
+ # # => ["b", "a"]
+ # @example Retrieve all members and their scores from a sorted set
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
+ # # => [["b", 64.0], ["a", 32.0]]
+ #
+ # @see #zrange
+ def zrevrange(key, start, stop, options = {})
+ args = []
- synchronize do
- @client.call [:zrangebyscore, key, min, max, *command.to_a]
- end
- end
+ with_scores = options[:with_scores] || options[:withscores]
+ args << "WITHSCORES" if with_scores
- # Count the members in a sorted set with scores within the given values.
- def zcount(key, start, stop)
synchronize do
- @client.call [:zcount, key, start, stop]
+ @client.call [:zrevrange, key, start, stop, *args] do |reply|
+ if with_scores
+ if reply
+ reply.each_slice(2).map do |member, score|
+ [member, Float(score)]
+ end
+ end
+ else
+ reply
+ end
+ end
end
end
- # Return a range of members in a sorted set, by index, with scores ordered
- # from high to low.
- def zrevrange(key, start, stop, options = {})
- command = CommandOptions.new(options) do |c|
- c.bool :withscores
- c.bool :with_scores
- end
+ # Return a range of members in a sorted set, by score.
+ #
+ # @example Retrieve members with score `>= 5` and `< 100`
+ # redis.zrangebyscore("zset", "5", "(100")
+ # # => ["a", "b"]
+ # @example Retrieve the first 2 members with score `>= 0`
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
+ # # => ["a", "b"]
+ # @example Retrieve members and their scores with scores `> 5`
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
+ # # => [["a", 32.0], ["b", 64.0]]
+ #
+ # @param [String] key
+ # @param [String] min
+ # - inclusive minimum score is specified verbatim
+ # - exclusive minimum score is specified by prefixing `(`
+ # @param [String] max
+ # - inclusive maximum score is specified verbatim
+ # - exclusive maximum score is specified by prefixing `(`
+ # @param [Hash] options
+ # - `:with_scores => true`: include scores in output
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
+ # `count` members
+ #
+ # @return [Array<String>, Array<(String, Float)>]
+ # - when `:with_scores` is not specified, an array of members
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
+ def zrangebyscore(key, min, max, options = {})
+ args = []
+ with_scores = options[:with_scores] || options[:withscores]
+ args.concat ["WITHSCORES"] if with_scores
+
+ limit = options[:limit]
+ args.concat ["LIMIT", *limit] if limit
+
synchronize do
- @client.call [:zrevrange, key, start, stop, *command.to_a]
+ @client.call [:zrangebyscore, key, min, max, *args] do |reply|
+ if with_scores
+ if reply
+ reply.each_slice(2).map do |member, score|
+ [member, Float(score)]
+ end
+ end
+ else
+ reply
+ end
+ end
end
end
# Return a range of members in a sorted set, by score, with scores ordered
# from high to low.
+ #
+ # @example Retrieve members with score `< 100` and `>= 5`
+ # redis.zrevrangebyscore("zset", "(100", "5")
+ # # => ["b", "a"]
+ # @example Retrieve the first 2 members with score `<= 0`
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
+ # # => ["b", "a"]
+ # @example Retrieve members and their scores with scores `> 5`
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
+ # # => [["b", 64.0], ["a", 32.0]]
+ #
+ # @see #zrangebyscore
def zrevrangebyscore(key, max, min, options = {})
- command = CommandOptions.new(options) do |c|
- c.splat :limit
- c.bool :withscores
- c.bool :with_scores
+ args = []
+
+ with_scores = options[:with_scores] || options[:withscores]
+ args.concat ["WITHSCORES"] if with_scores
+
+ limit = options[:limit]
+ args.concat ["LIMIT", *limit] if limit
+
+ synchronize do
+ @client.call [:zrevrangebyscore, key, max, min, *args] do |reply|
+ if with_scores
+ if reply
+ reply.each_slice(2).map do |member, score|
+ [member, Float(score)]
+ end
+ end
+ else
+ reply
+ end
+ end
end
+ end
+ # Count the members in a sorted set with scores within the given values.
+ #
+ # @example Count members with score `>= 5` and `< 100`
+ # redis.zcount("zset", "5", "(100")
+ # # => 2
+ # @example Count members with scores `> 5`
+ # redis.zcount("zset", "(5", "+inf")
+ # # => 2
+ #
+ # @param [String] key
+ # @param [String] min
+ # - inclusive minimum score is specified verbatim
+ # - exclusive minimum score is specified by prefixing `(`
+ # @param [String] max
+ # - inclusive maximum score is specified verbatim
+ # - exclusive maximum score is specified by prefixing `(`
+ # @return [Fixnum] number of members in within the specified range
+ def zcount(key, start, stop)
synchronize do
- @client.call [:zrevrangebyscore, key, max, min, *command.to_a]
+ @client.call [:zcount, key, start, stop]
end
end
# Remove all members in a sorted set within the given scores.
+ #
+ # @example Remove members with score `>= 5` and `< 100`
+ # redis.zremrangebyscore("zset", "5", "(100")
+ # # => 2
+ # @example Remove members with scores `> 5`
+ # redis.zremrangebyscore("zset", "(5", "+inf")
+ # # => 2
+ #
+ # @param [String] key
+ # @param [String] min
+ # - inclusive minimum score is specified verbatim
+ # - exclusive minimum score is specified by prefixing `(`
+ # @param [String] max
+ # - inclusive maximum score is specified verbatim
+ # - exclusive maximum score is specified by prefixing `(`
+ # @return [Fixnum] number of members that were removed
def zremrangebyscore(key, min, max)
synchronize do
@client.call [:zremrangebyscore, key, min, max]
end
end
# Remove all members in a sorted set within the given indexes.
+ #
+ # @example Remove first 5 members
+ # redis.zremrangebyrank("zset", 0, 4)
+ # # => 5
+ # @example Remove last 5 members
+ # redis.zremrangebyrank("zset", -5, -1)
+ # # => 5
+ #
+ # @param [String] key
+ # @param [Fixnum] start start index
+ # @param [Fixnum] stop stop index
+ # @return [Fixnum] number of members that were removed
def zremrangebyrank(key, start, stop)
synchronize do
@client.call [:zremrangebyrank, key, start, stop]
end
end
# Get the score associated with the given member in a sorted set.
+ #
+ # @example Get the score for member "a"
+ # redis.zscore("zset", "a")
+ # # => 32.0
+ #
+ # @param [String] key
+ # @param [String] member
+ # @return [Float] score of the member
def zscore(key, member)
synchronize do
- @client.call [:zscore, key, member]
+ @client.call [:zscore, key, member] do |reply|
+ Float(reply) if reply
+ end
end
end
- # Remove a member from a sorted set.
- def zrem(key, member)
- synchronize do
- _bool @client.call [:zrem, key, member]
- end
- end
-
# Intersect multiple sorted sets and store the resulting sorted set in a new
# key.
+ #
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
+ # # => 4
+ #
+ # @param [String] destination destination key
+ # @param [Array<String>] keys source keys
+ # @param [Hash] options
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
+ # sorted sets
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
+ # @return [Fixnum] number of elements in the resulting sorted set
def zinterstore(destination, keys, options = {})
command = CommandOptions.new(options) do |c|
c.splat :weights
c.value :aggregate
end
@@ -648,10 +1138,22 @@
@client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)]
end
end
# Add multiple sorted sets and store the resulting sorted set in a new key.
+ #
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
+ # # => 8
+ #
+ # @param [String] destination destination key
+ # @param [Array<String>] keys source keys
+ # @param [Hash] options
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
+ # sorted sets
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
+ # @return [Fixnum] number of elements in the resulting sorted set
def zunionstore(destination, keys, options = {})
command = CommandOptions.new(options) do |c|
c.splat :weights
c.value :aggregate
end
@@ -660,83 +1162,134 @@
@client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)]
end
end
# Move a key to another database.
+ #
+ # @example Move a key to another database
+ # redis.set "foo", "bar"
+ # # => "OK"
+ # redis.move "foo", 2
+ # # => true
+ # redis.exists "foo"
+ # # => false
+ # redis.select 2
+ # # => "OK"
+ # redis.exists "foo"
+ # # => true
+ # resis.get "foo"
+ # # => "bar"
+ #
+ # @param [String] key
+ # @param [Fixnum] db
+ # @return [Boolean] whether the key was moved or not
def move(key, db)
synchronize do
- _bool @client.call [:move, key, db]
+ @client.call [:move, key, db], &_boolify
end
end
# Set the value of a key, only if the key does not exist.
+ #
+ # @param [String] key
+ # @param [String] value
+ # @return [Boolean] whether the key was set or not
def setnx(key, value)
synchronize do
- _bool @client.call [:setnx, key, value]
+ @client.call [:setnx, key, value], &_boolify
end
end
- # Delete a key.
+ # Delete one or more keys.
+ #
+ # @param [String, Array<String>] keys
+ # @return [Fixnum] number of keys that were removed
def del(*keys)
synchronize do
@client.call [:del, *keys]
end
end
- # Rename a key.
+ # Rename a key. If the new key already exists it is overwritten.
+ #
+ # @param [String] old_name
+ # @param [String] new_name
+ # @return [String] `OK`
def rename(old_name, new_name)
synchronize do
@client.call [:rename, old_name, new_name]
end
end
# Rename a key, only if the new key does not exist.
+ #
+ # @param [String] old_name
+ # @param [String] new_name
+ # @return [Boolean] whether the key was renamed or not
def renamenx(old_name, new_name)
synchronize do
- _bool @client.call [:renamenx, old_name, new_name]
+ @client.call [:renamenx, old_name, new_name], &_boolify
end
end
# Set a key's time to live in seconds.
+ #
+ # @param [String] key
+ # @param [Fixnum] seconds time to live. After this timeout has expired,
+ # the key will automatically be deleted
+ # @return [Boolean] whether the timeout was set or not
def expire(key, seconds)
synchronize do
- _bool @client.call [:expire, key, seconds]
+ @client.call [:expire, key, seconds], &_boolify
end
end
# Remove the expiration from a key.
+ #
+ # @param [String] key
+ # @return [Boolean] whether the timeout was removed or not
def persist(key)
synchronize do
- _bool @client.call [:persist, key]
+ @client.call [:persist, key], &_boolify
end
end
# Get the time to live for a key.
+ #
+ # @param [String] key
+ # @return [Fixnum] remaining time to live in seconds, or -1 if the
+ # key does not exist or does not have a timeout
def ttl(key)
synchronize do
@client.call [:ttl, key]
end
end
# Set the expiration for a key as a UNIX timestamp.
+ #
+ # @param [String] key
+ # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
+ # (seconds since January 1, 1970). After this timeout has expired,
+ # the key will automatically be deleted
+ # @return [Boolean] whether the timeout was set or not
def expireat(key, unix_time)
synchronize do
- _bool @client.call [:expireat, key, unix_time]
+ @client.call [:expireat, key, unix_time], &_boolify
end
end
# Set the string value of a hash field.
def hset(key, field, value)
synchronize do
- _bool @client.call [:hset, key, field, value]
+ @client.call [:hset, key, field, value], &_boolify
end
end
# Set the value of a hash field, only if the field does not exist.
def hsetnx(key, field, value)
synchronize do
- _bool @client.call [:hsetnx, key, field, value]
+ @client.call [:hsetnx, key, field, value], &_boolify
end
end
# Set multiple hash fields to multiple values.
def hmset(key, *attrs)
@@ -748,23 +1301,27 @@
def mapped_hmset(key, hash)
hmset(key, *hash.to_a.flatten)
end
# Get the values of all the given hash fields.
- def hmget(key, *fields)
+ def hmget(key, *fields, &blk)
synchronize do
- @client.call [:hmget, key, *fields]
+ @client.call [:hmget, key, *fields], &blk
end
end
def mapped_hmget(key, *fields)
- reply = hmget(key, *fields)
-
- if reply.kind_of?(Array)
- Hash[*fields.zip(reply).flatten]
- else
- reply
+ hmget(key, *fields) do |reply|
+ if reply.kind_of?(Array)
+ hash = Hash.new
+ fields.zip(reply).each do |field, value|
+ hash[field] = value
+ end
+ hash
+ else
+ reply
+ end
end
end
# Get the number of fields in a hash.
def hlen(key)
@@ -795,11 +1352,11 @@
end
# Determine if a hash field exists.
def hexists(key, field)
synchronize do
- _bool @client.call [:hexists, key, field]
+ @client.call [:hexists, key, field], &_boolify
end
end
# Listen for all requests received by the server in real time.
def monitor(&block)
@@ -825,25 +1382,19 @@
synchronize do
@client.call [:sync]
end
end
- def [](key)
- get(key)
- end
-
- def []=(key,value)
- set(key, value)
- end
-
# Set the string value of a key.
def set(key, value)
synchronize do
@client.call [:set, key, value]
end
end
+ alias :[]= :set
+
# Sets or clears the bit at offset in the string value stored at key.
def setbit(key, offset, value)
synchronize do
@client.call [:setbit, key, offset, value]
end
@@ -884,16 +1435,20 @@
def mapped_msetnx(hash)
msetnx(*hash.to_a.flatten)
end
def mapped_mget(*keys)
- reply = mget(*keys)
-
- if reply.kind_of?(Array)
- Hash[*keys.zip(reply).flatten]
- else
- reply
+ mget(*keys) do |reply|
+ if reply.kind_of?(Array)
+ hash = Hash.new
+ keys.zip(reply).each do |field, value|
+ hash[field] = value
+ end
+ hash
+ else
+ reply
+ end
end
end
# Sort the elements in a list, set or sorted set.
def sort(key, options = {})
@@ -937,10 +1492,13 @@
@client.call [:decrby, key, decrement]
end
end
# Determine the type stored at key.
+ #
+ # @param [String] key
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
def type(key)
synchronize do
@client.call [:type, key]
end
end
@@ -948,37 +1506,44 @@
# Close the connection.
def quit
synchronize do
begin
@client.call [:quit]
- rescue Errno::ECONNRESET
+ rescue ConnectionError
ensure
@client.disconnect
end
end
end
# Synchronously save the dataset to disk and then shut down the server.
def shutdown
synchronize do
- @client.call_without_reply [:shutdown]
+ @client.without_reconnect do
+ begin
+ @client.call [:shutdown]
+ rescue ConnectionError
+ # This means Redis has probably exited.
+ nil
+ end
+ end
end
end
# Make the server a slave of another instance, or promote it as master.
def slaveof(host, port)
synchronize do
@client.call [:slaveof, host, port]
end
end
- def pipelined(options = {})
+ def pipelined
synchronize do
begin
original, @client = @client, Pipeline.new
yield
- original.call_pipelined(@client.commands, options) unless @client.commands.empty?
+ original.call_pipeline(@client)
ensure
@client = original
end
end
end
@@ -1006,19 +1571,20 @@
# Mark the start of a transaction block.
def multi
synchronize do
if !block_given?
- @client.call :multi
+ @client.call [:multi]
else
- result = pipelined(:raise => false) do
- multi
+ begin
+ pipeline = Pipeline::Multi.new
+ original, @client = @client, pipeline
yield(self)
- exec
+ original.call_pipeline(pipeline)
+ ensure
+ @client = original
end
-
- result.last
end
end
end
# Post a message to a channel.
@@ -1121,12 +1687,14 @@
private
# Commands returning 1 for true and 0 for false may be executed in a pipeline
# where the method call will return nil. Propagate the nil instead of falsely
# returning false.
- def _bool(value)
- value == 1 if value
+ def _boolify
+ lambda { |value|
+ value == 1 if value
+ }
end
def subscription(method, channels, block)
return @client.call [method, *channels] if subscribed?
@@ -1143,6 +1711,5 @@
require "redis/version"
require "redis/connection"
require "redis/client"
require "redis/pipeline"
require "redis/subscribe"
-require "redis/compat"