redis 4.4.0 → 4.5.0

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: d5ff2ee4b6a6f2b087ac26bf96a3c1769cf42f70ea90008361157f7ef04cdb14
4
- data.tar.gz: f2c24654294c4fa81a5cff4ddb545bc59e6f85b64ee61273278cb1e286a4edb8
3
+ metadata.gz: b1e483240f70aa8a92a7fe1f38968262ea82e84a97e436a9c7c695c032584313
4
+ data.tar.gz: 1cb164b2d588ef2f2701b91371a12a0d90299a78994f0d614cb14ab0daa36243
5
5
  SHA512:
6
- metadata.gz: 0d88c6621659a178dca04d3ca62f25520f5f1a846b6300f93fb008966551fb4795a3e6c37810f99ea18f77a6292b955fa180ae3e06f8c90df7e6a3ca27087ae1
7
- data.tar.gz: 1427cc268e867872388f214184d23a5c6062104fcb9d1e50d6026dd9daf6e51b9833866fee9fcf80c7a64e4de705295de8399d8e8ac22fa890bf358bd63ff707
6
+ metadata.gz: 72c3e91839061ae26e27cbb08330f752acbba5f440c8549cad4325b2c2f517a907a0db11330ca9ba221255aa7b570efe2050bde9cbb905217794c4754c53dadb
7
+ data.tar.gz: e92cc58111fb9e1553a3363c56569ef6064c3851263d5eb86147c907878650b697db5c598b2b2046f9c9025cc40ca7eda5ce16f4f21049ac49f09385cf80a92f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.5.0
4
+
5
+ * Handle parts of the command using incompatible encodings. See #1037.
6
+ * Add GET option to SET command. See #1036.
7
+ * Add ZRANDMEMBER command. See #1035.
8
+ * Add LMOVE/BLMOVE commands. See #1034.
9
+ * Add ZMSCORE command. See #1032.
10
+ * Add LT/GT options to ZADD. See #1033.
11
+ * Add SMISMEMBER command. See #1031.
12
+ * Add EXAT/PXAT options to SET. See #1028.
13
+ * Add GETDEL/GETEX commands. See #1024.
14
+ * `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`.
15
+ * Fix Redis < 6 detection during connect. See #1025.
16
+ * Fix fetching command details in Redis cluster when the first node is unhealthy. See #1026.
17
+
3
18
  # 4.4.0
4
19
 
5
20
  * Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
data/lib/redis/client.rb CHANGED
@@ -119,8 +119,12 @@ 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
+ else
126
+ raise
127
+ end
124
128
  end
125
129
  else
126
130
  call [:auth, password]
@@ -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
@@ -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
@@ -32,12 +32,30 @@ class Redis
32
32
  @write_timeout = (timeout if timeout && timeout > 0)
33
33
  end
34
34
 
35
- def read(nbytes)
36
- result = @buffer.slice!(0, nbytes)
35
+ string_capacity_support = begin
36
+ String.new(capacity: 0)
37
+ true # Ruby 2.4+
38
+ rescue ArgumentError
39
+ false # Ruby 2.3
40
+ end
41
+
42
+ if string_capacity_support
43
+ def read(nbytes)
44
+ result = @buffer.slice!(0, nbytes)
37
45
 
38
- result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
46
+ buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
47
+ result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
39
48
 
40
- result
49
+ result
50
+ end
51
+ else
52
+ def read(nbytes)
53
+ result = @buffer.slice!(0, nbytes)
54
+
55
+ result << _read_from_socket(nbytes - result.bytesize, "".b) while result.bytesize < nbytes
56
+
57
+ result
58
+ end
41
59
  end
42
60
 
43
61
  def gets
@@ -48,9 +66,9 @@ class Redis
48
66
  @buffer.slice!(0, crlf + CRLF.bytesize)
49
67
  end
50
68
 
51
- def _read_from_socket(nbytes)
69
+ def _read_from_socket(nbytes, buffer = nil)
52
70
  loop do
