redis 4.4.0 → 4.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -0
  3. data/README.md +25 -10
  4. data/lib/redis/client.rb +31 -25
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +8 -9
  7. data/lib/redis/cluster/node.rb +12 -0
  8. data/lib/redis/cluster/node_loader.rb +8 -11
  9. data/lib/redis/cluster/option.rb +10 -3
  10. data/lib/redis/cluster/slot_loader.rb +9 -12
  11. data/lib/redis/cluster.rb +24 -0
  12. data/lib/redis/commands/bitmaps.rb +63 -0
  13. data/lib/redis/commands/cluster.rb +45 -0
  14. data/lib/redis/commands/connection.rb +58 -0
  15. data/lib/redis/commands/geo.rb +84 -0
  16. data/lib/redis/commands/hashes.rb +251 -0
  17. data/lib/redis/commands/hyper_log_log.rb +37 -0
  18. data/lib/redis/commands/keys.rb +455 -0
  19. data/lib/redis/commands/lists.rb +290 -0
  20. data/lib/redis/commands/pubsub.rb +72 -0
  21. data/lib/redis/commands/scripting.rb +114 -0
  22. data/lib/redis/commands/server.rb +188 -0
  23. data/lib/redis/commands/sets.rb +223 -0
  24. data/lib/redis/commands/sorted_sets.rb +812 -0
  25. data/lib/redis/commands/streams.rb +382 -0
  26. data/lib/redis/commands/strings.rb +313 -0
  27. data/lib/redis/commands/transactions.rb +139 -0
  28. data/lib/redis/commands.rb +240 -0
  29. data/lib/redis/connection/command_helper.rb +2 -0
  30. data/lib/redis/connection/hiredis.rb +3 -2
  31. data/lib/redis/connection/ruby.rb +19 -9
  32. data/lib/redis/connection/synchrony.rb +10 -8
  33. data/lib/redis/connection.rb +1 -1
  34. data/lib/redis/distributed.rb +111 -23
  35. data/lib/redis/errors.rb +9 -0
  36. data/lib/redis/pipeline.rb +128 -3
  37. data/lib/redis/version.rb +1 -1
  38. data/lib/redis.rb +138 -3482
  39. metadata +22 -5
