redis 3.3.3 → 5.0.5

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 (136) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +280 -12
  3. data/README.md +141 -147
  4. data/lib/redis/client.rb +77 -539
  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 +285 -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 +818 -0
  18. data/lib/redis/commands/streams.rb +384 -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 +235 -0
  22. data/lib/redis/distributed.rb +300 -108
  23. data/lib/redis/errors.rb +22 -1
  24. data/lib/redis/hash_ring.rb +36 -79
  25. data/lib/redis/pipeline.rb +69 -83
  26. data/lib/redis/subscribe.rb +26 -19
  27. data/lib/redis/version.rb +3 -1
  28. data/lib/redis.rb +113 -2685
  29. metadata +40 -218
  30. data/.gitignore +0 -16
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -89
  33. data/.yardopts +0 -3
  34. data/Gemfile +0 -4
  35. data/Rakefile +0 -87
  36. data/benchmarking/logging.rb +0 -71
  37. data/benchmarking/pipeline.rb +0 -51
  38. data/benchmarking/speed.rb +0 -21
  39. data/benchmarking/suite.rb +0 -24
  40. data/benchmarking/worker.rb +0 -71
  41. data/examples/basic.rb +0 -15
  42. data/examples/consistency.rb +0 -114
  43. data/examples/dist_redis.rb +0 -43
  44. data/examples/incr-decr.rb +0 -17
  45. data/examples/list.rb +0 -26
  46. data/examples/pubsub.rb +0 -37
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sentinel.rb +0 -41
  50. data/examples/sets.rb +0 -36
  51. data/examples/unicorn/config.ru +0 -3
  52. data/examples/unicorn/unicorn.rb +0 -20
  53. data/lib/redis/connection/command_helper.rb +0 -44
  54. data/lib/redis/connection/hiredis.rb +0 -66
  55. data/lib/redis/connection/registry.rb +0 -12
  56. data/lib/redis/connection/ruby.rb +0 -429
  57. data/lib/redis/connection/synchrony.rb +0 -133
  58. data/lib/redis/connection.rb +0 -9
  59. data/redis.gemspec +0 -44
  60. data/test/bitpos_test.rb +0 -69
  61. data/test/blocking_commands_test.rb +0 -42
  62. data/test/client_test.rb +0 -59
  63. data/test/command_map_test.rb +0 -30
  64. data/test/commands_on_hashes_test.rb +0 -21
  65. data/test/commands_on_hyper_log_log_test.rb +0 -21
  66. data/test/commands_on_lists_test.rb +0 -20
  67. data/test/commands_on_sets_test.rb +0 -77
  68. data/test/commands_on_sorted_sets_test.rb +0 -137
  69. data/test/commands_on_strings_test.rb +0 -101
  70. data/test/commands_on_value_types_test.rb +0 -133
  71. data/test/connection_handling_test.rb +0 -277
  72. data/test/db/.gitkeep +0 -0
  73. data/test/distributed_blocking_commands_test.rb +0 -46
  74. data/test/distributed_commands_on_hashes_test.rb +0 -10
  75. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  76. data/test/distributed_commands_on_lists_test.rb +0 -22
  77. data/test/distributed_commands_on_sets_test.rb +0 -83
  78. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  79. data/test/distributed_commands_on_strings_test.rb +0 -59
  80. data/test/distributed_commands_on_value_types_test.rb +0 -95
  81. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  82. data/test/distributed_connection_handling_test.rb +0 -23
  83. data/test/distributed_internals_test.rb +0 -79
  84. data/test/distributed_key_tags_test.rb +0 -52
  85. data/test/distributed_persistence_control_commands_test.rb +0 -26
  86. data/test/distributed_publish_subscribe_test.rb +0 -92
  87. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  88. data/test/distributed_scripting_test.rb +0 -102
  89. data/test/distributed_sorting_test.rb +0 -20
  90. data/test/distributed_test.rb +0 -58
  91. data/test/distributed_transactions_test.rb +0 -32
  92. data/test/encoding_test.rb +0 -18
  93. data/test/error_replies_test.rb +0 -59
  94. data/test/fork_safety_test.rb +0 -65
  95. data/test/helper.rb +0 -232
  96. data/test/helper_test.rb +0 -24
  97. data/test/internals_test.rb +0 -457
  98. data/test/lint/blocking_commands.rb +0 -150
  99. data/test/lint/hashes.rb +0 -162
  100. data/test/lint/hyper_log_log.rb +0 -60
  101. data/test/lint/lists.rb +0 -143
  102. data/test/lint/sets.rb +0 -140
  103. data/test/lint/sorted_sets.rb +0 -316
  104. data/test/lint/strings.rb +0 -260
  105. data/test/lint/value_types.rb +0 -122
  106. data/test/persistence_control_commands_test.rb +0 -26
  107. data/test/pipelining_commands_test.rb +0 -242
  108. data/test/publish_subscribe_test.rb +0 -282
  109. data/test/remote_server_control_commands_test.rb +0 -118
  110. data/test/scanning_test.rb +0 -413
  111. data/test/scripting_test.rb +0 -78
  112. data/test/sentinel_command_test.rb +0 -80
  113. data/test/sentinel_test.rb +0 -255
  114. data/test/sorting_test.rb +0 -59
  115. data/test/ssl_test.rb +0 -73
  116. data/test/support/connection/hiredis.rb +0 -1
  117. data/test/support/connection/ruby.rb +0 -1
  118. data/test/support/connection/synchrony.rb +0 -17
  119. data/test/support/redis_mock.rb +0 -130
  120. data/test/support/ssl/gen_certs.sh +0 -31
  121. data/test/support/ssl/trusted-ca.crt +0 -25
  122. data/test/support/ssl/trusted-ca.key +0 -27
  123. data/test/support/ssl/trusted-cert.crt +0 -81
  124. data/test/support/ssl/trusted-cert.key +0 -28
  125. data/test/support/ssl/untrusted-ca.crt +0 -26
  126. data/test/support/ssl/untrusted-ca.key +0 -27
  127. data/test/support/ssl/untrusted-cert.crt +0 -82
  128. data/test/support/ssl/untrusted-cert.key +0 -28
  129. data/test/support/wire/synchrony.rb +0 -24
  130. data/test/support/wire/thread.rb +0 -5
  131. data/test/synchrony_driver.rb +0 -88
  132. data/test/test.conf.erb +0 -9
  133. data/test/thread_safety_test.rb +0 -62
  134. data/test/transactions_test.rb +0 -264
  135. data/test/unknown_commands_test.rb +0 -14
  136. data/test/url_param_test.rb +0 -138
