redis 4.6.0 → 5.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -1
  3. data/README.md +75 -146
  4. data/lib/redis/client.rb +77 -616
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +53 -27
  12. data/lib/redis/commands/lists.rb +19 -23
  13. data/lib/redis/commands/pubsub.rb +7 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +43 -36
  16. data/lib/redis/commands/sorted_sets.rb +27 -13
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +26 -3
  20. data/lib/redis/commands.rb +4 -9
  21. data/lib/redis/distributed.rb +100 -67
  22. data/lib/redis/errors.rb +15 -41
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +56 -203
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +90 -178
  28. metadata +9 -53
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -37
  35. data/lib/redis/cluster/option.rb +0 -93
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -49
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -431
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -39,7 +39,10 @@ class Redis
39
39
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
40
40
  # @return [Integer] the length of the string stored in `destkey`
41
41
  def bitop(operation, destkey, *keys)
42
- send_command([:bitop, operation, destkey, *keys])
42
+ keys.flatten!(1)
43
+ command = [:bitop, operation, destkey]
44
+ command.concat(keys)
45
+ send_command(command)
43
46
  end
44
47
 
45
48
  # Return the position of the first bit set to 1 or 0 in a string.
@@ -12,24 +12,7 @@ class Redis
12
12
  #
13
13
  # @return [Object] depends on the subcommand
14
14
  def cluster(subcommand, *args)
15
- subcommand = subcommand.to_s.downcase
16
- block = case subcommand
17
- when 'slots'
18
- HashifyClusterSlots
19
- when 'nodes'
20
- HashifyClusterNodes
21
- when 'slaves'
22
- HashifyClusterSlaves
23
- when 'info'
24
- HashifyInfo
25
- else
26
- Noop
27
- end
28
-
29
- # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
30
- block = Noop unless @cluster_mode
31
-
32
- send_command([:cluster, subcommand] + args, &block)
15
+ send_command([:cluster, subcommand] + args)
33
16
  end
34
17
 
35
18
  # Sends `ASKING` command to random node and returns its reply.
@@ -34,10 +34,7 @@ class Redis
34
34
  # @param [Integer] db zero-based index of the DB to use (0 to 15)
35
35
  # @return [String] `OK`
36
36
  def select(db)
37
- synchronize do |client|
38
- client.db = db
39
- client.call([:select, db])
40
- end
37
+ send_command([:select, db])
41
38
  end
42
39
 
43
40
  # Close the connection.
@@ -45,12 +42,10 @@ class Redis
45
42
  # @return [String] `OK`
46
43
  def quit
47
44
  synchronize do |client|
48
- begin
49
- client.call([:quit])
50
- rescue ConnectionError
51
- ensure
52
- client.disconnect
53
- end
45
+ client.call_v([:quit])
46
+ rescue ConnectionError
47
+ ensure
48
+ client.close
54
49
  end
55
50
  end
56
51
  end
@@ -74,9 +74,9 @@ class Redis
74
74
  private
75
75
 
76
76
  def _geoarguments(*args, options: nil, sort: nil, count: nil)
77
- args.push sort if sort
78
- args.push 'count', count if count
79
- args.push options if options
77
+ args << sort if sort
78
+ args << 'COUNT' << Integer(count) if count
79
+ args << options if options
80
80
  args
81
81
  end
82
82
  end
@@ -63,7 +63,7 @@ class Redis
63
63
  #
64
64
  # @see #hmset
65
65
  def mapped_hmset(key, hash)
66
- hmset(key, hash.to_a.flatten)
66
+ hmset(key, hash.flatten)
67
67
  end
68
68
 
69
69
  # Get the value of a hash field.
@@ -87,7 +87,8 @@ class Redis
87
87
  #
88
88
  # @see #mapped_hmget
89
89
  def hmget(key, *fields, &blk)
90
- send_command([:hmget, key] + fields, &blk)
90
+ fields.flatten!(1)
91
+ send_command([:hmget, key].concat(fields), &blk)
91
92
  end
92
93
 
93
94
  # Get the values of all the given hash fields.