@@ -0,0 +1,812 @@
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
+ send_command([:zpopmax, key, count].compact) do |members|
140
+ members = FloatifyPairs.call(members)
141
+ count.to_i > 1 ? members : members.first
142
+ end
143
+ end
144
+
145
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at key.
146
+ #
147
+ # @example Popping a member
148
+ # redis.zpopmin('zset')
149
+ # #=> ['a', 1.0]
150
+ # @example With count option
151
+ # redis.zpopmin('zset', 2)
152
+ # #=> [['a', 1.0], ['b', 2.0]]
153
+ #
154
+ # @params key [String] a key of the sorted set
155
+ # @params count [Integer] a number of members
156
+ #
157
+ # @return [Array<String, Float>] element and score pair if count is not specified
158
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
159
+ def zpopmin(key, count = nil)
160
+ send_command([:zpopmin, key, count].compact) do |members|
161
+ members = FloatifyPairs.call(members)
162
+ count.to_i > 1 ? members : members.first
163
+ end
164
+ end
165
+
166
+ # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
167
+ # or block until one is available.
168
+ #
169
+ # @example Popping a member from a sorted set
170
+ # redis.bzpopmax('zset', 1)
171
+ # #=> ['zset', 'b', 2.0]
172
+ # @example Popping a member from multiple sorted sets
173
+ # redis.bzpopmax('zset1', 'zset2', 1)
174
+ # #=> ['zset1', 'b', 2.0]
175
+ #
176
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
177
+ # @params timeout [Integer] the maximum number of seconds to block
178
+ #
179
+ # @return [Array<String, String, Float>] a touple of key, member and score
180
+ # @return [nil] when no element could be popped and the timeout expired
181
+ def bzpopmax(*args)
182
+ _bpop(:bzpopmax, args) do |reply|
183
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
184
+ end
185
+ end
186
+
187
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
188
+ # or block until one is available.
189
+ #
190
+ # @example Popping a member from a sorted set
191
+ # redis.bzpopmin('zset', 1)
192
+ # #=> ['zset', 'a', 1.0]
193
+ # @example Popping a member from multiple sorted sets
194
+ # redis.bzpopmin('zset1', 'zset2', 1)
195
+ # #=> ['zset1', 'a', 1.0]
196
+ #
197
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
198
+ # @params timeout [Integer] the maximum number of seconds to block
199
+ #
200
+ # @return [Array<String, String, Float>] a touple of key, member and score
201
+ # @return [nil] when no element could be popped and the timeout expired
202
+ def bzpopmin(*args)
203
+ _bpop(:bzpopmin, args) do |reply|
204
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
205
+ end
206
+ end
207
+
208
+ # Get the score associated with the given member in a sorted set.
209
+ #
210
+ # @example Get the score for member "a"
211
+ # redis.zscore("zset", "a")
212
+ # # => 32.0
213
+ #
214
+ # @param [String] key
215
+ # @param [String] member
216
+ # @return [Float] score of the member
217
+ def zscore(key, member)
218
+ send_command([:zscore, key, member], &Floatify)
219
+ end
220
+
221
+ # Get the scores associated with the given members in a sorted set.
222
+ #
223
+ # @example Get the scores for members "a" and "b"
224
+ # redis.zmscore("zset", "a", "b")
225
+ # # => [32.0, 48.0]
226
+ #
227
+ # @param [String] key
228
+ # @param [String, Array<String>] members
229
+ # @return [Array<Float>] scores of the members
230
+ def zmscore(key, *members)
231
+ send_command([:zmscore, key, *members]) do |reply|
232
+ reply.map(&Floatify)
233
+ end
234
+ end
235
+
236
+ # Get one or more random members from a sorted set.
237
+ #
238
+ # @example Get one random member
239
+ # redis.zrandmember("zset")
240
+ # # => "a"
241
+ # @example Get multiple random members
242
+ # redis.zrandmember("zset", 2)
243
+ # # => ["a", "b"]
244
+ # @example Get multiple random members with scores
245
+ # redis.zrandmember("zset", 2, with_scores: true)
246
+ # # => [["a", 2.0], ["b", 3.0]]
247
+ #
248
+ # @param [String] key
249
+ # @param [Integer] count
250
+ # @param [Hash] options
251
+ # - `:with_scores => true`: include scores in output
252
+ #
253
+ # @return [nil, String, Array<String>, Array<[String, Float]>]
254
+ # - when `key` does not exist or set is empty, `nil`
255
+ # - when `count` is not specified, a member
256
+ # - when `count` is specified and `:with_scores` is not specified, an array of members
257
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
258
+ def zrandmember(key, count = nil, withscores: false, with_scores: withscores)
259
+ if with_scores && count.nil?
260
+ raise ArgumentError, "count argument must be specified"
261
+ end
262
+
263
+ args = [:zrandmember, key]
264
+ args << count if count
265
+
266
+ if with_scores
267
+ args << "WITHSCORES"
268
+ block = FloatifyPairs
269
+ end
270
+
271
+ send_command(args, &block)
272
+ end
273
+
274
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
275
+ #
276
+ # @example Retrieve all members from a sorted set, by index
277
+ # redis.zrange("zset", 0, -1)
278
+ # # => ["a", "b"]
279
+ # @example Retrieve all members and their scores from a sorted set
280
+ # redis.zrange("zset", 0, -1, :with_scores => true)
281
+ # # => [["a", 32.0], ["b", 64.0]]
282
+ #
283
+ # @param [String] key
284
+ # @param [Integer] start start index
285
+ # @param [Integer] stop stop index
286
+ # @param [Hash] options
287
+ # - `:by_score => false`: return members by score
288
+ # - `:by_lex => false`: return members by lexicographical ordering
289
+ # - `:rev => false`: reverse the ordering, from highest to lowest
290
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
291
+ # `count` members
292
+ # - `:with_scores => true`: include scores in output
293
+ #
294
+ # @return [Array<String>, Array<[String, Float]>]
295
+ # - when `:with_scores` is not specified, an array of members
296
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
297
+ def zrange(key, start, stop, byscore: false, by_score: byscore, bylex: false, by_lex: bylex,
298
+ rev: false, limit: nil, withscores: false, with_scores: withscores)
299
+
300
+ if by_score && by_lex
301
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
302
+ end
303
+
304
+ args = [:zrange, key, start, stop]
305
+
306
+ if by_score
307
+ args << "BYSCORE"
308
+ elsif by_lex
309
+ args << "BYLEX"
310
+ end
311
+
312
+ args << "REV" if rev
313
+
314
+ if limit
315
+ args << "LIMIT"
316
+ args.concat(limit)
317
+ end
318
+
319
+ if with_scores
320
+ args << "WITHSCORES"
321
+ block = FloatifyPairs
322
+ end
323
+
324
+ send_command(args, &block)
325
+ end
326
+
327
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
328
+ # and store the resulting sorted set in a new key.
329
+ #
330
+ # @example
331
+ # redis.zadd("foo", [[1.0, "s1"], [2.0, "s2"], [3.0, "s3"]])
332
+ # redis.zrangestore("bar", "foo", 0, 1)
333
+ # # => 2
334
+ # redis.zrange("bar", 0, -1)
335
+ # # => ["s1", "s2"]
336
+ #
337
+ # @return [Integer] the number of elements in the resulting sorted set
338
+ # @see #zrange
339
+ def zrangestore(dest_key, src_key, start, stop, byscore: false, by_score: byscore,
340
+ bylex: false, by_lex: bylex, rev: false, limit: nil)
341
+ if by_score && by_lex
342
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
343
+ end
344
+
345
+ args = [:zrangestore, dest_key, src_key, start, stop]
346
+
347
+ if by_score
348
+ args << "BYSCORE"
349
+ elsif by_lex
350
+ args << "BYLEX"
351
+ end
352
+
353
+ args << "REV" if rev
354
+
355
+ if limit
356
+ args << "LIMIT"
357
+ args.concat(limit)
358
+ end
359
+
360
+ send_command(args)
361
+ end
362
+
363
+ # Return a range of members in a sorted set, by index, with scores ordered
364
+ # from high to low.
365
+ #
366
+ # @example Retrieve all members from a sorted set
367
+ # redis.zrevrange("zset", 0, -1)
368
+ # # => ["b", "a"]
369
+ # @example Retrieve all members and their scores from a sorted set
370
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
371
+ # # => [["b", 64.0], ["a", 32.0]]
372
+ #
373
+ # @see #zrange
374
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
375
+ args = [:zrevrange, key, start, stop]
376
+
377
+ if with_scores
378
+ args << "WITHSCORES"
379
+ block = FloatifyPairs
380
+ end
381
+
382
+ send_command(args, &block)
383
+ end
384
+
385
+ # Determine the index of a member in a sorted set.
386
+ #
387
+ # @param [String] key
388
+ # @param [String] member
389
+ # @return [Integer]
390
+ def zrank(key, member)
391
+ send_command([:zrank, key, member])
392
+ end
393
+
394
+ # Determine the index of a member in a sorted set, with scores ordered from
395
+ # high to low.
396
+ #
397
+ # @param [String] key
398
+ # @param [String] member
399
+ # @return [Integer]
400
+ def zrevrank(key, member)
401
+ send_command([:zrevrank, key, member])
402
+ end
403
+
404
+ # Remove all members in a sorted set within the given indexes.
405
+ #
406
+ # @example Remove first 5 members
407
+ # redis.zremrangebyrank("zset", 0, 4)
408
+ # # => 5
409
+ # @example Remove last 5 members
410
+ # redis.zremrangebyrank("zset", -5, -1)
411
+ # # => 5
412
+ #
413
+ # @param [String] key
414
+ # @param [Integer] start start index
415
+ # @param [Integer] stop stop index
416
+ # @return [Integer] number of members that were removed
417
+ def zremrangebyrank(key, start, stop)
418
+ send_command([:zremrangebyrank, key, start, stop])
419
+ end
420
+
421
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
422
+ #
423
+ # @example Count members matching a
424
+ # redis.zlexcount("zset", "[a", "[a\xff")
425
+ # # => 1
426
+ # @example Count members matching a-z
427
+ # redis.zlexcount("zset", "[a", "[z\xff")
428
+ # # => 26
429
+ #
430
+ # @param [String] key
431
+ # @param [String] min
432
+ # - inclusive minimum is specified by prefixing `(`
433
+ # - exclusive minimum is specified by prefixing `[`
434
+ # @param [String] max
435
+ # - inclusive maximum is specified by prefixing `(`
436
+ # - exclusive maximum is specified by prefixing `[`
437
+ #
438
+ # @return [Integer] number of members within the specified lexicographical range
439
+ def zlexcount(key, min, max)
440
+ send_command([:zlexcount, key, min, max])
441
+ end
442
+
443
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
444
+ #
445
+ # @example Retrieve members matching a
446
+ # redis.zrangebylex("zset", "[a", "[a\xff")
447
+ # # => ["aaren", "aarika", "abagael", "abby"]
448
+ # @example Retrieve the first 2 members matching a
449
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
450
+ # # => ["aaren", "aarika"]
451
+ #
452
+ # @param [String] key
453
+ # @param [String] min
454
+ # - inclusive minimum is specified by prefixing `(`
455
+ # - exclusive minimum is specified by prefixing `[`
456
+ # @param [String] max
457
+ # - inclusive maximum is specified by prefixing `(`
458
+ # - exclusive maximum is specified by prefixing `[`
459
+ # @param [Hash] options
460
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
461
+ # `count` members
462
+ #
463
+ # @return [Array<String>, Array<[String, Float]>]
464
+ def zrangebylex(key, min, max, limit: nil)
465
+ args = [:zrangebylex, key, min, max]
466
+
467
+ if limit
468
+ args << "LIMIT"
469
+ args.concat(limit)
470
+ end
471
+
472
+ send_command(args)
473
+ end
474
+
475
+ # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
476
+ # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
477
+ #
478
+ # @example Retrieve members matching a
479
+ # redis.zrevrangebylex("zset", "[a", "[a\xff")
480
+ # # => ["abbygail", "abby", "abagael", "aaren"]
481
+ # @example Retrieve the last 2 members matching a
482
+ # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
483
+ # # => ["abbygail", "abby"]
484
+ #
485
+ # @see #zrangebylex
486
+ def zrevrangebylex(key, max, min, limit: nil)
487
+ args = [:zrevrangebylex, key, max, min]
488
+
489
+ if limit
490
+ args << "LIMIT"
491
+ args.concat(limit)
492
+ end
493
+
494
+ send_command(args)
495
+ end
496
+
497
+ # Return a range of members in a sorted set, by score.
498
+ #
499
+ # @example Retrieve members with score `>= 5` and `< 100`
500
+ # redis.zrangebyscore("zset", "5", "(100")
501
+ # # => ["a", "b"]
502
+ # @example Retrieve the first 2 members with score `>= 0`
503
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
504
+ # # => ["a", "b"]
505
+ # @example Retrieve members and their scores with scores `> 5`
506
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
507
+ # # => [["a", 32.0], ["b", 64.0]]
508
+ #
509
+ # @param [String] key
510
+ # @param [String] min
511
+ # - inclusive minimum score is specified verbatim
512
+ # - exclusive minimum score is specified by prefixing `(`
513
+ # @param [String] max
514
+ # - inclusive maximum score is specified verbatim
515
+ # - exclusive maximum score is specified by prefixing `(`
516
+ # @param [Hash] options
517
+ # - `:with_scores => true`: include scores in output
518
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
519
+ # `count` members
520
+ #
521
+ # @return [Array<String>, Array<[String, Float]>]
522
+ # - when `:with_scores` is not specified, an array of members
523
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
524
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
525
+ args = [:zrangebyscore, key, min, max]
526
+
527
+ if with_scores
528
+ args << "WITHSCORES"
529
+ block = FloatifyPairs
530
+ end
531
+
532
+ if limit
533
+ args << "LIMIT"
534
+ args.concat(limit)
535
+ end
536
+
537
+ send_command(args, &block)
538
+ end
539
+
540
+ # Return a range of members in a sorted set, by score, with scores ordered
541
+ # from high to low.
542
+ #
543
+ # @example Retrieve members with score `< 100` and `>= 5`
544
+ # redis.zrevrangebyscore("zset", "(100", "5")
545
+ # # => ["b", "a"]
546
+ # @example Retrieve the first 2 members with score `<= 0`
547
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
548
+ # # => ["b", "a"]
549
+ # @example Retrieve members and their scores with scores `> 5`
550
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
551
+ # # => [["b", 64.0], ["a", 32.0]]
552
+ #
553
+ # @see #zrangebyscore
554
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
555
+ args = [:zrevrangebyscore, key, max, min]
556
+
557
+ if with_scores
558
+ args << "WITHSCORES"
559
+ block = FloatifyPairs
560
+ end
561
+
562
+ if limit
563
+ args << "LIMIT"
564
+ args.concat(limit)
565
+ end
566
+
567
+ send_command(args, &block)
568
+ end
569
+
570
+ # Remove all members in a sorted set within the given scores.
571
+ #
572
+ # @example Remove members with score `>= 5` and `< 100`
573
+ # redis.zremrangebyscore("zset", "5", "(100")
574
+ # # => 2
575
+ # @example Remove members with scores `> 5`
576
+ # redis.zremrangebyscore("zset", "(5", "+inf")
577
+ # # => 2
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
+ # @return [Integer] number of members that were removed
587
+ def zremrangebyscore(key, min, max)
588
+ send_command([:zremrangebyscore, key, min, max])
589
+ end
590
+
591
+ # Count the members in a sorted set with scores within the given values.
592
+ #
593
+ # @example Count members with score `>= 5` and `< 100`
594
+ # redis.zcount("zset", "5", "(100")
595
+ # # => 2
596
+ # @example Count members with scores `> 5`
597
+ # redis.zcount("zset", "(5", "+inf")
598
+ # # => 2
599
+ #
600
+ # @param [String] key
601
+ # @param [String] min
602
+ # - inclusive minimum score is specified verbatim
603
+ # - exclusive minimum score is specified by prefixing `(`
604
+ # @param [String] max
605
+ # - inclusive maximum score is specified verbatim
606
+ # - exclusive maximum score is specified by prefixing `(`
607
+ # @return [Integer] number of members in within the specified range
608
+ def zcount(key, min, max)
609
+ send_command([:zcount, key, min, max])
610
+ end
611
+
612
+ # Return the intersection of multiple sorted sets
613
+ #
614
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
615
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
616
+ # # => ["v1", "v2"]
617
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
618
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
619
+ # # => [["v1", 3.0], ["v2", 6.0]]
620
+ #
621
+ # @param [String, Array<String>] keys one or more keys to intersect
622
+ # @param [Hash] options
623
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
624
+ # sorted sets
625
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
626
+ # - `:with_scores => true`: include scores in output
627
+ #
628
+ # @return [Array<String>, Array<[String, Float]>]
629
+ # - when `:with_scores` is not specified, an array of members
630
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
631
+ def zinter(*args)
632
+ _zsets_operation(:zinter, *args)
633
+ end
634
+ ruby2_keywords(:zinter) if respond_to?(:ruby2_keywords, true)
635
+
636
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
637
+ # key.
638
+ #
639
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
640
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
641
+ # # => 4
642
+ #
643
+ # @param [String] destination destination key
644
+ # @param [Array<String>] keys source keys
645
+ # @param [Hash] options
646
+ # - `:weights => [Array<Float>]`: weights to associate with source
647
+ # sorted sets
648
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
649
+ # @return [Integer] number of elements in the resulting sorted set
650
+ def zinterstore(*args)
651
+ _zsets_operation_store(:zinterstore, *args)
652
+ end
653
+ ruby2_keywords(:zinterstore) if respond_to?(:ruby2_keywords, true)
654
+
655
+ # Return the union of multiple sorted sets
656
+ #
657
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`
658
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0])
659
+ # # => ["v1", "v2"]
660
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`, and their scores
661
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
662
+ # # => [["v1", 3.0], ["v2", 6.0]]
663
+ #
664
+ # @param [String, Array<String>] keys one or more keys to union
665
+ # @param [Hash] options
666
+ # - `:weights => [Array<Float>]`: weights to associate with source
667
+ # sorted sets
668
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
669
+ # - `:with_scores => true`: include scores in output
670
+ #
671
+ # @return [Array<String>, Array<[String, Float]>]
672
+ # - when `:with_scores` is not specified, an array of members
673
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
674
+ def zunion(*args)
675
+ _zsets_operation(:zunion, *args)
676
+ end
677
+ ruby2_keywords(:zunion) if respond_to?(:ruby2_keywords, true)
678
+
679
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
680
+ #
681
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
682
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
683
+ # # => 8
684
+ #
685
+ # @param [String] destination destination key
686
+ # @param [Array<String>] keys source keys
687
+ # @param [Hash] options
688
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
689
+ # sorted sets
690
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
691
+ # @return [Integer] number of elements in the resulting sorted set
692
+ def zunionstore(*args)
693
+ _zsets_operation_store(:zunionstore, *args)
694
+ end
695
+ ruby2_keywords(:zunionstore) if respond_to?(:ruby2_keywords, true)
696
+
697
+ # Return the difference between the first and all successive input sorted sets
698
+ #
699
+ # @example
700
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
701
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
702
+ # redis.zdiff("zsetA", "zsetB")
703
+ # => ["v1"]
704
+ # @example With scores
705
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
706
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
707
+ # redis.zdiff("zsetA", "zsetB", :with_scores => true)
708
+ # => [["v1", 1.0]]
709
+ #
710
+ # @param [String, Array<String>] keys one or more keys to compute the difference
711
+ # @param [Hash] options
712
+ # - `:with_scores => true`: include scores in output
713
+ #
714
+ # @return [Array<String>, Array<[String, Float]>]
715
+ # - when `:with_scores` is not specified, an array of members
716
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
717
+ def zdiff(*keys, with_scores: false)
718
+ _zsets_operation(:zdiff, *keys, with_scores: with_scores)
719
+ end
720
+
721
+ # Compute the difference between the first and all successive input sorted sets
722
+ # and store the resulting sorted set in a new key
723
+ #
724
+ # @example
725
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
726
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
727
+ # redis.zdiffstore("zsetA", "zsetB")
728
+ # # => 1
729
+ #
730
+ # @param [String] destination destination key
731
+ # @param [Array<String>] keys source keys
732
+ # @return [Integer] number of elements in the resulting sorted set
733
+ def zdiffstore(*args)
734
+ _zsets_operation_store(:zdiffstore, *args)
735
+ end
736
+ ruby2_keywords(:zdiffstore) if respond_to?(:ruby2_keywords, true)
737
+
738
+ # Scan a sorted set
739
+ #
740
+ # @example Retrieve the first batch of key/value pairs in a hash
741
+ # redis.zscan("zset", 0)
742
+ #
743
+ # @param [String, Integer] cursor the cursor of the iteration
744
+ # @param [Hash] options
745
+ # - `:match => String`: only return keys matching the pattern
746
+ # - `:count => Integer`: return count keys at most per iteration
747
+ #
748
+ # @return [String, Array<[String, Float]>] the next cursor and all found
749
+ # members and scores
750
+ def zscan(key, cursor, **options)
751
+ _scan(:zscan, cursor, [key], **options) do |reply|
752
+ [reply[0], FloatifyPairs.call(reply[1])]
753
+ end
754
+ end
755
+
756
+ # Scan a sorted set
757
+ #
758
+ # @example Retrieve all of the members/scores in a sorted set
759
+ # redis.zscan_each("zset").to_a
760
+ # # => [["key70", "70"], ["key80", "80"]]
761
+ #
762
+ # @param [Hash] options
763
+ # - `:match => String`: only return keys matching the pattern
764
+ # - `:count => Integer`: return count keys at most per iteration
765
+ #
766
+ # @return [Enumerator] an enumerator for all found scores and members
767
+ def zscan_each(key, **options, &block)
768
+ return to_enum(:zscan_each, key, **options) unless block_given?
769
+
770
+ cursor = 0
771
+ loop do
772
+ cursor, values = zscan(key, cursor, **options)
773
+ values.each(&block)
774
+ break if cursor == "0"
775
+ end
776
+ end
777
+
778
+ private
779
+
780
+ def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
781
+ command = [cmd, keys.size, *keys]
782
+
783
+ if weights
784
+ command << "WEIGHTS"
785
+ command.concat(weights)
786
+ end
787
+
788
+ command << "AGGREGATE" << aggregate if aggregate
789
+
790
+ if with_scores
791
+ command << "WITHSCORES"
792
+ block = FloatifyPairs
793
+ end
794
+
795
+ send_command(command, &block)
796
+ end
797
+
798
+ def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
799
+ command = [cmd, destination, keys.size, *keys]
800
+
801
+ if weights
802
+ command << "WEIGHTS"
803
+ command.concat(weights)
804
+ end
805
+
806
+ command << "AGGREGATE" << aggregate if aggregate
807
+
808
+ send_command(command)
809
+ end
810
+ end
811
+ end
812
+ end