redis 4.4.0 → 4.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -0
  3. data/README.md +25 -10
  4. data/lib/redis/client.rb +31 -25
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +8 -9
  7. data/lib/redis/cluster/node.rb +12 -0
  8. data/lib/redis/cluster/node_loader.rb +8 -11
  9. data/lib/redis/cluster/option.rb +10 -3
  10. data/lib/redis/cluster/slot_loader.rb +9 -12
  11. data/lib/redis/cluster.rb +24 -0
  12. data/lib/redis/commands/bitmaps.rb +63 -0
  13. data/lib/redis/commands/cluster.rb +45 -0
  14. data/lib/redis/commands/connection.rb +58 -0
  15. data/lib/redis/commands/geo.rb +84 -0
  16. data/lib/redis/commands/hashes.rb +251 -0
  17. data/lib/redis/commands/hyper_log_log.rb +37 -0
  18. data/lib/redis/commands/keys.rb +455 -0
  19. data/lib/redis/commands/lists.rb +290 -0
  20. data/lib/redis/commands/pubsub.rb +72 -0
  21. data/lib/redis/commands/scripting.rb +114 -0
  22. data/lib/redis/commands/server.rb +188 -0
  23. data/lib/redis/commands/sets.rb +223 -0
  24. data/lib/redis/commands/sorted_sets.rb +812 -0
  25. data/lib/redis/commands/streams.rb +382 -0
  26. data/lib/redis/commands/strings.rb +313 -0
  27. data/lib/redis/commands/transactions.rb +139 -0
  28. data/lib/redis/commands.rb +240 -0
  29. data/lib/redis/connection/command_helper.rb +2 -0
  30. data/lib/redis/connection/hiredis.rb +3 -2
  31. data/lib/redis/connection/ruby.rb +19 -9
  32. data/lib/redis/connection/synchrony.rb +10 -8
  33. data/lib/redis/connection.rb +1 -1
  34. data/lib/redis/distributed.rb +111 -23
  35. data/lib/redis/errors.rb +9 -0
  36. data/lib/redis/pipeline.rb +128 -3
  37. data/lib/redis/version.rb +1 -1
  38. data/lib/redis.rb +138 -3482
  39. metadata +22 -5
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "hash_ring"
3
+ require "redis/hash_ring"
4
4
 
5
5
  class Redis
6
6
  class Distributed
@@ -115,13 +115,13 @@ class Redis
115
115
  end
116
116
 
117
117
  # Set a key's time to live in seconds.
118
- def expire(key, seconds)
119
- node_for(key).expire(key, seconds)
118
+ def expire(key, seconds, **kwargs)
119
+ node_for(key).expire(key, seconds, **kwargs)
120
120
  end
121
121
 
122
122
  # Set the expiration for a key as a UNIX timestamp.
123
- def expireat(key, unix_time)
124
- node_for(key).expireat(key, unix_time)
123
+ def expireat(key, unix_time, **kwargs)
124
+ node_for(key).expireat(key, unix_time, **kwargs)
125
125
  end
126
126
 
127
127
  # Get the time to live (in seconds) for a key.
@@ -130,13 +130,13 @@ class Redis
130
130
  end
131
131
 
132
132
  # Set a key's time to live in milliseconds.
133
- def pexpire(key, milliseconds)
134
- node_for(key).pexpire(key, milliseconds)
133
+ def pexpire(key, milliseconds, **kwarg)
134
+ node_for(key).pexpire(key, milliseconds, **kwarg)
135
135
  end
136
136
 
137
137
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
138
- def pexpireat(key, ms_unix_time)
139
- node_for(key).pexpireat(key, ms_unix_time)
138
+ def pexpireat(key, ms_unix_time, **kwarg)
139
+ node_for(key).pexpireat(key, ms_unix_time, **kwarg)
140
140
  end
141
141
 
142
142
  # Get the time to live (in milliseconds) for a key.
@@ -178,15 +178,11 @@ class Redis
178
178
  # Determine if a key exists.
179
179
  def exists(*args)
180
180
  if !Redis.exists_returns_integer && args.size == 1
181
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
181
+ ::Redis.deprecate!(
182
+ "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
182
183
  "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
183
184
  "(#{::Kernel.caller(1, 1).first})\n"
184
-
185
- if defined?(::Warning)
186
- ::Warning.warn(message)
187
- else
188
- warn(message)
189
- end
185
+ )
190
186
  exists?(*args)
