redis 4.8.1 → 6.0.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -1
  3. data/README.md +285 -169
  4. data/lib/redis/client.rb +116 -611
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +109 -7
  9. data/lib/redis/commands/hashes.rb +179 -8
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +32 -24
  12. data/lib/redis/commands/lists.rb +167 -25
  13. data/lib/redis/commands/modules/json.rb +530 -0
  14. data/lib/redis/commands/modules/search/aggregation.rb +418 -0
  15. data/lib/redis/commands/modules/search/dialect.rb +14 -0
  16. data/lib/redis/commands/modules/search/field.rb +306 -0
  17. data/lib/redis/commands/modules/search/hybrid.rb +359 -0
  18. data/lib/redis/commands/modules/search/index.rb +351 -0
  19. data/lib/redis/commands/modules/search/index_definition.rb +114 -0
  20. data/lib/redis/commands/modules/search/miscellaneous.rb +607 -0
  21. data/lib/redis/commands/modules/search/query.rb +738 -0
  22. data/lib/redis/commands/modules/search/result.rb +488 -0
  23. data/lib/redis/commands/modules/search/schema.rb +211 -0
  24. data/lib/redis/commands/modules/search.rb +19 -0
  25. data/lib/redis/commands/pubsub.rb +34 -25
  26. data/lib/redis/commands/server.rb +15 -15
  27. data/lib/redis/commands/sets.rb +76 -40
  28. data/lib/redis/commands/sorted_sets.rb +128 -19
  29. data/lib/redis/commands/streams.rb +75 -28
  30. data/lib/redis/commands/strings.rb +18 -17
  31. data/lib/redis/commands/transactions.rb +7 -31
  32. data/lib/redis/commands.rb +39 -20
  33. data/lib/redis/distributed.rb +407 -73
  34. data/lib/redis/errors.rb +20 -50
  35. data/lib/redis/hash_ring.rb +26 -26
  36. data/lib/redis/lib_identity.rb +105 -0
  37. data/lib/redis/pipeline.rb +47 -222
  38. data/lib/redis/subscribe.rb +51 -15
  39. data/lib/redis/version.rb +1 -1
  40. data/lib/redis.rb +213 -188
  41. metadata +25 -59
  42. data/lib/redis/cluster/command.rb +0 -79
  43. data/lib/redis/cluster/command_loader.rb +0 -33
  44. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  45. data/lib/redis/cluster/node.rb +0 -120
  46. data/lib/redis/cluster/node_key.rb +0 -31
  47. data/lib/redis/cluster/node_loader.rb +0 -34
  48. data/lib/redis/cluster/option.rb +0 -100
  49. data/lib/redis/cluster/slot.rb +0 -86
  50. data/lib/redis/cluster/slot_loader.rb +0 -46
  51. data/lib/redis/cluster.rb +0 -315
  52. data/lib/redis/connection/command_helper.rb +0 -41
  53. data/lib/redis/connection/hiredis.rb +0 -68
  54. data/lib/redis/connection/registry.rb +0 -13
  55. data/lib/redis/connection/ruby.rb +0 -437
  56. data/lib/redis/connection/synchrony.rb +0 -148
  57. data/lib/redis/connection.rb +0 -11
@@ -27,9 +27,13 @@ class Redis
27
27
  # @param [String] key
28
28
  # @param [Integer] start start index
29
29
  # @param [Integer] stop stop index
30
+ # @param [String, Symbol] scale the scale of the offset range
31
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
30
32
  # @return [Integer] the number of bits set to 1
31
- def bitcount(key, start = 0, stop = -1)
32
- send_command([:bitcount, key, start, stop])
33
+ def bitcount(key, start = 0, stop = -1, scale: nil)
34
+ command = [:bitcount, key, start, stop]
35
+ command << scale if scale
36
+ send_command(command)
33
37
  end
34
38
 
35
39
  # Perform a bitwise operation between strings and store the resulting string in a key.
@@ -39,7 +43,10 @@ class Redis
39
43
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
40
44
  # @return [Integer] the length of the string stored in `destkey`
