redis 4.4.0 → 4.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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