@@ -102,7 +103,8 @@ class Redis
102
103
  #
103
104
  # @see #hmget
104
105
  def mapped_hmget(key, *fields)
105
- hmget(key, *fields) do |reply|
106
+ fields.flatten!(1)
107
+ hmget(key, fields) do |reply|
106
108
  if reply.is_a?(Array)
107
109
  Hash[fields.zip(reply)]
108
110
  else
@@ -152,7 +154,8 @@ class Redis
152
154
  # @param [String, Array<String>] field
153
155
  # @return [Integer] the number of fields that were removed from the hash
154
156
  def hdel(key, *fields)
155
- send_command([:hdel, key, *fields])
157
+ fields.flatten!(1)
158
+ send_command([:hdel, key].concat(fields))
156
159
  end
157
160
 
158
161
  # Determine if a hash field exists.
@@ -171,7 +174,7 @@ class Redis
171
174
  # @param [Integer] increment
172
175
  # @return [Integer] value of the field after incrementing it
173
176
  def hincrby(key, field, increment)
174
- send_command([:hincrby, key, field, increment])
177
+ send_command([:hincrby, key, field, Integer(increment)])
175
178
  end
176
179
 
177
180
  # Increment the numeric value of a hash field by the given float number.
@@ -181,7 +184,7 @@ class Redis
181
184
  # @param [Float] increment
182
185
  # @return [Float] value of the field after incrementing it
183
186
  def hincrbyfloat(key, field, increment)
184
- send_command([:hincrbyfloat, key, field, increment], &Floatify)
187
+ send_command([:hincrbyfloat, key, field, Float(increment)], &Floatify)
185
188
  end
186
189
 
187
190
  # Get all the fields in a hash.
@@ -20,7 +20,7 @@ class Redis
20
20
  # @param [String, Array<String>] keys
21
21
  # @return [Integer]
22
22
  def pfcount(*keys)
23
- send_command([:pfcount] + keys)
23
+ send_command([:pfcount] + keys.flatten(1))
24
24
  end
25
25
 
26
26
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -69,18 +69,40 @@ class Redis
69
69
  #
70
70
  # @param [String] key
71
71
  # @param [Integer] seconds time to live
72
+ # @param [Hash] options
73
+ # - `:nx => true`: Set expiry only when the key has no expiry.
74
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
75
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
76
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
72
77
  # @return [Boolean] whether the timeout was set or not
73
- def expire(key, seconds)
74
- send_command([:expire, key, seconds], &Boolify)
78
+ def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
+ args = [:expire, key, Integer(seconds)]
80
+ args << "NX" if nx
81
+ args << "XX" if xx
82
+ args << "GT" if gt
83
+ args << "LT" if lt
84
+
85
+ send_command(args, &Boolify)
75
86
  end
76
87
 
77
88
  # Set the expiration for a key as a UNIX timestamp.
78
89
  #
79
90
  # @param [String] key
80
91
  # @param [Integer] unix_time expiry time specified as a UNIX timestamp
92
+ # @param [Hash] options
93
+ # - `:nx => true`: Set expiry only when the key has no expiry.
94
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
95
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
96
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
81
97
  # @return [Boolean] whether the timeout was set or not
82
- def expireat(key, unix_time)
83
- send_command([:expireat, key, unix_time], &Boolify)
98
+ def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
+ args = [:expireat, key, Integer(unix_time)]
100
+ args << "NX" if nx
101
+ args << "XX" if xx
102
+ args << "GT" if gt
103
+ args << "LT" if lt
104
+
105
+ send_command(args, &Boolify)
84
106
  end
85
107
 
86
108
  # Get the time to live (in seconds) for a key.
@@ -103,18 +125,40 @@ class Redis
103
125
  #
104
126
  # @param [String] key
105
127
  # @param [Integer] milliseconds time to live
128
+ # @param [Hash] options
129
+ # - `:nx => true`: Set expiry only when the key has no expiry.
130
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
131
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
132
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
106
133
  # @return [Boolean] whether the timeout was set or not