41
45
  def bitop(operation, destkey, *keys)
42
- send_command([:bitop, operation, destkey, *keys])
46
+ keys.flatten!(1)
47
+ command = [:bitop, operation, destkey]
48
+ command.concat(keys)
49
+ send_command(command)
43
50
  end
44
51
 
45
52
  # Return the position of the first bit set to 1 or 0 in a string.
@@ -48,14 +55,17 @@ class Redis
48
55
  # @param [Integer] bit whether to look for the first 1 or 0 bit
49
56
  # @param [Integer] start start index
50
57
  # @param [Integer] stop stop index
58
+ # @param [String, Symbol] scale the scale of the offset range
59
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
51
60
  # @return [Integer] the position of the first 1/0 bit.
52
61
  # -1 if looking for 1 and it is not found or start and stop are given.
53
- def bitpos(key, bit, start = nil, stop = nil)
62
+ def bitpos(key, bit, start = nil, stop = nil, scale: nil)
54
63
  raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
55
64
 
56
65
  command = [:bitpos, key, bit]
57
66
  command << start if start
58
67
  command << stop if stop
68
+ command << scale if scale
59
69
  send_command(command)
60
70
  end
61
71
  end
@@ -12,24 +12,7 @@ class Redis
12
12
  #
13
13
  # @return [Object] depends on the subcommand
14
14
  def cluster(subcommand, *args)
15
- subcommand = subcommand.to_s.downcase
16
- block = case subcommand
17
- when 'slots'
18
- HashifyClusterSlots
19
- when 'nodes'
20
- HashifyClusterNodes
21
- when 'slaves'
22
- HashifyClusterSlaves
23
- when 'info'
24
- HashifyInfo
25
- else
26
- Noop
27
- end
28
-
29
- # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
30
- block = Noop unless @cluster_mode
31
-
32
- send_command([:cluster, subcommand] + args, &block)
15
+ send_command([:cluster, subcommand] + args)
33
16
  end
34
17
 
35
18
  # Sends `ASKING` command to random node and returns its reply.
@@ -34,10 +34,7 @@ class Redis
34
34
  # @param [Integer] db zero-based index of the DB to use (0 to 15)
35
35
  # @return [String] `OK`
36
36
  def select(db)
37
- synchronize do |client|
38
- client.db = db
39
- client.call([:select, db])
40
- end
37
+ send_command([:select, db])
41
38
  end
42
39
 
43
40
  # Close the connection.
@@ -45,12 +42,10 @@ class Redis
45
42
  # @return [String] `OK`
46
43
  def quit
47
44
  synchronize do |client|
48
- begin
49
- client.call([:quit])
50
- rescue ConnectionError
51
- ensure
52
- client.disconnect
53
- end
45
+ client.call_v([:quit])
46
+ rescue ConnectionError
47
+ ensure
48
+ client.close
54
49
  end
55
50
  end
56
51
  end
@@ -7,9 +7,19 @@ class Redis
7
7
  #
8
8
  # @param [String] key
9
9
  # @param [Array] member arguemnts for member or members: longitude, latitude, name
10
- # @return [Integer] number of elements added to the sorted set
11
- def geoadd(key, *member)
12
- send_command([:geoadd, key, *member])
10
+ # @param [Boolean] nx don't update already existing elements, always add new ones (since Redis 6.2)
11
+ # @param [Boolean] xx only update elements that already exist, never add new ones (since Redis 6.2)
12
+ # @param [Boolean] ch modify the return value to the number of changed elements (since Redis 6.2)
13
+ # @return [Integer] number of elements added to the sorted set, or changed when `ch` is set
14
+ def geoadd(key, *member, nx: false, xx: false, ch: false)
15
+ raise ArgumentError, "can't supply both nx and xx" if nx && xx
16
+
17
+ args = [:geoadd, key]
18
+ args << "NX" if nx
19
+ args << "XX" if xx
20
+ args << "CH" if ch
21
+ args.concat(member)
22
+ send_command(args)
13
23
  end
