gorsuch-redis 3.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +10 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +113 -0
- data/LICENSE +20 -0
- data/README.md +214 -0
- data/Rakefile +260 -0
- data/TODO.md +4 -0
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis/client.rb +303 -0
- data/lib/redis/connection/command_helper.rb +44 -0
- data/lib/redis/connection/hiredis.rb +52 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +136 -0
- data/lib/redis/connection/synchrony.rb +131 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/distributed.rb +696 -0
- data/lib/redis/errors.rb +38 -0
- data/lib/redis/hash_ring.rb +131 -0
- data/lib/redis/pipeline.rb +106 -0
- data/lib/redis/subscribe.rb +79 -0
- data/lib/redis/version.rb +3 -0
- data/lib/redis.rb +1724 -0
- data/redis.gemspec +43 -0
- data/test/command_map_test.rb +29 -0
- data/test/commands_on_hashes_test.rb +20 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +76 -0
- data/test/commands_on_sorted_sets_test.rb +108 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +87 -0
- data/test/connection_handling_test.rb +204 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +11 -0
- data/test/distributed_commands_on_lists_test.rb +23 -0
- data/test/distributed_commands_on_sets_test.rb +84 -0
- data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
- data/test/distributed_commands_on_strings_test.rb +49 -0
- data/test/distributed_commands_on_value_types_test.rb +72 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +24 -0
- data/test/distributed_internals_test.rb +27 -0
- data/test/distributed_key_tags_test.rb +52 -0
- data/test/distributed_persistence_control_commands_test.rb +23 -0
- data/test/distributed_publish_subscribe_test.rb +100 -0
- data/test/distributed_remote_server_control_commands_test.rb +42 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +59 -0
- data/test/distributed_transactions_test.rb +33 -0
- data/test/encoding_test.rb +15 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +155 -0
- data/test/helper_test.rb +8 -0
- data/test/internals_test.rb +152 -0
- data/test/lint/hashes.rb +140 -0
- data/test/lint/internals.rb +36 -0
- data/test/lint/lists.rb +107 -0
- data/test/lint/sets.rb +90 -0
- data/test/lint/sorted_sets.rb +196 -0
- data/test/lint/strings.rb +133 -0
- data/test/lint/value_types.rb +81 -0
- data/test/persistence_control_commands_test.rb +21 -0
- data/test/pipelining_commands_test.rb +186 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +89 -0
- data/test/remote_server_control_commands_test.rb +88 -0
- data/test/sorting_test.rb +43 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +9 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +173 -0
- data/test/unknown_commands_test.rb +13 -0
- data/test/url_param_test.rb +59 -0
- metadata +236 -0
data/lib/redis/errors.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class Redis
|
2
|
+
# Base error for all redis-rb errors.
|
3
|
+
class BaseError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Raised by the connection when a protocol error occurs.
|
7
|
+
class ProtocolError < BaseError
|
8
|
+
def initialize(reply_type)
|
9
|
+
super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
|
10
|
+
Got '#{reply_type}' as initial reply byte.
|
11
|
+
If you're running in a multi-threaded environment, make sure you
|
12
|
+
pass the :thread_safe option when initializing the connection.
|
13
|
+
If you're in a forking environment, such as Unicorn, you need to
|
14
|
+
connect to Redis after forking.
|
15
|
+
EOS
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised by the client when command execution returns an error reply.
|
20
|
+
class CommandError < BaseError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Base error for connection related errors.
|
24
|
+
class BaseConnectionError < BaseError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when connection to a Redis server cannot be made.
|
28
|
+
class CannotConnectError < BaseConnectionError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when connection to a Redis server is lost.
|
32
|
+
class ConnectionError < BaseConnectionError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Raised when performing I/O times out.
|
36
|
+
class TimeoutError < BaseConnectionError
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class HashRing
|
5
|
+
|
6
|
+
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
|
+
|
8
|
+
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
9
|
+
|
10
|
+
# nodes is a list of objects that have a proper to_s representation.
|
11
|
+
# replicas indicates how many virtual points should be used pr. node,
|
12
|
+
# replicas are required to improve the distribution.
|
13
|
+
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
@replicas = replicas
|
15
|
+
@ring = {}
|
16
|
+
@nodes = []
|
17
|
+
@sorted_keys = []
|
18
|
+
nodes.each do |node|
|
19
|
+
add_node(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds a `node` to the hash ring (including a number of replicas).
|
24
|
+
def add_node(node)
|
25
|
+
@nodes << node
|
26
|
+
@replicas.times do |i|
|
27
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
28
|
+
@ring[key] = node
|
29
|
+
@sorted_keys << key
|
30
|
+
end
|
31
|
+
@sorted_keys.sort!
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_node(node)
|
35
|
+
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@replicas.times do |i|
|
37
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
38
|
+
@ring.delete(key)
|
39
|
+
@sorted_keys.reject! {|k| k == key}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# get the node in the hash ring for this key
|
44
|
+
def get_node(key)
|
45
|
+
get_node_pos(key)[0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_node_pos(key)
|
49
|
+
return [nil,nil] if @ring.size == 0
|
50
|
+
crc = Zlib.crc32(key)
|
51
|
+
idx = HashRing.binary_search(@sorted_keys, crc)
|
52
|
+
return [@ring[@sorted_keys[idx]], idx]
|
53
|
+
end
|
54
|
+
|
55
|
+
def iter_nodes(key)
|
56
|
+
return [nil,nil] if @ring.size == 0
|
57
|
+
_, pos = get_node_pos(key)
|
58
|
+
@sorted_keys[pos..-1].each do |k|
|
59
|
+
yield @ring[k]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
|
65
|
+
# gem install RubyInline to use this code
|
66
|
+
# Native extension to perform the binary search within the hashring.
|
67
|
+
# There's a pure ruby version below so this is purely optional
|
68
|
+
# for performance. In testing 20k gets and sets, the native
|
69
|
+
# binary search shaved about 12% off the runtime (9sec -> 8sec).
|
70
|
+
begin
|
71
|
+
require 'inline'
|
72
|
+
inline do |builder|
|
73
|
+
builder.c <<-EOM
|
74
|
+
int binary_search(VALUE ary, unsigned int r) {
|
75
|
+
int upper = RARRAY_LEN(ary) - 1;
|
76
|
+
int lower = 0;
|
77
|
+
int idx = 0;
|
78
|
+
|
79
|
+
while (lower <= upper) {
|
80
|
+
idx = (lower + upper) / 2;
|
81
|
+
|
82
|
+
VALUE continuumValue = RARRAY_PTR(ary)[idx];
|
83
|
+
unsigned int l = NUM2UINT(continuumValue);
|
84
|
+
if (l == r) {
|
85
|
+
return idx;
|
86
|
+
}
|
87
|
+
else if (l > r) {
|
88
|
+
upper = idx - 1;
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
lower = idx + 1;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
if (upper < 0) {
|
95
|
+
upper = RARRAY_LEN(ary) - 1;
|
96
|
+
}
|
97
|
+
return upper;
|
98
|
+
}
|
99
|
+
EOM
|
100
|
+
end
|
101
|
+
rescue Exception
|
102
|
+
# Find the closest index in HashRing with value <= the given value
|
103
|
+
def binary_search(ary, value, &block)
|
104
|
+
upper = ary.size - 1
|
105
|
+
lower = 0
|
106
|
+
idx = 0
|
107
|
+
|
108
|
+
while(lower <= upper) do
|
109
|
+
idx = (lower + upper) / 2
|
110
|
+
comp = ary[idx] <=> value
|
111
|
+
|
112
|
+
if comp == 0
|
113
|
+
return idx
|
114
|
+
elsif comp > 0
|
115
|
+
upper = idx - 1
|
116
|
+
else
|
117
|
+
lower = idx + 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if upper < 0
|
122
|
+
upper = ary.size - 1
|
123
|
+
end
|
124
|
+
return upper
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Redis
|
2
|
+
unless defined?(::BasicObject)
|
3
|
+
class BasicObject
|
4
|
+
instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Pipeline
|
9
|
+
attr :futures
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@without_reconnect = false
|
13
|
+
@shutdown = false
|
14
|
+
@futures = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def without_reconnect?
|
18
|
+
@without_reconnect
|
19
|
+
end
|
20
|
+
|
21
|
+
def shutdown?
|
22
|
+
@shutdown
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(command, &block)
|
26
|
+
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
27
|
+
# the connection is gone.
|
28
|
+
@shutdown = true if command.first == :shutdown
|
29
|
+
future = Future.new(command, block)
|
30
|
+
@futures << future
|
31
|
+
future
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_pipeline(pipeline)
|
35
|
+
@shutdown = true if pipeline.shutdown?
|
36
|
+
@futures.concat(pipeline.futures)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def commands
|
41
|
+
@futures.map { |f| f._command }
|
42
|
+
end
|
43
|
+
|
44
|
+
def without_reconnect(&block)
|
45
|
+
@without_reconnect = true
|
46
|
+
yield
|
47
|
+
end
|
48
|
+
|
49
|
+
def finish(replies)
|
50
|
+
futures.each_with_index.map do |future, i|
|
51
|
+
future._set(replies[i])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Multi < self
|
56
|
+
def finish(replies)
|
57
|
+
return if replies.last.nil? # The transaction failed because of WATCH.
|
58
|
+
|
59
|
+
if replies.last.size < futures.size - 2
|
60
|
+
# Some command wasn't recognized by Redis.
|
61
|
+
raise replies.detect { |r| r.kind_of?(::Exception) }
|
62
|
+
end
|
63
|
+
|
64
|
+
super(replies.last)
|
65
|
+
end
|
66
|
+
|
67
|
+
def commands
|
68
|
+
[[:multi]] + super + [[:exec]]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class FutureNotReady < RuntimeError
|
74
|
+
def initialize
|
75
|
+
super("Value will be available once the pipeline executes.")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Future < BasicObject
|
80
|
+
FutureNotReady = ::Redis::FutureNotReady.new
|
81
|
+
|
82
|
+
def initialize(command, transformation)
|
83
|
+
@command = command
|
84
|
+
@transformation = transformation
|
85
|
+
@object = FutureNotReady
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
"<Redis::Future #{@command.inspect}>"
|
90
|
+
end
|
91
|
+
|
92
|
+
def _set(object)
|
93
|
+
@object = @transformation ? @transformation.call(object) : object
|
94
|
+
value
|
95
|
+
end
|
96
|
+
|
97
|
+
def _command
|
98
|
+
@command
|
99
|
+
end
|
100
|
+
|
101
|
+
def value
|
102
|
+
::Kernel.raise(@object) if @object.kind_of?(::Exception)
|
103
|
+
@object
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Redis
|
2
|
+
class SubscribedClient
|
3
|
+
def initialize(client)
|
4
|
+
@client = client
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(command)
|
8
|
+
@client.process([command])
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(*channels, &block)
|
12
|
+
subscription("subscribe", "unsubscribe", channels, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def psubscribe(*channels, &block)
|
16
|
+
subscription("psubscribe", "punsubscribe", channels, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def unsubscribe(*channels)
|
20
|
+
call [:unsubscribe, *channels]
|
21
|
+
end
|
22
|
+
|
23
|
+
def punsubscribe(*channels)
|
24
|
+
call [:punsubscribe, *channels]
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def subscription(start, stop, channels, block)
|
30
|
+
sub = Subscription.new(&block)
|
31
|
+
|
32
|
+
begin
|
33
|
+
@client.call_loop([start, *channels]) do |line|
|
34
|
+
type, *rest = line
|
35
|
+
sub.callbacks[type].call(*rest)
|
36
|
+
break if type == stop && rest.last == 0
|
37
|
+
end
|
38
|
+
ensure
|
39
|
+
send(stop)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Subscription
|
45
|
+
attr :callbacks
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@callbacks = Hash.new do |hash, key|
|
49
|
+
hash[key] = lambda { |*_| }
|
50
|
+
end
|
51
|
+
|
52
|
+
yield(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe(&block)
|
56
|
+
@callbacks["subscribe"] = block
|
57
|
+
end
|
58
|
+
|
59
|
+
def unsubscribe(&block)
|
60
|
+
@callbacks["unsubscribe"] = block
|
61
|
+
end
|
62
|
+
|
63
|
+
def message(&block)
|
64
|
+
@callbacks["message"] = block
|
65
|
+
end
|
66
|
+
|
67
|
+
def psubscribe(&block)
|
68
|
+
@callbacks["psubscribe"] = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def punsubscribe(&block)
|
72
|
+
@callbacks["punsubscribe"] = block
|
73
|
+
end
|
74
|
+
|
75
|
+
def pmessage(&block)
|
76
|
+
@callbacks["pmessage"] = block
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/redis.rb
ADDED
@@ -0,0 +1,1724 @@
|
|
1
|
+
require "monitor"
|
2
|
+
require "redis/errors"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
|
7
|
+
def self.deprecate(message, trace = caller[0])
|
8
|
+
$stderr.puts "\n#{message} (in #{trace})"
|
9
|
+
end
|
10
|
+
|
11
|
+
attr :client
|
12
|
+
|
13
|
+
def self.connect(options = {})
|
14
|
+
new(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.current
|
18
|
+
Thread.current[:redis] ||= Redis.connect
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.current=(redis)
|
22
|
+
Thread.current[:redis] = redis
|
23
|
+
end
|
24
|
+
|
25
|
+
include MonitorMixin
|
26
|
+
|
27
|
+
def parse_options(options)
|
28
|
+
options = options.dup
|
29
|
+
|
30
|
+
if options[:path]
|
31
|
+
uri = URI::Generic.new('unix', nil, nil, nil, nil, options[:path], nil, nil, nil)
|
32
|
+
else
|
33
|
+
url = options.delete(:url) || ENV["REDIS_URL"]
|
34
|
+
if url
|
35
|
+
uri = URI.parse(url)
|
36
|
+
else
|
37
|
+
uri = URI::Generic.new('redis', nil, '127.0.0.1', 6379, nil, '/0', nil, nil, nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
uri.host = options.delete(:host) if options[:host]
|
41
|
+
uri.port = options.delete(:port) if options[:port]
|
42
|
+
uri.user = 'redis'
|
43
|
+
uri.password = options.delete(:password) if options[:password]
|
44
|
+
uri.path = "/#{options.delete(:db)}" if options[:db]
|
45
|
+
end
|
46
|
+
|
47
|
+
options[:uri] = uri
|
48
|
+
options
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(options = {})
|
52
|
+
options = parse_options(options)
|
53
|
+
@client = Client.new(options)
|
54
|
+
|
55
|
+
if options[:thread_safe] == false
|
56
|
+
@synchronizer = lambda { |&block| block.call }
|
57
|
+
else
|
58
|
+
@synchronizer = lambda { |&block| mon_synchronize { block.call } }
|
59
|
+
super() # Monitor#initialize
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def synchronize
|
64
|
+
@synchronizer.call { yield }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Run code without the client reconnecting
|
68
|
+
def without_reconnect(&block)
|
69
|
+
synchronize do
|
70
|
+
@client.without_reconnect(&block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Authenticate to the server.
|
75
|
+
#
|
76
|
+
# @param [String] password must match the password specified in the
|
77
|
+
# `requirepass` directive in the configuration file
|
78
|
+
# @return [String] `OK`
|
79
|
+
def auth(password)
|
80
|
+
synchronize do
|
81
|
+
@client.call [:auth, password]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Change the selected database for the current connection.
|
86
|
+
#
|
87
|
+
# @param [Fixnum] db zero-based index of the DB to use (0 to 15)
|
88
|
+
# @return [String] `OK`
|
89
|
+
def select(db)
|
90
|
+
synchronize do
|
91
|
+
@client.db = db
|
92
|
+
@client.call [:select, db]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get information and statistics about the server.
|
97
|
+
#
|
98
|
+
# @param [String, Symbol] cmd e.g. "commandstats"
|
99
|
+
# @return [Hash<String, String>]
|
100
|
+
def info(cmd = nil)
|
101
|
+
synchronize do
|
102
|
+
@client.call [:info, cmd].compact do |reply|
|
103
|
+
if reply.kind_of?(String)
|
104
|
+
reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
|
105
|
+
|
106
|
+
if cmd && cmd.to_s == "commandstats"
|
107
|
+
# Extract nested hashes for INFO COMMANDSTATS
|
108
|
+
reply = Hash[reply.map do |k, v|
|
109
|
+
[k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
|
110
|
+
end]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
reply
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Get or set server configuration parameters.
|
120
|
+
#
|
121
|
+
# @param [String] action e.g. `get`, `set`, `resetstat`
|
122
|
+
# @return [String, Hash] string reply, or hash when retrieving more than one
|
123
|
+
# property with `CONFIG GET`
|
124
|
+
def config(action, *args)
|
125
|
+
synchronize do
|
126
|
+
@client.call [:config, action, *args] do |reply|
|
127
|
+
if reply.kind_of?(Array) && action == :get
|
128
|
+
Hash[*reply]
|
129
|
+
else
|
130
|
+
reply
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Remove all keys from the current database.
|
137
|
+
#
|
138
|
+
# @return [String] `OK`
|
139
|
+
def flushdb
|
140
|
+
synchronize do
|
141
|
+
@client.call [:flushdb]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Remove all keys from all databases.
|
146
|
+
#
|
147
|
+
# @return [String] `OK`
|
148
|
+
def flushall
|
149
|
+
synchronize do
|
150
|
+
@client.call [:flushall]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Synchronously save the dataset to disk.
|
155
|
+
#
|
156
|
+
# @return [String]
|
157
|
+
def save
|
158
|
+
synchronize do
|
159
|
+
@client.call [:save]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Asynchronously save the dataset to disk.
|
164
|
+
#
|
165
|
+
# @return [String] `OK`
|
166
|
+
def bgsave
|
167
|
+
synchronize do
|
168
|
+
@client.call [:bgsave]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Asynchronously rewrite the append-only file.
|
173
|
+
#
|
174
|
+
# @return [String] `OK`
|
175
|
+
def bgrewriteaof
|
176
|
+
synchronize do
|
177
|
+
@client.call [:bgrewriteaof]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Get the value of a key.
|
182
|
+
#
|
183
|
+
# @param [String] key
|
184
|
+
# @return [String]
|
185
|
+
def get(key)
|
186
|
+
synchronize do
|
187
|
+
@client.call [:get, key]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
alias :[] :get
|
192
|
+
|
193
|
+
# Returns the bit value at offset in the string value stored at key.
|
194
|
+
#
|
195
|
+
# @param [String] key
|
196
|
+
# @param [Fixnum] offset bit offset
|
197
|
+
# @return [Fixnum] `0` or `1`
|
198
|
+
def getbit(key, offset)
|
199
|
+
synchronize do
|
200
|
+
@client.call [:getbit, key, offset]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Get a substring of the string stored at a key.
|
205
|
+
#
|
206
|
+
# @param [String] key
|
207
|
+
# @param [Fixnum] start zero-based start offset
|
208
|
+
# @param [Fixnum] stop zero-based end offset. Use -1 for representing
|
209
|
+
# the end of the string
|
210
|
+
# @return [Fixnum] `0` or `1`
|
211
|
+
def getrange(key, start, stop)
|
212
|
+
synchronize do
|
213
|
+
@client.call [:getrange, key, start, stop]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Set the string value of a key and return its old value.
|
218
|
+
#
|
219
|
+
# @param [String] key
|
220
|
+
# @param [String] value value to replace the current value with
|
221
|
+
# @return [String] the old value stored in the key, or `nil` if the key
|
222
|
+
# did not exist
|
223
|
+
def getset(key, value)
|
224
|
+
synchronize do
|
225
|
+
@client.call [:getset, key, value]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Get the values of all the given keys.
|
230
|
+
#
|
231
|
+
# @param [Array<String>] keys
|
232
|
+
# @return [Array<String>]
|
233
|
+
def mget(*keys, &blk)
|
234
|
+
synchronize do
|
235
|
+
@client.call [:mget, *keys], &blk
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Append a value to a key.
|
240
|
+
#
|
241
|
+
# @param [String] key
|
242
|
+
# @param [String] value value to append
|
243
|
+
# @return [Fixnum] length of the string after appending
|
244
|
+
def append(key, value)
|
245
|
+
synchronize do
|
246
|
+
@client.call [:append, key, value]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Get the length of the value stored in a key.
|
251
|
+
#
|
252
|
+
# @param [String] key
|
253
|
+
# @return [Fixnum] the length of the value stored in the key, or 0
|
254
|
+
# if the key does not exist
|
255
|
+
def strlen(key)
|
256
|
+
synchronize do
|
257
|
+
@client.call [:strlen, key]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Get all the fields and values in a hash.
|
262
|
+
#
|
263
|
+
# @param [String] key
|
264
|
+
# @return [Hash<String, String>]
|
265
|
+
def hgetall(key)
|
266
|
+
synchronize do
|
267
|
+
@client.call [:hgetall, key] do |reply|
|
268
|
+
if reply.kind_of?(Array)
|
269
|
+
hash = Hash.new
|
270
|
+
reply.each_slice(2) do |field, value|
|
271
|
+
hash[field] = value
|
272
|
+
end
|
273
|
+
hash
|
274
|
+
else
|
275
|
+
reply
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Get the value of a hash field.
|
282
|
+
#
|
283
|
+
# @param [String] key
|
284
|
+
# @param [String] field
|
285
|
+
# @return [String]
|
286
|
+
def hget(key, field)
|
287
|
+
synchronize do
|
288
|
+
@client.call [:hget, key, field]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Delete one or more hash fields.
|
293
|
+
#
|
294
|
+
# @param [String] key
|
295
|
+
# @param [String, Array<String>] field
|
296
|
+
# @return [Fixnum] the number of fields that were removed from the hash
|
297
|
+
def hdel(key, field)
|
298
|
+
synchronize do
|
299
|
+
@client.call [:hdel, key, field]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Get all the fields in a hash.
|
304
|
+
#
|
305
|
+
# @param [String] key
|
306
|
+
# @return [Array<String>]
|
307
|
+
def hkeys(key)
|
308
|
+
synchronize do
|
309
|
+
@client.call [:hkeys, key]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Find all keys matching the given pattern.
|
314
|
+
#
|
315
|
+
# @param [String] pattern
|
316
|
+
# @return [Array<String>]
|
317
|
+
def keys(pattern = "*")
|
318
|
+
synchronize do
|
319
|
+
@client.call [:keys, pattern] do |reply|
|
320
|
+
if reply.kind_of?(String)
|
321
|
+
reply.split(" ")
|
322
|
+
else
|
323
|
+
reply
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# Return a random key from the keyspace.
|
330
|
+
#
|
331
|
+
# @return [String]
|
332
|
+
def randomkey
|
333
|
+
synchronize do
|
334
|
+
@client.call [:randomkey]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Echo the given string.
|
339
|
+
#
|
340
|
+
# @param [String] value
|
341
|
+
# @return [String]
|
342
|
+
def echo(value)
|
343
|
+
synchronize do
|
344
|
+
@client.call [:echo, value]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Ping the server.
|
349
|
+
#
|
350
|
+
# @return [String] `PONG`
|
351
|
+
def ping
|
352
|
+
synchronize do
|
353
|
+
@client.call [:ping]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Get the UNIX time stamp of the last successful save to disk.
|
358
|
+
#
|
359
|
+
# @return [Fixnum]
|
360
|
+
def lastsave
|
361
|
+
synchronize do
|
362
|
+
@client.call [:lastsave]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Return the number of keys in the selected database.
|
367
|
+
#
|
368
|
+
# @return [Fixnum]
|
369
|
+
def dbsize
|
370
|
+
synchronize do
|
371
|
+
@client.call [:dbsize]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Determine if a key exists.
|
376
|
+
#
|
377
|
+
# @param [String] key
|
378
|
+
# @return [Boolean]
|
379
|
+
def exists(key)
|
380
|
+
synchronize do
|
381
|
+
@client.call [:exists, key], &_boolify
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Get the length of a list.
|
386
|
+
#
|
387
|
+
# @param [String] key
|
388
|
+
# @return [Fixnum]
|
389
|
+
def llen(key)
|
390
|
+
synchronize do
|
391
|
+
@client.call [:llen, key]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Get a range of elements from a list.
|
396
|
+
#
|
397
|
+
# @param [String] key
|
398
|
+
# @param [Fixnum] start start index
|
399
|
+
# @param [Fixnum] stop stop index
|
400
|
+
# @return [Array<String>]
|
401
|
+
def lrange(key, start, stop)
|
402
|
+
synchronize do
|
403
|
+
@client.call [:lrange, key, start, stop]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Trim a list to the specified range.
|
408
|
+
#
|
409
|
+
# @param [String] key
|
410
|
+
# @param [Fixnum] start start index
|
411
|
+
# @param [Fixnum] stop stop index
|
412
|
+
# @return [String] `OK`
|
413
|
+
def ltrim(key, start, stop)
|
414
|
+
synchronize do
|
415
|
+
@client.call [:ltrim, key, start, stop]
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# Get an element from a list by its index.
|
420
|
+
#
|
421
|
+
# @param [String] key
|
422
|
+
# @param [Fixnum] index
|
423
|
+
# @return [String]
|
424
|
+
def lindex(key, index)
|
425
|
+
synchronize do
|
426
|
+
@client.call [:lindex, key, index]
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# Insert an element before or after another element in a list.
|
431
|
+
#
|
432
|
+
# @param [String] key
|
433
|
+
# @param [String, Symbol] where `BEFORE` or `AFTER`
|
434
|
+
# @param [String] pivot reference element
|
435
|
+
# @param [String] value
|
436
|
+
# @return [Fixnum] length of the list after the insert operation, or `-1`
|
437
|
+
# when the element `pivot` was not found
|
438
|
+
def linsert(key, where, pivot, value)
|
439
|
+
synchronize do
|
440
|
+
@client.call [:linsert, key, where, pivot, value]
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Set the value of an element in a list by its index.
|
445
|
+
#
|
446
|
+
# @param [String] key
|
447
|
+
# @param [Fixnum] index
|
448
|
+
# @param [String] value
|
449
|
+
# @return [String] `OK`
|
450
|
+
def lset(key, index, value)
|
451
|
+
synchronize do
|
452
|
+
@client.call [:lset, key, index, value]
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# Remove elements from a list.
|
457
|
+
#
|
458
|
+
# @param [String] key
|
459
|
+
# @param [Fixnum] count number of elements to remove. Use a positive
|
460
|
+
# value to remove the first `count` occurrences of `value`. A negative
|
461
|
+
# value to remove the last `count` occurrences of `value`. Or zero, to
|
462
|
+
# remove all occurrences of `value` from the list.
|
463
|
+
# @param [String] value
|
464
|
+
# @return [Fixnum] the number of removed elements
|
465
|
+
def lrem(key, count, value)
|
466
|
+
synchronize do
|
467
|
+
@client.call [:lrem, key, count, value]
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Append one or more values to a list, creating the list if it doesn't exist
|
472
|
+
#
|
473
|
+
# @param [String] key
|
474
|
+
# @param [String] value
|
475
|
+
# @return [Fixnum] the length of the list after the push operation
|
476
|
+
def rpush(key, value)
|
477
|
+
synchronize do
|
478
|
+
@client.call [:rpush, key, value]
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# Append a value to a list, only if the list exists.
|
483
|
+
#
|
484
|
+
# @param [String] key
|
485
|
+
# @param [String] value
|
486
|
+
# @return [Fixnum] the length of the list after the push operation
|
487
|
+
def rpushx(key, value)
|
488
|
+
synchronize do
|
489
|
+
@client.call [:rpushx, key, value]
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Prepend one or more values to a list, creating the list if it doesn't exist
|
494
|
+
#
|
495
|
+
# @param [String] key
|
496
|
+
# @param [String] value
|
497
|
+
# @return [Fixnum] the length of the list after the push operation
|
498
|
+
def lpush(key, value)
|
499
|
+
synchronize do
|
500
|
+
@client.call [:lpush, key, value]
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Prepend a value to a list, only if the list exists.
|
505
|
+
#
|
506
|
+
# @param [String] key
|
507
|
+
# @param [String] value
|
508
|
+
# @return [Fixnum] the length of the list after the push operation
|
509
|
+
def lpushx(key, value)
|
510
|
+
synchronize do
|
511
|
+
@client.call [:lpushx, key, value]
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
# Remove and get the last element in a list.
|
516
|
+
#
|
517
|
+
# @param [String] key
|
518
|
+
# @return [String]
|
519
|
+
def rpop(key)
|
520
|
+
synchronize do
|
521
|
+
@client.call [:rpop, key]
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# Remove and get the first element in a list, or block until one is available.
|
526
|
+
#
|
527
|
+
# @param [Array<String>] args one or more keys to perform a blocking pop on,
|
528
|
+
# followed by a `Fixnum` timeout value
|
529
|
+
# @return [nil, Array<String>] tuple of list that was popped from and element
|
530
|
+
# that was popped, or nil when the blocking operation timed out
|
531
|
+
def blpop(*args)
|
532
|
+
synchronize do
|
533
|
+
@client.call_without_timeout [:blpop, *args]
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
# Remove and get the last element in a list, or block until one is available.
|
538
|
+
#
|
539
|
+
# @param [Array<String>] args one or more keys to perform a blocking pop on,
|
540
|
+
# followed by a `Fixnum` timeout value
|
541
|
+
# @return [nil, Array<String>] tuple of list that was popped from and element
|
542
|
+
# that was popped, or nil when the blocking operation timed out
|
543
|
+
def brpop(*args)
|
544
|
+
synchronize do
|
545
|
+
@client.call_without_timeout [:brpop, *args]
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
# Pop a value from a list, push it to another list and return it; or block
|
550
|
+
# until one is available.
|
551
|
+
#
|
552
|
+
# @param [String] source source key
|
553
|
+
# @param [String] destination destination key
|
554
|
+
# @param [Fixnum] timeout
|
555
|
+
# @return [nil, String] the element, or nil when the blocking operation timed out
|
556
|
+
def brpoplpush(source, destination, timeout)
|
557
|
+
synchronize do
|
558
|
+
@client.call_without_timeout [:brpoplpush, source, destination, timeout]
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
# Remove the last element in a list, append it to another list and return it.
|
563
|
+
#
|
564
|
+
# @param [String] source source key
|
565
|
+
# @param [String] destination destination key
|
566
|
+
# @return [nil, String] the element, or nil when the source key does not exist
|
567
|
+
def rpoplpush(source, destination)
|
568
|
+
synchronize do
|
569
|
+
@client.call [:rpoplpush, source, destination]
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# Remove and get the first element in a list.
|
574
|
+
#
|
575
|
+
# @param [String] key
|
576
|
+
# @return [String]
|
577
|
+
def lpop(key)
|
578
|
+
synchronize do
|
579
|
+
@client.call [:lpop, key]
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
# Interact with the slowlog (get, len, reset)
|
584
|
+
#
|
585
|
+
# @param [String] subcommand e.g. `get`, `len`, `reset`
|
586
|
+
# @param [Fixnum] length maximum number of entries to return
|
587
|
+
# @return [Array<String>, Fixnum, String] depends on subcommand
|
588
|
+
def slowlog(subcommand, length=nil)
|
589
|
+
synchronize do
|
590
|
+
args = [:slowlog, subcommand]
|
591
|
+
args << length if length
|
592
|
+
@client.call args
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# Get all the members in a set.
|
597
|
+
#
|
598
|
+
# @param [String] key
|
599
|
+
# @return [Array<String>]
|
600
|
+
def smembers(key)
|
601
|
+
synchronize do
|
602
|
+
@client.call [:smembers, key]
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
# Determine if a given value is a member of a set.
|
607
|
+
#
|
608
|
+
# @param [String] key
|
609
|
+
# @param [String] member
|
610
|
+
# @return [Boolean]
|
611
|
+
def sismember(key, member)
|
612
|
+
synchronize do
|
613
|
+
@client.call [:sismember, key, member], &_boolify
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
# Add one or more members to a set.
|
618
|
+
#
|
619
|
+
# @param [String] key
|
620
|
+
# @param [String, Array<String>] member one member, or array of members
|
621
|
+
# @return [Boolean, Fixnum] `Boolean` when a single member is specified,
|
622
|
+
# holding whether or not adding the member succeeded, or `Fixnum` when an
|
623
|
+
# array of members is specified, holding the number of members that were
|
624
|
+
# successfully added
|
625
|
+
def sadd(key, member)
|
626
|
+
synchronize do
|
627
|
+
@client.call [:sadd, key, member] do |reply|
|
628
|
+
if member.is_a? Array
|
629
|
+
# Variadic: return integer
|
630
|
+
reply
|
631
|
+
else
|
632
|
+
# Single argument: return boolean
|
633
|
+
_boolify.call(reply)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# Remove one or more members from a set.
|
640
|
+
#
|
641
|
+
# @param [String] key
|
642
|
+
# @param [String, Array<String>] member one member, or array of members
|
643
|
+
# @return [Boolean, Fixnum] `Boolean` when a single member is specified,
|
644
|
+
# holding whether or not removing the member succeeded, or `Fixnum` when an
|
645
|
+
# array of members is specified, holding the number of members that were
|
646
|
+
# successfully removed
|
647
|
+
def srem(key, member)
|
648
|
+
synchronize do
|
649
|
+
@client.call [:srem, key, member] do |reply|
|
650
|
+
if member.is_a? Array
|
651
|
+
# Variadic: return integer
|
652
|
+
reply
|
653
|
+
else
|
654
|
+
# Single argument: return boolean
|
655
|
+
_boolify.call(reply)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
# Move a member from one set to another.
|
662
|
+
#
|
663
|
+
# @param [String] source source key
|
664
|
+
# @param [String] destination destination key
|
665
|
+
# @param [String] member member to move from `source` to `destination`
|
666
|
+
# @return [Boolean]
|
667
|
+
def smove(source, destination, member)
|
668
|
+
synchronize do
|
669
|
+
@client.call [:smove, source, destination, member], &_boolify
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
# Remove and return a random member from a set.
|
674
|
+
#
|
675
|
+
# @param [String] key
|
676
|
+
# @return [String]
|
677
|
+
def spop(key)
|
678
|
+
synchronize do
|
679
|
+
@client.call [:spop, key]
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
# Get the number of members in a set.
|
684
|
+
#
|
685
|
+
# @param [String] key
|
686
|
+
# @return [Fixnum]
|
687
|
+
def scard(key)
|
688
|
+
synchronize do
|
689
|
+
@client.call [:scard, key]
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
# Intersect multiple sets.
|
694
|
+
#
|
695
|
+
# @param [String, Array<String>] keys keys pointing to sets to intersect
|
696
|
+
# @return [Array<String>] members in the intersection
|
697
|
+
def sinter(*keys)
|
698
|
+
synchronize do
|
699
|
+
@client.call [:sinter, *keys]
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
# Intersect multiple sets and store the resulting set in a key.
|
704
|
+
#
|
705
|
+
# @param [String] destination destination key
|
706
|
+
# @param [String, Array<String>] keys keys pointing to sets to intersect
|
707
|
+
# @return [Fixnum] number of elements in the resulting set
|
708
|
+
def sinterstore(destination, *keys)
|
709
|
+
synchronize do
|
710
|
+
@client.call [:sinterstore, destination, *keys]
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
# Add multiple sets.
|
715
|
+
#
|
716
|
+
# @param [String, Array<String>] keys keys pointing to sets to unify
|
717
|
+
# @return [Array<String>] members in the union
|
718
|
+
def sunion(*keys)
|
719
|
+
synchronize do
|
720
|
+
@client.call [:sunion, *keys]
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
# Add multiple sets and store the resulting set in a key.
|
725
|
+
#
|
726
|
+
# @param [String] destination destination key
|
727
|
+
# @param [String, Array<String>] keys keys pointing to sets to unify
|
728
|
+
# @return [Fixnum] number of elements in the resulting set
|
729
|
+
def sunionstore(destination, *keys)
|
730
|
+
synchronize do
|
731
|
+
@client.call [:sunionstore, destination, *keys]
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
# Subtract multiple sets.
|
736
|
+
#
|
737
|
+
# @param [String, Array<String>] keys keys pointing to sets to subtract
|
738
|
+
# @return [Array<String>] members in the difference
|
739
|
+
def sdiff(*keys)
|
740
|
+
synchronize do
|
741
|
+
@client.call [:sdiff, *keys]
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
# Subtract multiple sets and store the resulting set in a key.
|
746
|
+
#
|
747
|
+
# @param [String] destination destination key
|
748
|
+
# @param [String, Array<String>] keys keys pointing to sets to subtract
|
749
|
+
# @return [Fixnum] number of elements in the resulting set
|
750
|
+
def sdiffstore(destination, *keys)
|
751
|
+
synchronize do
|
752
|
+
@client.call [:sdiffstore, destination, *keys]
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
# Get a random member from a set.
|
757
|
+
#
|
758
|
+
# @param [String] key
|
759
|
+
# @return [String]
|
760
|
+
def srandmember(key)
|
761
|
+
synchronize do
|
762
|
+
@client.call [:srandmember, key]
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
# Add one or more members to a sorted set, or update the score for members
|
767
|
+
# that already exist.
|
768
|
+
#
|
769
|
+
# @example Add a single `(score, member)` pair to a sorted set
|
770
|
+
# redis.zadd("zset", 32.0, "member")
|
771
|
+
# @example Add an array of `(score, member)` pairs to a sorted set
|
772
|
+
# redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
|
773
|
+
#
|
774
|
+
# @param [String] key
|
775
|
+
# @param [(Float, String), Array<(Float,String)>] args
|
776
|
+
# - a single `(score, member)` pair
|
777
|
+
# - an array of `(score, member)` pairs
|
778
|
+
#
|
779
|
+
# @return [Boolean, Fixnum]
|
780
|
+
# - `Boolean` when a single pair is specified, holding whether or not it was
|
781
|
+
# **added** to the sorted set
|
782
|
+
# - `Fixnum` when an array of pairs is specified, holding the number of
|
783
|
+
# pairs that were **added** to the sorted set
|
784
|
+
def zadd(key, *args)
|
785
|
+
synchronize do
|
786
|
+
if args.size == 1 && args[0].is_a?(Array)
|
787
|
+
# Variadic: return integer
|
788
|
+
@client.call [:zadd, key] + args[0]
|
789
|
+
elsif args.size == 2
|
790
|
+
# Single pair: return boolean
|
791
|
+
@client.call [:zadd, key, args[0], args[1]], &_boolify
|
792
|
+
else
|
793
|
+
raise ArgumentError, "wrong number of arguments"
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
# Remove one or more members from a sorted set.
|
799
|
+
#
|
800
|
+
# @example Remove a single member from a sorted set
|
801
|
+
# redis.zrem("zset", "a")
|
802
|
+
# @example Remove an array of members from a sorted set
|
803
|
+
# redis.zrem("zset", ["a", "b"])
|
804
|
+
#
|
805
|
+
# @param [String] key
|
806
|
+
# @param [String, Array<String>] member
|
807
|
+
# - a single member
|
808
|
+
# - an array of members
|
809
|
+
#
|
810
|
+
# @return [Boolean, Fixnum]
|
811
|
+
# - `Boolean` when a single member is specified, holding whether or not it
|
812
|
+
# was removed from the sorted set
|
813
|
+
# - `Fixnum` when an array of pairs is specified, holding the number of
|
814
|
+
# members that were removed to the sorted set
|
815
|
+
def zrem(key, member)
|
816
|
+
synchronize do
|
817
|
+
@client.call [:zrem, key, member] do |reply|
|
818
|
+
if member.is_a? Array
|
819
|
+
# Variadic: return integer
|
820
|
+
reply
|
821
|
+
else
|
822
|
+
# Single argument: return boolean
|
823
|
+
_boolify.call(reply)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
# Determine the index of a member in a sorted set.
|
830
|
+
#
|
831
|
+
# @param [String] key
|
832
|
+
# @param [String] member
|
833
|
+
# @return [Fixnum]
|
834
|
+
def zrank(key, member)
|
835
|
+
synchronize do
|
836
|
+
@client.call [:zrank, key, member]
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
# Determine the index of a member in a sorted set, with scores ordered from
|
841
|
+
# high to low.
|
842
|
+
#
|
843
|
+
# @param [String] key
|
844
|
+
# @param [String] member
|
845
|
+
# @return [Fixnum]
|
846
|
+
def zrevrank(key, member)
|
847
|
+
synchronize do
|
848
|
+
@client.call [:zrevrank, key, member]
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
# Increment the score of a member in a sorted set.
|
853
|
+
#
|
854
|
+
# @example
|
855
|
+
# redis.zincrby("zset", 32.0, "a")
|
856
|
+
# # => 64.0
|
857
|
+
#
|
858
|
+
# @param [String] key
|
859
|
+
# @param [Float] increment
|
860
|
+
# @param [String] member
|
861
|
+
# @return [Float] score of the member after incrementing it
|
862
|
+
def zincrby(key, increment, member)
|
863
|
+
synchronize do
|
864
|
+
@client.call [:zincrby, key, increment, member] do |reply|
|
865
|
+
Float(reply) if reply
|
866
|
+
end
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
# Get the number of members in a sorted set.
|
871
|
+
#
|
872
|
+
# @example
|
873
|
+
# redis.zcard("zset")
|
874
|
+
# # => 4
|
875
|
+
#
|
876
|
+
# @param [String] key
|
877
|
+
# @return [Fixnum]
|
878
|
+
def zcard(key)
|
879
|
+
synchronize do
|
880
|
+
@client.call [:zcard, key]
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
884
|
+
# Return a range of members in a sorted set, by index.
|
885
|
+
#
|
886
|
+
# @example Retrieve all members from a sorted set
|
887
|
+
# redis.zrange("zset", 0, -1)
|
888
|
+
# # => ["a", "b"]
|
889
|
+
# @example Retrieve all members and their scores from a sorted set
|
890
|
+
# redis.zrange("zset", 0, -1, :with_scores => true)
|
891
|
+
# # => [["a", 32.0], ["b", 64.0]]
|
892
|
+
#
|
893
|
+
# @param [String] key
|
894
|
+
# @param [Fixnum] start start index
|
895
|
+
# @param [Fixnum] stop stop index
|
896
|
+
# @param [Hash] options
|
897
|
+
# - `:with_scores => true`: include scores in output
|
898
|
+
#
|
899
|
+
# @return [Array<String>, Array<(String, Float)>]
|
900
|
+
# - when `:with_scores` is not specified, an array of members
|
901
|
+
# - when `:with_scores` is specified, an array with `(member, score)` pairs
|
902
|
+
def zrange(key, start, stop, options = {})
|
903
|
+
args = []
|
904
|
+
|
905
|
+
with_scores = options[:with_scores] || options[:withscores]
|
906
|
+
args << "WITHSCORES" if with_scores
|
907
|
+
|
908
|
+
synchronize do
|
909
|
+
@client.call [:zrange, key, start, stop, *args] do |reply|
|
910
|
+
if with_scores
|
911
|
+
if reply
|
912
|
+
reply.each_slice(2).map do |member, score|
|
913
|
+
[member, Float(score)]
|
914
|
+
end
|
915
|
+
end
|
916
|
+
else
|
917
|
+
reply
|
918
|
+
end
|
919
|
+
end
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
# Return a range of members in a sorted set, by index, with scores ordered
|
924
|
+
# from high to low.
|
925
|
+
#
|
926
|
+
# @example Retrieve all members from a sorted set
|
927
|
+
# redis.zrevrange("zset", 0, -1)
|
928
|
+
# # => ["b", "a"]
|
929
|
+
# @example Retrieve all members and their scores from a sorted set
|
930
|
+
# redis.zrevrange("zset", 0, -1, :with_scores => true)
|
931
|
+
# # => [["b", 64.0], ["a", 32.0]]
|
932
|
+
#
|
933
|
+
# @see #zrange
|
934
|
+
def zrevrange(key, start, stop, options = {})
|
935
|
+
args = []
|
936
|
+
|
937
|
+
with_scores = options[:with_scores] || options[:withscores]
|
938
|
+
args << "WITHSCORES" if with_scores
|
939
|
+
|
940
|
+
synchronize do
|
941
|
+
@client.call [:zrevrange, key, start, stop, *args] do |reply|
|
942
|
+
if with_scores
|
943
|
+
if reply
|
944
|
+
reply.each_slice(2).map do |member, score|
|
945
|
+
[member, Float(score)]
|
946
|
+
end
|
947
|
+
end
|
948
|
+
else
|
949
|
+
reply
|
950
|
+
end
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
# Return a range of members in a sorted set, by score.
|
956
|
+
#
|
957
|
+
# @example Retrieve members with score `>= 5` and `< 100`
|
958
|
+
# redis.zrangebyscore("zset", "5", "(100")
|
959
|
+
# # => ["a", "b"]
|
960
|
+
# @example Retrieve the first 2 members with score `>= 0`
|
961
|
+
# redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
|
962
|
+
# # => ["a", "b"]
|
963
|
+
# @example Retrieve members and their scores with scores `> 5`
|
964
|
+
# redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
|
965
|
+
# # => [["a", 32.0], ["b", 64.0]]
|
966
|
+
#
|
967
|
+
# @param [String] key
|
968
|
+
# @param [String] min
|
969
|
+
# - inclusive minimum score is specified verbatim
|
970
|
+
# - exclusive minimum score is specified by prefixing `(`
|
971
|
+
# @param [String] max
|
972
|
+
# - inclusive maximum score is specified verbatim
|
973
|
+
# - exclusive maximum score is specified by prefixing `(`
|
974
|
+
# @param [Hash] options
|
975
|
+
# - `:with_scores => true`: include scores in output
|
976
|
+
# - `:limit => [offset, count]`: skip `offset` members, return a maximum of
|
977
|
+
# `count` members
|
978
|
+
#
|
979
|
+
# @return [Array<String>, Array<(String, Float)>]
|
980
|
+
# - when `:with_scores` is not specified, an array of members
|
981
|
+
# - when `:with_scores` is specified, an array with `(member, score)` pairs
|
982
|
+
def zrangebyscore(key, min, max, options = {})
|
983
|
+
args = []
|
984
|
+
|
985
|
+
with_scores = options[:with_scores] || options[:withscores]
|
986
|
+
args.concat ["WITHSCORES"] if with_scores
|
987
|
+
|
988
|
+
limit = options[:limit]
|
989
|
+
args.concat ["LIMIT", *limit] if limit
|
990
|
+
|
991
|
+
synchronize do
|
992
|
+
@client.call [:zrangebyscore, key, min, max, *args] do |reply|
|
993
|
+
if with_scores
|
994
|
+
if reply
|
995
|
+
reply.each_slice(2).map do |member, score|
|
996
|
+
[member, Float(score)]
|
997
|
+
end
|
998
|
+
end
|
999
|
+
else
|
1000
|
+
reply
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
# Return a range of members in a sorted set, by score, with scores ordered
|
1007
|
+
# from high to low.
|
1008
|
+
#
|
1009
|
+
# @example Retrieve members with score `< 100` and `>= 5`
|
1010
|
+
# redis.zrevrangebyscore("zset", "(100", "5")
|
1011
|
+
# # => ["b", "a"]
|
1012
|
+
# @example Retrieve the first 2 members with score `<= 0`
|
1013
|
+
# redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
|
1014
|
+
# # => ["b", "a"]
|
1015
|
+
# @example Retrieve members and their scores with scores `> 5`
|
1016
|
+
# redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
|
1017
|
+
# # => [["b", 64.0], ["a", 32.0]]
|
1018
|
+
#
|
1019
|
+
# @see #zrangebyscore
|
1020
|
+
def zrevrangebyscore(key, max, min, options = {})
|
1021
|
+
args = []
|
1022
|
+
|
1023
|
+
with_scores = options[:with_scores] || options[:withscores]
|
1024
|
+
args.concat ["WITHSCORES"] if with_scores
|
1025
|
+
|
1026
|
+
limit = options[:limit]
|
1027
|
+
args.concat ["LIMIT", *limit] if limit
|
1028
|
+
|
1029
|
+
synchronize do
|
1030
|
+
@client.call [:zrevrangebyscore, key, max, min, *args] do |reply|
|
1031
|
+
if with_scores
|
1032
|
+
if reply
|
1033
|
+
reply.each_slice(2).map do |member, score|
|
1034
|
+
[member, Float(score)]
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
else
|
1038
|
+
reply
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
# Count the members in a sorted set with scores within the given values.
|
1045
|
+
#
|
1046
|
+
# @example Count members with score `>= 5` and `< 100`
|
1047
|
+
# redis.zcount("zset", "5", "(100")
|
1048
|
+
# # => 2
|
1049
|
+
# @example Count members with scores `> 5`
|
1050
|
+
# redis.zcount("zset", "(5", "+inf")
|
1051
|
+
# # => 2
|
1052
|
+
#
|
1053
|
+
# @param [String] key
|
1054
|
+
# @param [String] min
|
1055
|
+
# - inclusive minimum score is specified verbatim
|
1056
|
+
# - exclusive minimum score is specified by prefixing `(`
|
1057
|
+
# @param [String] max
|
1058
|
+
# - inclusive maximum score is specified verbatim
|
1059
|
+
# - exclusive maximum score is specified by prefixing `(`
|
1060
|
+
# @return [Fixnum] number of members in within the specified range
|
1061
|
+
def zcount(key, start, stop)
|
1062
|
+
synchronize do
|
1063
|
+
@client.call [:zcount, key, start, stop]
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
# Remove all members in a sorted set within the given scores.
|
1068
|
+
#
|
1069
|
+
# @example Remove members with score `>= 5` and `< 100`
|
1070
|
+
# redis.zremrangebyscore("zset", "5", "(100")
|
1071
|
+
# # => 2
|
1072
|
+
# @example Remove members with scores `> 5`
|
1073
|
+
# redis.zremrangebyscore("zset", "(5", "+inf")
|
1074
|
+
# # => 2
|
1075
|
+
#
|
1076
|
+
# @param [String] key
|
1077
|
+
# @param [String] min
|
1078
|
+
# - inclusive minimum score is specified verbatim
|
1079
|
+
# - exclusive minimum score is specified by prefixing `(`
|
1080
|
+
# @param [String] max
|
1081
|
+
# - inclusive maximum score is specified verbatim
|
1082
|
+
# - exclusive maximum score is specified by prefixing `(`
|
1083
|
+
# @return [Fixnum] number of members that were removed
|
1084
|
+
def zremrangebyscore(key, min, max)
|
1085
|
+
synchronize do
|
1086
|
+
@client.call [:zremrangebyscore, key, min, max]
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
# Remove all members in a sorted set within the given indexes.
|
1091
|
+
#
|
1092
|
+
# @example Remove first 5 members
|
1093
|
+
# redis.zremrangebyrank("zset", 0, 4)
|
1094
|
+
# # => 5
|
1095
|
+
# @example Remove last 5 members
|
1096
|
+
# redis.zremrangebyrank("zset", -5, -1)
|
1097
|
+
# # => 5
|
1098
|
+
#
|
1099
|
+
# @param [String] key
|
1100
|
+
# @param [Fixnum] start start index
|
1101
|
+
# @param [Fixnum] stop stop index
|
1102
|
+
# @return [Fixnum] number of members that were removed
|
1103
|
+
def zremrangebyrank(key, start, stop)
|
1104
|
+
synchronize do
|
1105
|
+
@client.call [:zremrangebyrank, key, start, stop]
|
1106
|
+
end
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
# Get the score associated with the given member in a sorted set.
|
1110
|
+
#
|
1111
|
+
# @example Get the score for member "a"
|
1112
|
+
# redis.zscore("zset", "a")
|
1113
|
+
# # => 32.0
|
1114
|
+
#
|
1115
|
+
# @param [String] key
|
1116
|
+
# @param [String] member
|
1117
|
+
# @return [Float] score of the member
|
1118
|
+
def zscore(key, member)
|
1119
|
+
synchronize do
|
1120
|
+
@client.call [:zscore, key, member] do |reply|
|
1121
|
+
Float(reply) if reply
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
1127
|
+
# key.
|
1128
|
+
#
|
1129
|
+
# @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
|
1130
|
+
# redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
|
1131
|
+
# # => 4
|
1132
|
+
#
|
1133
|
+
# @param [String] destination destination key
|
1134
|
+
# @param [Array<String>] keys source keys
|
1135
|
+
# @param [Hash] options
|
1136
|
+
# - `:weights => [Float, Float, ...]`: weights to associate with source
|
1137
|
+
# sorted sets
|
1138
|
+
# - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
|
1139
|
+
# @return [Fixnum] number of elements in the resulting sorted set
|
1140
|
+
def zinterstore(destination, keys, options = {})
|
1141
|
+
command = CommandOptions.new(options) do |c|
|
1142
|
+
c.splat :weights
|
1143
|
+
c.value :aggregate
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
synchronize do
|
1147
|
+
@client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)]
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
1152
|
+
#
|
1153
|
+
# @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
|
1154
|
+
# redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
|
1155
|
+
# # => 8
|
1156
|
+
#
|
1157
|
+
# @param [String] destination destination key
|
1158
|
+
# @param [Array<String>] keys source keys
|
1159
|
+
# @param [Hash] options
|
1160
|
+
# - `:weights => [Float, Float, ...]`: weights to associate with source
|
1161
|
+
# sorted sets
|
1162
|
+
# - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
|
1163
|
+
# @return [Fixnum] number of elements in the resulting sorted set
|
1164
|
+
def zunionstore(destination, keys, options = {})
|
1165
|
+
command = CommandOptions.new(options) do |c|
|
1166
|
+
c.splat :weights
|
1167
|
+
c.value :aggregate
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
synchronize do
|
1171
|
+
@client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)]
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
# Move a key to another database.
|
1176
|
+
#
|
1177
|
+
# @example Move a key to another database
|
1178
|
+
# redis.set "foo", "bar"
|
1179
|
+
# # => "OK"
|
1180
|
+
# redis.move "foo", 2
|
1181
|
+
# # => true
|
1182
|
+
# redis.exists "foo"
|
1183
|
+
# # => false
|
1184
|
+
# redis.select 2
|
1185
|
+
# # => "OK"
|
1186
|
+
# redis.exists "foo"
|
1187
|
+
# # => true
|
1188
|
+
# resis.get "foo"
|
1189
|
+
# # => "bar"
|
1190
|
+
#
|
1191
|
+
# @param [String] key
|
1192
|
+
# @param [Fixnum] db
|
1193
|
+
# @return [Boolean] whether the key was moved or not
|
1194
|
+
def move(key, db)
|
1195
|
+
synchronize do
|
1196
|
+
@client.call [:move, key, db], &_boolify
|
1197
|
+
end
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
# Set the value of a key, only if the key does not exist.
|
1201
|
+
#
|
1202
|
+
# @param [String] key
|
1203
|
+
# @param [String] value
|
1204
|
+
# @return [Boolean] whether the key was set or not
|
1205
|
+
def setnx(key, value)
|
1206
|
+
synchronize do
|
1207
|
+
@client.call [:setnx, key, value], &_boolify
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# Delete one or more keys.
|
1212
|
+
#
|
1213
|
+
# @param [String, Array<String>] keys
|
1214
|
+
# @return [Fixnum] number of keys that were removed
|
1215
|
+
def del(*keys)
|
1216
|
+
synchronize do
|
1217
|
+
@client.call [:del, *keys]
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
# Rename a key. If the new key already exists it is overwritten.
|
1222
|
+
#
|
1223
|
+
# @param [String] old_name
|
1224
|
+
# @param [String] new_name
|
1225
|
+
# @return [String] `OK`
|
1226
|
+
def rename(old_name, new_name)
|
1227
|
+
synchronize do
|
1228
|
+
@client.call [:rename, old_name, new_name]
|
1229
|
+
end
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
# Rename a key, only if the new key does not exist.
|
1233
|
+
#
|
1234
|
+
# @param [String] old_name
|
1235
|
+
# @param [String] new_name
|
1236
|
+
# @return [Boolean] whether the key was renamed or not
|
1237
|
+
def renamenx(old_name, new_name)
|
1238
|
+
synchronize do
|
1239
|
+
@client.call [:renamenx, old_name, new_name], &_boolify
|
1240
|
+
end
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
# Set a key's time to live in seconds.
|
1244
|
+
#
|
1245
|
+
# @param [String] key
|
1246
|
+
# @param [Fixnum] seconds time to live. After this timeout has expired,
|
1247
|
+
# the key will automatically be deleted
|
1248
|
+
# @return [Boolean] whether the timeout was set or not
|
1249
|
+
def expire(key, seconds)
|
1250
|
+
synchronize do
|
1251
|
+
@client.call [:expire, key, seconds], &_boolify
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
# Remove the expiration from a key.
|
1256
|
+
#
|
1257
|
+
# @param [String] key
|
1258
|
+
# @return [Boolean] whether the timeout was removed or not
|
1259
|
+
def persist(key)
|
1260
|
+
synchronize do
|
1261
|
+
@client.call [:persist, key], &_boolify
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
# Get the time to live for a key.
|
1266
|
+
#
|
1267
|
+
# @param [String] key
|
1268
|
+
# @return [Fixnum] remaining time to live in seconds, or -1 if the
|
1269
|
+
# key does not exist or does not have a timeout
|
1270
|
+
def ttl(key)
|
1271
|
+
synchronize do
|
1272
|
+
@client.call [:ttl, key]
|
1273
|
+
end
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
# Set the expiration for a key as a UNIX timestamp.
|
1277
|
+
#
|
1278
|
+
# @param [String] key
|
1279
|
+
# @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
|
1280
|
+
# (seconds since January 1, 1970). After this timeout has expired,
|
1281
|
+
# the key will automatically be deleted
|
1282
|
+
# @return [Boolean] whether the timeout was set or not
|
1283
|
+
def expireat(key, unix_time)
|
1284
|
+
synchronize do
|
1285
|
+
@client.call [:expireat, key, unix_time], &_boolify
|
1286
|
+
end
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
# Set the string value of a hash field.
|
1290
|
+
def hset(key, field, value)
|
1291
|
+
synchronize do
|
1292
|
+
@client.call [:hset, key, field, value], &_boolify
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
# Set the value of a hash field, only if the field does not exist.
|
1297
|
+
def hsetnx(key, field, value)
|
1298
|
+
synchronize do
|
1299
|
+
@client.call [:hsetnx, key, field, value], &_boolify
|
1300
|
+
end
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
# Set multiple hash fields to multiple values.
|
1304
|
+
def hmset(key, *attrs)
|
1305
|
+
synchronize do
|
1306
|
+
@client.call [:hmset, key, *attrs]
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
def mapped_hmset(key, hash)
|
1311
|
+
hmset(key, *hash.to_a.flatten)
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
# Get the values of all the given hash fields.
|
1315
|
+
def hmget(key, *fields, &blk)
|
1316
|
+
synchronize do
|
1317
|
+
@client.call [:hmget, key, *fields], &blk
|
1318
|
+
end
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
def mapped_hmget(key, *fields)
|
1322
|
+
hmget(key, *fields) do |reply|
|
1323
|
+
if reply.kind_of?(Array)
|
1324
|
+
hash = Hash.new
|
1325
|
+
fields.zip(reply).each do |field, value|
|
1326
|
+
hash[field] = value
|
1327
|
+
end
|
1328
|
+
hash
|
1329
|
+
else
|
1330
|
+
reply
|
1331
|
+
end
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
# Get the number of fields in a hash.
|
1336
|
+
def hlen(key)
|
1337
|
+
synchronize do
|
1338
|
+
@client.call [:hlen, key]
|
1339
|
+
end
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
# Get all the values in a hash.
|
1343
|
+
def hvals(key)
|
1344
|
+
synchronize do
|
1345
|
+
@client.call [:hvals, key]
|
1346
|
+
end
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
# Increment the integer value of a hash field by the given number.
|
1350
|
+
def hincrby(key, field, increment)
|
1351
|
+
synchronize do
|
1352
|
+
@client.call [:hincrby, key, field, increment]
|
1353
|
+
end
|
1354
|
+
end
|
1355
|
+
|
1356
|
+
# Discard all commands issued after MULTI.
|
1357
|
+
def discard
|
1358
|
+
synchronize do
|
1359
|
+
@client.call [:discard]
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
# Determine if a hash field exists.
|
1364
|
+
def hexists(key, field)
|
1365
|
+
synchronize do
|
1366
|
+
@client.call [:hexists, key, field], &_boolify
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
# Listen for all requests received by the server in real time.
|
1371
|
+
def monitor(&block)
|
1372
|
+
synchronize do
|
1373
|
+
@client.call_loop([:monitor], &block)
|
1374
|
+
end
|
1375
|
+
end
|
1376
|
+
|
1377
|
+
def debug(*args)
|
1378
|
+
synchronize do
|
1379
|
+
@client.call [:debug, *args]
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
def object(*args)
|
1384
|
+
synchronize do
|
1385
|
+
@client.call [:object, *args]
|
1386
|
+
end
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
# Internal command used for replication.
|
1390
|
+
def sync
|
1391
|
+
synchronize do
|
1392
|
+
@client.call [:sync]
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
# Set the string value of a key.
|
1397
|
+
def set(key, value)
|
1398
|
+
synchronize do
|
1399
|
+
@client.call [:set, key, value]
|
1400
|
+
end
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
alias :[]= :set
|
1404
|
+
|
1405
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
1406
|
+
def setbit(key, offset, value)
|
1407
|
+
synchronize do
|
1408
|
+
@client.call [:setbit, key, offset, value]
|
1409
|
+
end
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# Set the value and expiration of a key.
|
1413
|
+
def setex(key, ttl, value)
|
1414
|
+
synchronize do
|
1415
|
+
@client.call [:setex, key, ttl, value]
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
# Overwrite part of a string at key starting at the specified offset.
|
1420
|
+
def setrange(key, offset, value)
|
1421
|
+
synchronize do
|
1422
|
+
@client.call [:setrange, key, offset, value]
|
1423
|
+
end
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
# Set multiple keys to multiple values.
|
1427
|
+
def mset(*args)
|
1428
|
+
synchronize do
|
1429
|
+
@client.call [:mset, *args]
|
1430
|
+
end
|
1431
|
+
end
|
1432
|
+
|
1433
|
+
def mapped_mset(hash)
|
1434
|
+
mset(*hash.to_a.flatten)
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
# Set multiple keys to multiple values, only if none of the keys exist.
|
1438
|
+
def msetnx(*args)
|
1439
|
+
synchronize do
|
1440
|
+
@client.call [:msetnx, *args]
|
1441
|
+
end
|
1442
|
+
end
|
1443
|
+
|
1444
|
+
def mapped_msetnx(hash)
|
1445
|
+
msetnx(*hash.to_a.flatten)
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
def mapped_mget(*keys)
|
1449
|
+
mget(*keys) do |reply|
|
1450
|
+
if reply.kind_of?(Array)
|
1451
|
+
hash = Hash.new
|
1452
|
+
keys.zip(reply).each do |field, value|
|
1453
|
+
hash[field] = value
|
1454
|
+
end
|
1455
|
+
hash
|
1456
|
+
else
|
1457
|
+
reply
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
# Sort the elements in a list, set or sorted set.
|
1463
|
+
def sort(key, options = {})
|
1464
|
+
command = CommandOptions.new(options) do |c|
|
1465
|
+
c.value :by
|
1466
|
+
c.splat :limit
|
1467
|
+
c.multi :get
|
1468
|
+
c.words :order
|
1469
|
+
c.value :store
|
1470
|
+
end
|
1471
|
+
|
1472
|
+
synchronize do
|
1473
|
+
@client.call [:sort, key, *command.to_a]
|
1474
|
+
end
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
# Increment the integer value of a key by one.
|
1478
|
+
def incr(key)
|
1479
|
+
synchronize do
|
1480
|
+
@client.call [:incr, key]
|
1481
|
+
end
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
# Increment the integer value of a key by the given number.
|
1485
|
+
def incrby(key, increment)
|
1486
|
+
synchronize do
|
1487
|
+
@client.call [:incrby, key, increment]
|
1488
|
+
end
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
# Decrement the integer value of a key by one.
|
1492
|
+
def decr(key)
|
1493
|
+
synchronize do
|
1494
|
+
@client.call [:decr, key]
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
# Decrement the integer value of a key by the given number.
|
1499
|
+
def decrby(key, decrement)
|
1500
|
+
synchronize do
|
1501
|
+
@client.call [:decrby, key, decrement]
|
1502
|
+
end
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
# Determine the type stored at key.
|
1506
|
+
#
|
1507
|
+
# @param [String] key
|
1508
|
+
# @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
|
1509
|
+
def type(key)
|
1510
|
+
synchronize do
|
1511
|
+
@client.call [:type, key]
|
1512
|
+
end
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
# Close the connection.
|
1516
|
+
def quit
|
1517
|
+
synchronize do
|
1518
|
+
begin
|
1519
|
+
@client.call [:quit]
|
1520
|
+
rescue ConnectionError
|
1521
|
+
ensure
|
1522
|
+
@client.disconnect
|
1523
|
+
end
|
1524
|
+
end
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
# Synchronously save the dataset to disk and then shut down the server.
|
1528
|
+
def shutdown
|
1529
|
+
synchronize do
|
1530
|
+
@client.without_reconnect do
|
1531
|
+
begin
|
1532
|
+
@client.call [:shutdown]
|
1533
|
+
rescue ConnectionError
|
1534
|
+
# This means Redis has probably exited.
|
1535
|
+
nil
|
1536
|
+
end
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
# Make the server a slave of another instance, or promote it as master.
|
1542
|
+
def slaveof(host, port)
|
1543
|
+
synchronize do
|
1544
|
+
@client.call [:slaveof, host, port]
|
1545
|
+
end
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
def pipelined
|
1549
|
+
synchronize do
|
1550
|
+
begin
|
1551
|
+
original, @client = @client, Pipeline.new
|
1552
|
+
yield
|
1553
|
+
original.call_pipeline(@client)
|
1554
|
+
ensure
|
1555
|
+
@client = original
|
1556
|
+
end
|
1557
|
+
end
|
1558
|
+
end
|
1559
|
+
|
1560
|
+
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
1561
|
+
def watch(*keys)
|
1562
|
+
synchronize do
|
1563
|
+
@client.call [:watch, *keys]
|
1564
|
+
end
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
# Forget about all watched keys.
|
1568
|
+
def unwatch
|
1569
|
+
synchronize do
|
1570
|
+
@client.call [:unwatch]
|
1571
|
+
end
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
# Execute all commands issued after MULTI.
|
1575
|
+
def exec
|
1576
|
+
synchronize do
|
1577
|
+
@client.call [:exec]
|
1578
|
+
end
|
1579
|
+
end
|
1580
|
+
|
1581
|
+
# Mark the start of a transaction block.
|
1582
|
+
def multi
|
1583
|
+
synchronize do
|
1584
|
+
if !block_given?
|
1585
|
+
@client.call [:multi]
|
1586
|
+
else
|
1587
|
+
begin
|
1588
|
+
pipeline = Pipeline::Multi.new
|
1589
|
+
original, @client = @client, pipeline
|
1590
|
+
yield(self)
|
1591
|
+
original.call_pipeline(pipeline)
|
1592
|
+
ensure
|
1593
|
+
@client = original
|
1594
|
+
end
|
1595
|
+
end
|
1596
|
+
end
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
# Post a message to a channel.
|
1600
|
+
def publish(channel, message)
|
1601
|
+
synchronize do
|
1602
|
+
@client.call [:publish, channel, message]
|
1603
|
+
end
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
def subscribed?
|
1607
|
+
synchronize do
|
1608
|
+
@client.kind_of? SubscribedClient
|
1609
|
+
end
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
# Stop listening for messages posted to the given channels.
|
1613
|
+
def unsubscribe(*channels)
|
1614
|
+
synchronize do
|
1615
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
1616
|
+
@client.unsubscribe(*channels)
|
1617
|
+
end
|
1618
|
+
end
|
1619
|
+
|
1620
|
+
# Stop listening for messages posted to channels matching the given patterns.
|
1621
|
+
def punsubscribe(*channels)
|
1622
|
+
synchronize do
|
1623
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
1624
|
+
@client.punsubscribe(*channels)
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
# Listen for messages published to the given channels.
|
1629
|
+
def subscribe(*channels, &block)
|
1630
|
+
synchronize do
|
1631
|
+
subscription(:subscribe, channels, block)
|
1632
|
+
end
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
# Listen for messages published to channels matching the given patterns.
|
1636
|
+
def psubscribe(*channels, &block)
|
1637
|
+
synchronize do
|
1638
|
+
subscription(:psubscribe, channels, block)
|
1639
|
+
end
|
1640
|
+
end
|
1641
|
+
|
1642
|
+
def id
|
1643
|
+
synchronize do
|
1644
|
+
@client.id
|
1645
|
+
end
|
1646
|
+
end
|
1647
|
+
|
1648
|
+
def inspect
|
1649
|
+
synchronize do
|
1650
|
+
"#<Redis client v#{Redis::VERSION} connected to #{id} (Redis v#{info["redis_version"]})>"
|
1651
|
+
end
|
1652
|
+
end
|
1653
|
+
|
1654
|
+
def method_missing(command, *args)
|
1655
|
+
synchronize do
|
1656
|
+
@client.call [command, *args]
|
1657
|
+
end
|
1658
|
+
end
|
1659
|
+
|
1660
|
+
class CommandOptions
|
1661
|
+
def initialize(options)
|
1662
|
+
@result = []
|
1663
|
+
@options = options
|
1664
|
+
yield(self)
|
1665
|
+
end
|
1666
|
+
|
1667
|
+
def bool(name)
|
1668
|
+
insert(name) { |argument, value| [argument] }
|
1669
|
+
end
|
1670
|
+
|
1671
|
+
def value(name)
|
1672
|
+
insert(name) { |argument, value| [argument, value] }
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
def splat(name)
|
1676
|
+
insert(name) { |argument, value| [argument, *value] }
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
def multi(name)
|
1680
|
+
insert(name) { |argument, value| [argument].product(Array(value)).flatten }
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
def words(name)
|
1684
|
+
insert(name) { |argument, value| value.split(" ") }
|
1685
|
+
end
|
1686
|
+
|
1687
|
+
def to_a
|
1688
|
+
@result
|
1689
|
+
end
|
1690
|
+
|
1691
|
+
def insert(name)
|
1692
|
+
@result += yield(name.to_s.upcase.gsub("_", ""), @options[name]) if @options[name]
|
1693
|
+
end
|
1694
|
+
end
|
1695
|
+
|
1696
|
+
private
|
1697
|
+
|
1698
|
+
# Commands returning 1 for true and 0 for false may be executed in a pipeline
|
1699
|
+
# where the method call will return nil. Propagate the nil instead of falsely
|
1700
|
+
# returning false.
|
1701
|
+
def _boolify
|
1702
|
+
lambda { |value|
|
1703
|
+
value == 1 if value
|
1704
|
+
}
|
1705
|
+
end
|
1706
|
+
|
1707
|
+
def subscription(method, channels, block)
|
1708
|
+
return @client.call [method, *channels] if subscribed?
|
1709
|
+
|
1710
|
+
begin
|
1711
|
+
original, @client = @client, SubscribedClient.new(@client)
|
1712
|
+
@client.send(method, *channels, &block)
|
1713
|
+
ensure
|
1714
|
+
@client = original
|
1715
|
+
end
|
1716
|
+
end
|
1717
|
+
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
require "redis/version"
|
1721
|
+
require "redis/connection"
|
1722
|
+
require "redis/client"
|
1723
|
+
require "redis/pipeline"
|
1724
|
+
require "redis/subscribe"
|