107
- def pexpire(key, milliseconds)
108
- send_command([:pexpire, key, milliseconds], &Boolify)
134
+ def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
+ args = [:pexpire, key, Integer(milliseconds)]
136
+ args << "NX" if nx
137
+ args << "XX" if xx
138
+ args << "GT" if gt
139
+ args << "LT" if lt
140
+
141
+ send_command(args, &Boolify)
109
142
  end
110
143
 
111
144
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
112
145
  #
113
146
  # @param [String] key
114
147
  # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
148
+ # @param [Hash] options
149
+ # - `:nx => true`: Set expiry only when the key has no expiry.
150
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
151
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
152
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
115
153
  # @return [Boolean] whether the timeout was set or not
116
- def pexpireat(key, ms_unix_time)
117
- send_command([:pexpireat, key, ms_unix_time], &Boolify)
154
+ def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
+ args = [:pexpireat, key, Integer(ms_unix_time)]
156
+ args << "NX" if nx
157
+ args << "XX" if xx
158
+ args << "GT" if gt
159
+ args << "LT" if lt
160
+
161
+ send_command(args, &Boolify)
118
162
  end
119
163
 
120
164
  # Get the time to live (in milliseconds) for a key.
@@ -205,24 +249,6 @@ class Redis
205
249
  # @param [String, Array<String>] keys
206
250
  # @return [Integer]
207
251
  def exists(*keys)
208
- if !Redis.exists_returns_integer && keys.size == 1
209
- if Redis.exists_returns_integer.nil?
210
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
211
- "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
212
- "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
213
- "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
214
- "(#{::Kernel.caller(1, 1).first})\n"
215
-
216
- ::Redis.deprecate!(message)
217
- end
218
-
219
- exists?(*keys)
220
- else
221
- _exists(*keys)
222
- end
223
- end
224
-
225
- def _exists(*keys)
226
252
  send_command([:exists, *keys])
227
253
  end
228
254
 
@@ -401,7 +427,7 @@ class Redis
401
427
 
402
428
  args << cursor
403
429
  args << "MATCH" << match if match
404
- args << "COUNT" << count if count
430
+ args << "COUNT" << Integer(count) if count
405
431
  args << "TYPE" << type if type
406
432
 
407
433
  send_command([command] + args, &block)
@@ -48,7 +48,7 @@ class Redis
48
48
  # @param [String, Symbol] where_destination where to push the element to the source list
49
49
  # e.g. 'LEFT' - to head, 'RIGHT' - to tail
50
50
  # @param [Hash] options
51
- # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
51
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
52
52
  #
53
53
  # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
54
54
  #
@@ -99,10 +99,10 @@ class Redis
99
99
  #
100
100
  # @param [String] key
101
101
  # @param [Integer] count number of elements to remove
102
- # @return [String, Array<String>] the values of the first elements
102
+ # @return [nil, String, Array<String>] the values of the first elements
103
103
  def lpop(key, count = nil)
104
104
  command = [:lpop, key]
105
- command << count if count
105
+ command << Integer(count) if count
106
106
  send_command(command)
107
107
  end
108
108
 
@@ -110,10 +110,10 @@ class Redis
110
110
  #
111
111
  # @param [String] key
112
112
  # @param [Integer] count number of elements to remove
113
- # @return [String, Array<String>] the values of the last elements
113
+ # @return [nil, String, Array<String>] the values of the last elements
114
114
  def rpop(key, count = nil)
115
115
  command = [:rpop, key]
116
- command << count if count
116
+ command << Integer(count) if count
117
117
  send_command(command)
118
118
  end
119
119
 
@@ -142,7 +142,7 @@ class Redis
142
142
  # @param [String, Array<String>] keys one or more keys to perform the
143
143
  # blocking pop on
144
144
  # @param [Hash] options
145
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
145
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
146
146
  #
147
147
  # @return [nil, [String, String]]
148
148
  # - `nil` when the operation timed out
@@ -156,7 +156,7 @@ class Redis
156
156
  # @param [String, Array<String>] keys one or more keys to perform the
157
157
  # blocking pop on
