redis 4.3.1 → 4.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 416a2f007042c19453c13361aa4440a507e47fb32c28adc68e7c574c6651f5b4
4
- data.tar.gz: 1a845f2af649d64f8b274962c9d5d10e6eb5d046474b6e44288676432fe8a98b
3
+ metadata.gz: 7a7232fef186e6d6a11a90d8dd9aa7c71114f017e0afe9378999a96c9e6b4e05
4
+ data.tar.gz: 689f176b87909bf61eb60e57d1eb795198162a7e039104c80facf36880964bda
5
5
  SHA512:
6
- metadata.gz: 3766992242ae284ca474bc8564c6760de88e635a8c3bc3c80da08062d698cc891bf00455b5d98768709ecc766f8ad305fe03cc5806f03fda3ebb93049e0a1cce
7
- data.tar.gz: f440c984ec58ff091a6a696952239cb04cf145752b485543e5da7215a327b40be4391b3fe6ca67753f84ec43913b9d90ec0b6f812e1696890a7c17cbf3aa3630
6
+ metadata.gz: 22b11dee92e298b46bb94a707156d3dbf9afb83c8e9e8cbf82cf366d582a7c1b7295d7f09a0fec01965245f4800c2482c0559d646f46ff1c6bba6423ab398ba9
7
+ data.tar.gz: 9a74ba29c8cb3d7634a44c78e30018a048e19cca66c7fad6a226722183f66546bebb370c92ecdf20522bb35ee72a09b2fda64891ff6d94db1702375bd1ba6b46
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.5.1
4
+
5
+ * Restore the accidential auth behavior of redis-rb 4.3.0 with a warning. If provided with the `default` user's password, but a wrong username,
6
+ redis-rb will first try to connect as the provided user, but then will fallback to connect as the `default` user with the provided password.
7
+ This behavior is deprecated and will be removed in Redis 4.6.0. Fix #1038.
8
+
9
+ # 4.5.0
10
+
11
+ * Handle parts of the command using incompatible encodings. See #1037.
12
+ * Add GET option to SET command. See #1036.
13
+ * Add ZRANDMEMBER command. See #1035.
14
+ * Add LMOVE/BLMOVE commands. See #1034.
15
+ * Add ZMSCORE command. See #1032.
16
+ * Add LT/GT options to ZADD. See #1033.
17
+ * Add SMISMEMBER command. See #1031.
18
+ * Add EXAT/PXAT options to SET. See #1028.
19
+ * Add GETDEL/GETEX commands. See #1024.
20
+ * `Redis#exists` now returns an Integer by default, as warned since 4.2.0. The old behavior can be restored with `Redis.exists_returns_integer = false`.
21
+ * Fix Redis < 6 detection during connect. See #1025.
22
+ * Fix fetching command details in Redis cluster when the first node is unhealthy. See #1026.
23
+
24
+ # 4.4.0
25
+
26
+ * Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
27
+ * Add support for `XAUTOCLAIM`. See #1018.
28
+ * Properly issue `READONLY` when reconnecting to replicas. Fix #1017.
29
+ * Make `del` a noop if passed an empty list of keys. See #998.
30
+ * Add support for `ZINTER`. See #995.
31
+
3
32
  # 4.3.1
4
33
 
5
34
  * Fix password authentication against redis server 5 and older.
data/lib/redis/client.rb CHANGED
@@ -119,13 +119,29 @@ class Redis
119
119
  if username
120
120
  begin
121
121
  call [:auth, username, password]
