redis 3.3.5 → 4.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +225 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +177 -100
  5. data/lib/redis/cluster/command.rb +79 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +120 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +34 -0
  11. data/lib/redis/cluster/option.rb +100 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +46 -0
  14. data/lib/redis/cluster.rb +315 -0
  15. data/lib/redis/commands/bitmaps.rb +63 -0
  16. data/lib/redis/commands/cluster.rb +45 -0
  17. data/lib/redis/commands/connection.rb +58 -0
  18. data/lib/redis/commands/geo.rb +84 -0
  19. data/lib/redis/commands/hashes.rb +251 -0
  20. data/lib/redis/commands/hyper_log_log.rb +37 -0
  21. data/lib/redis/commands/keys.rb +411 -0
  22. data/lib/redis/commands/lists.rb +289 -0
  23. data/lib/redis/commands/pubsub.rb +72 -0
  24. data/lib/redis/commands/scripting.rb +114 -0
  25. data/lib/redis/commands/server.rb +188 -0
  26. data/lib/redis/commands/sets.rb +207 -0
  27. data/lib/redis/commands/sorted_sets.rb +812 -0
  28. data/lib/redis/commands/streams.rb +382 -0
  29. data/lib/redis/commands/strings.rb +313 -0
  30. data/lib/redis/commands/transactions.rb +139 -0
  31. data/lib/redis/commands.rb +242 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +136 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +231 -72
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +30 -73
  41. data/lib/redis/pipeline.rb +178 -13
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +173 -2661
  45. metadata +66 -202
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -89
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -87
  52. data/benchmarking/logging.rb +0 -71
  53. data/benchmarking/pipeline.rb +0 -51
  54. data/benchmarking/speed.rb +0 -21
  55. data/benchmarking/suite.rb +0 -24
  56. data/benchmarking/worker.rb +0 -71
  57. data/examples/basic.rb +0 -15
  58. data/examples/consistency.rb +0 -114
  59. data/examples/dist_redis.rb +0 -43
  60. data/examples/incr-decr.rb +0 -17
  61. data/examples/list.rb +0 -26
  62. data/examples/pubsub.rb +0 -37
  63. data/examples/sentinel/sentinel.conf +0 -9
  64. data/examples/sentinel/start +0 -49
  65. data/examples/sentinel.rb +0 -41
  66. data/examples/sets.rb +0 -36
  67. data/examples/unicorn/config.ru +0 -3
  68. data/examples/unicorn/unicorn.rb +0 -20
  69. data/redis.gemspec +0 -44
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/client_test.rb +0 -59
  73. data/test/command_map_test.rb +0 -30
  74. data/test/commands_on_hashes_test.rb +0 -21
  75. data/test/commands_on_hyper_log_log_test.rb +0 -21
  76. data/test/commands_on_lists_test.rb +0 -20
  77. data/test/commands_on_sets_test.rb +0 -77
  78. data/test/commands_on_sorted_sets_test.rb +0 -137
  79. data/test/commands_on_strings_test.rb +0 -101
  80. data/test/commands_on_value_types_test.rb +0 -133
  81. data/test/connection_handling_test.rb +0 -277
  82. data/test/connection_test.rb +0 -57
  83. data/test/db/.gitkeep +0 -0
  84. data/test/distributed_blocking_commands_test.rb +0 -46
  85. data/test/distributed_commands_on_hashes_test.rb +0 -10
  86. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  87. data/test/distributed_commands_on_lists_test.rb +0 -22
  88. data/test/distributed_commands_on_sets_test.rb +0 -83
  89. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  90. data/test/distributed_commands_on_strings_test.rb +0 -59
  91. data/test/distributed_commands_on_value_types_test.rb +0 -95
  92. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  93. data/test/distributed_connection_handling_test.rb +0 -23
  94. data/test/distributed_internals_test.rb +0 -79
  95. data/test/distributed_key_tags_test.rb +0 -52
  96. data/test/distributed_persistence_control_commands_test.rb +0 -26
  97. data/test/distributed_publish_subscribe_test.rb +0 -92
  98. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  99. data/test/distributed_scripting_test.rb +0 -102
  100. data/test/distributed_sorting_test.rb +0 -20
  101. data/test/distributed_test.rb +0 -58
  102. data/test/distributed_transactions_test.rb +0 -32
  103. data/test/encoding_test.rb +0 -18
  104. data/test/error_replies_test.rb +0 -59
  105. data/test/fork_safety_test.rb +0 -65
  106. data/test/helper.rb +0 -232
  107. data/test/helper_test.rb +0 -24
  108. data/test/internals_test.rb +0 -417
  109. data/test/lint/blocking_commands.rb +0 -150
  110. data/test/lint/hashes.rb +0 -162
  111. data/test/lint/hyper_log_log.rb +0 -60
  112. data/test/lint/lists.rb +0 -143
  113. data/test/lint/sets.rb +0 -140
  114. data/test/lint/sorted_sets.rb +0 -316
  115. data/test/lint/strings.rb +0 -260
  116. data/test/lint/value_types.rb +0 -122
  117. data/test/persistence_control_commands_test.rb +0 -26
  118. data/test/pipelining_commands_test.rb +0 -242
  119. data/test/publish_subscribe_test.rb +0 -282
  120. data/test/remote_server_control_commands_test.rb +0 -118
  121. data/test/scanning_test.rb +0 -413
  122. data/test/scripting_test.rb +0 -78
  123. data/test/sentinel_command_test.rb +0 -80
  124. data/test/sentinel_test.rb +0 -255
  125. data/test/sorting_test.rb +0 -59
  126. data/test/ssl_test.rb +0 -73
  127. data/test/support/connection/hiredis.rb +0 -1
  128. data/test/support/connection/ruby.rb +0 -1
  129. data/test/support/connection/synchrony.rb +0 -17
  130. data/test/support/redis_mock.rb +0 -130
  131. data/test/support/ssl/gen_certs.sh +0 -31
  132. data/test/support/ssl/trusted-ca.crt +0 -25
  133. data/test/support/ssl/trusted-ca.key +0 -27
  134. data/test/support/ssl/trusted-cert.crt +0 -81
  135. data/test/support/ssl/trusted-cert.key +0 -28
  136. data/test/support/ssl/untrusted-ca.crt +0 -26
  137. data/test/support/ssl/untrusted-ca.key +0 -27
  138. data/test/support/ssl/untrusted-cert.crt +0 -82
  139. data/test/support/ssl/untrusted-cert.key +0 -28
  140. data/test/support/wire/synchrony.rb +0 -24
  141. data/test/support/wire/thread.rb +0 -5
  142. data/test/synchrony_driver.rb +0 -88
  143. data/test/test.conf.erb +0 -9
  144. data/test/thread_safety_test.rb +0 -62
  145. data/test/transactions_test.rb +0 -264
  146. data/test/unknown_commands_test.rb +0 -14
  147. data/test/url_param_test.rb +0 -138
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Transactions
6
+ # Mark the start of a transaction block.
7
+ #
8
+ # Passing a block is optional.
9
+ #
10
+ # @example With a block
11
+ # redis.multi do |multi|
12
+ # multi.set("key", "value")
13
+ # multi.incr("counter")
14
+ # end # => ["OK", 6]
15
+ #
16
+ # @example Without a block
17
+ # redis.multi
18
+ # # => "OK"
19
+ # redis.set("key", "value")
20
+ # # => "QUEUED"
21
+ # redis.incr("counter")
22
+ # # => "QUEUED"
23
+ # redis.exec
24
+ # # => ["OK", 6]
25
+ #
26
+ # @yield [multi] the commands that are called inside this block are cached
27
+ # and written to the server upon returning from it
28
+ # @yieldparam [Redis] multi `self`
29
+ #
30
+ # @return [String, Array<...>]
31
+ # - when a block is not given, `OK`
32
+ # - when a block is given, an array with replies
33
+ #
34
+ # @see #watch
35
+ # @see #unwatch
36
+ def multi(&block) # :nodoc:
37
+ if block_given?
38
+ if block&.arity == 0
39
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
40
+ end
41
+
42
+ synchronize do |prior_client|
43
+ pipeline = Pipeline::Multi.new(prior_client)
44
+ pipelined_connection = PipelinedConnection.new(pipeline)
45
+ yield pipelined_connection
46
+ prior_client.call_pipeline(pipeline)
47
+ end
48
+ else
49
+ send_command([:multi])
50
+ end
51
+ end
52
+
53
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
54
+ #
55
+ # Using a block is optional, but is necessary for thread-safety.
56
+ #
57
+ # An `#unwatch` is automatically issued if an exception is raised within the
58
+ # block that is a subclass of StandardError and is not a ConnectionError.
59
+ #
60
+ # @example With a block
61
+ # redis.watch("key") do
62
+ # if redis.get("key") == "some value"
63
+ # redis.multi do |multi|
64
+ # multi.set("key", "other value")
65
+ # multi.incr("counter")
66
+ # end
67
+ # else
68
+ # redis.unwatch
69
+ # end
70
+ # end
71
+ # # => ["OK", 6]
72
+ #
73
+ # @example Without a block
74
+ # redis.watch("key")
75
+ # # => "OK"
76
+ #
77
+ # @param [String, Array<String>] keys one or more keys to watch
78
+ # @return [Object] if using a block, returns the return value of the block
79
+ # @return [String] if not using a block, returns `OK`
80
+ #
81
+ # @see #unwatch
82
+ # @see #multi
83
+ def watch(*keys)
84
+ synchronize do |client|
85
+ res = client.call([:watch, *keys])
86
+
87
+ if block_given?
88
+ begin
89
+ yield(self)
90
+ rescue ConnectionError
91
+ raise
92
+ rescue StandardError
93
+ unwatch
94
+ raise
95
+ end
96
+ else
97
+ res
98
+ end
99
+ end
100
+ end
101
+
102
+ # Forget about all watched keys.
103
+ #
104
+ # @return [String] `OK`
105
+ #
106
+ # @see #watch
107
+ # @see #multi
108
+ def unwatch
109
+ send_command([:unwatch])
110
+ end
111
+
112
+ # Execute all commands issued after MULTI.
113
+ #
114
+ # Only call this method when `#multi` was called **without** a block.
115
+ #
116
+ # @return [nil, Array<...>]
117
+ # - when commands were not executed, `nil`
118
+ # - when commands were executed, an array with their replies
119
+ #
120
+ # @see #multi
121
+ # @see #discard
122
+ def exec
123
+ send_command([:exec])
124
+ end
125
+
126
+ # Discard all commands issued after MULTI.
127
+ #
128
+ # Only call this method when `#multi` was called **without** a block.
129
+ #
130
+ # @return [String] `"OK"`
131
+ #
132
+ # @see #multi
133
+ # @see #exec
134
+ def discard
135
+ send_command([:discard])
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/commands/bitmaps"
4
+ require "redis/commands/cluster"
5
+ require "redis/commands/connection"
6
+ require "redis/commands/geo"
7
+ require "redis/commands/hashes"
8
+ require "redis/commands/hyper_log_log"
9
+ require "redis/commands/keys"
10
+ require "redis/commands/lists"
11
+ require "redis/commands/pubsub"
12
+ require "redis/commands/scripting"
13
+ require "redis/commands/server"
14
+ require "redis/commands/sets"
15
+ require "redis/commands/sorted_sets"
16
+ require "redis/commands/streams"
17
+ require "redis/commands/strings"
18
+ require "redis/commands/transactions"
19
+
20
+ class Redis
21
+ module Commands
22
+ include Bitmaps
23
+ include Cluster
24
+ include Connection
25
+ include Geo
26
+ include Hashes
27
+ include HyperLogLog
28
+ include Keys
29
+ include Lists
30
+ include Pubsub
31
+ include Scripting
32
+ include Server
33
+ include Sets
34
+ include SortedSets
35
+ include Streams
36
+ include Strings
37
+ include Transactions
38
+
39
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
40
+ # where the method call will return nil. Propagate the nil instead of falsely
41
+ # returning false.
42
+ Boolify = lambda { |value|
43
+ case value
44
+ when 1
45
+ true
46
+ when 0
47
+ false
48
+ else
49
+ value
50
+ end
51
+ }
52
+
53
+ BoolifySet = lambda { |value|
54
+ case value
55
+ when "OK"
56
+ true
57
+ when nil
58
+ false
59
+ else
60
+ value
61
+ end
62
+ }
63
+
64
+ Hashify = lambda { |value|
65
+ if value.respond_to?(:each_slice)
66
+ value.each_slice(2).to_h
67
+ else
68
+ value
69
+ end
70
+ }
71
+
72
+ Pairify = lambda { |value|
73
+ if value.respond_to?(:each_slice)
74
+ value.each_slice(2).to_a
75
+ else
76
+ value
77
+ end
78
+ }
79
+
80
+ Floatify = lambda { |value|
81
+ case value
82
+ when "inf"
83
+ Float::INFINITY
84
+ when "-inf"
85
+ -Float::INFINITY
86
+ when String
87
+ Float(value)
88
+ else
89
+ value
90
+ end
91
+ }
92
+
93
+ FloatifyPairs = lambda { |value|
94
+ return value unless value.respond_to?(:each_slice)
95
+
96
+ value.each_slice(2).map do |member, score|
97
+ [member, Floatify.call(score)]
98
+ end
99
+ }
100
+
101
+ HashifyInfo = lambda { |reply|
102
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
103
+ lines.map! { |line| line.split(':', 2) }
104
+ lines.compact!
105
+ lines.to_h
106
+ }
107
+
108
+ HashifyStreams = lambda { |reply|
109
+ case reply
110
+ when nil
111
+ {}
112
+ else
113
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
114
+ end
115
+ }
116
+
117
+ EMPTY_STREAM_RESPONSE = [nil].freeze
118
+ private_constant :EMPTY_STREAM_RESPONSE
119
+
120
+ HashifyStreamEntries = lambda { |reply|
121
+ reply.compact.map do |entry_id, values|
122
+ [entry_id, values&.each_slice(2)&.to_h]
123
+ end
124
+ }
125
+
126
+ HashifyStreamAutoclaim = lambda { |reply|
127
+ {
128
+ 'next' => reply[0],
129
+ 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
130
+ }
131
+ }
132
+
133
+ HashifyStreamAutoclaimJustId = lambda { |reply|
134
+ {
135
+ 'next' => reply[0],
136
+ 'entries' => reply[1]
137
+ }
138
+ }
139
+
140
+ HashifyStreamPendings = lambda { |reply|
141
+ {
142
+ 'size' => reply[0],
143
+ 'min_entry_id' => reply[1],
144
+ 'max_entry_id' => reply[2],
145
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
146
+ }
147
+ }
148
+
149
+ HashifyStreamPendingDetails = lambda { |reply|
150
+ reply.map do |arr|
151
+ {
152
+ 'entry_id' => arr[0],
153
+ 'consumer' => arr[1],
154
+ 'elapsed' => arr[2],
155
+ 'count' => arr[3]
156
+ }
157
+ end
158
+ }
159
+
160
+ HashifyClusterNodeInfo = lambda { |str|
161
+ arr = str.split(' ')
162
+ {
163
+ 'node_id' => arr[0],
164
+ 'ip_port' => arr[1],
165
+ 'flags' => arr[2].split(','),
166
+ 'master_node_id' => arr[3],
167
+ 'ping_sent' => arr[4],
168
+ 'pong_recv' => arr[5],
169
+ 'config_epoch' => arr[6],
170
+ 'link_state' => arr[7],
171
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
172
+ }
173
+ }
174
+
175
+ HashifyClusterSlots = lambda { |reply|
176
+ reply.map do |arr|
177
+ first_slot, last_slot = arr[0..1]
178
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
179
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
180
+ {
181
+ 'start_slot' => first_slot,
182
+ 'end_slot' => last_slot,
183
+ 'master' => master,
184
+ 'replicas' => replicas
185
+ }
186
+ end
187
+ }
188
+
189
+ HashifyClusterNodes = lambda { |reply|
190
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
191
+ }
192
+
193
+ HashifyClusterSlaves = lambda { |reply|
194
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
195
+ }
196
+
197
+ Noop = ->(reply) { reply }
198
+
199
+ # Sends a command to Redis and returns its reply.
200
+ #
201
+ # Replies are converted to Ruby objects according to the RESP protocol, so
202
+ # you can expect a Ruby array, integer or nil when Redis sends one. Higher
203
+ # level transformations, such as converting an array of pairs into a Ruby
204
+ # hash, are up to consumers.
205
+ #
206
+ # Redis error replies are raised as Ruby exceptions.
207
+ def call(*command)
208
+ send_command(command)
209
+ end
210
+
211
+ # Interact with the sentinel command (masters, master, slaves, failover)
212
+ #
213
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
214
+ # @param [Array<String>] args depends on subcommand
215
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
216
+ def sentinel(subcommand, *args)
217
+ subcommand = subcommand.to_s.downcase
218
+ send_command([:sentinel, subcommand] + args) do |reply|
219
+ case subcommand
220
+ when "get-master-addr-by-name"
221
+ reply
222
+ else
223
+ if reply.is_a?(Array)
224
+ if reply[0].is_a?(Array)
225
+ reply.map(&Hashify)
226
+ else
227
+ Hashify.call(reply)
228
+ end
229
+ else
230
+ reply
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def method_missing(*command) # rubocop:disable Style/MissingRespondToMissing
239
+ send_command(command)
240
+ end
241
+ end
242
+ end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  module Connection
3
5
  module CommandHelper