158
158
  # @param [Hash] options
159
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
159
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
160
160
  #
161
161
  # @return [nil, [String, String]]
162
162
  # - `nil` when the operation timed out
@@ -173,12 +173,12 @@ class Redis
173
173
  # @param [String] source source key
174
174
  # @param [String] destination destination key
175
175
  # @param [Hash] options
176
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
176
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
177
177
  #
178
178
  # @return [nil, String]
179
179
  # - `nil` when the operation timed out
180
180
  # - the element was popped and pushed otherwise
181
- def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
181
+ def brpoplpush(source, destination, timeout: 0)
182
182
  command = [:brpoplpush, source, destination, timeout]
183
183
  send_blocking_command(command, timeout)
184
184
  end
@@ -189,7 +189,7 @@ class Redis
189
189
  # @param [Integer] index
190
190
  # @return [String]
191
191
  def lindex(key, index)
192
- send_command([:lindex, key, index])
192
+ send_command([:lindex, key, Integer(index)])
193
193
  end
194
194
 
195
195
  # Insert an element before or after another element in a list.
@@ -211,7 +211,7 @@ class Redis
211
211
  # @param [Integer] stop stop index
212
212
  # @return [Array<String>]
213
213
  def lrange(key, start, stop)
214
- send_command([:lrange, key, start, stop])
214
+ send_command([:lrange, key, Integer(start), Integer(stop)])
215
215
  end
216
216
 
217
217
  # Remove elements from a list.
@@ -224,7 +224,7 @@ class Redis
224
224
  # @param [String] value
225
225
  # @return [Integer] the number of removed elements
226
226
  def lrem(key, count, value)
227
- send_command([:lrem, key, count, value])
227
+ send_command([:lrem, key, Integer(count), value])
228
228
  end
229
229
 
230
230
  # Set the value of an element in a list by its index.
@@ -234,7 +234,7 @@ class Redis
234
234
  # @param [String] value
235
235
  # @return [String] `OK`
236
236
  def lset(key, index, value)
237
- send_command([:lset, key, index, value])
237
+ send_command([:lset, key, Integer(index), value])
238
238
  end
239
239
 
240
240
  # Trim a list to the specified range.
@@ -244,7 +244,7 @@ class Redis
244
244
  # @param [Integer] stop stop index
245
245
  # @return [String] `OK`
246
246
  def ltrim(key, start, stop)
247
- send_command([:ltrim, key, start, stop])
247
+ send_command([:ltrim, key, Integer(start), Integer(stop)])
248
248
  end
249
249
 
250
250
  private
@@ -253,20 +253,16 @@ class Redis
253
253
  timeout = if args.last.is_a?(Hash)
254
254
  options = args.pop
255
255
  options[:timeout]
256
- elsif args.last.respond_to?(:to_int)
257
- # Issue deprecation notice in obnoxious mode...
258
- args.pop.to_int
259
256
  end
260
257
 
261
258
  timeout ||= 0
262
-
263
- if args.size > 1
264
- # Issue deprecation notice in obnoxious mode...
259
+ unless timeout.is_a?(Integer) || timeout.is_a?(Float)
260
+ raise ArgumentError, "timeout must be an Integer or Float, got: #{timeout.class}"
265
261
  end
266
262
 
267
- keys = args.flatten
268
-
269
- command = [cmd, keys, timeout]
263
+ args.flatten!(1)
264
+ command = [cmd].concat(args)
265
+ command << timeout
270
266
  send_blocking_command(command, timeout, &blk)
271
267
  end
272
268
 
@@ -9,57 +9,39 @@ class Redis
9
9
  end
10
10
 
11
11
  def subscribed?
12
- synchronize do |client|
13
- client.is_a? SubscribedClient
14
- end
12
+ !@subscription_client.nil?
15
13
  end
16
14
 
17
15
  # Listen for messages published to the given channels.
18
16
  def subscribe(*channels, &block)
19
- synchronize do |_client|
20
- _subscription(:subscribe, 0, channels, block)
21
- end
17
+ _subscription(:subscribe, 0, channels, block)
22
18
  end