191
187
  else
192
188
  keys_per_node = args.group_by { |key| node_for(key) }
@@ -215,6 +211,13 @@ class Redis
215
211
  node_for(key).move(key, db)
216
212
  end
217
213
 
214
+ # Copy a value from one key to another.
215
+ def copy(source, destination, **options)
216
+ ensure_same_node(:copy, [source, destination]) do |node|
217
+ node.copy(source, destination, **options)
218
+ end
219
+ end
220
+
218
221
  # Return a random key from the keyspace.
219
222
  def randomkey
220
223
  raise CannotDistribute, :randomkey
@@ -316,6 +319,16 @@ class Redis
316
319
  node_for(key).get(key)
317
320
  end
318
321
 
322
+ # Get the value of a key and delete it.
323
+ def getdel(key)
324
+ node_for(key).getdel(key)
325
+ end
326
+
327
+ # Get the value of a key and sets its time to live based on options.
328
+ def getex(key, **options)
329
+ node_for(key).getex(key, **options)
330
+ end
331
+
319
332
  # Get the values of all the given keys as an Array.
320
333
  def mget(*keys)
321
334
  mapped_mget(*keys).values_at(*keys)
@@ -393,6 +406,21 @@ class Redis
393
406
  node_for(key).llen(key)
394
407
  end
395
408
 
409
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
410
+ def lmove(source, destination, where_source, where_destination)
411
+ ensure_same_node(:lmove, [source, destination]) do |node|
412
+ node.lmove(source, destination, where_source, where_destination)
413
+ end
414
+ end
415
+
416
+ # Remove the first/last element in a list and append/prepend it
417
+ # to another list and return it, or block until one is available.
418
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
419
+ ensure_same_node(:lmove, [source, destination]) do |node|
420
+ node.blmove(source, destination, where_source, where_destination, timeout: timeout)
421
+ end
422
+ end
423
+
396
424
  # Prepend one or more values to a list.
397
425
  def lpush(key, value)
398
426
  node_for(key).lpush(key, value)
@@ -436,12 +464,13 @@ class Redis
436
464
  options = args.pop
437
465
  options[:timeout]
438
466
  elsif args.last.respond_to?(:to_int)
439
- # Issue deprecation notice in obnoxious mode...
440
- args.pop.to_int
441
- end
442
-
443
- if args.size > 1
444
- # Issue deprecation notice in obnoxious mode...
467
+ last_arg = args.pop
468
+ ::Redis.deprecate!(
469
+ "Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
470
+ " redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
471
+ "(called from: #{caller(2, 1).first})"
472
+ )
473
+ last_arg.to_int
445
474
  end
446
475
 
447
476
  keys = args.flatten
@@ -515,11 +544,21 @@ class Redis
515
544
  node_for(key).sadd(key, member)
516
545
  end
517
546
 
547
+ # Add one or more members to a set.
548
+ def sadd?(key, member)
549
+ node_for(key).sadd?(key, member)
550
+ end
551
+
518
552
  # Remove one or more members from a set.
519
553
  def srem(key, member)
520
554
  node_for(key).srem(key, member)
521
555
  end
522
556
 
557
+ # Remove one or more members from a set.
558
+ def srem?(key, member)
559
+ node_for(key).srem?(key, member)
560
+ end
561
+
523
562
  # Remove and return a random member from a set.
524
563
  def spop(key, count = nil)
525
564
  node_for(key).spop(key, count)
@@ -542,6 +581,11 @@ class Redis
542
581
  node_for(key).sismember(key, member)
543
582
  end
544
583
 
584
+ # Determine if multiple values are members of a set.
585
+ def smismember(key, *members)
586
+ node_for(key).smismember(key, *members)
587
+ end
588
+
545
589
  # Get all the members in a set.
546
590
  def smembers(key)
547
591
  node_for(key).smembers(key)
@@ -626,11 +670,29 @@ class Redis
626
670
  node_for(key).zscore(key, member)
627
671
  end
628
672
 
629
- # Return a range of members in a sorted set, by index.
673
+ # Get one or more random members from a sorted set.
674
+ def zrandmember(key, count = nil, **options)
675
+ node_for(key).zrandmember(key, count, **options)
676
+ end
677
+
678
+ # Get the scores associated with the given members in a sorted set.
679
+ def zmscore(key, *members)
680
+ node_for(key).zmscore(key, *members)
681
+ end
682
+
683
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
630
684
  def zrange(key, start, stop, **options)