@@ -0,0 +1,818 @@
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 the highest scores in the sorted set stored at keys,
171
+ # or block until one is available.
172
+ #
173
+ # @example Popping a member from a sorted set
174
+ # redis.bzpopmax('zset', 1)
175
+ # #=> ['zset', 'b', 2.0]
176
+ # @example Popping a member from multiple sorted sets
177
+ # redis.bzpopmax('zset1', 'zset2', 1)
178
+ # #=> ['zset1', 'b', 2.0]
179
+ #
180
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
181
+ # @params timeout [Integer] the maximum number of seconds to block
182
+ #
183
+ # @return [Array<String, String, Float>] a touple of key, member and score
184
+ # @return [nil] when no element could be popped and the timeout expired
185
+ def bzpopmax(*args)
186
+ _bpop(:bzpopmax, args) do |reply|
187
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
188
+ end
189
+ end
190
+
191
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
192
+ # or block until one is available.
193
+ #
194
+ # @example Popping a member from a sorted set
195
+ # redis.bzpopmin('zset', 1)
196
+ # #=> ['zset', 'a', 1.0]
197
+ # @example Popping a member from multiple sorted sets
198
+ # redis.bzpopmin('zset1', 'zset2', 1)
199
+ # #=> ['zset1', 'a', 1.0]
200
+ #
201
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
202
+ # @params timeout [Integer] the maximum number of seconds to block
203
+ #
204
+ # @return [Array<String, String, Float>] a touple of key, member and score
205
+ # @return [nil] when no element could be popped and the timeout expired
206
+ def bzpopmin(*args)
207
+ _bpop(:bzpopmin, args) do |reply|
208
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
209
+ end
210
+ end
211
+
212
+ # Get the score associated with the given member in a sorted set.
213
+ #
214
+ # @example Get the score for member "a"
215
+ # redis.zscore("zset", "a")
216
+ # # => 32.0
217
+ #
218
+ # @param [String] key
219
+ # @param [String] member
220
+ # @return [Float] score of the member
221
+ def zscore(key, member)
222
+ send_command([:zscore, key, member], &Floatify)
223
+ end
224
+
225
+ # Get the scores associated with the given members in a sorted set.
226
+ #
227
+ # @example Get the scores for members "a" and "b"
228
+ # redis.zmscore("zset", "a", "b")
229
+ # # => [32.0, 48.0]
230
+ #
231
+ # @param [String] key
232
+ # @param [String, Array<String>] members
233
+ # @return [Array<Float>] scores of the members
234
+ def zmscore(key, *members)
235
+ send_command([:zmscore, key, *members]) do |reply|
236
+ reply.map(&Floatify)
237
+ end
238
+ end
239
+
240
+ # Get one or more random members from a sorted set.
241
+ #
242
+ # @example Get one random member
243
+ # redis.zrandmember("zset")
244
+ # # => "a"
245
+ # @example Get multiple random members
246
+ # redis.zrandmember("zset", 2)
247
+ # # => ["a", "b"]
248
+ # @example Get multiple random members with scores
249
+ # redis.zrandmember("zset", 2, with_scores: true)
250
+ # # => [["a", 2.0], ["b", 3.0]]
251
+ #
252
+ # @param [String] key
253
+ # @param [Integer] count
254
+ # @param [Hash] options
255
+ # - `:with_scores => true`: include scores in output
256
+ #
257
+ # @return [nil, String, Array<String>, Array<[String, Float]>]
258
+ # - when `key` does not exist or set is empty, `nil`
259
+ # - when `count` is not specified, a member
260
+ # - when `count` is specified and `:with_scores` is not specified, an array of members
261
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
262
+ def zrandmember(key, count = nil, withscores: false, with_scores: withscores)
263
+ if with_scores && count.nil?
264
+ raise ArgumentError, "count argument must be specified"
265
+ end
266
+
267
+ args = [:zrandmember, key]
268
+ args << Integer(count) if count
269
+
270
+ if with_scores
271
+ args << "WITHSCORES"
272
+ block = FloatifyPairs
273
+ end
274
+
275
+ send_command(args, &block)
276
+ end
277
+
278
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
279
+ #
280
+ # @example Retrieve all members from a sorted set, by index
281
+ # redis.zrange("zset", 0, -1)
282
+ # # => ["a", "b"]
283
+ # @example Retrieve all members and their scores from a sorted set
284
+ # redis.zrange("zset", 0, -1, :with_scores => true)
285
+ # # => [["a", 32.0], ["b", 64.0]]
286
+ #
287
+ # @param [String] key
288
+ # @param [Integer] start start index
289
+ # @param [Integer] stop stop index
290
+ # @param [Hash] options
291
+ # - `:by_score => false`: return members by score
292
+ # - `:by_lex => false`: return members by lexicographical ordering
293
+ # - `:rev => false`: reverse the ordering, from highest to lowest
294
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
295
+ # `count` members
296
+ # - `:with_scores => true`: include scores in output
297
+ #
298
+ # @return [Array<String>, Array<[String, Float]>]
299
+ # - when `:with_scores` is not specified, an array of members
300
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
301
+ def zrange(key, start, stop, byscore: false, by_score: byscore, bylex: false, by_lex: bylex,
302
+ rev: false, limit: nil, withscores: false, with_scores: withscores)
303
+
304
+ if by_score && by_lex
305
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
306
+ end
307
+
308
+ args = [:zrange, key, start, stop]
309
+
310
+ if by_score
311
+ args << "BYSCORE"
312
+ elsif by_lex
313
+ args << "BYLEX"
314
+ end
315
+
316
+ args << "REV" if rev
317
+
318
+ if limit
319
+ args << "LIMIT"
320
+ args.concat(limit.map { |l| Integer(l) })
321
+ end
322
+
323
+ if with_scores
324
+ args << "WITHSCORES"
325
+ block = FloatifyPairs
326
+ end
327
+
328
+ send_command(args, &block)
329
+ end
330
+
331
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
332
+ # and store the resulting sorted set in a new key.
333
+ #
334
+ # @example
335
+ # redis.zadd("foo", [[1.0, "s1"], [2.0, "s2"], [3.0, "s3"]])
336
+ # redis.zrangestore("bar", "foo", 0, 1)
337
+ # # => 2
338
+ # redis.zrange("bar", 0, -1)
339
+ # # => ["s1", "s2"]
340
+ #
341
+ # @return [Integer] the number of elements in the resulting sorted set
342
+ # @see #zrange
343
+ def zrangestore(dest_key, src_key, start, stop, byscore: false, by_score: byscore,
344
+ bylex: false, by_lex: bylex, rev: false, limit: nil)
345
+ if by_score && by_lex
346
+ raise ArgumentError, "only one of :by_score or :by_lex can be specified"
347
+ end
348
+
349
+ args = [:zrangestore, dest_key, src_key, start, stop]
350
+
351
+ if by_score
352
+ args << "BYSCORE"
353
+ elsif by_lex
354
+ args << "BYLEX"
355
+ end
356
+
357
+ args << "REV" if rev
358
+
359
+ if limit
360
+ args << "LIMIT"
361
+ args.concat(limit.map { |l| Integer(l) })
362
+ end
363
+
364
+ send_command(args)
365
+ end
366
+
367
+ # Return a range of members in a sorted set, by index, with scores ordered
368
+ # from high to low.
369
+ #
370
+ # @example Retrieve all members from a sorted set
371
+ # redis.zrevrange("zset", 0, -1)
372
+ # # => ["b", "a"]
373
+ # @example Retrieve all members and their scores from a sorted set
374
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
375
+ # # => [["b", 64.0], ["a", 32.0]]
376
+ #
377
+ # @see #zrange
378
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
379
+ args = [:zrevrange, key, Integer(start), Integer(stop)]
380
+
381
+ if with_scores
382
+ args << "WITHSCORES"
383
+ block = FloatifyPairs
384
+ end
385
+
386
+ send_command(args, &block)
387
+ end
388
+
389
+ # Determine the index of a member in a sorted set.
390
+ #
391
+ # @param [String] key
392
+ # @param [String] member
393
+ # @return [Integer]
394
+ def zrank(key, member)
395
+ send_command([:zrank, key, member])
396
+ end
397
+
398
+ # Determine the index of a member in a sorted set, with scores ordered from
399
+ # high to low.
400
+ #
401
+ # @param [String] key
402
+ # @param [String] member
403
+ # @return [Integer]
404
+ def zrevrank(key, member)
405
+ send_command([:zrevrank, key, member])
406
+ end
407
+
408
+ # Remove all members in a sorted set within the given indexes.
409
+ #
410
+ # @example Remove first 5 members
411
+ # redis.zremrangebyrank("zset", 0, 4)
412
+ # # => 5
413
+ # @example Remove last 5 members
414
+ # redis.zremrangebyrank("zset", -5, -1)
415
+ # # => 5
416
+ #
417
+ # @param [String] key
418
+ # @param [Integer] start start index
419
+ # @param [Integer] stop stop index
420
+ # @return [Integer] number of members that were removed
421
+ def zremrangebyrank(key, start, stop)
422
+ send_command([:zremrangebyrank, key, start, stop])
423
+ end
424
+
425
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
426
+ #
427
+ # @example Count members matching a
428
+ # redis.zlexcount("zset", "[a", "[a\xff")
429
+ # # => 1
430
+ # @example Count members matching a-z
431
+ # redis.zlexcount("zset", "[a", "[z\xff")
432
+ # # => 26
433
+ #
434
+ # @param [String] key
435
+ # @param [String] min
436
+ # - inclusive minimum is specified by prefixing `(`
437
+ # - exclusive minimum is specified by prefixing `[`
438
+ # @param [String] max
439
+ # - inclusive maximum is specified by prefixing `(`
440
+ # - exclusive maximum is specified by prefixing `[`
441
+ #
442
+ # @return [Integer] number of members within the specified lexicographical range
443
+ def zlexcount(key, min, max)
444
+ send_command([:zlexcount, key, min, max])
445
+ end
446
+
447
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
448
+ #
449
+ # @example Retrieve members matching a
450
+ # redis.zrangebylex("zset", "[a", "[a\xff")
451
+ # # => ["aaren", "aarika", "abagael", "abby"]
452
+ # @example Retrieve the first 2 members matching a
453
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
454
+ # # => ["aaren", "aarika"]
455
+ #
456
+ # @param [String] key
457
+ # @param [String] min
458
+ # - inclusive minimum is specified by prefixing `(`
459
+ # - exclusive minimum is specified by prefixing `[`
460
+ # @param [String] max
461
+ # - inclusive maximum is specified by prefixing `(`
462
+ # - exclusive maximum is specified by prefixing `[`
463
+ # @param [Hash] options
464
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
465
+ # `count` members
466
+ #
467
+ # @return [Array<String>, Array<[String, Float]>]
468
+ def zrangebylex(key, min, max, limit: nil)
469
+ args = [:zrangebylex, key, min, max]
470
+
471
+ if limit
472
+ args << "LIMIT"
473
+ args.concat(limit.map { |l| Integer(l) })
474
+ end
475
+
476
+ send_command(args)
477
+ end
478
+
479
+ # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
480
+ # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
481
+ #
482
+ # @example Retrieve members matching a
483
+ # redis.zrevrangebylex("zset", "[a", "[a\xff")
484
+ # # => ["abbygail", "abby", "abagael", "aaren"]
485
+ # @example Retrieve the last 2 members matching a
486
+ # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
487
+ # # => ["abbygail", "abby"]
488
+ #
489
+ # @see #zrangebylex
490
+ def zrevrangebylex(key, max, min, limit: nil)
491
+ args = [:zrevrangebylex, key, max, min]
492
+
493
+ if limit
494
+ args << "LIMIT"
495
+ args.concat(limit.map { |l| Integer(l) })
496
+ end
497
+
498
+ send_command(args)
499
+ end
500
+
501
+ # Return a range of members in a sorted set, by score.
502
+ #
503
+ # @example Retrieve members with score `>= 5` and `< 100`
504
+ # redis.zrangebyscore("zset", "5", "(100")
505
+ # # => ["a", "b"]
506
+ # @example Retrieve the first 2 members with score `>= 0`
507
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
508
+ # # => ["a", "b"]
509
+ # @example Retrieve members and their scores with scores `> 5`
510
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
511
+ # # => [["a", 32.0], ["b", 64.0]]
512
+ #
513
+ # @param [String] key
514
+ # @param [String] min
515
+ # - inclusive minimum score is specified verbatim
516
+ # - exclusive minimum score is specified by prefixing `(`
517
+ # @param [String] max
518
+ # - inclusive maximum score is specified verbatim
519
+ # - exclusive maximum score is specified by prefixing `(`
520
+ # @param [Hash] options
521
+ # - `:with_scores => true`: include scores in output
522
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
523
+ # `count` members
524
+ #
525
+ # @return [Array<String>, Array<[String, Float]>]
526
+ # - when `:with_scores` is not specified, an array of members
527
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
528
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
529
+ args = [:zrangebyscore, key, min, max]
530
+
531
+ if with_scores
532
+ args << "WITHSCORES"
533
+ block = FloatifyPairs
534
+ end
535
+
536
+ if limit
537
+ args << "LIMIT"
538
+ args.concat(limit.map { |l| Integer(l) })
539
+ end
540
+
541
+ send_command(args, &block)
542
+ end
543
+
544
+ # Return a range of members in a sorted set, by score, with scores ordered
545
+ # from high to low.
546
+ #
547
+ # @example Retrieve members with score `< 100` and `>= 5`
548
+ # redis.zrevrangebyscore("zset", "(100", "5")
549
+ # # => ["b", "a"]
550
+ # @example Retrieve the first 2 members with score `<= 0`
551
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
552
+ # # => ["b", "a"]
553
+ # @example Retrieve members and their scores with scores `> 5`
554
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
555
+ # # => [["b", 64.0], ["a", 32.0]]
556
+ #
557
+ # @see #zrangebyscore
558
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
559
+ args = [:zrevrangebyscore, key, max, min]
560
+
561
+ if with_scores
562
+ args << "WITHSCORES"
563
+ block = FloatifyPairs
564
+ end
565
+
566
+ if limit
567
+ args << "LIMIT"
568
+ args.concat(limit.map { |l| Integer(l) })
569
+ end
570
+
571
+ send_command(args, &block)
572
+ end
573
+
574
+ # Remove all members in a sorted set within the given scores.
575
+ #
576
+ # @example Remove members with score `>= 5` and `< 100`
577
+ # redis.zremrangebyscore("zset", "5", "(100")
578
+ # # => 2
579
+ # @example Remove members with scores `> 5`
580
+ # redis.zremrangebyscore("zset", "(5", "+inf")
581
+ # # => 2
582
+ #
583
+ # @param [String] key
584
+ # @param [String] min
585
+ # - inclusive minimum score is specified verbatim
586
+ # - exclusive minimum score is specified by prefixing `(`
587
+ # @param [String] max
588
+ # - inclusive maximum score is specified verbatim
589
+ # - exclusive maximum score is specified by prefixing `(`
590
+ # @return [Integer] number of members that were removed
591
+ def zremrangebyscore(key, min, max)
592
+ send_command([:zremrangebyscore, key, min, max])
593
+ end
594
+
595
+ # Count the members in a sorted set with scores within the given values.
596
+ #
597
+ # @example Count members with score `>= 5` and `< 100`
598
+ # redis.zcount("zset", "5", "(100")
599
+ # # => 2
600
+ # @example Count members with scores `> 5`
601
+ # redis.zcount("zset", "(5", "+inf")
602
+ # # => 2
603
+ #
604
+ # @param [String] key
605
+ # @param [String] min
606
+ # - inclusive minimum score is specified verbatim
607
+ # - exclusive minimum score is specified by prefixing `(`
608
+ # @param [String] max
609
+ # - inclusive maximum score is specified verbatim
610
+ # - exclusive maximum score is specified by prefixing `(`
611
+ # @return [Integer] number of members in within the specified range
612
+ def zcount(key, min, max)
613
+ send_command([:zcount, key, min, max])
614
+ end
615
+
616
+ # Return the intersection of multiple sorted sets
617
+ #
618
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
619
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
620
+ # # => ["v1", "v2"]
621
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
622
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
623
+ # # => [["v1", 3.0], ["v2", 6.0]]
624
+ #
625
+ # @param [String, Array<String>] keys one or more keys to intersect
626
+ # @param [Hash] options
627
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
628
+ # sorted sets
629
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
630
+ # - `:with_scores => true`: include scores in output
631
+ #
632
+ # @return [Array<String>, Array<[String, Float]>]
633
+ # - when `:with_scores` is not specified, an array of members
634
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
635
+ def zinter(*args)
636
+ _zsets_operation(:zinter, *args)
637
+ end
638
+ ruby2_keywords(:zinter) if respond_to?(:ruby2_keywords, true)
639
+
640
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
641
+ # key.
642
+ #
643
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
644
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
645
+ # # => 4
646
+ #
647
+ # @param [String] destination destination key
648
+ # @param [Array<String>] keys source keys
649
+ # @param [Hash] options
650
+ # - `:weights => [Array<Float>]`: weights to associate with source
651
+ # sorted sets
652
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
653
+ # @return [Integer] number of elements in the resulting sorted set
654
+ def zinterstore(*args)
655
+ _zsets_operation_store(:zinterstore, *args)
656
+ end
657
+ ruby2_keywords(:zinterstore) if respond_to?(:ruby2_keywords, true)
658
+
659
+ # Return the union of multiple sorted sets
660
+ #
661
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`
662
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0])
663
+ # # => ["v1", "v2"]
664
+ # @example Retrieve the union of `2*zsetA` and `1*zsetB`, and their scores
665
+ # redis.zunion("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
666
+ # # => [["v1", 3.0], ["v2", 6.0]]
667
+ #
668
+ # @param [String, Array<String>] keys one or more keys to union
669
+ # @param [Hash] options
670
+ # - `:weights => [Array<Float>]`: weights to associate with source
671
+ # sorted sets
672
+ # - `:aggregate => String`: aggregate function to use (sum, min, max)
673
+ # - `:with_scores => true`: include scores in output
674
+ #
675
+ # @return [Array<String>, Array<[String, Float]>]
676
+ # - when `:with_scores` is not specified, an array of members
677
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
678
+ def zunion(*args)
679
+ _zsets_operation(:zunion, *args)
680
+ end
681
+ ruby2_keywords(:zunion) if respond_to?(:ruby2_keywords, true)
682
+
683
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
684
+ #
685
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
686
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
687
+ # # => 8
688
+ #
689
+ # @param [String] destination destination key
690
+ # @param [Array<String>] keys source keys
691
+ # @param [Hash] options
692
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
693
+ # sorted sets
694
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
695
+ # @return [Integer] number of elements in the resulting sorted set
696
+ def zunionstore(*args)
697
+ _zsets_operation_store(:zunionstore, *args)
698
+ end
699
+ ruby2_keywords(:zunionstore) if respond_to?(:ruby2_keywords, true)
700
+
701
+ # Return the difference between the first and all successive input sorted sets
702
+ #
703
+ # @example
704
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
705
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
706
+ # redis.zdiff("zsetA", "zsetB")
707
+ # => ["v1"]
708
+ # @example With scores
709
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
710
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
711
+ # redis.zdiff("zsetA", "zsetB", :with_scores => true)
712
+ # => [["v1", 1.0]]
713
+ #
714
+ # @param [String, Array<String>] keys one or more keys to compute the difference
715
+ # @param [Hash] options
716
+ # - `:with_scores => true`: include scores in output
717
+ #
718
+ # @return [Array<String>, Array<[String, Float]>]
719
+ # - when `:with_scores` is not specified, an array of members
720
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
721
+ def zdiff(*keys, with_scores: false)
722
+ _zsets_operation(:zdiff, *keys, with_scores: with_scores)
723
+ end
724
+
725
+ # Compute the difference between the first and all successive input sorted sets
726
+ # and store the resulting sorted set in a new key
727
+ #
728
+ # @example
729
+ # redis.zadd("zsetA", [[1.0, "v1"], [2.0, "v2"]])
730
+ # redis.zadd("zsetB", [[3.0, "v2"], [2.0, "v3"]])
731
+ # redis.zdiffstore("zsetA", "zsetB")
732
+ # # => 1
733
+ #
734
+ # @param [String] destination destination key
735
+ # @param [Array<String>] keys source keys
736
+ # @return [Integer] number of elements in the resulting sorted set
737
+ def zdiffstore(*args)
738
+ _zsets_operation_store(:zdiffstore, *args)
739
+ end
740
+ ruby2_keywords(:zdiffstore) if respond_to?(:ruby2_keywords, true)
741
+
742
+ # Scan a sorted set
743
+ #
744
+ # @example Retrieve the first batch of key/value pairs in a hash
745
+ # redis.zscan("zset", 0)
746
+ #
747
+ # @param [String, Integer] cursor the cursor of the iteration
748
+ # @param [Hash] options
749
+ # - `:match => String`: only return keys matching the pattern
750
+ # - `:count => Integer`: return count keys at most per iteration
751
+ #
752
+ # @return [String, Array<[String, Float]>] the next cursor and all found
753
+ # members and scores
754
+ def zscan(key, cursor, **options)
755
+ _scan(:zscan, cursor, [key], **options) do |reply|
756
+ [reply[0], FloatifyPairs.call(reply[1])]
757
+ end
758
+ end
759
+
760
+ # Scan a sorted set
761
+ #
762
+ # @example Retrieve all of the members/scores in a sorted set
763
+ # redis.zscan_each("zset").to_a
764
+ # # => [["key70", "70"], ["key80", "80"]]
765
+ #
766
+ # @param [Hash] options
767
+ # - `:match => String`: only return keys matching the pattern
768
+ # - `:count => Integer`: return count keys at most per iteration
769
+ #
770
+ # @return [Enumerator] an enumerator for all found scores and members
771
+ def zscan_each(key, **options, &block)
772
+ return to_enum(:zscan_each, key, **options) unless block_given?
773
+
774
+ cursor = 0
775
+ loop do
776
+ cursor, values = zscan(key, cursor, **options)
777
+ values.each(&block)
778
+ break if cursor == "0"
779
+ end
780
+ end
781
+
782
+ private
783
+
784
+ def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
785
+ keys.flatten!(1)
786
+ command = [cmd, keys.size].concat(keys)
787
+
788
+ if weights
789
+ command << "WEIGHTS"
790
+ command.concat(weights)
791
+ end
792
+
793
+ command << "AGGREGATE" << aggregate if aggregate
794
+
795
+ if with_scores
796
+ command << "WITHSCORES"
797
+ block = FloatifyPairs
798
+ end
799
+
800
+ send_command(command, &block)
801
+ end
802
+
803
+ def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
804
+ keys.flatten!(1)
805
+ command = [cmd, destination, keys.size].concat(keys)
806
+
807
+ if weights
808
+ command << "WEIGHTS"
809
+ command.concat(weights)
810
+ end
811
+
812
+ command << "AGGREGATE" << aggregate if aggregate
813
+
814
+ send_command(command)
815
+ end
816
+ end
817
+ end
818
+ end