redis 4.0.1 → 4.8.1

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.
Files changed (148) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +220 -0
  3. data/README.md +152 -28
  4. data/lib/redis/client.rb +171 -107
  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 +455 -0
  22. data/lib/redis/commands/lists.rb +290 -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 +223 -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 +240 -0
  32. data/lib/redis/connection/command_helper.rb +5 -2
  33. data/lib/redis/connection/hiredis.rb +7 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +139 -111
  36. data/lib/redis/connection/synchrony.rb +17 -10
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +244 -87
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +15 -14
  41. data/lib/redis/pipeline.rb +181 -10
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +180 -2716
  45. metadata +45 -195
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -13
  48. data/.travis.yml +0 -73
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -3
  51. data/benchmarking/logging.rb +0 -71
  52. data/benchmarking/pipeline.rb +0 -51
  53. data/benchmarking/speed.rb +0 -21
  54. data/benchmarking/suite.rb +0 -24
  55. data/benchmarking/worker.rb +0 -71
  56. data/bors.toml +0 -14
  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/makefile +0 -42
  70. data/redis.gemspec +0 -42
  71. data/test/bitpos_test.rb +0 -63
  72. data/test/blocking_commands_test.rb +0 -40
  73. data/test/client_test.rb +0 -59
  74. data/test/command_map_test.rb +0 -28
  75. data/test/commands_on_hashes_test.rb +0 -19
  76. data/test/commands_on_hyper_log_log_test.rb +0 -19
  77. data/test/commands_on_lists_test.rb +0 -18
  78. data/test/commands_on_sets_test.rb +0 -75
  79. data/test/commands_on_sorted_sets_test.rb +0 -150
  80. data/test/commands_on_strings_test.rb +0 -99
  81. data/test/commands_on_value_types_test.rb +0 -171
  82. data/test/connection_handling_test.rb +0 -275
  83. data/test/connection_test.rb +0 -57
  84. data/test/db/.gitkeep +0 -0
  85. data/test/distributed_blocking_commands_test.rb +0 -44
  86. data/test/distributed_commands_on_hashes_test.rb +0 -8
  87. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -31
  88. data/test/distributed_commands_on_lists_test.rb +0 -20
  89. data/test/distributed_commands_on_sets_test.rb +0 -106
  90. data/test/distributed_commands_on_sorted_sets_test.rb +0 -16
  91. data/test/distributed_commands_on_strings_test.rb +0 -69
  92. data/test/distributed_commands_on_value_types_test.rb +0 -93
  93. data/test/distributed_commands_requiring_clustering_test.rb +0 -162
  94. data/test/distributed_connection_handling_test.rb +0 -21
  95. data/test/distributed_internals_test.rb +0 -68
  96. data/test/distributed_key_tags_test.rb +0 -50
  97. data/test/distributed_persistence_control_commands_test.rb +0 -24
  98. data/test/distributed_publish_subscribe_test.rb +0 -90
  99. data/test/distributed_remote_server_control_commands_test.rb +0 -64
  100. data/test/distributed_scripting_test.rb +0 -100
  101. data/test/distributed_sorting_test.rb +0 -18
  102. data/test/distributed_test.rb +0 -56
  103. data/test/distributed_transactions_test.rb +0 -30
  104. data/test/encoding_test.rb +0 -14
  105. data/test/error_replies_test.rb +0 -57
  106. data/test/fork_safety_test.rb +0 -60
  107. data/test/helper.rb +0 -201
  108. data/test/helper_test.rb +0 -22
  109. data/test/internals_test.rb +0 -389
  110. data/test/lint/blocking_commands.rb +0 -150
  111. data/test/lint/hashes.rb +0 -162
  112. data/test/lint/hyper_log_log.rb +0 -60
  113. data/test/lint/lists.rb +0 -143
  114. data/test/lint/sets.rb +0 -140
  115. data/test/lint/sorted_sets.rb +0 -316
  116. data/test/lint/strings.rb +0 -246
  117. data/test/lint/value_types.rb +0 -130
  118. data/test/persistence_control_commands_test.rb +0 -24
  119. data/test/pipelining_commands_test.rb +0 -238
  120. data/test/publish_subscribe_test.rb +0 -280
  121. data/test/remote_server_control_commands_test.rb +0 -175
  122. data/test/scanning_test.rb +0 -407
  123. data/test/scripting_test.rb +0 -76
  124. data/test/sentinel_command_test.rb +0 -78
  125. data/test/sentinel_test.rb +0 -253
  126. data/test/sorting_test.rb +0 -57
  127. data/test/ssl_test.rb +0 -69
  128. data/test/support/connection/hiredis.rb +0 -1
  129. data/test/support/connection/ruby.rb +0 -1
  130. data/test/support/connection/synchrony.rb +0 -17
  131. data/test/support/redis_mock.rb +0 -130
  132. data/test/support/ssl/gen_certs.sh +0 -31
  133. data/test/support/ssl/trusted-ca.crt +0 -25
  134. data/test/support/ssl/trusted-ca.key +0 -27
  135. data/test/support/ssl/trusted-cert.crt +0 -81
  136. data/test/support/ssl/trusted-cert.key +0 -28
  137. data/test/support/ssl/untrusted-ca.crt +0 -26
  138. data/test/support/ssl/untrusted-ca.key +0 -27
  139. data/test/support/ssl/untrusted-cert.crt +0 -82
  140. data/test/support/ssl/untrusted-cert.key +0 -28
  141. data/test/support/wire/synchrony.rb +0 -24
  142. data/test/support/wire/thread.rb +0 -5
  143. data/test/synchrony_driver.rb +0 -85
  144. data/test/test.conf.erb +0 -9
  145. data/test/thread_safety_test.rb +0 -60
  146. data/test/transactions_test.rb +0 -262
  147. data/test/unknown_commands_test.rb +0 -12
  148. data/test/url_param_test.rb +0 -136