23
19
 
24
20
  # Listen for messages published to the given channels. Throw a timeout error
25
21
  # if there is no messages for a timeout period.
26
22
  def subscribe_with_timeout(timeout, *channels, &block)
27
- synchronize do |_client|
28
- _subscription(:subscribe_with_timeout, timeout, channels, block)
29
- end
23
+ _subscription(:subscribe_with_timeout, timeout, channels, block)
30
24
  end
31
25
 
32
26
  # Stop listening for messages posted to the given channels.
33
27
  def unsubscribe(*channels)
34
- synchronize do |client|
35
- raise "Can't unsubscribe if not subscribed." unless subscribed?
36
-
37
- client.unsubscribe(*channels)
38
- end
28
+ _subscription(:unsubscribe, 0, channels, nil)
39
29
  end
40
30
 
41
31
  # Listen for messages published to channels matching the given patterns.
42
32
  def psubscribe(*channels, &block)
43
- synchronize do |_client|
44
- _subscription(:psubscribe, 0, channels, block)
45
- end
33
+ _subscription(:psubscribe, 0, channels, block)
46
34
  end
47
35
 
48
36
  # Listen for messages published to channels matching the given patterns.
49
37
  # Throw a timeout error if there is no messages for a timeout period.
50
38
  def psubscribe_with_timeout(timeout, *channels, &block)
51
- synchronize do |_client|
52
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
53
- end
39
+ _subscription(:psubscribe_with_timeout, timeout, channels, block)
54
40
  end
55
41
 
56
42
  # Stop listening for messages posted to channels matching the given patterns.
57
43
  def punsubscribe(*channels)
58
- synchronize do |client|
59
- raise "Can't unsubscribe if not subscribed." unless subscribed?
60
-
61
- client.punsubscribe(*channels)
62
- end
44
+ _subscription(:punsubscribe, 0, channels, nil)
63
45
  end
64
46
 
65
47
  # Inspect the state of the Pub/Sub subsystem.
@@ -36,7 +36,7 @@ class Redis
36
36
  #
37
37
  # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
38
38
  # @return [String, Hash] depends on subcommand
39
- def client(subcommand = nil, *args)
39
+ def client(subcommand, *args)
40
40
  send_command([:client, subcommand] + args) do |reply|
41
41
  if subcommand.to_s == "list"
42
42
  reply.lines.map do |line|
@@ -117,9 +117,13 @@ class Redis
117
117
  #
118
118
  # @yield a block to be called for every line of output
119
119
  # @yieldparam [String] line timestamp and command that was executed
120
- def monitor(&block)
120
+ def monitor
121
121
  synchronize do |client|
122
- client.call_loop([:monitor], &block)
122
+ client = client.pubsub
123
+ client.call_v([:monitor])
124
+ loop do
125
+ yield client.next_event
126
+ end
123
127
  end
124
128
  end
125
129
 
@@ -133,13 +137,11 @@ class Redis
133
137
  # Synchronously save the dataset to disk and then shut down the server.
134
138
  def shutdown
135
139
  synchronize do |client|
136
- client.with_reconnect(false) do
137
- begin
138
- client.call([:shutdown])
139
- rescue ConnectionError
140
- # This means Redis has probably exited.
141
- nil
142
- end
140
+ client.disable_reconnection do
141
+ client.call_v([:shutdown])
142
+ rescue ConnectionError
143
+ # This means Redis has probably exited.
144
+ nil
143
145
  end
144
146
  end
145
147
  end
@@ -155,11 +157,9 @@ class Redis
155
157
  # @param [Integer] length maximum number of entries to return
156
158
  # @return [Array<String>, Integer, String] depends on subcommand
157
159
  def slowlog(subcommand, length = nil)
158
- synchronize do |client|
159
- args = [:slowlog, subcommand]
160
- args << length if length
161
- client.call args
162
- end
160
+ args = [:slowlog, subcommand]
161
+ args << Integer(length) if length
162
+ send_command(args)
163
163
  end
164
164
 
165
165
  # Internal command used for replication.