redis 4.2.5 → 5.0.7

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