14
24
 
15
25
  # Returns geohash string representing position for specified members of the specified key.
@@ -28,6 +38,7 @@ class Redis
28
38
  # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
29
39
  # or the farthest to the nearest relative to the center
30
40
  # @param [Integer] count limit the results to the first N matching items
41
+ # @param [Boolean] count_any return as soon as enough matches found (only with count, since Redis 6.2)
31
42
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
32
43
  # @return [Array<String>] may be changed with `options`
33
44
  def georadius(*args, **geoptions)
@@ -43,6 +54,7 @@ class Redis
43
54
  # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
44
55
  # to the nearest relative to the center
45
56
  # @param [Integer] count limit the results to the first N matching items
57
+ # @param [Boolean] count_any return as soon as enough matches found (only with count, since Redis 6.2)
46
58
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
47
59
  # @return [Array<String>] may be changed with `options`
48
60
  def georadiusbymember(*args, **geoptions)
@@ -61,6 +73,93 @@ class Redis
61
73
  send_command([:geopos, key, member])
62
74
  end
63
75
 
76
+ # Return the members of a geospatial sorted set that are within the borders of the
77
+ # area specified by a given shape, either a circle (BYRADIUS) or a rectangle (BYBOX),
78
+ # starting from a center point given either by member (FROMMEMBER) or by longitude and
79
+ # latitude (FROMLONLAT). Available since Redis 6.2.
80
+ #
81
+ # @example Search by radius from longitude/latitude
82
+ # redis.geosearch("Sicily", fromlonlat: [15, 37], byradius: [200, "km"], sort: "asc")
83
+ # # => ["Catania", "Palermo"]
84
+ #
85
+ # @example Search by box from an existing member, with extras
86
+ # redis.geosearch("Sicily", frommember: "Catania", bybox: [400, 400, "km"],
87
+ # sort: "asc", withcoord: true, withdist: true)
88
+ # # => [["Catania", "0.0000", ["15.087...", "37.502..."]], ...]
89
+ #
90
+ # @param [String] key
91
+ # @param [String] frommember use the position of the given existing member as the center
92
+ # @param [Array<Numeric>] fromlonlat a [longitude, latitude] pair used as the center
93
+ # @param [Array] byradius a [radius, unit] pair where unit is one of 'm', 'km', 'ft', 'mi'
94
+ # @param [Array] bybox a [width, height, unit] triple where unit is one of 'm', 'km', 'ft', 'mi'
95
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest, or vice versa
96
+ # @param [Integer] count limit the results to the first N matching items
97
+ # @param [Boolean] count_any return as soon as enough matches are found (only with count)
98
+ # @param [Boolean] withcoord also return the longitude and latitude of matching items
99
+ # @param [Boolean] withdist also return the distance from the center point
100
+ # @param [Boolean] withhash also return the raw geohash-encoded sorted set score of the item
101
+ # @return [Array<String>] may be changed with WITH* flags
102
+ def geosearch(key, frommember: nil, fromlonlat: nil, byradius: nil, bybox: nil,
103
+ sort: nil, count: nil, count_any: false,
104
+ withcoord: false, withdist: false, withhash: false)
105
+ args = [key]
106
+ args << "FROMMEMBER" << frommember if frommember
107
+ args << "FROMLONLAT" << fromlonlat[0] << fromlonlat[1] if fromlonlat
108
+ args << "BYRADIUS" << byradius[0] << byradius[1] if byradius
109
+ args << "BYBOX" << bybox[0] << bybox[1] << bybox[2] if bybox
110
+
111
+ options = []
112
+ options << "WITHCOORD" if withcoord
113
+ options << "WITHDIST" if withdist
114
+ options << "WITHHASH" if withhash
115
+
116
+ geoarguments = _geoarguments(*args, sort: sort, count: count, count_any: count_any, options: options)
117
+
118
+ send_command([:geosearch, *geoarguments])
119
+ end
120
+
121
+ # Like GEOSEARCH, but stores the result in a destination key. By default the destination
122
+ # is populated with the matching members and their geospatial scores; when STOREDIST is
123
+ # set, the members are stored with their distance from the center point as the score.
124
+ # Available since Redis 6.2.
125
+ #
126
+ # @example Store the three nearest members
127
+ # redis.geosearchstore("nearest", "Sicily",
128
+ # fromlonlat: [15, 37], bybox: [400, 400, "km"], sort: "asc", count: 3)
129
+ # # => 3
130
+ #
131
+ # @example Store distances as scores
132
+ # redis.geosearchstore("distances", "Sicily",
133
+ # fromlonlat: [15, 37], bybox: [400, 400, "km"], storedist: true)
134
+ # # => 3
135
+ #
136
+ # @param [String] destination key to store the result in
137
+ # @param [String] source geospatial sorted set to search
138
+ # @param [String] frommember use the position of the given existing member as the center
139
+ # @param [Array<Numeric>] fromlonlat a [longitude, latitude] pair used as the center
140
+ # @param [Array] byradius a [radius, unit] pair where unit is one of 'm', 'km', 'ft', 'mi'
141
+ # @param [Array] bybox a [width, height, unit] triple where unit is one of 'm', 'km', 'ft', 'mi'
142
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest, or vice versa
143
+ # @param [Integer] count limit the results to the first N matching items
144
+ # @param [Boolean] count_any return as soon as enough matches are found (only with count)
145
+ # @param [Boolean] storedist store the distance from the center point as the score
146
+ # @return [Integer] number of elements stored in the destination key
147
+ def geosearchstore(destination, source, frommember: nil, fromlonlat: nil, byradius: nil, bybox: nil,
148
+ sort: nil, count: nil, count_any: false, storedist: false)
149
+ args = [destination, source]
150
+ args << "FROMMEMBER" << frommember if frommember
151
+ args << "FROMLONLAT" << fromlonlat[0] << fromlonlat[1] if fromlonlat
152
+ args << "BYRADIUS" << byradius[0] << byradius[1] if byradius
153
+ args << "BYBOX" << bybox[0] << bybox[1] << bybox[2] if bybox
154
+
155
+ options = []
156
+ options << "STOREDIST" if storedist
157
+
158
+ geoarguments = _geoarguments(*args, sort: sort, count: count, count_any: count_any, options: options)
159
+
160
+ send_command([:geosearchstore, *geoarguments])
161
+ end
162
+
64
163
  # Returns the distance between two members of a geospatial index