122
- rescue CommandError # Likely on Redis < 6
123
- call [:auth, password]
122
+ rescue CommandError => err # Likely on Redis < 6
123
+ if err.message.match?(/ERR wrong number of arguments for \'auth\' command/)
124
+ call [:auth, password]
125
+ elsif err.message.match?(/WRONGPASS invalid username-password pair/)
126
+ begin
127
+ call [:auth, password]
128
+ rescue CommandError
129
+ raise err
130
+ end
131
+ ::Kernel.warn(
132
+ "[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
133
+ " the provided password was for the default user. This will start failing in redis-rb 4.6."
134
+ )
135
+ else
136
+ raise
137
+ end
124
138
  end
125
139
  else
126
140
  call [:auth, password]
127
141
  end
128
142
  end
143
+
144
+ call [:readonly] if @options[:readonly]
129
145
  call [:select, db] if db != 0
130
146
  call [:client, :setname, @options[:id]] if @options[:id]
131
147
  @connector.check(self)
@@ -10,22 +10,21 @@ class Redis
10
10
  module_function
11
11
 
12
12
  def load(nodes)
13
- details = {}
14
-
15
13
  nodes.each do |node|
16
- details = fetch_command_details(node)
17
- details.empty? ? next : break
14
+ begin
15
+ return fetch_command_details(node)
16
+ rescue CannotConnectError, ConnectionError, CommandError
17
+ next # can retry on another node
18
+ end
18
19
  end
19
20
 
20
- details
21
+ raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
21
22
  end
22
23
 
23
24
  def fetch_command_details(node)
24
25
  node.call(%i[command]).map do |reply|
25
26
  [reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
26
27
  end.to_h
27
- rescue CannotConnectError, ConnectionError, CommandError
28
- {} # can retry on another node
29
28
  end
30
29
 
31
30
  private_class_method :fetch_command_details
@@ -76,8 +76,9 @@ class Redis
76
76
  clients = options.map do |node_key, option|
77
77
  next if replica_disabled? && slave?(node_key)
78
78
 
79
+ option = option.merge(readonly: true) if slave?(node_key)
80
+
79
81
  client = Client.new(option)
80
- client.call(%i[readonly]) if slave?(node_key)
81
82
  [node_key, client]
82
83
  end
83
84
 
data/lib/redis/cluster.rb CHANGED
@@ -78,11 +78,13 @@ class Redis
78
78
  end
79
79
 
80
80
  def call_pipeline(pipeline)
81
- node_keys, command_keys = extract_keys_in_pipeline(pipeline)
82
- raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
81
+ node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
82
+ if node_keys.size > 1
83
+ raise(CrossSlotPipeliningError,
84
+ pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
85
+ end
83
86
 
84
- node = find_node(node_keys.first)
85
- try_send(node, :call_pipeline, pipeline)
87
+ try_send(find_node(node_keys.first), :call_pipeline, pipeline)
86
88
  end
87
89
 
88
90
  def call_with_timeout(command, timeout, &block)
@@ -253,14 +255,14 @@ class Redis
253
255
  find_node(node_key)
254
256
  end
255
257
 
256
- def find_node_key(command)
258
+ def find_node_key(command, primary_only: false)
257
259
  key = @command.extract_first_key(command)
258
260
  return if key.empty?
259
261
 
260
262
  slot = KeySlotConverter.convert(key)
261
263
  return unless @slot.exists?(slot)
262
264
 
263
- if @command.should_send_to_master?(command)
265
+ if @command.should_send_to_master?(command) || primary_only
264
266
  @slot.find_node_key_of_master(slot)
265
267
  else
266
268
  @slot.find_node_key_of_slave(slot)
@@ -285,11 +287,5 @@ class Redis
285
287
  @node.map(&:disconnect)
286
288
  @node, @slot = fetch_cluster_info!(@option)
287
289
  end
288
-
289
- def extract_keys_in_pipeline(pipeline)
290
- node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
291
- command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
292
- [node_keys, command_keys]
293
- end
294
290
  end
295
291
  end
@@ -12,11 +12,13 @@ class Redis
12
12
  if i.is_a? Array
13
13
  i.each do |j|
14
14
  j = j.to_s
15
+ j = j.encoding == Encoding::BINARY ? j : j.b
15
16
  command << "$#{j.bytesize}"
16
17
  command << j
17
18
  end
18
19
  else
19
20
  i = i.to_s
21
+ i = i.encoding == Encoding::BINARY ? i : i.b
20
22
  command << "$#{i.bytesize}"
21
23
  command << i
22
24
  end
@@ -21,7 +21,7 @@ class Redis
21
21
  super(*args)
22
22
 
23
23
  @timeout = @write_timeout = nil
24
- @buffer = "".dup
24
+ @buffer = "".b
25
25
  end
26
26
 
27
27
  def timeout=(timeout)
@@ -35,7 +35,8 @@ class Redis
35
35
  def read(nbytes)
36
36
  result = @buffer.slice!(0, nbytes)
37
37
 
38
- result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
38
+ buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
39
+ result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
39
40
 
40
41
  result
41
42
  end
@@ -48,9 +49,9 @@ class Redis
48
49
  @buffer.slice!(0, crlf + CRLF.bytesize)
49
50
  end
50
51
 
51
- def _read_from_socket(nbytes)
52
+ def _read_from_socket(nbytes, buffer = nil)
52
53
  loop do
53
- case chunk = read_nonblock(nbytes, exception: false)
54
+ case chunk = read_nonblock(nbytes, buffer, exception: false)
54
55
  when :wait_readable
55
56
  unless wait_readable(@timeout)
56
57
  raise Redis::TimeoutError
@@ -316,6 +316,16 @@ class Redis
316
316
  node_for(key).get(key)
317
317
  end
318
318
 
319
+ # Get the value of a key and delete it.
320
+ def getdel(key)
321
+ node_for(key).getdel(key)
322
+ end
323
+
324
+ # Get the value of a key and sets its time to live based on options.
325
+ def getex(key, **options)
326
+ node_for(key).getex(key, **options)
327
+ end
328
+
319
329
  # Get the values of all the given keys as an Array.
320
330
  def mget(*keys)
321
331
  mapped_mget(*keys).values_at(*keys)
@@ -393,6 +403,21 @@ class Redis
393
403
  node_for(key).llen(key)
394
404
  end
395
405
 
406
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
407
+ def lmove(source, destination, where_source, where_destination)
408
+ ensure_same_node(:lmove, [source, destination]) do |node|
409
+ node.lmove(source, destination, where_source, where_destination)
410
+ end
411
+ end
412
+
413
+ # Remove the first/last element in a list and append/prepend it
414
+ # to another list and return it, or block until one is available.
415
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
416
+ ensure_same_node(:lmove, [source, destination]) do |node|
417
+ node.blmove(source, destination, where_source, where_destination, timeout: timeout)
418
+ end
419
+ end
420
+
396
421
  # Prepend one or more values to a list.
397
422
  def lpush(key, value)
398
423
  node_for(key).lpush(key, value)
@@ -542,6 +567,11 @@ class Redis
542
567
  node_for(key).sismember(key, member)
543
568
  end
544
569
 
570
+ # Determine if multiple values are members of a set.
571
+ def smismember(key, *members)
572
+ node_for(key).smismember(key, *members)
573
+ end
574
+
545
575
  # Get all the members in a set.
546
576
  def smembers(key)
547
577
  node_for(key).smembers(key)
@@ -626,6 +656,16 @@ class Redis
626
656
  node_for(key).zscore(key, member)
627
657
  end
628
658
 
659
+ # Get one or more random members from a sorted set.
660
+ def zrandmember(key, count = nil, **options)
661
+ node_for(key).zrandmember(key, count, **options)
662
+ end
663
+
664
+ # Get the scores associated with the given members in a sorted set.
665
+ def zmscore(key, *members)
666
+ node_for(key).zmscore(key, *members)
667
+ end
668
+
629
669
  # Return a range of members in a sorted set, by index.
630
670
  def zrange(key, start, stop, **options)
631
671
  node_for(key).zrange(key, start, stop, **options)
@@ -674,6 +714,13 @@ class Redis
674
714
  node_for(key).zcount(key, min, max)
675
715
  end
676
716
 
717
+ # Get the intersection of multiple sorted sets
718
+ def zinter(*keys, **options)
719
+ ensure_same_node(:zinter, keys) do |node|
720
+ node.zinter(*keys, **options)
721
+ end
722
+ end
723
+
677
724
  # Intersect multiple sorted sets and store the resulting sorted set in a new
678
725
  # key.
679
726
  def zinterstore(destination, keys, **options)
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.3.1'
4
+ VERSION = '4.5.1'
5
5
  end
data/lib/redis.rb CHANGED
@@ -4,6 +4,8 @@ require "monitor"
4
4
  require_relative "redis/errors"
5
5
 
6
6
  class Redis
7
+ @exists_returns_integer = true
8
+
7
9
  class << self
8
10
  attr_reader :exists_returns_integer
9
11
 
@@ -555,6 +557,9 @@ class Redis
555
557
  # @param [String, Array<String>] keys
556
558
  # @return [Integer] number of keys that were deleted
557
559
  def del(*keys)
560
+ keys.flatten!(1)
561
+ return 0 if keys.empty?
562
+
558
563
  synchronize do |client|
559
564
  client.call([:del] + keys)
560
565
  end
@@ -831,17 +836,23 @@ class Redis
831
836
  # @param [Hash] options
832
837
  # - `:ex => Integer`: Set the specified expire time, in seconds.
833
838
  # - `:px => Integer`: Set the specified expire time, in milliseconds.
839
+ # - `:exat => Integer` : Set the specified Unix time at which the key will expire, in seconds.
840
+ # - `:pxat => Integer` : Set the specified Unix time at which the key will expire, in milliseconds.
834
841
  # - `:nx => true`: Only set the key if it does not already exist.
835
842
  # - `:xx => true`: Only set the key if it already exist.
836
843
  # - `:keepttl => true`: Retain the time to live associated with the key.
844
+ # - `:get => true`: Return the old string stored at key, or nil if key did not exist.
837
845
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
838
- def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
846
+ def set(key, value, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil, keepttl: nil, get: nil)
839
847
  args = [:set, key, value.to_s]
840
848
  args << "EX" << ex if ex
841
849
  args << "PX" << px if px
850
+ args << "EXAT" << exat if exat
851
+ args << "PXAT" << pxat if pxat
842
852
  args << "NX" if nx
843
853
  args << "XX" if xx
844
854
  args << "KEEPTTL" if keepttl
855
+ args << "GET" if get
845
856
 
846
857
  synchronize do |client|
847
858
  if nx || xx
@@ -1107,6 +1118,45 @@ class Redis
1107
1118
  end
1108
1119
  end
1109
1120
 
1121
+ # Get the value of key and delete the key. This command is similar to GET,
1122
+ # except for the fact that it also deletes the key on success.
1123
+ #
1124
+ # @param [String] key
1125
+ # @return [String] the old value stored in the key, or `nil` if the key
1126
+ # did not exist
1127
+ def getdel(key)
1128
+ synchronize do |client|
1129
+ client.call([:getdel, key])
1130
+ end
1131
+ end
1132
+
1133
+ # Get the value of key and optionally set its expiration. GETEX is similar to
1134
+ # GET, but is a write command with additional options. When no options are
1135
+ # provided, GETEX behaves like GET.
1136
+ #
1137
+ # @param [String] key
1138
+ # @param [Hash] options
1139
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
1140
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
1141
+ # - `:exat => true`: Set the specified Unix time at which the key will
1142
+ # expire, in seconds.
1143
+ # - `:pxat => true`: Set the specified Unix time at which the key will
1144
+ # expire, in milliseconds.
1145
+ # - `:persist => true`: Remove the time to live associated with the key.
1146
+ # @return [String] The value of key, or nil when key does not exist.
1147
+ def getex(key, ex: nil, px: nil, exat: nil, pxat: nil, persist: false)
1148
+ args = [:getex, key]
1149
+ args << "EX" << ex if ex
1150
+ args << "PX" << px if px
1151
+ args << "EXAT" << exat if exat
1152
+ args << "PXAT" << pxat if pxat
1153
+ args << "PERSIST" if persist
1154
+
1155
+ synchronize do |client|
1156
+ client.call(args)
1157
+ end
1158
+ end
1159
+
1110
1160
  # Get the length of the value stored in a key.
1111
1161
  #
1112
1162
  # @param [String] key
@@ -1128,6 +1178,59 @@ class Redis
1128
1178
  end
1129
1179
  end
1130
1180
 
1181
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
1182
+ #
1183
+ # @param [String] source source key
1184
+ # @param [String] destination destination key
1185
+ # @param [String, Symbol] where_source from where to remove the element from the source list
1186
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
1187
+ # @param [String, Symbol] where_destination where to push the element to the source list
1188
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
1189
+ #
1190
+ # @return [nil, String] the element, or nil when the source key does not exist
1191
+ #
1192
+ # @note This command comes in place of the now deprecated RPOPLPUSH.
1193
+ # Doing LMOVE RIGHT LEFT is equivalent.
1194
+ def lmove(source, destination, where_source, where_destination)
1195
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
1196
+
1197
+ synchronize do |client|
1198
+ client.call([:lmove, source, destination, where_source, where_destination])
1199
+ end
1200
+ end
1201
+
1202
+ # Remove the first/last element in a list and append/prepend it
1203
+ # to another list and return it, or block until one is available.
1204
+ #
1205
+ # @example With timeout
1206
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT", timeout: 5)
1207
+ # # => nil on timeout
1208
+ # # => "element" on success
1209
+ # @example Without timeout
1210
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT")
1211
+ # # => "element"
1212
+ #
1213
+ # @param [String] source source key
1214
+ # @param [String] destination destination key
1215
+ # @param [String, Symbol] where_source from where to remove the element from the source list
1216
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
1217
+ # @param [String, Symbol] where_destination where to push the element to the source list
1218
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
1219
+ # @param [Hash] options
1220
+ # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
1221
+ #
1222
+ # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
1223
+ #
1224
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
1225
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
1226
+
1227
+ synchronize do |client|
1228
+ command = [:blmove, source, destination, where_source, where_destination, timeout]
1229
+ timeout += client.timeout if timeout > 0
1230
+ client.call_with_timeout(command, timeout)
1231
+ end
1232
+ end
1233
+
1131
1234
  # Prepend one or more values to a list, creating the list if it doesn't exist
1132
1235
  #
1133
1236
  # @param [String] key
@@ -1476,6 +1579,19 @@ class Redis
1476
1579
  end
1477
1580
  end
1478
1581
 
1582
+ # Determine if multiple values are members of a set.
1583
+ #
1584
+ # @param [String] key
1585
+ # @param [String, Array<String>] members
1586
+ # @return [Array<Boolean>]
1587
+ def smismember(key, *members)
1588
+ synchronize do |client|
1589
+ client.call([:smismember, key, *members]) do |reply|
1590
+ reply.map(&Boolify)
1591
+ end
1592
+ end
1593
+ end
1594
+
1479
1595
  # Get all the members in a set.
1480
1596
  #
1481
1597
  # @param [String] key
@@ -1580,6 +1696,10 @@ class Redis
1580
1696
  # add elements)
1581
1697
  # - `:nx => true`: Don't update already existing elements (always
1582
1698
  # add new elements)
1699
+ # - `:lt => true`: Only update existing elements if the new score
1700
+ # is less than the current score
1701
+ # - `:gt => true`: Only update existing elements if the new score
1702
+ # is greater than the current score
1583
1703
  # - `:ch => true`: Modify the return value from the number of new
1584
1704
  # elements added, to the total number of elements changed (CH is an
1585
1705
  # abbreviation of changed); changed elements are new elements added
@@ -1594,10 +1714,12 @@ class Redis
1594
1714
  # pairs that were **added** to the sorted set.
1595
1715
  # - `Float` when option :incr is specified, holding the score of the member
1596
1716
  # after incrementing it.
1597
- def zadd(key, *args, nx: nil, xx: nil, ch: nil, incr: nil)
1717
+ def zadd(key, *args, nx: nil, xx: nil, lt: nil, gt: nil, ch: nil, incr: nil)
1598
1718
  command = [:zadd, key]
1599
1719
  command << "NX" if nx
1600
1720
  command << "XX" if xx
1721
+ command << "LT" if lt
1722
+ command << "GT" if gt
1601
1723
  command << "CH" if ch
1602
1724
  command << "INCR" if incr
1603
1725
 
@@ -1760,6 +1882,63 @@ class Redis
1760
1882
  end
1761
1883
  end
1762
1884
 
1885
+ # Get the scores associated with the given members in a sorted set.
1886
+ #
1887
+ # @example Get the scores for members "a" and "b"
1888
+ # redis.zmscore("zset", "a", "b")
1889
+ # # => [32.0, 48.0]
1890
+ #
1891
+ # @param [String] key
1892
+ # @param [String, Array<String>] members
1893
+ # @return [Array<Float>] scores of the members
1894
+ def zmscore(key, *members)
1895
+ synchronize do |client|
1896
+ client.call([:zmscore, key, *members]) do |reply|
1897
+ reply.map(&Floatify)
1898
+ end
1899
+ end
1900
+ end
1901
+
1902
+ # Get one or more random members from a sorted set.
1903
+ #
1904
+ # @example Get one random member
1905
+ # redis.zrandmember("zset")
1906
+ # # => "a"
1907
+ # @example Get multiple random members
1908
+ # redis.zrandmember("zset", 2)
1909
+ # # => ["a", "b"]
1910
+ # @example Gem multiple random members with scores
1911
+ # redis.zrandmember("zset", 2, with_scores: true)
1912
+ # # => [["a", 2.0], ["b", 3.0]]
1913
+ #
1914
+ # @param [String] key
1915
+ # @param [Integer] count
1916
+ # @param [Hash] options
1917
+ # - `:with_scores => true`: include scores in output
1918
+ #
1919
+ # @return [nil, String, Array<String>, Array<[String, Float]>]
1920
+ # - when `key` does not exist or set is empty, `nil`
1921
+ # - when `count` is not specified, a member
1922
+ # - when `count` is specified and `:with_scores` is not specified, an array of members
1923
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
1924
+ def zrandmember(key, count = nil, withscores: false, with_scores: withscores)
1925
+ if with_scores && count.nil?
1926
+ raise ArgumentError, "count argument must be specified"
1927
+ end
1928
+
1929
+ args = [:zrandmember, key]
1930
+ args << count if count
1931
+
1932
+ if with_scores
1933
+ args << "WITHSCORES"
1934
+ block = FloatifyPairs
1935
+ end
1936
+
1937
+ synchronize do |client|
1938
+ client.call(args, &block)
1939
+ end
1940
+ end
1941
+
1763
1942
  # Return a range of members in a sorted set, by index.
1764
1943
  #
1765
1944
  # @example Retrieve all members from a sorted set
@@ -2062,6 +2241,45 @@ class Redis
2062
2241
  end
2063
2242
  end
2064
2243
 
2244
+ # Return the intersection of multiple sorted sets
2245
+ #
2246
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
2247
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
2248
+ # # => ["v1", "v2"]
2249
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
2250
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
2251
+ # # => [["v1", 3.0], ["v2", 6.0]]
2252
+ #
2253
+ # @param [String, Array<String>] keys one or more keys to intersect
2254
+ # @param [Hash] options
2255
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
2256
+ # sorted sets
2257
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2258
+ # - `:with_scores => true`: include scores in output
2259
+ #
2260
+ # @return [Array<String>, Array<[String, Float]>]
2261
+ # - when `:with_scores` is not specified, an array of members
2262
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
2263
+ def zinter(*keys, weights: nil, aggregate: nil, with_scores: false)
2264
+ args = [:zinter, keys.size, *keys]
2265
+
2266
+ if weights
2267
+ args << "WEIGHTS"
2268
+ args.concat(weights)
2269
+ end
2270
+
2271
+ args << "AGGREGATE" << aggregate if aggregate
2272
+
2273
+ if with_scores
2274
+ args << "WITHSCORES"
2275
+ block = FloatifyPairs
2276
+ end
2277
+
2278
+ synchronize do |client|
2279
+ client.call(args, &block)
2280
+ end
2281
+ end
2282
+
2065
2283
  # Intersect multiple sorted sets and store the resulting sorted set in a new
2066
2284
  # key.
2067
2285
  #
@@ -3238,6 +3456,38 @@ class Redis
3238
3456
  synchronize { |client| client.call(args, &blk) }
3239
3457
  end
3240
3458
 
3459
+ # Transfers ownership of pending stream entries that match the specified criteria.
3460
+ #
3461
+ # @example Claim next pending message stuck > 5 minutes and mark as retry
3462
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
3463
+ # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
3464
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
3465
+ # @example Claim next pending message stuck > 5 minutes and don't mark as retry
3466
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
3467
+ # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
3468
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
3469
+ #
3470
+ # @param key [String] the stream key
3471
+ # @param group [String] the consumer group name
3472
+ # @param consumer [String] the consumer name
3473
+ # @param min_idle_time [Integer] the number of milliseconds
3474
+ # @param start [String] entry id to start scanning from or 0-0 for everything
3475
+ # @param count [Integer] number of messages to claim (default 1)
3476
+ # @param justid [Boolean] whether to fetch just an array of entry ids or not.
3477
+ # Does not increment retry count when true
3478
+ #
3479
+ # @return [Hash{String => Hash}] the entries successfully claimed
3480
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3481
+ def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
3482
+ args = [:xautoclaim, key, group, consumer, min_idle_time, start]
3483
+ if count
3484
+ args << 'COUNT' << count.to_s
3485
+ end
3486
+ args << 'JUSTID' if justid
3487
+ blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
3488
+ synchronize { |client| client.call(args, &blk) }
3489
+ end
3490
+
3241
3491
  # Fetches not acknowledging pending entries
3242
3492
  #
3243
3493
  # @example With key and group
@@ -3444,10 +3694,24 @@ class Redis
3444
3694
 
3445
3695
  HashifyStreamEntries = lambda { |reply|
3446
3696
  reply.compact.map do |entry_id, values|
3447
- [entry_id, values.each_slice(2).to_h]
3697
+ [entry_id, values&.each_slice(2)&.to_h]
3448
3698
  end
3449
3699
  }
3450
3700
 
3701
+ HashifyStreamAutoclaim = lambda { |reply|
3702
+ {
3703
+ 'next' => reply[0],
3704
+ 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
3705
+ }
3706
+ }
3707
+
3708
+ HashifyStreamAutoclaimJustId = lambda { |reply|
3709
+ {
3710
+ 'next' => reply[0],
3711
+ 'entries' => reply[1]
3712
+ }
3713
+ }
3714
+
3451
3715
  HashifyStreamPendings = lambda { |reply|
3452
3716
  {
3453
3717
  'size' => reply[0],
@@ -3547,6 +3811,21 @@ class Redis
3547
3811
  end
3548
3812
  end
3549
3813
  end
3814
+
3815
+ def _normalize_move_wheres(where_source, where_destination)
3816
+ where_source = where_source.to_s.upcase
3817
+ where_destination = where_destination.to_s.upcase
3818
+
3819
+ if where_source != "LEFT" && where_source != "RIGHT"
3820
+ raise ArgumentError, "where_source must be 'LEFT' or 'RIGHT'"
3821
+ end
3822
+
3823
+ if where_destination != "LEFT" && where_destination != "RIGHT"
3824
+ raise ArgumentError, "where_destination must be 'LEFT' or 'RIGHT'"
3825
+ end
3826
+
3827
+ [where_source, where_destination]
3828
+ end
3550
3829
  end
3551
3830
 
3552
3831
  require_relative "redis/version"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.1
4
+ version: 4.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2021-06-11 00:00:00.000000000 Z
19
+ date: 2021-10-15 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: em-synchrony
@@ -102,9 +102,9 @@ licenses:
102
102
  metadata:
103
103
  bug_tracker_uri: https://github.com/redis/redis-rb/issues
104
104
  changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
105
- documentation_uri: https://www.rubydoc.info/gems/redis/4.3.1
105
+ documentation_uri: https://www.rubydoc.info/gems/redis/4.5.1
106
106
  homepage_uri: https://github.com/redis/redis-rb
107
- source_code_uri: https://github.com/redis/redis-rb/tree/v4.3.1
107
+ source_code_uri: https://github.com/redis/redis-rb/tree/v4.5.1
108
108
  post_install_message:
109
109
  rdoc_options: []
110
110
  require_paths:
@@ -113,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - ">="
115
115
  - !ruby/object:Gem::Version
116
- version: 2.3.0
116
+ version: 2.4.0
117
117
  required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  requirements:
119
119
  - - ">="