631
685
  node_for(key).zrange(key, start, stop, **options)
632
686
  end
633
687
 
688
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
689
+ # and store the resulting sorted set in a new key.
690
+ def zrangestore(dest_key, src_key, start, stop, **options)
691
+ ensure_same_node(:zrangestore, [dest_key, src_key]) do |node|
692
+ node.zrangestore(dest_key, src_key, start, stop, **options)
693
+ end
694
+ end
695
+
634
696
  # Return a range of members in a sorted set, by index, with scores ordered
635
697
  # from high to low.
636
698
  def zrevrange(key, start, stop, **options)
@@ -689,6 +751,13 @@ class Redis
689
751
  end
690
752
  end
691
753
 
754
+ # Return the union of multiple sorted sets.
755
+ def zunion(*keys, **options)
756
+ ensure_same_node(:zunion, keys) do |node|
757
+ node.zunion(*keys, **options)
758
+ end
759
+ end
760
+
692
761
  # Add multiple sorted sets and store the resulting sorted set in a new key.
693
762
  def zunionstore(destination, keys, **options)
694
763
  ensure_same_node(:zunionstore, [destination] + keys) do |node|
@@ -696,6 +765,21 @@ class Redis
696
765
  end
697
766
  end
698
767
 
768
+ # Return the difference between the first and all successive input sorted sets.
769
+ def zdiff(*keys, **options)
770
+ ensure_same_node(:zdiff, keys) do |node|
771
+ node.zdiff(*keys, **options)
772
+ end
773
+ end
774
+
775
+ # Compute the difference between the first and all successive input sorted sets
776
+ # and store the resulting sorted set in a new key.
777
+ def zdiffstore(destination, keys, **options)
778
+ ensure_same_node(:zdiffstore, [destination] + keys) do |node|
779
+ node.zdiffstore(destination, keys, **options)
780
+ end
781
+ end
782
+
699
783
  # Get the number of fields in a hash.
700
784
  def hlen(key)
701
785
  node_for(key).hlen(key)
@@ -734,6 +818,10 @@ class Redis
734
818
  Hash[*fields.zip(hmget(key, *fields)).flatten]
735
819
  end
736
820
 
821
+ def hrandfield(key, count = nil, **options)
822
+ node_for(key).hrandfield(key, count, **options)
823
+ end
824
+
737
825
  # Delete one or more hash fields.
738
826
  def hdel(key, *fields)
739
827
  node_for(key).hdel(key, *fields)
data/lib/redis/errors.rb CHANGED
@@ -45,6 +45,15 @@ class Redis
45
45
  end
46
46
 
47
47
  class Cluster
48
+ # Raised when client connected to redis as cluster mode
49
+ # and failed to fetch cluster state information by commands.
50
+ class InitialSetupError < BaseError
51
+ # @param errors [Array<Redis::BaseError>]
52
+ def initialize(errors)
53
+ super("Redis client could not fetch cluster information: #{errors.map(&:message).uniq.join(',')}")
54
+ end
55
+ end
56
+
48
57
  # Raised when client connected to redis as cluster mode
49
58
  # and some cluster subcommands were called.
50
59
  class OrchestrationCommandNotSupported < BaseError
@@ -1,11 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
4
+
3
5
  class Redis
6
+ class PipelinedConnection
7
+ def initialize(pipeline)
8
+ @pipeline = pipeline
9
+ end
10
+
11
+ include Commands
12
+
13
+ def db
14
+ @pipeline.db
15
+ end
16
+
17
+ def db=(db)
18
+ @pipeline.db = db
19
+ end
20
+
21
+ def pipelined
22
+ yield self
23
+ end
24
+
25
+ def call_pipeline(pipeline)
26
+ @pipeline.call_pipeline(pipeline)
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ def synchronize
33
+ yield self
34
+ end
35
+
36
+ def send_command(command, &block)
37
+ @pipeline.call(command, &block)
38
+ end
39
+
40
+ def send_blocking_command(command, timeout, &block)
41
+ @pipeline.call_with_timeout(command, timeout, &block)
42
+ end
43
+ end
44
+
4
45
  class Pipeline