65
164
  #
66
165
  # @param [String ]key
@@ -73,10 +172,13 @@ class Redis
73
172
 
74
173
  private
75
174
 
76
- def _geoarguments(*args, options: nil, sort: nil, count: nil)
77
- args.push sort if sort
78
- args.push 'count', count if count
79
- args.push options if options
175
+ def _geoarguments(*args, options: nil, sort: nil, count: nil, count_any: false)
176
+ args << sort if sort
177
+ if count
178
+ args << 'COUNT' << Integer(count)
179
+ args << 'ANY' if count_any
180
+ end
181
+ args.concat(Array(options))
80
182
  args
81
183
  end
82
184
  end
@@ -63,7 +63,7 @@ class Redis
63
63
  #
64
64
  # @see #hmset
65
65
  def mapped_hmset(key, hash)
66
- hmset(key, hash.to_a.flatten)
66
+ hmset(key, hash.flatten)
67
67
  end
68
68
 
69
69
  # Get the value of a hash field.
@@ -87,7 +87,8 @@ class Redis
87
87
  #
88
88
  # @see #mapped_hmget
89
89
  def hmget(key, *fields, &blk)
90
- send_command([:hmget, key] + fields, &blk)
90
+ fields.flatten!(1)
91
+ send_command([:hmget, key].concat(fields), &blk)
91
92
  end
92
93
 
93
94
  # Get the values of all the given hash fields.
@@ -102,7 +103,8 @@ class Redis
102
103
  #
103
104
  # @see #hmget
104
105
  def mapped_hmget(key, *fields)
