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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -0
- data/README.md +25 -10
- data/lib/redis/client.rb +31 -25
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +8 -9
- data/lib/redis/cluster/node.rb +12 -0
- data/lib/redis/cluster/node_loader.rb +8 -11
- data/lib/redis/cluster/option.rb +10 -3
- data/lib/redis/cluster/slot_loader.rb +9 -12
- data/lib/redis/cluster.rb +24 -0
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +455 -0
- data/lib/redis/commands/lists.rb +290 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +223 -0
- data/lib/redis/commands/sorted_sets.rb +812 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +139 -0
- data/lib/redis/commands.rb +240 -0
- data/lib/redis/connection/command_helper.rb +2 -0
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/ruby.rb +19 -9
- data/lib/redis/connection/synchrony.rb +10 -8
- data/lib/redis/connection.rb +1 -1
- data/lib/redis/distributed.rb +111 -23
- data/lib/redis/errors.rb +9 -0
- data/lib/redis/pipeline.rb +128 -3
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +138 -3482
- metadata +22 -5
data/lib/redis/distributed.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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
|
-
#
|
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
|
data/lib/redis/pipeline.rb
CHANGED
@@ -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.
|
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
|
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
|
-
::
|
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