4
-
5
6
  COMMAND_DELIMITER = "\r\n"
6
7
 
7
8
  def build_command(args)
@@ -11,11 +12,13 @@ class Redis
11
12
  if i.is_a? Array
12
13
  i.each do |j|
13
14
  j = j.to_s
15
+ j = j.encoding == Encoding::BINARY ? j : j.b
14
16
  command << "$#{j.bytesize}"
15
17
  command << j
16
18
  end
17
19
  else
18
20
  i = i.to_s
21
+ i = i.encoding == Encoding::BINARY ? i : i.b
19
22
  command << "$#{i.bytesize}"
20
23
  command << i
21
24
  end
@@ -28,16 +31,10 @@ class Redis
28
31
  command.join(COMMAND_DELIMITER)
29
32
  end
30
33
 
31
- protected
34
+ protected
32
35
 
33
- if defined?(Encoding::default_external)
34
- def encode(string)
35
- string.force_encoding(Encoding::default_external)
36
- end
37
- else
38
- def encode(string)
39
- string
40
- end
36
+ def encode(string)
37
+ string.force_encoding(Encoding.default_external)
41
38
  end
42
39
  end
43
40
  end
@@ -1,20 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "redis/connection/registry"
2
4
  require "redis/errors"
5
+
3
6
  require "hiredis/connection"