105
- hmget(key, *fields) do |reply|
106
+ fields.flatten!(1)
107
+ hmget(key, fields) do |reply|
106
108
  if reply.is_a?(Array)
107
109
  Hash[fields.zip(reply)]
108
110
  else
@@ -152,7 +154,8 @@ class Redis
152
154
  # @param [String, Array<String>] field
153
155
  # @return [Integer] the number of fields that were removed from the hash
154
156
  def hdel(key, *fields)
155
- send_command([:hdel, key, *fields])
157
+ fields.flatten!(1)
158
+ send_command([:hdel, key].concat(fields))
156
159
  end
157
160
 
158
161
  # Determine if a hash field exists.
@@ -171,7 +174,7 @@ class Redis
171
174
  # @param [Integer] increment
172
175
  # @return [Integer] value of the field after incrementing it
173
176
  def hincrby(key, field, increment)
174
- send_command([:hincrby, key, field, increment])
177
+ send_command([:hincrby, key, field, Integer(increment)])
175
178
  end
176
179
 
177
180
  # Increment the numeric value of a hash field by the given float number.
@@ -181,7 +184,7 @@ class Redis
181
184
  # @param [Float] increment
182
185
  # @return [Float] value of the field after incrementing it
183
186
  def hincrbyfloat(key, field, increment)
184
- send_command([:hincrbyfloat, key, field, increment], &Floatify)
187
+ send_command([:hincrbyfloat, key, field, Float(increment)], &Floatify)
185
188
  end
186
189
 
187
190
  # Get all the fields in a hash.
@@ -217,11 +220,20 @@ class Redis
217
220
  # @param [Hash] options
218
221
  # - `:match => String`: only return keys matching the pattern
219
222
  # - `:count => Integer`: return count keys at most per iteration
223
+ # - `:novalues => Boolean`: whether or not to include values in the output (default: false)
220
224
  #
221
- # @return [String, Array<[String, String]>] the next cursor and all found keys
225
+ # @return [String, Array<[String, String]>, Array<String>] the next cursor and all found keys
226
+ # - when `:novalues` is false: [cursor, [[field1, value1], [field2, value2], ...]]
227
+ # - when `:novalues` is true: [cursor, [field1, field2, ...]]
228
+ #
229
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
222
230
  def hscan(key, cursor, **options)
223
231
  _scan(:hscan, cursor, [key], **options) do |reply|
224
- [reply[0], reply[1].each_slice(2).to_a]
232
+ if options[:novalues]
233
+ reply
234
+ else
235
+ [reply[0], reply[1].each_slice(2).to_a]
236
+ end
225
237
  end
226
238
  end
227
239
 
@@ -234,8 +246,11 @@ class Redis
234
246
  # @param [Hash] options
235
247
  # - `:match => String`: only return keys matching the pattern
236
248
  # - `:count => Integer`: return count keys at most per iteration
249
+ # - `:novalues => Boolean`: whether or not to include values in the output (default: false)
237
250
  #
238
251
  # @return [Enumerator] an enumerator for all found keys
252
+ #
253
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
239
254
  def hscan_each(key, **options, &block)
240
255
  return to_enum(:hscan_each, key, **options) unless block_given?
241
256
 
@@ -246,6 +261,162 @@ class Redis
246
261
  break if cursor == "0"
247
262
  end
248
263
  end
