redis 4.2.5 → 5.0.7

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -0
  3. data/README.md +102 -162
  4. data/lib/redis/client.rb +84 -589
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +220 -75
  23. data/lib/redis/errors.rb +15 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +66 -120
  26. data/lib/redis/subscribe.rb +23 -15
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +110 -3441
  29. metadata +27 -54
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -34
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -107
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -90
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -295
  40. data/lib/redis/connection/command_helper.rb +0 -39
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -427
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
@@ -0,0 +1,884 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module SortedSets
6
+ # Get the number of members in a sorted set.
7
+ #
8
+ # @example
9
+ # redis.zcard("zset")
10
+ # # => 4
11
+ #
12
+ # @param [String] key
13
+ # @return [Integer]
14
+ def zcard(key)
15
+ send_command([:zcard, key])
16
+ end
17
+
18
+ # Add one or more members to a sorted set, or update the score for members
19
+ # that already exist.
20
+ #
21
+ # @example Add a single `[score, member]` pair to a sorted set
22
+ # redis.zadd("zset", 32.0, "member")
23
+ # @example Add an array of `[score, member]` pairs to a sorted set
24
+ # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
25
+ #
26
+ # @param [String] key
27
+ # @param [[Float, String], Array<[Float, String]>] args
28
+ # - a single `[score, member]` pair
29
+ # - an array of `[score, member]` pairs
30
+ # @param [Hash] options
31
+ # - `:xx => true`: Only update elements that already exist (never
32
+ # add elements)
33
+ # - `:nx => true`: Don't update already existing elements (always
34
+ # add new elements)
35
+ # - `:lt => true`: Only update existing elements if the new score
36
+ # is less than the current score
37
+ # - `:gt => true`: Only update existing elements if the new score
38
+ # is greater than the current score
39
+ # - `:ch => true`: Modify the return value from the number of new
40
+ # elements added, to the total number of elements changed (CH is an
41
+ # abbreviation of changed); changed elements are new elements added
42
+ # and elements already existing for which the score was updated
43
+ # - `:incr => true`: When this option is specified ZADD acts like
44
+ # ZINCRBY; only one score-element pair can be specified in this mode
45
+ #
46
+ # @return [Boolean, Integer, Float]
47
+ # - `Boolean` when a single pair is specified, holding whether or not it was
48
+ # **added** to the sorted set.
49
+ # - `Integer` when an array of pairs is specified, holding the number of
50
+ # pairs that were **added** to the sorted set.
51
+ # - `Float` when option :incr is specified, holding the score of the member
52
+ # after incrementing it.
53
+ def zadd(key, *args, nx: nil, xx: nil, lt: nil, gt: nil, ch: nil, incr: nil)
54
+ command = [:zadd, key]
55
+ command << "NX" if nx
56
+ command << "XX" if xx
57
+ command << "LT" if lt
58
+ command << "GT" if gt
59
+ command << "CH" if ch
60
+ command << "INCR" if incr
61
+
62
+ if args.size == 1 && args[0].is_a?(Array)
63
+ members_to_add = args[0]
64
+ return 0 if members_to_add.empty?
65
+
66
+ # Variadic: return float if INCR, integer if !INCR
67
+ send_command(command + members_to_add, &(incr ? Floatify : nil))
68
+ elsif args.size == 2
69
+ # Single pair: return float if INCR, boolean if !INCR
70
+ send_command(command + args, &(incr ? Floatify : Boolify))
71
+ else
72
+ raise ArgumentError, "wrong number of arguments"
73
+ end
74
+ end
75
+
76
+ # Increment the score of a member in a sorted set.
77
+ #
78
+ # @example
79
+ # redis.zincrby("zset", 32.0, "a")
80
+ # # => 64.0
81
+ #
82
+ # @param [String] key
83
+ # @param [Float] increment
84
+ # @param [String] member
85
+ # @return [Float] score of the member after incrementing it
86
+ def zincrby(key, increment, member)
87
+ send_command([:zincrby, key, increment, member], &Floatify)
88
+ end
89
+
90
+ # Remove one or more members from a sorted set.
91
+ #
92
+ # @example Remove a single member from a sorted set
93
+ # redis.zrem("zset", "a")
94
+ # @example Remove an array of members from a sorted set
95
+ # redis.zrem("zset", ["a", "b"])
96
+ #
97
+ # @param [String] key
98
+ # @param [String, Array<String>] member
99
+ # - a single member
100
+ # - an array of members
101
+ #
102
+ # @return [Boolean, Integer]
103
+ # - `Boolean` when a single member is specified, holding whether or not it
104
+ # was removed from the sorted set
105
+ # - `Integer` when an array of pairs is specified, holding the number of
106
+ # members that were removed to the sorted set
107
+ def zrem(key, member)
108
+ if member.is_a?(Array)
109
+ members_to_remove = member
110
+ return 0 if members_to_remove.empty?
111
+ end
112
+
113
+ send_command([:zrem, key, member]) do |reply|
114
+ if member.is_a? Array
115
+ # Variadic: return integer
116
+ reply
117
+ else
118
+ # Single argument: return boolean
119
+ Boolify.call(reply)
120
+ end
121
+ end
122
+ end
123
+
124
+ # Removes and returns up to count members with the highest scores in the sorted set stored at key.
125
+ #
126
+ # @example Popping a member
127
+ # redis.zpopmax('zset')
128
+ # #=> ['b', 2.0]
129
+ # @example With count option
130
+ # redis.zpopmax('zset', 2)
131
+ # #=> [['b', 2.0], ['a', 1.0]]
132
+ #
133
+ # @params key [String] a key of the sorted set
134
+ # @params count [Integer] a number of members
135
+ #
136
+ # @return [Array<String, Float>] element and score pair if count is not specified
137
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
138
+ def zpopmax(key, count = nil)
139
+ command = [:zpopmax, key]
140
+ command << Integer(count) if count
141
+ send_command(command) do |members|
142
+ members = FloatifyPairs.call(members)
143
+ count.to_i > 1 ? members : members.first
144
+ end
145
+ end
146
+
147
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at key.
148
+ #
149
+ # @example Popping a member
150
+ # redis.zpopmin('zset')
151
+ # #=> ['a', 1.0]
152
+ # @example With count option
153
+ # redis.zpopmin('zset', 2)
154
+ # #=> [['a', 1.0], ['b', 2.0]]
155
+ #
156
+ # @params key [String] a key of the sorted set
157
+ # @params count [Integer] a number of members
158
+ #
159
+ # @return [Array<String, Float>] element and score pair if count is not specified
160
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
161
+ def zpopmin(key, count = nil)
162
+ command = [:zpopmin, key]
163
+ command << Integer(count) if count
164
+ send_command(command) do |members|
165
+ members = FloatifyPairs.call(members)
166
+ count.to_i > 1 ? members : members.first
167
+ end
168
+ end
169
+
170
+ # Removes and returns up to count members with scores in the sorted set stored at key.
171
+ #
172
+ # @example Popping a member
173
+ # redis.bzmpop('zset')
174
+ # #=> ['zset', ['a', 1.0]]
175
+ # @example With count option
176
+ # redis.bzmpop('zset', count: 2)
177
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
178
+ #
179
+ # @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
180
+ # A timeout of zero can be used to block indefinitely.
181
+ # @params key [String, Array<String>] one or more keys with sorted sets
182
+ # @params modifier [String]
183
+ # - when `"MIN"` - the elements popped are those with lowest scores
184
+ # - when `"MAX"` - the elements popped are those with the highest scores
185
+ # @params count [Integer] a number of members to pop
186
+ #
187
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
188
+ def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
189
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
190
+
191
+ args = [:bzmpop, timeout, keys.size, *keys, modifier]
192
+ args << "COUNT" << Integer(count) if count
193
+
194
+ send_blocking_command(args, timeout) do |response|
195
+ response&.map do |entry|
196
+ case entry
197
+ when String then entry
198
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ # Removes and returns up to count members with scores in the sorted set stored at key.
205
+ #
206
+ # @example Popping a member
207
+ # redis.zmpop('zset')
208
+ # #=> ['zset', ['a', 1.0]]
209
+ # @example With count option
210
+ # redis.zmpop('zset', count: 2)
211
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
212
+ #
213
+ # @params key [String, Array<String>] one or more keys with sorted sets
214
+ # @params modifier [String]
215
+ # - when `"MIN"` - the elements popped are those with lowest scores
216
+ # - when `"MAX"` - the elements popped are those with the highest scores
217
+ # @params count [Integer] a number of members to pop
218
+ #
219
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
220
+ def zmpop(*keys, modifier: "MIN", count: nil)
221
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
222
+
223
+ args = [:zmpop, keys.size, *keys, modifier]
224
+ args << "COUNT" << Integer(count) if count
225
+
226
+ send_command(args) do |response|
227
+ response&.map do |entry|
228
+ case entry
229
+ when String then entry
230
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
237
+ # or block until one is available.
238
+ #
239
+ # @example Popping a member from a sorted set
240
+ # redis.bzpopmax('zset', 1)
241
+ # #=> ['zset', 'b', 2.0]
242
+ # @example Popping a member from multiple sorted sets
243
+ # redis.bzpopmax('zset1', 'zset2', 1)
244
+ # #=> ['zset1', 'b', 2.0]
245
+ #
246
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
247
+ # @params timeout [Integer] the maximum number of seconds to block
248
+ #
249
+ # @return [Array<String, String, Float>] a touple of key, member and score
250
+ # @return [nil] when no element could be popped and the timeout expired
251
+ def bzpopmax(*args)
252
+ _bpop(:bzpopmax, args) do |reply|
253
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
254
+ end
255
+ end
256
+
257
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
258
+ # or block until one is available.
259
+ #
260
+ # @example Popping a member from a sorted set
261
+ # redis.bzpopmin('zset', 1)
262
+ # #=> ['zset', 'a', 1.0]
263
+ # @example Popping a member from multiple sorted sets
264
+ # redis.bzpopmin('zset1', 'zset2', 1)
265
+ # #=> ['zset1', 'a', 1.0]
266
+ #
267
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
268
+ # @params timeout [Integer] the maximum number of seconds to block
269
+ #
270
+ # @return [Array<String, String, Float>] a touple of key, member and score
271
+ # @return [nil] when no element could be popped and the timeout expired
272
+ def bzpopmin(*args)
273
+ _bpop(:bzpopmin, args) do |reply|
274
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
275
+ end
276
+ end
277
+
278
+ # Get the score associated with the given member in a sorted set.
279
+ #
280
+ # @example Get the score for member "a"
281
+ # redis.zscore("zset", "a")
282
+ # # => 32.0
283
+ #
284
+ # @param [String] key
285
+ # @param [String] member
286
+ # @return [Float] score of the member
287
+ def zscore(key, member)
288
+ send_command([:zscore, key, member], &Floatify)
289
+ end
290
+
291
+ # Get the scores associated with the given members in a sorted set.
292
+ #
293
+ # @example Get the scores for members "a" and "b"
294
+ # redis.zmscore("zset", "a", "b")
295
+ # # => [32.0, 48.0]
296
+ #
297
+ # @param [String] key
298
+ # @param [String, Array<String>] members
299
+ # @return [Array<Float>] scores of the members
300
+ def zmscore(key, *members)
301
+ send_command([:zmscore, key, *members]) do |reply|
302
+ reply.map(&Floatify)
303
+ end
304
+ end
305
+
306
+ # Get one or more random members from a sorted set.
307
+ #
308
+ # @example Get one random member
309
+ # redis.zrandmember("zset")
310
+ # # => "a"
311
+ # @example Get multiple random members
312
+ # redis.zrandmember("zset", 2)
313
+ # # => ["a", "b"]
314
+ # @example Get multiple random members with scores
315
+ # redis.zrandmember("zset", 2, with_scores: true)
316
+ # # => [["a", 2.0], ["b", 3.0]]
317
+ #
318
+ # @param [String] key
319
+ # @param [Integer] count
320
+ # @param [Hash] options
321
+ # - `:with_scores => true`: include scores in output
322
+ #
323
+ # @return [nil, String, Array<String>, Array<[String, Float]>]
324
+ # - when `key` does not exist or set is empty, `nil`
325
+ # - when `count` is not specified, a member
326
+ # - when `count` is specified and `:with_scores` is not specified, an array of members
327
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
328
+ def zrandmember(key, count = nil, withscores: false, with_scores: withscores)
329
+ if with_scores && count.nil?
330
+ raise ArgumentError, "count argument must be specified"
331
+ end
332
+
333
+ args = [:zrandmember, key]
334
+ args << Integer(count) if count
335
+
336
+ if with_scores
337
+ args << "WITHSCORES"
338
+ block = FloatifyPairs
339
+ end
340
+
341
+ send_command(args, &block)
342
+ end
343
+
344
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
345
+ #
346
+ # @example Retrieve all members from a sorted set, by index
347
+ # redis.zrange("zset", 0, -1)
348
+ # # => ["a", "b"]
349
+ # @example Retrieve all members and their scores from a sorted set
350
+ # redis.zrange("zset", 0, -1, :with_scores => true)
351
+ # # => [["a", 32.0], ["b", 64.0]]
352
+ #
353
+ # @param [String] key
354
+ # @param [Integer] start start index
355
+ # @param [Integer] stop stop index
356
+ # @param [Hash] options
357
+ # - `:by_score => false`: return members by score
358
+ # - `:by_lex => false`: return members by lexicographical ordering
359
+ # - `:rev => false`: reverse the ordering, from highest to lowest
360
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
361
+ # `count` members
362
+ # - `:with_scores => true`: include scores in output
363
+ #
364
+ # @return [Array<String>, Array<[String, Float]>]
365
+ # - when `:with_scores` is not specified, an array of members
366
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
367
+ def zrange(key, start, stop, byscore: false, by_score: byscore, bylex: false, by_lex: bylex,
368
+ rev: false, limit: nil, withscores: false, with_scores: withscores)
369
+
370
+ if by_score && by_lex
371
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
372
+ end
373
+
374
+ args = [:zrange, key, start, stop]
375
+
376
+ if by_score
377
+ args << "BYSCORE"
378
+ elsif by_lex
379
+ args << "BYLEX"
380
+ end
381
+
382
+ args << "REV" if rev
383
+
384
+ if limit
385
+ args << "LIMIT"
386
+ args.concat(limit.map { |l| Integer(l) })
387
+ end
388
+
389
+ if with_scores
390
+ args << "WITHSCORES"
391
+ block = FloatifyPairs
392
+ end
393
+
394
+ send_command(args, &block)
395
+ end
396
+
397
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
398
+ # and store the resulting sorted set in a new key.
399
+ #
400
+ # @example
401
+ # redis.zadd("foo", [[1.0, "s1"], [2.0, "s2"], [3.0, "s3"]])
402
+ # redis.zrangestore("bar", "foo", 0, 1)
403
+ # # => 2
404
+ # redis.zrange("bar", 0, -1)
405
+ # # => ["s1", "s2"]
406
+ #
407
+ # @return [Integer] the number of elements in the resulting sorted set
408
+ # @see #zrange
409
+ def zrangestore(dest_key, src_key, start, stop, byscore: false, by_score: byscore,
410
+ bylex: false, by_lex: bylex, rev: false, limit: nil)
411
+ if by_score && by_lex
412
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
413
+ end
414
+
415
+ args = [:zrangestore, dest_key, src_key, start, stop]
416
+
417
+ if by_score
418
+ args << "BYSCORE"
419
+ elsif by_lex
420
+ args << "BYLEX"
421
+ end
422
+
423
+ args << "REV" if rev
424
+
425
+ if limit
426
+ args << "LIMIT"
427
+ args.concat(limit.map { |l| Integer(l) })
428
+ end
429
+
430
+ send_command(args)
431
+ end
432
+
433
+ # Return a range of members in a sorted set, by index, with scores ordered
434
+ # from high to low.
435
+ #
436
+ # @example Retrieve all members from a sorted set
437
+ # redis.zrevrange("zset", 0, -1)
438
+ # # => ["b", "a"]
439
+ # @example Retrieve all members and their scores from a sorted set
440
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
441
+ # # => [["b", 64.0], ["a", 32.0]]
442
+ #
443
+ # @see #zrange
444
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
445
+ args = [:zrevrange, key, Integer(start), Integer(stop)]
446
+
447
+ if with_scores
448
+ args << "WITHSCORES"
449
+ block = FloatifyPairs
450
+ end
451
+
452
+ send_command(args, &block)
453
+ end
454
+
455
+ # Determine the index of a member in a sorted set.
456
+ #
457
+ # @param [String] key
458
+ # @param [String] member
459
+ # @return [Integer]
460
+ def zrank(key, member)
461
+ send_command([:zrank, key, member])
462
+ end
463
+
464
+ # Determine the index of a member in a sorted set, with scores ordered from
465
+ # high to low.
466
+ #
467
+ # @param [String] key
468
+ # @param [String] member
469
+ # @return [Integer]
470
+ def zrevrank(key, member)
471
+ send_command([:zrevrank, key, member])
472
+ end
473
+
474
+ # Remove all members in a sorted set within the given indexes.
475
+ #
476
+ # @example Remove first 5 members
477
+ # redis.zremrangebyrank("zset", 0, 4)
478
+ # # => 5
479
+ # @example Remove last 5 members
480
+ # redis.zremrangebyrank("zset", -5, -1)
481
+ # # => 5
482
+ #
483
+ # @param [String] key
484
+ # @param [Integer] start start index
485
+ # @param [Integer] stop stop index
486
+ # @return [Integer] number of members that were removed
487
+ def zremrangebyrank(key, start, stop)
488
+ send_command([:zremrangebyrank, key, start, stop])
489
+ end
490
+
491
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
492
+ #
493
+ # @example Count members matching a
494
+ # redis.zlexcount("zset", "[a", "[a\xff")
495
+ # # => 1
496
+ # @example Count members matching a-z
497
+ # redis.zlexcount("zset", "[a", "[z\xff")
498
+ # # => 26
499
+ #
500
+ # @param [String] key
501
+ # @param [String] min
502
+ # - inclusive minimum is specified by prefixing `(`
503
+ # - exclusive minimum is specified by prefixing `[`
504
+ # @param [String] max
505
+ # - inclusive maximum is specified by prefixing `(`
506
+ # - exclusive maximum is specified by prefixing `[`
507
+ #
508
+ # @return [Integer] number of members within the specified lexicographical range
509
+ def zlexcount(key, min, max)
510
+ send_command([:zlexcount, key, min, max])
511
+ end
512
+
513
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
514
+ #
515
+ # @example Retrieve members matching a
516
+ # redis.zrangebylex("zset", "[a", "[a\xff")
517
+ # # => ["aaren", "aarika", "abagael", "abby"]
518
+ # @example Retrieve the first 2 members matching a
519
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
520
+ # # => ["aaren", "aarika"]
521
+ #
522
+ # @param [String] key
523
+ # @param [String] min
524
+ # - inclusive minimum is specified by prefixing `(`
525
+ # - exclusive minimum is specified by prefixing `[`
526
+ # @param [String] max
527
+ # - inclusive maximum is specified by prefixing `(`
528
+ # - exclusive maximum is specified by prefixing `[`
529
+ # @param [Hash] options
530
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
531
+ # `count` members
532
+ #
533
+ # @return [Array<String>, Array<[String, Float]>]
534
+ def zrangebylex(key, min, max, limit: nil)
535
+ args = [:zrangebylex, key, min, max]
536
+
537
+ if limit
538
+ args << "LIMIT"
539
+ args.concat(limit.map { |l| Integer(l) })
540
+ end
541
+
542
+ send_command(args)
543
+ end
544
+
545
+ # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
546
+ # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
547
+ #
548
+ # @example Retrieve members matching a
549
+ # redis.zrevrangebylex("zset", "[a", "[a\xff")
550
+ # # => ["abbygail", "abby", "abagael", "aaren"]
551
+ # @example Retrieve the last 2 members matching a
552
+ # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
553
+ # # => ["abbygail", "abby"]
554
+ #
555
+ # @see #zrangebylex
556
+ def zrevrangebylex(key, max, min, limit: nil)
557
+ args = [:zrevrangebylex, key, max, min]
558
+
559
+ if limit
560
+ args << "LIMIT"
561
+ args.concat(limit.map { |l| Integer(l) })
562
+ end
563
+
564
+ send_command(args)
565
+ end
566
+
567
+ # Return a range of members in a sorted set, by score.
568
+ #
569
+ # @example Retrieve members with score `>= 5` and `< 100`
570
+ # redis.zrangebyscore("zset", "5", "(100")
571
+ # # => ["a", "b"]
572
+ # @example Retrieve the first 2 members with score `>= 0`
573
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
574
+ # # => ["a", "b"]
575
+ # @example Retrieve members and their scores with scores `> 5`
576
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
577
+ # # => [["a", 32.0], ["b", 64.0]]
578
+ #
579
+ # @param [String] key
580
+ # @param [String] min
581
+ # - inclusive minimum score is specified verbatim
582
+ # - exclusive minimum score is specified by prefixing `(`
583
+ # @param [String] max
584
+ # - inclusive maximum score is specified verbatim
585
+ # - exclusive maximum score is specified by prefixing `(`
586
+ # @param [Hash] options
587
+ # - `:with_scores => true`: include scores in output
588
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
589
+ # `count` members
590
+ #
591
+ # @return [Array<String>, Array<[String, Float]>]
592
+ # - when `:with_scores` is not specified, an array of members
593
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
594
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
595
+ args = [:zrangebyscore, key, min, max]
596
+
597
+ if with_scores
598
+ args << "WITHSCORES"
599
+ block = FloatifyPairs
600
+ end
601
+
602
+ if limit
603
+ args << "LIMIT"
604
+ args.concat(limit.map { |l| Integer(l) })
605
+ end
606
+
607
+ send_command(args, &block)
608
+ end
609
+
610
+ # Return a range of members in a sorted set, by score, with scores ordered
611
+ # from high to low.
612
+ #
613
+ # @example Retrieve members with score `< 100` and `>= 5`
614
+ # redis.zrevrangebyscore("zset", "(100", "5")
615
+ # # => ["b", "a"]
616
+ # @example Retrieve the first 2 members with score `<= 0`
617
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
618
+ # # => ["b", "a"]
619
+ # @example Retrieve members and their scores with scores `> 5`
620
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
621
+ # # => [["b", 64.0], ["a", 32.0]]
622
+ #
623
+ # @see #zrangebyscore
624
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
625
+ args = [:zrevrangebyscore, key, max, min]
626
+
627
+ if with_scores
628
+ args << "WITHSCORES"
629
+ block = FloatifyPairs
630
+ end
631
+
632
+ if limit
633
+ args << "LIMIT"
634
+ args.concat(limit.map { |l| Integer(l) })
635
+ end
636
+
637
+ send_command(args, &block)
638
+ end
639
+
640
+ # Remove all members in a sorted set within the given scores.
641
+ #
642
+ # @example Remove members with score `>= 5` and `< 100`
643
+ # redis.zremrangebyscore("zset", "5", "(100")
644
+ # # => 2
645
+ # @example Remove members with scores `> 5`
646
+ # redis.zremrangebyscore("zset", "(5", "+inf")
647
+ # # => 2
648
+ #
649
+ # @param [String] key
650
+ # @param [String] min
651
+ # - inclusive minimum score is specified verbatim
652
+ # - exclusive minimum score is specified by prefixing `(`
653
+ # @param [String] max
654
+ # - inclusive maximum score is specified verbatim
655
+ # - exclusive maximum score is specified by prefixing `(`
656
+ # @return [Integer] number of members that were removed
657
+ def zremrangebyscore(key, min, max)
658
+ send_command([:zremrangebyscore, key, min, max])
659
+ end
660
+
661
+ # Count the members in a sorted set with scores within the given values.
662
+ #
663
+ # @example Count members with score `>= 5` and `< 100`
664
+ # redis.zcount("zset", "5", "(100")
665
+ # # => 2
666
+ # @example Count members with scores `> 5`
667
+ # redis.zcount("zset", "(5", "+inf")
668
+ # # => 2
669
+ #
670
+ # @param [String] key
671
+ # @param [String] min
672
+ # - inclusive minimum score is specified verbatim
673
+ # - exclusive minimum score is specified by prefixing `(`
674
+ # @param [String] max
675
+ # - inclusive maximum score is specified verbatim
676
+ # - exclusive maximum score is specified by prefixing `(`
677
+ # @return [Integer] number of members in within the specified range
678
+ def zcount(key, min, max)
679
+ send_command([:zcount, key, min, max])
680
+ end
681
+
682
+ # Return the intersection of multiple sorted sets
683
+ #
684
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
685
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
686
+ # # => ["v1", "v2"]
687
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
688
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
689
+ # # => [["v1", 3.0], ["v2", 6.0]]
690
+ #
691
+ # @param [String, Array<String>] keys one or more keys to intersect
692
+ # @param [Hash] options
693
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
694
+ # sorted sets
695
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
696
+ # - `:with_scores => true`: include scores in output
697
+ #
698
+ # @return [Array<String>, Array<[String, Float]>]
699
+ # - when `:with_scores` is not specified, an array of members
700
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
701
+ def zinter(*args)
702
+ _zsets_operation(:zinter, *args)
703
+ end
704
+ ruby2_keywords(:zinter) if respond_to?(:ruby2_keywords, true)
705
+
706
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
707
+ # key.
708
+ #
709
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
710
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
711
+ # # => 4
712
+ #
713
+ # @param [String] destination destination key
714
+ # @param [Array<String>] keys source keys
715
+ # @param [Hash] options
716
+ # - `:weights => [Array<Float>]`: weights to associate with source
717
+ # sorted sets
718
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
719
+ # @return [Integer] number of elements in the resulting sorted set
720
+ def zinterstore(*args)
721
+ _zsets_operation_store(:zinterstore, *args)
722
+ end
723
+ ruby2_keywords(:zinterstore) if respond_to?(:ruby2_keywords, true)
724
+
725
+ # Return the union of multiple sorted sets
726
+ #
727
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`
728
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0])
729
+ # # => ["v1", "v2"]
730
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`, and their scores
731
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
732
+ # # => [["v1", 3.0], ["v2", 6.0]]
733
+ #
734
+ # @param [String, Array<String>] keys one or more keys to union
735
+ # @param [Hash] options
736
+ # - `:weights => [Array<Float>]`: weights to associate with source
737
+ # sorted sets
738
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
739
+ # - `:with_scores => true`: include scores in output
740
+ #
741
+ # @return [Array<String>, Array<[String, Float]>]
742
+ # - when `:with_scores` is not specified, an array of members
743
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
744
+ def zunion(*args)
745
+ _zsets_operation(:zunion, *args)
746
+ end
747
+ ruby2_keywords(:zunion) if respond_to?(:ruby2_keywords, true)
748
+
749
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
750
+ #
751
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
752
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
753
+ # # => 8
754
+ #
755
+ # @param [String] destination destination key
756
+ # @param [Array<String>] keys source keys
757
+ # @param [Hash] options
758
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
759
+ # sorted sets
760
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
761
+ # @return [Integer] number of elements in the resulting sorted set
762
+ def zunionstore(*args)
763
+ _zsets_operation_store(:zunionstore, *args)
764
+ end
765
+ ruby2_keywords(:zunionstore) if respond_to?(:ruby2_keywords, true)
766
+
767
+ # Return the difference between the first and all successive input sorted sets
768
+ #
769
+ # @example
770
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
771
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
772
+ # redis.zdiff("zsetA", "zsetB")
773
+ # => ["v1"]
774
+ # @example With scores
775
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
776
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
777
+ # redis.zdiff("zsetA", "zsetB", :with_scores => true)
778
+ # => [["v1", 1.0]]
779
+ #
780
+ # @param [String, Array<String>] keys one or more keys to compute the difference
781
+ # @param [Hash] options
782
+ # - `:with_scores => true`: include scores in output
783
+ #
784
+ # @return [Array<String>, Array<[String, Float]>]
785
+ # - when `:with_scores` is not specified, an array of members
786
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
787
+ def zdiff(*keys, with_scores: false)
788
+ _zsets_operation(:zdiff, *keys, with_scores: with_scores)
789
+ end
790
+
791
+ # Compute the difference between the first and all successive input sorted sets
792
+ # and store the resulting sorted set in a new key
793
+ #
794
+ # @example
795
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
796
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
797
+ # redis.zdiffstore("zsetA", "zsetB")
798
+ # # => 1
799
+ #
800
+ # @param [String] destination destination key
801
+ # @param [Array<String>] keys source keys
802
+ # @return [Integer] number of elements in the resulting sorted set
803
+ def zdiffstore(*args)
804
+ _zsets_operation_store(:zdiffstore, *args)
805
+ end
806
+ ruby2_keywords(:zdiffstore) if respond_to?(:ruby2_keywords, true)
807
+
808
+ # Scan a sorted set
809
+ #
810
+ # @example Retrieve the first batch of key/value pairs in a hash
811
+ # redis.zscan("zset", 0)
812
+ #
813
+ # @param [String, Integer] cursor the cursor of the iteration
814
+ # @param [Hash] options
815
+ # - `:match => String`: only return keys matching the pattern
816
+ # - `:count => Integer`: return count keys at most per iteration
817
+ #
818
+ # @return [String, Array<[String, Float]>] the next cursor and all found
819
+ # members and scores
820
+ def zscan(key, cursor, **options)
821
+ _scan(:zscan, cursor, [key], **options) do |reply|
822
+ [reply[0], FloatifyPairs.call(reply[1])]
823
+ end
824
+ end
825
+
826
+ # Scan a sorted set
827
+ #
828
+ # @example Retrieve all of the members/scores in a sorted set
829
+ # redis.zscan_each("zset").to_a
830
+ # # => [["key70", "70"], ["key80", "80"]]
831
+ #
832
+ # @param [Hash] options
833
+ # - `:match => String`: only return keys matching the pattern
834
+ # - `:count => Integer`: return count keys at most per iteration
835
+ #
836
+ # @return [Enumerator] an enumerator for all found scores and members
837
+ def zscan_each(key, **options, &block)
838
+ return to_enum(:zscan_each, key, **options) unless block_given?
839
+
840
+ cursor = 0
841
+ loop do
842
+ cursor, values = zscan(key, cursor, **options)
843
+ values.each(&block)
844
+ break if cursor == "0"
845
+ end
846
+ end
847
+
848
+ private
849
+
850
+ def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
851
+ keys.flatten!(1)
852
+ command = [cmd, keys.size].concat(keys)
853
+
854
+ if weights
855
+ command << "WEIGHTS"
856
+ command.concat(weights)
857
+ end
858
+
859
+ command << "AGGREGATE" << aggregate if aggregate
860
+
861
+ if with_scores
862
+ command << "WITHSCORES"
863
+ block = FloatifyPairs
864
+ end
865
+
866
+ send_command(command, &block)
867
+ end
868
+
869
+ def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
870
+ keys.flatten!(1)
871
+ command = [cmd, destination, keys.size].concat(keys)
872
+
873
+ if weights
874
+ command << "WEIGHTS"
875
+ command.concat(weights)
876
+ end
877
+
878
+ command << "AGGREGATE" << aggregate if aggregate
879
+
880
+ send_command(command)
881
+ end
882
+ end
883
+ end
884
+ end