4
7
  require "timeout"
5
8
 
6
9
  class Redis
7
10
  module Connection
8
11
  class Hiredis
9
-
10
12
  def self.connect(config)
11
13
  connection = ::Hiredis::Connection.new
12
14
  connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
13
15
 
14
16
  if config[:scheme] == "unix"
15
17
  connection.connect_unix(config[:path], connect_timeout)
16
- elsif config[:scheme] == "rediss" || config[:ssl]
17
- raise NotImplementedError, "SSL not supported by hiredis driver"
18
18
  else
19
19
  connection.connect(config[:host], config[:port], connect_timeout)
20
20
  end
@@ -31,7 +31,7 @@ class Redis
31
31
  end
32
32
 
33
33
  def connected?
34
- @connection && @connection.connected?
34
+ @connection&.connected?
35
35
  end
36
36
 
37
37
  def timeout=(timeout)
@@ -57,7 +57,7 @@ class Redis
57
57
  rescue Errno::EAGAIN
58
58
  raise TimeoutError
59
59
  rescue RuntimeError => err
60
- raise ProtocolError.new(err.message)
60
+ raise ProtocolError, err.message
61
61
  end
62
62
  end
63
63
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  module Connection
3
-
4
5
  # Store a list of loaded connection drivers in the Connection module.
5
6
  # Redis::Client uses the last required driver by default, and will be aware
6
7
  # of the loaded connection drivers if the user chooses to override the