264
+
265
+ # Sets the time to live in seconds for one or more fields.
266
+ #
267
+ # @example
268
+ # redis.hset("hash", "f1", "v1")
269
+ # redis.hexpire("hash", 10, "f1", "f2") # => [1, -2]
270
+ #
271
+ # @param [String] key
272
+ # @param [Integer] ttl
273
+ # @param [Array<String>] fields
274
+ # @return [Array<Integer>] Feedback on if the fields have been updated.
275
+ #
276
+ # See https://redis.io/docs/latest/commands/hexpire/#return-information for array reply.
277
+ def hexpire(key, ttl, *fields)
278
+ send_command([:hexpire, key, ttl, 'FIELDS', fields.length, *fields])
279
+ end
280
+
281
+ # Returns the time to live in seconds for one or more fields.
282
+ #
283
+ # @example
284
+ # redis.hset("hash", "f1", "v1", "f2", "v2")
285
+ # redis.hexpire("hash", 10, "f1") # => [1]
286
+ # redis.httl("hash", "f1", "f2", "f3") # => [10, -1, -2]
287
+ #
288
+ # @param [String] key
289
+ # @param [Array<String>] fields
290
+ # @return [Array<Integer>] Feedback on the TTL of the fields.
291
+ #
292
+ # See https://redis.io/docs/latest/commands/httl/#return-information for array reply.
293
+ def httl(key, *fields)
294
+ send_command([:httl, key, 'FIELDS', fields.length, *fields])
295
+ end
296
+
297
+ # Sets the time to live in milliseconds for one or more fields.
298
+ #
299
+ # @example
300
+ # redis.hset("hash", "f1", "v1")
301
+ # redis.hpexpire("hash", 500, "f1", "f2") # => [1, -2]
302
+ # redis.hpexpire("hash", 500, "f1", "f2", nx: true) # => [0, -2]
303
+ #
304
+ # @param [String] key
305
+ # @param [Integer] ttl
306
+ # @param [Hash] options
307
+ # - `:nx => true`: Set expiry only when the key has no expiry.
308
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
309
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
310
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
311
+ # @param [Array<String>] fields
312
+ # @return [Array<Integer>] Feedback on if the fields have been updated.
313
+ #
314
+ # See https://redis.io/docs/latest/commands/hpexpire/#return-information for array reply.
315
+ def hpexpire(key, ttl, *fields, nx: nil, xx: nil, gt: nil, lt: nil)
316
+ args = [:hpexpire, key, ttl]
317
+ args << "NX" if nx
318
+ args << "XX" if xx
319
+ args << "GT" if gt
320
+ args << "LT" if lt
321
+ args.concat(['FIELDS', fields.length, *fields])
322
+
323
+ send_command(args)
324
+ end
325
+
326
+ # Returns the time to live in milliseconds for one or more fields.
327
+ #
328
+ # @example
329
+ # redis.hset("hash", "f1", "v1", "f2", "v2")
330
+ # redis.hpexpire("hash", 500, "f1") # => [1]
331
+ # redis.hpttl("hash", "f1", "f2", "f3") # => [500, -1, -2]
332
+ #
333
+ # @param [String] key
334
+ # @param [Array<String>] fields
335
+ # @return [Array<Integer>] Feedback on the TTL of the fields.
336
+ #
337
+ # See https://redis.io/docs/latest/commands/hpttl/#return-information for array reply.
338
+ def hpttl(key, *fields)
339
+ send_command([:hpttl, key, 'FIELDS', fields.length, *fields])
340
+ end
341
+
342
+ # Register an ordered list of hash field names under +fieldset_name+ for
343
+ # use by subsequent #himport_set calls on the same connection.
344
+ #
345
+ # Fieldsets are server-side session state scoped to the current physical
346
+ # connection: they vanish on disconnect or RESET and are invisible to
347
+ # other connections. See the README "Bulk hash ingestion (HIMPORT)"
348
+ # section for connection-scoping guidance. Re-preparing an existing
349
+ # +fieldset_name+ silently replaces it. Field order is preserved as
350
+ # given; it defines the positional pairing used by #himport_set.
351
+ #
352
+ # @note HIMPORT support is experimental: the client API may change in a
353
+ # future minor release without a major version bump.
354
+ #
355
+ # @example
356
+ # redis.himport_prepare("shared", ["name", "email", "age"])
357
+ # # => "OK"
358
+ #
359
+ # @param [String] fieldset_name name used by later SET and DISCARD calls
360
+ # @param [String, Array<String>] fields one or more field names
361
+ # @return [String] `"OK"`
362
+ # @api experimental
363
+ def himport_prepare(fieldset_name, *fields)
364
+ fields.flatten!(1)
365
+ raise ArgumentError, "fields must not be empty" if fields.empty?
366
+
367
+ send_command([:himport, "PREPARE", fieldset_name].concat(fields))
368
+ end
369
+
370
+ # Create or fully replace the hash at +key+ using the field list
371
+ # registered under +fieldset_name+ on this connection. Values pair
372
+ # positionally with the fields given to #himport_prepare; the value
373
+ # count must equal the field count.
374
+ #
375
+ # The fieldset must exist on the executing connection, otherwise the
376
+ # server replies with a "no such fieldset" error.
377
+ #
378
+ # @note HIMPORT support is experimental: the client API may change in a
379
+ # future minor release without a major version bump.
380
+ #
381
+ # @example
382
+ # redis.himport_set("shared:1", "shared", ["alice", "alice@example.com", "25"])
383
+ # # => "OK"
384
+ #
385
+ # @param [String] key hash key to create or overwrite
386
+ # @param [String] fieldset_name fieldset previously prepared on this connection
387
+ # @param [String, Array<String>] values one or more values, order preserved
388
+ # @return [String] `"OK"`
389
+ # @api experimental
390
+ def himport_set(key, fieldset_name, *values)
391
+ values.flatten!(1)
392
+ raise ArgumentError, "values must not be empty" if values.empty?
393
+
394
+ send_command([:himport, "SET", key, fieldset_name].concat(values))
395
+ end
396
+
397
+ # Remove +fieldset_name+ from this connection's session. Keys already
398
+ # written through the fieldset are not affected.
399
+ #
400
+ # @note HIMPORT support is experimental: the client API may change in a
401
+ # future minor release without a major version bump.
402
+ #
403
+ # @param [String] fieldset_name
404
+ # @return [Integer] `1` if the fieldset was removed, `0` if it did not exist
405
+ # @api experimental
406
+ def himport_discard(fieldset_name)
407
+ send_command([:himport, "DISCARD", fieldset_name])
408
+ end
409
+
410
+ # Remove all fieldsets from this connection's session.
411
+ #
412
+ # @note HIMPORT support is experimental: the client API may change in a
413
+ # future minor release without a major version bump.
414
+ #
415
+ # @return [Integer] number of fieldsets removed
416
+ # @api experimental
417
+ def himport_discard_all
418
+ send_command([:himport, "DISCARDALL"])
419
+ end
249
420
  end