@@ -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,240 @@
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 Integer
45
+ value > 0
46
+ else
47
+ value
48
+ end
49
+ }
50
+
51
+ BoolifySet = lambda { |value|
52
+ case value
53
+ when "OK"
54
+ true
55
+ when nil
56
+ false
57
+ else
58
+ value
59
+ end
60
+ }
61
+
62
+ Hashify = lambda { |value|
63
+ if value.respond_to?(:each_slice)
64
+ value.each_slice(2).to_h
65
+ else
66
+ value
67
+ end
68
+ }
69
+
70
+ Pairify = lambda { |value|
71
+ if value.respond_to?(:each_slice)
72
+ value.each_slice(2).to_a
73
+ else
74
+ value
75
+ end
76
+ }
77
+
78
+ Floatify = lambda { |value|
79
+ case value
80
+ when "inf"
81
+ Float::INFINITY
82
+ when "-inf"
83
+ -Float::INFINITY
84
+ when String
85
+ Float(value)
86
+ else
87
+ value
88
+ end
89
+ }
90
+
91
+ FloatifyPairs = lambda { |value|
92
+ return value unless value.respond_to?(:each_slice)
93
+
94
+ value.each_slice(2).map do |member, score|
95
+ [member, Floatify.call(score)]
96
+ end
97
+ }
98
+
99
+ HashifyInfo = lambda { |reply|
100
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
101
+ lines.map! { |line| line.split(':', 2) }
102
+ lines.compact!
103
+ lines.to_h
104
+ }
105
+
106
+ HashifyStreams = lambda { |reply|
107
+ case reply
108
+ when nil
109
+ {}
110
+ else
111
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
112
+ end
113
+ }
114
+
115
+ EMPTY_STREAM_RESPONSE = [nil].freeze
116
+ private_constant :EMPTY_STREAM_RESPONSE
117
+
118
+ HashifyStreamEntries = lambda { |reply|
119
+ reply.compact.map do |entry_id, values|
120
+ [entry_id, values&.each_slice(2)&.to_h]
121
+ end
122
+ }
123
+
124
+ HashifyStreamAutoclaim = lambda { |reply|
125
+ {
126
+ 'next' => reply[0],
127
+ 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
128
+ }
129
+ }
130
+
131
+ HashifyStreamAutoclaimJustId = lambda { |reply|
132
+ {
133
+ 'next' => reply[0],
134
+ 'entries' => reply[1]
135
+ }
136
+ }
137
+
138
+ HashifyStreamPendings = lambda { |reply|
139
+ {
140
+ 'size' => reply[0],
141
+ 'min_entry_id' => reply[1],
142
+ 'max_entry_id' => reply[2],
143
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
144
+ }
145
+ }
146
+
147
+ HashifyStreamPendingDetails = lambda { |reply|
148
+ reply.map do |arr|
149
+ {
150
+ 'entry_id' => arr[0],
151
+ 'consumer' => arr[1],
152
+ 'elapsed' => arr[2],
153
+ 'count' => arr[3]
154
+ }
155
+ end
156
+ }
157
+
158
+ HashifyClusterNodeInfo = lambda { |str|
159
+ arr = str.split(' ')
160
+ {
161
+ 'node_id' => arr[0],
162
+ 'ip_port' => arr[1],
163
+ 'flags' => arr[2].split(','),
164
+ 'master_node_id' => arr[3],
165
+ 'ping_sent' => arr[4],
166
+ 'pong_recv' => arr[5],
167
+ 'config_epoch' => arr[6],
168
+ 'link_state' => arr[7],
169
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
170
+ }
171
+ }
172
+
173
+ HashifyClusterSlots = lambda { |reply|
174
+ reply.map do |arr|
175
+ first_slot, last_slot = arr[0..1]
176
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
177
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
178
+ {
179
+ 'start_slot' => first_slot,
180
+ 'end_slot' => last_slot,
181
+ 'master' => master,
182
+ 'replicas' => replicas
183
+ }
184
+ end
185
+ }
186
+
187
+ HashifyClusterNodes = lambda { |reply|
188
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
189
+ }
190
+
191
+ HashifyClusterSlaves = lambda { |reply|
192
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
193
+ }
194
+
195
+ Noop = ->(reply) { reply }
196
+
197
+ # Sends a command to Redis and returns its reply.
198
+ #
199
+ # Replies are converted to Ruby objects according to the RESP protocol, so
200
+ # you can expect a Ruby array, integer or nil when Redis sends one. Higher
201
+ # level transformations, such as converting an array of pairs into a Ruby
202
+ # hash, are up to consumers.
203
+ #
204
+ # Redis error replies are raised as Ruby exceptions.
205
+ def call(*command)
206
+ send_command(command)
207
+ end
208
+
209
+ # Interact with the sentinel command (masters, master, slaves, failover)
210
+ #
211
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
212
+ # @param [Array<String>] args depends on subcommand
213
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
214
+ def sentinel(subcommand, *args)
215
+ subcommand = subcommand.to_s.downcase
216
+ send_command([:sentinel, subcommand] + args) do |reply|
217
+ case subcommand
218
+ when "get-master-addr-by-name"
219
+ reply
220
+ else
221
+ if reply.is_a?(Array)
222
+ if reply[0].is_a?(Array)
223
+ reply.map(&Hashify)
224
+ else
225
+ Hashify.call(reply)
226
+ end
227
+ else
228
+ reply
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ def method_missing(*command) # rubocop:disable Style/MissingRespondToMissing
237
+ send_command(command)
238
+ end
239
+ end
240
+ 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,7 +31,7 @@ class Redis
28
31
  command.join(COMMAND_DELIMITER)
29
32
  end
30
33
 
31
- protected
34
+ protected
32
35
 
33
36
  def encode(string)
34
37
  string.force_encoding(Encoding.default_external)
@@ -1,12 +1,14 @@
1
- require_relative "registry"
2
- require_relative "../errors"
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/connection/registry"
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
@@ -31,7 +33,7 @@ class Redis
31
33
  end
32
34
 
33
35
  def connected?
34
- @connection && @connection.connected?
36
+ @connection&.connected?
35
37
  end
36
38
 
37
39
  def timeout=(timeout)
@@ -57,7 +59,7 @@ class Redis
57
59
  rescue Errno::EAGAIN
58
60
  raise TimeoutError
59
61
  rescue RuntimeError => err
60
- raise ProtocolError.new(err.message)
62
+ raise ProtocolError, err.message
61
63
  end
62
64
  end
63
65
  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