53
- case chunk = read_nonblock(nbytes, exception: false)
71
+ case chunk = read_nonblock(nbytes, buffer, exception: false)
54
72
  when :wait_readable
55
73
  unless wait_readable(@timeout)
56
74
  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)
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.5.0'
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
 
@@ -834,17 +836,23 @@ class Redis
834
836
  # @param [Hash] options
835
837
  # - `:ex => Integer`: Set the specified expire time, in seconds.
836
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.
837
841
  # - `:nx => true`: Only set the key if it does not already exist.
838
842
  # - `:xx => true`: Only set the key if it already exist.
839
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.
840
845
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
841
- 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)
842
847
  args = [:set, key, value.to_s]
843
848
  args << "EX" << ex if ex
844
849
  args << "PX" << px if px
850
+ args << "EXAT" << exat if exat
851
+ args << "PXAT" << pxat if pxat
845
852
  args << "NX" if nx
846
853
  args << "XX" if xx
847
854
  args << "KEEPTTL" if keepttl
855
+ args << "GET" if get
848
856
 
849
857
  synchronize do |client|
850
858
  if nx || xx
@@ -1110,6 +1118,45 @@ class Redis
1110
1118
  end
1111
1119
  end
1112
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
+
1113
1160
  # Get the length of the value stored in a key.
1114
1161
  #
1115
1162
  # @param [String] key
@@ -1131,6 +1178,59 @@ class Redis
1131
1178
  end
1132
1179
  end
1133
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
+
1134
1234
  # Prepend one or more values to a list, creating the list if it doesn't exist
1135
1235
  #
1136
1236
  # @param [String] key
@@ -1479,6 +1579,19 @@ class Redis
1479
1579
  end
1480
1580
  end
1481
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
+
1482
1595
  # Get all the members in a set.
1483
1596
  #
1484
1597
  # @param [String] key
@@ -1583,6 +1696,10 @@ class Redis
1583
1696
  # add elements)
1584
1697
  # - `:nx => true`: Don't update already existing elements (always
1585
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
1586
1703
  # - `:ch => true`: Modify the return value from the number of new
1587
1704
  # elements added, to the total number of elements changed (CH is an
1588
1705
  # abbreviation of changed); changed elements are new elements added
@@ -1597,10 +1714,12 @@ class Redis
1597
1714
  # pairs that were **added** to the sorted set.
1598
1715
  # - `Float` when option :incr is specified, holding the score of the member
1599
1716
  # after incrementing it.
1600
- 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)
1601
1718
  command = [:zadd, key]
1602
1719
  command << "NX" if nx
1603
1720
  command << "XX" if xx
1721
+ command << "LT" if lt
1722
+ command << "GT" if gt
1604
1723
  command << "CH" if ch
1605
1724
  command << "INCR" if incr
1606
1725
 
@@ -1763,6 +1882,63 @@ class Redis
1763
1882
  end
1764
1883
  end
1765
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
+
1766
1942
  # Return a range of members in a sorted set, by index.
1767
1943
  #
1768
1944
  # @example Retrieve all members from a sorted set
@@ -3518,7 +3694,7 @@ class Redis
3518
3694
 
3519
3695
  HashifyStreamEntries = lambda { |reply|
3520
3696
  reply.compact.map do |entry_id, values|
3521
- [entry_id, values.each_slice(2).to_h]
3697
+ [entry_id, values&.each_slice(2)&.to_h]
3522
3698
  end
3523
3699
  }
3524
3700
 
@@ -3635,6 +3811,21 @@ class Redis
3635
3811
  end
3636
3812
  end
3637
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
3638
3829
  end
3639
3830
 
3640
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.4.0
4
+ version: 4.5.0
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-07-28 00:00:00.000000000 Z
19
+ date: 2021-10-14 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.4.0
105
+ documentation_uri: https://www.rubydoc.info/gems/redis/4.5.0
106
106
  homepage_uri: https://github.com/redis/redis-rb
107
- source_code_uri: https://github.com/redis/redis-rb/tree/v4.4.0
107
+ source_code_uri: https://github.com/redis/redis-rb/tree/v4.5.0
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
  - - ">="