250
421
  end
251
422
  end
@@ -20,7 +20,7 @@ class Redis
20
20
  # @param [String, Array<String>] keys
21
21
  # @return [Integer]
22
22
  def pfcount(*keys)
23
- send_command([:pfcount] + keys)
23
+ send_command([:pfcount] + keys.flatten(1))
24
24
  end
25
25
 
26
26
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -22,6 +22,8 @@ class Redis
22
22
  # - `:type => String`: return keys only of the given type
23
23
  #
24
24
  # @return [String, Array<String>] the next cursor and all found keys
25
+ #
26
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
25
27
  def scan(cursor, **options)
26
28
  _scan(:scan, cursor, [], **options)
27
29
  end
@@ -46,6 +48,8 @@ class Redis
46
48
  # - `:type => String`: return keys only of the given type
47
49
  #
48
50
  # @return [Enumerator] an enumerator for all found keys
51
+ #
52
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
49
53
  def scan_each(**options, &block)
50
54
  return to_enum(:scan_each, **options) unless block_given?
51
55
 
@@ -76,7 +80,7 @@ class Redis
76
80
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
77
81
  # @return [Boolean] whether the timeout was set or not
78
82
  def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
- args = [:expire, key, seconds]
83
+ args = [:expire, key, Integer(seconds)]
80
84
  args << "NX" if nx
81
85
  args << "XX" if xx
82
86
  args << "GT" if gt
@@ -96,7 +100,7 @@ class Redis
96
100
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
97
101
  # @return [Boolean] whether the timeout was set or not
98
102
  def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
- args = [:expireat, key, unix_time]
103
+ args = [:expireat, key, Integer(unix_time)]
100
104
  args << "NX" if nx
