redis 4.4.0 → 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 (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