redis 3.3.5 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +220 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +176 -99
  5. data/lib/redis/cluster/command.rb +79 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +120 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +34 -0
  11. data/lib/redis/cluster/option.rb +100 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +46 -0
  14. data/lib/redis/cluster.rb +315 -0
  15. data/lib/redis/commands/bitmaps.rb +63 -0
  16. data/lib/redis/commands/cluster.rb +45 -0
  17. data/lib/redis/commands/connection.rb +58 -0
  18. data/lib/redis/commands/geo.rb +84 -0
  19. data/lib/redis/commands/hashes.rb +251 -0
  20. data/lib/redis/commands/hyper_log_log.rb +37 -0
  21. data/lib/redis/commands/keys.rb +411 -0
  22. data/lib/redis/commands/lists.rb +289 -0
  23. data/lib/redis/commands/pubsub.rb +72 -0
  24. data/lib/redis/commands/scripting.rb +114 -0
  25. data/lib/redis/commands/server.rb +188 -0
  26. data/lib/redis/commands/sets.rb +207 -0
  27. data/lib/redis/commands/sorted_sets.rb +812 -0
  28. data/lib/redis/commands/streams.rb +382 -0
  29. data/lib/redis/commands/strings.rb +313 -0
  30. data/lib/redis/commands/transactions.rb +139 -0
  31. data/lib/redis/commands.rb +242 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +130 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +231 -72
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +30 -73
  41. data/lib/redis/pipeline.rb +178 -13
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +173 -2661
  45. metadata +66 -202
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -89
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -87
  52. data/benchmarking/logging.rb +0 -71
  53. data/benchmarking/pipeline.rb +0 -51
  54. data/benchmarking/speed.rb +0 -21
  55. data/benchmarking/suite.rb +0 -24
  56. data/benchmarking/worker.rb +0 -71
  57. data/examples/basic.rb +0 -15
  58. data/examples/consistency.rb +0 -114
  59. data/examples/dist_redis.rb +0 -43
  60. data/examples/incr-decr.rb +0 -17
  61. data/examples/list.rb +0 -26
  62. data/examples/pubsub.rb +0 -37
  63. data/examples/sentinel/sentinel.conf +0 -9
  64. data/examples/sentinel/start +0 -49
  65. data/examples/sentinel.rb +0 -41
  66. data/examples/sets.rb +0 -36
  67. data/examples/unicorn/config.ru +0 -3
  68. data/examples/unicorn/unicorn.rb +0 -20
  69. data/redis.gemspec +0 -44
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/client_test.rb +0 -59
  73. data/test/command_map_test.rb +0 -30
  74. data/test/commands_on_hashes_test.rb +0 -21
  75. data/test/commands_on_hyper_log_log_test.rb +0 -21
  76. data/test/commands_on_lists_test.rb +0 -20
  77. data/test/commands_on_sets_test.rb +0 -77
  78. data/test/commands_on_sorted_sets_test.rb +0 -137
  79. data/test/commands_on_strings_test.rb +0 -101
  80. data/test/commands_on_value_types_test.rb +0 -133
  81. data/test/connection_handling_test.rb +0 -277
  82. data/test/connection_test.rb +0 -57
  83. data/test/db/.gitkeep +0 -0
  84. data/test/distributed_blocking_commands_test.rb +0 -46
  85. data/test/distributed_commands_on_hashes_test.rb +0 -10
  86. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  87. data/test/distributed_commands_on_lists_test.rb +0 -22
  88. data/test/distributed_commands_on_sets_test.rb +0 -83
  89. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  90. data/test/distributed_commands_on_strings_test.rb +0 -59
  91. data/test/distributed_commands_on_value_types_test.rb +0 -95
  92. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  93. data/test/distributed_connection_handling_test.rb +0 -23
  94. data/test/distributed_internals_test.rb +0 -79
  95. data/test/distributed_key_tags_test.rb +0 -52
  96. data/test/distributed_persistence_control_commands_test.rb +0 -26
  97. data/test/distributed_publish_subscribe_test.rb +0 -92
  98. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  99. data/test/distributed_scripting_test.rb +0 -102
  100. data/test/distributed_sorting_test.rb +0 -20
  101. data/test/distributed_test.rb +0 -58
  102. data/test/distributed_transactions_test.rb +0 -32
  103. data/test/encoding_test.rb +0 -18
  104. data/test/error_replies_test.rb +0 -59
  105. data/test/fork_safety_test.rb +0 -65
  106. data/test/helper.rb +0 -232
  107. data/test/helper_test.rb +0 -24
  108. data/test/internals_test.rb +0 -417
  109. data/test/lint/blocking_commands.rb +0 -150
  110. data/test/lint/hashes.rb +0 -162
  111. data/test/lint/hyper_log_log.rb +0 -60
  112. data/test/lint/lists.rb +0 -143
  113. data/test/lint/sets.rb +0 -140
  114. data/test/lint/sorted_sets.rb +0 -316
  115. data/test/lint/strings.rb +0 -260
  116. data/test/lint/value_types.rb +0 -122
  117. data/test/persistence_control_commands_test.rb +0 -26
  118. data/test/pipelining_commands_test.rb +0 -242
  119. data/test/publish_subscribe_test.rb +0 -282
  120. data/test/remote_server_control_commands_test.rb +0 -118
  121. data/test/scanning_test.rb +0 -413
  122. data/test/scripting_test.rb +0 -78
  123. data/test/sentinel_command_test.rb +0 -80
  124. data/test/sentinel_test.rb +0 -255
  125. data/test/sorting_test.rb +0 -59
  126. data/test/ssl_test.rb +0 -73
  127. data/test/support/connection/hiredis.rb +0 -1
  128. data/test/support/connection/ruby.rb +0 -1
  129. data/test/support/connection/synchrony.rb +0 -17
  130. data/test/support/redis_mock.rb +0 -130
  131. data/test/support/ssl/gen_certs.sh +0 -31
  132. data/test/support/ssl/trusted-ca.crt +0 -25
  133. data/test/support/ssl/trusted-ca.key +0 -27
  134. data/test/support/ssl/trusted-cert.crt +0 -81
  135. data/test/support/ssl/trusted-cert.key +0 -28
  136. data/test/support/ssl/untrusted-ca.crt +0 -26
  137. data/test/support/ssl/untrusted-ca.key +0 -27
  138. data/test/support/ssl/untrusted-cert.crt +0 -82
  139. data/test/support/ssl/untrusted-cert.key +0 -28
  140. data/test/support/wire/synchrony.rb +0 -24
  141. data/test/support/wire/thread.rb +0 -5
  142. data/test/synchrony_driver.rb +0 -88
  143. data/test/test.conf.erb +0 -9
  144. data/test/thread_safety_test.rb +0 -62
  145. data/test/transactions_test.rb +0 -264
  146. data/test/unknown_commands_test.rb +0 -14
  147. data/test/url_param_test.rb +0 -138
@@ -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