redis 2.2.2 → 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 +2 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +65 -1
- data/README.md +6 -0
- data/Rakefile +19 -27
- data/lib/redis.rb +737 -170
- data/lib/redis/client.rb +82 -67
- data/lib/redis/connection/command_helper.rb +15 -16
- data/lib/redis/connection/hiredis.rb +6 -3
- data/lib/redis/connection/ruby.rb +2 -1
- data/lib/redis/connection/synchrony.rb +3 -1
- data/lib/redis/distributed.rb +20 -18
- data/lib/redis/errors.rb +38 -0
- data/lib/redis/hash_ring.rb +2 -2
- data/lib/redis/pipeline.rb +91 -19
- data/lib/redis/subscribe.rb +1 -16
- data/lib/redis/version.rb +1 -1
- data/redis.gemspec +30 -11
- data/test/command_map_test.rb +29 -0
- data/test/commands_on_hashes_test.rb +3 -3
- data/test/commands_on_lists_test.rb +1 -1
- data/test/commands_on_sets_test.rb +0 -2
- data/test/commands_on_sorted_sets_test.rb +8 -9
- data/test/commands_on_strings_test.rb +3 -3
- data/test/commands_on_value_types_test.rb +0 -1
- data/test/connection_handling_test.rb +120 -4
- data/test/distributed_commands_on_hashes_test.rb +0 -1
- data/test/distributed_commands_on_lists_test.rb +0 -1
- data/test/distributed_commands_on_sets_test.rb +0 -1
- data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
- data/test/distributed_commands_on_strings_test.rb +0 -1
- data/test/distributed_commands_on_value_types_test.rb +0 -1
- data/test/distributed_connection_handling_test.rb +0 -1
- data/test/distributed_key_tags_test.rb +0 -1
- data/test/distributed_persistence_control_commands_test.rb +0 -1
- data/test/distributed_publish_subscribe_test.rb +1 -2
- data/test/distributed_remote_server_control_commands_test.rb +2 -3
- data/test/distributed_transactions_test.rb +0 -1
- data/test/encoding_test.rb +0 -1
- data/test/helper.rb +14 -4
- data/test/helper_test.rb +8 -0
- data/test/internals_test.rb +25 -33
- data/test/lint/hashes.rb +17 -3
- data/test/lint/internals.rb +2 -3
- data/test/lint/lists.rb +17 -3
- data/test/lint/sets.rb +30 -6
- data/test/lint/sorted_sets.rb +56 -27
- data/test/lint/strings.rb +9 -13
- data/test/lint/value_types.rb +12 -15
- data/test/persistence_control_commands_test.rb +0 -1
- data/test/pipelining_commands_test.rb +69 -6
- data/test/publish_subscribe_test.rb +1 -1
- data/test/redis_mock.rb +14 -5
- data/test/remote_server_control_commands_test.rb +8 -2
- data/test/sorting_test.rb +0 -1
- data/test/test.conf +1 -0
- data/test/transactions_test.rb +88 -15
- data/test/unknown_commands_test.rb +1 -2
- data/test/url_param_test.rb +0 -1
- metadata +68 -16
- data/lib/redis/compat.rb +0 -21
data/lib/redis/hash_ring.rb
CHANGED
@@ -54,7 +54,7 @@ class Redis
|
|
54
54
|
|
55
55
|
def iter_nodes(key)
|
56
56
|
return [nil,nil] if @ring.size == 0
|
57
|
-
|
57
|
+
_, pos = get_node_pos(key)
|
58
58
|
@sorted_keys[pos..-1].each do |k|
|
59
59
|
yield @ring[k]
|
60
60
|
end
|
@@ -98,7 +98,7 @@ class Redis
|
|
98
98
|
}
|
99
99
|
EOM
|
100
100
|
end
|
101
|
-
rescue Exception
|
101
|
+
rescue Exception
|
102
102
|
# Find the closest index in HashRing with value <= the given value
|
103
103
|
def binary_search(ary, value, &block)
|
104
104
|
upper = ary.size - 1
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,34 +1,106 @@
|
|
1
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
|
+
|
2
8
|
class Pipeline
|
3
|
-
attr :
|
9
|
+
attr :futures
|
4
10
|
|
5
11
|
def initialize
|
6
|
-
@
|
12
|
+
@without_reconnect = false
|
13
|
+
@shutdown = false
|
14
|
+
@futures = []
|
7
15
|
end
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
if args.first.is_a?(Array) && args.size == 1
|
13
|
-
command = args.first
|
14
|
-
else
|
15
|
-
command = args
|
16
|
-
end
|
17
|
+
def without_reconnect?
|
18
|
+
@without_reconnect
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
def shutdown?
|
22
|
+
@shutdown
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@
|
26
|
-
|
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
|
27
32
|
end
|
28
33
|
|
29
|
-
def
|
30
|
-
@
|
34
|
+
def call_pipeline(pipeline)
|
35
|
+
@shutdown = true if pipeline.shutdown?
|
36
|
+
@futures.concat(pipeline.futures)
|
31
37
|
nil
|
32
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
|
33
105
|
end
|
34
106
|
end
|
data/lib/redis/subscribe.rb
CHANGED
@@ -4,25 +4,10 @@ class Redis
|
|
4
4
|
@client = client
|
5
5
|
end
|
6
6
|
|
7
|
-
|
8
|
-
# array argument. Check its size for backwards compat.
|
9
|
-
def call(*args)
|
10
|
-
if args.first.is_a?(Array) && args.size == 1
|
11
|
-
command = args.first
|
12
|
-
else
|
13
|
-
command = args
|
14
|
-
end
|
15
|
-
|
7
|
+
def call(command)
|
16
8
|
@client.process([command])
|
17
9
|
end
|
18
10
|
|
19
|
-
# Assume that this method is called with a single array argument. No
|
20
|
-
# backwards compat here, since it was introduced in 2.2.2.
|
21
|
-
def call_without_reply(command)
|
22
|
-
@commands.push command
|
23
|
-
nil
|
24
|
-
end
|
25
|
-
|
26
11
|
def subscribe(*channels, &block)
|
27
12
|
subscription("subscribe", "unsubscribe", channels, block)
|
28
13
|
end
|
data/lib/redis/version.rb
CHANGED
data/redis.gemspec
CHANGED
@@ -1,24 +1,43 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
$:.unshift File.expand_path("../lib", __FILE__)
|
4
|
+
|
4
5
|
require "redis/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
+
s.name = "redis"
|
9
|
+
|
8
10
|
s.version = Redis::VERSION
|
9
11
|
|
10
|
-
s.
|
11
|
-
|
12
|
-
s.
|
13
|
-
|
14
|
-
s.
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
s.homepage = "https://github.com/ezmobius/redis-rb"
|
13
|
+
|
14
|
+
s.summary = "A Ruby client library for the Redis key-value store."
|
15
|
+
|
16
|
+
s.description = <<-EOS
|
17
|
+
A simple Ruby client trying to match Redis' API one-to-one while still providing a Rubystic interface.
|
18
|
+
It features thread safety, client-side sharding, and an obsession for performance.
|
19
|
+
EOS
|
20
|
+
|
21
|
+
s.authors = [
|
22
|
+
"Ezra Zygmuntowicz",
|
23
|
+
"Taylor Weibley",
|
24
|
+
"Matthew Clark",
|
25
|
+
"Brian McKinney",
|
26
|
+
"Salvatore Sanfilippo",
|
27
|
+
"Luca Guidi",
|
28
|
+
"Michel Martens",
|
29
|
+
"Damian Janowski",
|
30
|
+
"Pieter Noordhuis"
|
31
|
+
]
|
32
|
+
|
33
|
+
s.email = ["redis-db@googlegroups.com"]
|
18
34
|
|
19
35
|
s.files = `git ls-files`.split("\n")
|
20
36
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
37
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
-
|
23
|
-
s.
|
38
|
+
|
39
|
+
s.add_development_dependency("rake")
|
40
|
+
s.add_development_dependency("cutest")
|
41
|
+
s.add_development_dependency("hiredis")
|
42
|
+
s.add_development_dependency("em-synchrony")
|
24
43
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
setup do
|
6
|
+
init Redis.new(OPTIONS)
|
7
|
+
end
|
8
|
+
|
9
|
+
test "Override existing commands" do |r|
|
10
|
+
r.set("counter", 1)
|
11
|
+
|
12
|
+
assert 2 == r.incr("counter")
|
13
|
+
|
14
|
+
r.client.command_map[:incr] = :decr
|
15
|
+
|
16
|
+
assert 1 == r.incr("counter")
|
17
|
+
end
|
18
|
+
|
19
|
+
test "Override non-existing commands" do |r|
|
20
|
+
r.set("key", "value")
|
21
|
+
|
22
|
+
assert_raise Redis::CommandError do
|
23
|
+
r.idontexist("key")
|
24
|
+
end
|
25
|
+
|
26
|
+
r.client.command_map[:idontexist] = :get
|
27
|
+
|
28
|
+
assert "value" == r.idontexist("key")
|
29
|
+
end
|
@@ -8,13 +8,13 @@ end
|
|
8
8
|
|
9
9
|
load './test/lint/hashes.rb'
|
10
10
|
|
11
|
-
test "Mapped HMGET in a pipeline returns
|
11
|
+
test "Mapped HMGET in a pipeline returns hash" do |r|
|
12
12
|
r.hset("foo", "f1", "s1")
|
13
13
|
r.hset("foo", "f2", "s2")
|
14
14
|
|
15
15
|
result = r.pipelined do
|
16
|
-
|
16
|
+
r.mapped_hmget("foo", "f1", "f2")
|
17
17
|
end
|
18
18
|
|
19
|
-
assert result[0] ==
|
19
|
+
assert result[0] == { "f1" => "s1", "f2" => "s2" }
|
20
20
|
end
|
@@ -79,8 +79,8 @@ test "ZINTERSTORE with WEIGHTS" do |r|
|
|
79
79
|
assert 2 == r.zinterstore("foobar", ["foo", "bar"], :weights => [10, 1])
|
80
80
|
assert ["s2", "s3"] == r.zrange("foobar", 0, -1)
|
81
81
|
|
82
|
-
assert
|
83
|
-
assert
|
82
|
+
assert 40.0 == r.zscore("foobar", "s2")
|
83
|
+
assert 60.0 == r.zscore("foobar", "s3")
|
84
84
|
end
|
85
85
|
|
86
86
|
test "ZINTERSTORE with AGGREGATE" do |r|
|
@@ -93,17 +93,16 @@ test "ZINTERSTORE with AGGREGATE" do |r|
|
|
93
93
|
|
94
94
|
assert 2 == r.zinterstore("foobar", ["foo", "bar"])
|
95
95
|
assert ["s2", "s3"] == r.zrange("foobar", 0, -1)
|
96
|
-
assert
|
97
|
-
assert
|
96
|
+
assert 22.0 == r.zscore("foobar", "s2")
|
97
|
+
assert 33.0 == r.zscore("foobar", "s3")
|
98
98
|
|
99
99
|
assert 2 == r.zinterstore("foobar", ["foo", "bar"], :aggregate => :min)
|
100
100
|
assert ["s2", "s3"] == r.zrange("foobar", 0, -1)
|
101
|
-
assert
|
102
|
-
assert
|
101
|
+
assert 2.0 == r.zscore("foobar", "s2")
|
102
|
+
assert 3.0 == r.zscore("foobar", "s3")
|
103
103
|
|
104
104
|
assert 2 == r.zinterstore("foobar", ["foo", "bar"], :aggregate => :max)
|
105
105
|
assert ["s2", "s3"] == r.zrange("foobar", 0, -1)
|
106
|
-
assert
|
107
|
-
assert
|
106
|
+
assert 20.0 == r.zscore("foobar", "s2")
|
107
|
+
assert 30.0 == r.zscore("foobar", "s3")
|
108
108
|
end
|
109
|
-
|
@@ -32,15 +32,15 @@ test "MGET mapped" do |r|
|
|
32
32
|
assert nil == response["baz"]
|
33
33
|
end
|
34
34
|
|
35
|
-
test "Mapped MGET in a pipeline returns
|
35
|
+
test "Mapped MGET in a pipeline returns hash" do |r|
|
36
36
|
r.set("foo", "s1")
|
37
37
|
r.set("bar", "s2")
|
38
38
|
|
39
39
|
result = r.pipelined do
|
40
|
-
|
40
|
+
r.mapped_mget("foo", "bar")
|
41
41
|
end
|
42
42
|
|
43
|
-
assert result[0] ==
|
43
|
+
assert result[0] == { "foo" => "s1", "bar" => "s2" }
|
44
44
|
end
|
45
45
|
|
46
46
|
test "MSET" do |r|
|
@@ -44,7 +44,11 @@ test "QUIT" do |r|
|
|
44
44
|
end
|
45
45
|
|
46
46
|
test "SHUTDOWN" do
|
47
|
-
|
47
|
+
commands = {
|
48
|
+
:shutdown => lambda { :exit }
|
49
|
+
}
|
50
|
+
|
51
|
+
redis_mock(commands) do
|
48
52
|
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
49
53
|
|
50
54
|
# SHUTDOWN does not reply: test that it does not raise here.
|
@@ -52,6 +56,117 @@ test "SHUTDOWN" do
|
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
59
|
+
test "SHUTDOWN with error" do
|
60
|
+
connections = 0
|
61
|
+
commands = {
|
62
|
+
:select => lambda { |*_| connections += 1; "+OK\r\n" },
|
63
|
+
:connections => lambda { ":#{connections}\r\n" },
|
64
|
+
:shutdown => lambda { "-ERR could not shutdown\r\n" }
|
65
|
+
}
|
66
|
+
|
67
|
+
redis_mock(commands) do
|
68
|
+
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
69
|
+
|
70
|
+
connections = redis.connections
|
71
|
+
|
72
|
+
# SHUTDOWN replies with an error: test that it gets raised
|
73
|
+
assert_raise Redis::CommandError do
|
74
|
+
redis.shutdown
|
75
|
+
end
|
76
|
+
|
77
|
+
# The connection should remain in tact
|
78
|
+
assert connections == redis.connections
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
test "SHUTDOWN from pipeline" do
|
83
|
+
commands = {
|
84
|
+
:shutdown => lambda { :exit }
|
85
|
+
}
|
86
|
+
|
87
|
+
redis_mock(commands) do
|
88
|
+
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
89
|
+
|
90
|
+
result = redis.pipelined do
|
91
|
+
redis.shutdown
|
92
|
+
end
|
93
|
+
|
94
|
+
assert nil == result
|
95
|
+
assert !redis.client.connected?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
test "SHUTDOWN with error from pipeline" do
|
100
|
+
connections = 0
|
101
|
+
commands = {
|
102
|
+
:select => lambda { |*_| connections += 1; "+OK\r\n" },
|
103
|
+
:connections => lambda { ":#{connections}\r\n" },
|
104
|
+
:shutdown => lambda { "-ERR could not shutdown\r\n" }
|
105
|
+
}
|
106
|
+
|
107
|
+
redis_mock(commands) do
|
108
|
+
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
109
|
+
|
110
|
+
connections = redis.connections
|
111
|
+
|
112
|
+
# SHUTDOWN replies with an error: test that it gets raised
|
113
|
+
assert_raise Redis::CommandError do
|
114
|
+
redis.pipelined do
|
115
|
+
redis.shutdown
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# The connection should remain in tact
|
120
|
+
assert connections == redis.connections
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
test "SHUTDOWN from MULTI/EXEC" do
|
125
|
+
commands = {
|
126
|
+
:multi => lambda { "+OK\r\n" },
|
127
|
+
:shutdown => lambda { "+QUEUED\r\n" },
|
128
|
+
:exec => lambda { :exit }
|
129
|
+
}
|
130
|
+
|
131
|
+
redis_mock(commands) do
|
132
|
+
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
133
|
+
|
134
|
+
result = redis.multi do
|
135
|
+
redis.shutdown
|
136
|
+
end
|
137
|
+
|
138
|
+
assert nil == result
|
139
|
+
assert !redis.client.connected?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
test "SHUTDOWN with error from MULTI/EXEC" do
|
144
|
+
connections = 0
|
145
|
+
commands = {
|
146
|
+
:select => lambda { |*_| connections += 1; "+OK\r\n" },
|
147
|
+
:connections => lambda { ":#{connections}\r\n" },
|
148
|
+
:multi => lambda { "+OK\r\n" },
|
149
|
+
:shutdown => lambda { "+QUEUED\r\n" },
|
150
|
+
:exec => lambda { "*1\r\n-ERR could not shutdown\r\n" }
|
151
|
+
}
|
152
|
+
|
153
|
+
redis_mock(commands) do
|
154
|
+
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
155
|
+
|
156
|
+
connections = redis.connections
|
157
|
+
|
158
|
+
# SHUTDOWN replies with an error: test that it gets returned
|
159
|
+
# We should test for Redis::CommandError here, but hiredis doesn't yet do
|
160
|
+
# custom error classes.
|
161
|
+
assert_raise(StandardError) do
|
162
|
+
redis.multi { redis.shutdown }
|
163
|
+
end
|
164
|
+
|
165
|
+
# The connection should remain intact
|
166
|
+
assert connections == redis.connections
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
55
170
|
test "SLAVEOF" do
|
56
171
|
redis_mock(:slaveof => lambda { |host, port| "+SLAVEOF #{host} #{port}" }) do
|
57
172
|
redis = Redis.new(OPTIONS.merge(:port => 6380))
|
@@ -69,9 +184,11 @@ test "BGREWRITEAOF" do
|
|
69
184
|
end
|
70
185
|
|
71
186
|
test "CONFIG GET" do |r|
|
72
|
-
assert
|
187
|
+
assert r.config(:get, "*")["timeout"] != nil
|
73
188
|
|
74
|
-
|
189
|
+
config = r.config(:get, "timeout")
|
190
|
+
assert ["timeout"] == config.keys
|
191
|
+
assert config.values.compact.size > 0
|
75
192
|
end
|
76
193
|
|
77
194
|
test "CONFIG SET" do |r|
|
@@ -85,4 +202,3 @@ test "CONFIG SET" do |r|
|
|
85
202
|
r.config :set, "timeout", 300
|
86
203
|
end
|
87
204
|
end
|
88
|
-
|