redis 4.1.0 → 4.6.0

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 (43) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +158 -0
  3. data/README.md +91 -27
  4. data/lib/redis/client.rb +148 -92
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +6 -7
  7. data/lib/redis/cluster/node.rb +17 -1
  8. data/lib/redis/cluster/node_key.rb +3 -7
  9. data/lib/redis/cluster/option.rb +30 -14
  10. data/lib/redis/cluster/slot.rb +30 -13
  11. data/lib/redis/cluster/slot_loader.rb +4 -4
  12. data/lib/redis/cluster.rb +46 -17
  13. data/lib/redis/commands/bitmaps.rb +63 -0
  14. data/lib/redis/commands/cluster.rb +45 -0
  15. data/lib/redis/commands/connection.rb +58 -0
  16. data/lib/redis/commands/geo.rb +84 -0
  17. data/lib/redis/commands/hashes.rb +251 -0
  18. data/lib/redis/commands/hyper_log_log.rb +37 -0
  19. data/lib/redis/commands/keys.rb +411 -0
  20. data/lib/redis/commands/lists.rb +289 -0
  21. data/lib/redis/commands/pubsub.rb +72 -0
  22. data/lib/redis/commands/scripting.rb +114 -0
  23. data/lib/redis/commands/server.rb +188 -0
  24. data/lib/redis/commands/sets.rb +207 -0
  25. data/lib/redis/commands/sorted_sets.rb +804 -0
  26. data/lib/redis/commands/streams.rb +382 -0
  27. data/lib/redis/commands/strings.rb +313 -0
  28. data/lib/redis/commands/transactions.rb +92 -0
  29. data/lib/redis/commands.rb +242 -0
  30. data/lib/redis/connection/command_helper.rb +5 -2
  31. data/lib/redis/connection/hiredis.rb +7 -5
  32. data/lib/redis/connection/registry.rb +2 -1
  33. data/lib/redis/connection/ruby.rb +129 -110
  34. data/lib/redis/connection/synchrony.rb +17 -10
  35. data/lib/redis/connection.rb +3 -1
  36. data/lib/redis/distributed.rb +209 -70
  37. data/lib/redis/errors.rb +2 -0
  38. data/lib/redis/hash_ring.rb +15 -14
  39. data/lib/redis/pipeline.rb +139 -8
  40. data/lib/redis/subscribe.rb +11 -12
  41. data/lib/redis/version.rb +3 -1
  42. data/lib/redis.rb +167 -3377
  43. metadata +32 -25
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Transactions
6
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
7
+ #
8
+ # Using a block is optional, but is necessary for thread-safety.
9
+ #
10
+ # An `#unwatch` is automatically issued if an exception is raised within the
11
+ # block that is a subclass of StandardError and is not a ConnectionError.
12
+ #
13
+ # @example With a block
14
+ # redis.watch("key") do
15
+ # if redis.get("key") == "some value"
16
+ # redis.multi do |multi|
17
+ # multi.set("key", "other value")
18
+ # multi.incr("counter")
19
+ # end
20
+ # else
21
+ # redis.unwatch
22
+ # end
23
+ # end
24
+ # # => ["OK", 6]
25
+ #
26
+ # @example Without a block
27
+ # redis.watch("key")
28
+ # # => "OK"
29
+ #
30
+ # @param [String, Array<String>] keys one or more keys to watch
31
+ # @return [Object] if using a block, returns the return value of the block
32
+ # @return [String] if not using a block, returns `OK`
33
+ #
34
+ # @see #unwatch
35
+ # @see #multi
36
+ def watch(*keys)
37
+ synchronize do |client|
38
+ res = client.call([:watch, *keys])
39
+
40
+ if block_given?
41
+ begin
42
+ yield(self)
43
+ rescue ConnectionError
44
+ raise
45
+ rescue StandardError
46
+ unwatch
47
+ raise
48
+ end
49
+ else
50
+ res
51
+ end
52
+ end
53
+ end
54
+
55
+ # Forget about all watched keys.
56
+ #
57
+ # @return [String] `OK`
58
+ #
59
+ # @see #watch
60
+ # @see #multi
61
+ def unwatch
62
+ send_command([:unwatch])
63
+ end
64
+
65
+ # Execute all commands issued after MULTI.
66
+ #
67
+ # Only call this method when `#multi` was called **without** a block.
68
+ #
69
+ # @return [nil, Array<...>]
70
+ # - when commands were not executed, `nil`
71
+ # - when commands were executed, an array with their replies
72
+ #
73
+ # @see #multi
74
+ # @see #discard
75
+ def exec
76
+ send_command([:exec])
77
+ end
78
+
79
+ # Discard all commands issued after MULTI.
80
+ #
81
+ # Only call this method when `#multi` was called **without** a block.
82
+ #
83
+ # @return [String] `"OK"`
84
+ #
85
+ # @see #multi
86
+ # @see #exec
87
+ def discard
88
+ send_command([:discard])
89
+ end
90
+ end
91
+ end
92
+ 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,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