46
+ REDIS_INTERNAL_PATH = File.expand_path("..", __dir__).freeze
47
+ # Redis use MonitorMixin#synchronize and this class use DelegateClass which we want to filter out.
48
+ # Both are in the stdlib so we can simply filter the entire stdlib out.
49
+ STDLIB_PATH = File.expand_path("..", MonitorMixin.instance_method(:synchronize).source_location.first).freeze
50
+
51
+ class << self
52
+ def deprecation_warning(method, caller_locations) # :nodoc:
53
+ callsite = caller_locations.find { |l| !l.path.start_with?(REDIS_INTERNAL_PATH, STDLIB_PATH) }
54
+ callsite ||= caller_locations.last # The caller_locations should be large enough, but just in case.
55
+ ::Redis.deprecate! <<~MESSAGE
56
+ Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.
57
+
58
+ redis.#{method} do
59
+ redis.get("key")
60
+ end
61
+
62
+ should be replaced by
63
+
64
+ redis.#{method} do |pipeline|
65
+ pipeline.get("key")
66
+ end
67
+
68
+ (called from #{callsite}}
69
+ MESSAGE
70
+ end
71
+ end
72
+
5
73
  attr_accessor :db
6
74
  attr_reader :client
7
75
 
8
76
  attr :futures
77
+ alias materialized_futures futures
9
78
 
10
79
  def initialize(client)
11
80
  @client = client.is_a?(Pipeline) ? client.client : client
@@ -49,7 +118,7 @@ class Redis
49
118
 
50
119
  def call_pipeline(pipeline)
51
120
  @shutdown = true if pipeline.shutdown?
52
- @futures.concat(pipeline.futures)
121
+ @futures.concat(pipeline.materialized_futures)
53
122
  @db = pipeline.db
54
123
  nil
55
124
  end
@@ -106,6 +175,18 @@ class Redis
106
175
  end
107
176
  end
108
177
 
178
+ def materialized_futures
179
+ if empty?
180
+ []
181
+ else
182
+ [
183
+ Future.new([:multi], nil, 0),
184
+ *futures,
185
+ MultiFuture.new(futures)
186
+ ]
187
+ end
188
+ end
189
+
109
190
  def timeouts
110
191
  if empty?
111
192
  []
@@ -124,6 +205,36 @@ class Redis
124
205
  end
125
206
  end
126
207
 
208
+ class DeprecatedPipeline < DelegateClass(Pipeline)
209
+ def initialize(pipeline)
210
+ super(pipeline)
211
+ @deprecation_displayed = false
212
+ end
213
+
214
+ def __getobj__
215
+ unless @deprecation_displayed
216
+ Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 10))
217
+ @deprecation_displayed = true
218
+ end
219
+ @delegate_dc_obj
220
+ end
221
+ end
222
+
223
+ class DeprecatedMulti < DelegateClass(Pipeline::Multi)
224
+ def initialize(pipeline)
225
+ super(pipeline)
226
+ @deprecation_displayed = false
227
+ end
228
+
229
+ def __getobj__
230
+ unless @deprecation_displayed
231
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 10))
232
+ @deprecation_displayed = true
233
+ end
234
+ @delegate_dc_obj
235
+ end
236
+ end
237
+
127
238
  class FutureNotReady < RuntimeError
128
239
  def initialize
129
240
  super("Value will be available once the pipeline executes.")
@@ -143,11 +254,11 @@ class Redis
143
254
  end
144
255
 
145
256
  def ==(_other)
146
- message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
257
+ message = +"The methods == and != are deprecated for Redis::Future and will be removed in 5.0.0"
147
258
  message << " - You probably meant to call .value == or .value !="
148
259
  message << " (#{::Kernel.caller(1, 1).first})\n"
149
260
 
150
- ::Kernel.warn(message)
261
+ ::Redis.deprecate!(message)
151
262
 
152
263
  super
153
264
  end
@@ -178,4 +289,18 @@ class Redis
178
289
  Future
179
290
  end
180
291
  end
292
+
293
+ class MultiFuture < Future
294
+ def initialize(futures)
295
+ @futures = futures
296
+ @command = [:exec]
297
+ end
298
+
299
+ def _set(replies)
300
+ @futures.each_with_index do |future, index|
301
+ future._set(replies[index])
302
+ end
303
+ replies
304
+ end
305
+ end
181
306
  end
data/lib/redis/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Redis
4
- VERSION = '4.4.0'
4
+ VERSION = '4.8.1'
5
5
  end