101
105
  args << "XX" if xx
102
106
  args << "GT" if gt
@@ -105,6 +109,14 @@ class Redis
105
109
  send_command(args, &Boolify)
106
110
  end
107
111
 
112
+ # Get a key's expiry time specified as number of seconds from UNIX Epoch
113
+ #
114
+ # @param [String] key
115
+ # @return [Integer] expiry time specified as number of seconds from UNIX Epoch
116
+ def expiretime(key)
117
+ send_command([:expiretime, key])
118
+ end
119
+
108
120
  # Get the time to live (in seconds) for a key.
109
121
  #
110
122
  # @param [String] key
@@ -132,7 +144,7 @@ class Redis
132
144
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
133
145
  # @return [Boolean] whether the timeout was set or not
134
146
  def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
- args = [:pexpire, key, milliseconds]
147
+ args = [:pexpire, key, Integer(milliseconds)]
136
148
  args << "NX" if nx
137
149
  args << "XX" if xx
138
150
  args << "GT" if gt
@@ -152,7 +164,7 @@ class Redis
152
164
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
153
165
  # @return [Boolean] whether the timeout was set or not
154
166
  def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
- args = [:pexpireat, key, ms_unix_time]
167
+ args = [:pexpireat, key, Integer(ms_unix_time)]
156
168
  args << "NX" if nx
157
169
  args << "XX" if xx
158
170
  args << "GT" if gt
@@ -161,6 +173,14 @@ class Redis
161
173
  send_command(args, &Boolify)
162
174
  end
163
175
 
176
+ # Get a key's expiry time specified as number of milliseconds from UNIX Epoch
177
+ #
178
+ # @param [String] key
179
+ # @return [Integer] expiry time specified as number of milliseconds from UNIX Epoch
180
+ def pexpiretime(key)
181
+ send_command([:pexpiretime, key])
182
+ end
183
+
164
184
  # Get the time to live (in milliseconds) for a key.
165
185
  #
166
186
  # @param [String] key
@@ -241,6 +261,9 @@ class Redis
241
261
  # @param [String, Array<String>] keys
242
262
  # @return [Integer] number of keys that were unlinked
243
263
  def unlink(*keys)
264
+ keys.flatten!(1)
265
+ return 0 if keys.empty?
266
+
244
267
  send_command([:unlink] + keys)
245
268
  end
246
269
 
@@ -249,24 +272,6 @@ class Redis
249
272
  # @param [String, Array<String>] keys
250
273
  # @return [Integer]
251
274
  def exists(*keys)
252
- if !Redis.exists_returns_integer && keys.size == 1
253
- if Redis.exists_returns_integer.nil?
254
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
255
- "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
256
- "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
257
- "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
258
- "(#{::Kernel.caller(1, 1).first})\n"
259
-
260
- ::Redis.deprecate!(message)
261
- end
262
-
263
- exists?(*keys)
264
- else
265
- _exists(*keys)
266
- end
267
- end
268
-
269
- def _exists(*keys)
270
275
  send_command([:exists, *keys])
271
276
  end
272
277
 
@@ -284,6 +289,8 @@ class Redis
284
289
  #
285
290
  # @param [String] pattern
286
291
  # @return [Array<String>]
292
+ #
293
+ # See the [Redis Server KEYS documentation](https://redis.io/docs/latest/commands/keys/) for further details
287
294
  def keys(pattern = "*")
288
295
  send_command([:keys, pattern]) do |reply|
289
296
  if reply.is_a?(String)
@@ -440,13 +447,14 @@ class Redis
440
447
 
441
448
  private
442
449
 
443
- def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
450
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, novalues: false, &block)
444
451
  # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
445
452
 
446
453
  args << cursor
447
454
  args << "MATCH" << match if match
448
- args << "COUNT" << count if count
455
+ args << "COUNT" << Integer(count) if count
449
456
  args << "TYPE" << type if type
457
+ args << "NOVALUES" if novalues
450
458
 
451
459
  send_command([command] + args, &block)
452
460
  end