redis 3.3.5 → 4.8.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 +232 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +177 -100
  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 +455 -0
  22. data/lib/redis/commands/lists.rb +290 -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 +223 -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 +240 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -3
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +136 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +255 -85
  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 +174 -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,455 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Keys
6
+ # Scan the keyspace
7
+ #
8
+ # @example Retrieve the first batch of keys
9
+ # redis.scan(0)
10
+ # # => ["4", ["key:21", "key:47", "key:42"]]
11
+ # @example Retrieve a batch of keys matching a pattern
12
+ # redis.scan(4, :match => "key:1?")
13
+ # # => ["92", ["key:13", "key:18"]]
14
+ # @example Retrieve a batch of keys of a certain type
15
+ # redis.scan(92, :type => "zset")
16
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
17
+ #
18
+ # @param [String, Integer] cursor the cursor of the iteration
19
+ # @param [Hash] options
20
+ # - `:match => String`: only return keys matching the pattern
21
+ # - `:count => Integer`: return count keys at most per iteration
22
+ # - `:type => String`: return keys only of the given type
23
+ #
24
+ # @return [String, Array<String>] the next cursor and all found keys
25
+ def scan(cursor, **options)
26
+ _scan(:scan, cursor, [], **options)
27
+ end
28
+
29
+ # Scan the keyspace
30
+ #
31
+ # @example Retrieve all of the keys (with possible duplicates)
32
+ # redis.scan_each.to_a
33
+ # # => ["key:21", "key:47", "key:42"]
34
+ # @example Execute block for each key matching a pattern
35
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
36
+ # # => key:13
37
+ # # => key:18
38
+ # @example Execute block for each key of a type
39
+ # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
40
+ # # => "hash"
41
+ # # => "hash"
42
+ #
43
+ # @param [Hash] options
44
+ # - `:match => String`: only return keys matching the pattern
45
+ # - `:count => Integer`: return count keys at most per iteration
46
+ # - `:type => String`: return keys only of the given type
47
+ #
48
+ # @return [Enumerator] an enumerator for all found keys
49
+ def scan_each(**options, &block)
50
+ return to_enum(:scan_each, **options) unless block_given?
51
+
52
+ cursor = 0
53
+ loop do
54
+ cursor, keys = scan(cursor, **options)
55
+ keys.each(&block)
56
+ break if cursor == "0"
57
+ end
58
+ end
59
+
60
+ # Remove the expiration from a key.
61
+ #
62
+ # @param [String] key
63
+ # @return [Boolean] whether the timeout was removed or not
64
+ def persist(key)
65
+ send_command([:persist, key], &Boolify)
66
+ end
67
+
68
+ # Set a key's time to live in seconds.
69
+ #
70
+ # @param [String] key
71
+ # @param [Integer] seconds time to live
72
+ # @param [Hash] options
73
+ # - `:nx => true`: Set expiry only when the key has no expiry.
74
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
75
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
76
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
77
+ # @return [Boolean] whether the timeout was set or not
78
+ def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
+ args = [:expire, key, seconds]
80
+ args << "NX" if nx
81
+ args << "XX" if xx
82
+ args << "GT" if gt
83
+ args << "LT" if lt
84
+
85
+ send_command(args, &Boolify)
86
+ end
87
+
88
+ # Set the expiration for a key as a UNIX timestamp.
89
+ #
90
+ # @param [String] key
91
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
92
+ # @param [Hash] options
93
+ # - `:nx => true`: Set expiry only when the key has no expiry.
94
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
95
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
96
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
97
+ # @return [Boolean] whether the timeout was set or not
98
+ def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
+ args = [:expireat, key, unix_time]
100
+ args << "NX" if nx
101
+ args << "XX" if xx
102
+ args << "GT" if gt
103
+ args << "LT" if lt
104
+
105
+ send_command(args, &Boolify)
106
+ end
107
+
108
+ # Get the time to live (in seconds) for a key.
109
+ #
110
+ # @param [String] key
111
+ # @return [Integer] remaining time to live in seconds.
112
+ #
113
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
114
+ # the key exist but has no associated expire.
115
+ #
116
+ # Starting with Redis 2.8 the return value in case of error changed:
117
+ #
118
+ # - The command returns -2 if the key does not exist.
119
+ # - The command returns -1 if the key exists but has no associated expire.
120
+ def ttl(key)
121
+ send_command([:ttl, key])
122
+ end
123
+
124
+ # Set a key's time to live in milliseconds.
125
+ #
126
+ # @param [String] key
127
+ # @param [Integer] milliseconds time to live
128
+ # @param [Hash] options
129
+ # - `:nx => true`: Set expiry only when the key has no expiry.
130
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
131
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
132
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
133
+ # @return [Boolean] whether the timeout was set or not
134
+ def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
+ args = [:pexpire, key, milliseconds]
136
+ args << "NX" if nx
137
+ args << "XX" if xx
138
+ args << "GT" if gt
139
+ args << "LT" if lt
140
+
141
+ send_command(args, &Boolify)
142
+ end
143
+
144
+ # Set the expiration for a key as number of milliseconds from UNIX Epoch.
145
+ #
146
+ # @param [String] key
147
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
148
+ # @param [Hash] options
149
+ # - `:nx => true`: Set expiry only when the key has no expiry.
150
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
151
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
152
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
153
+ # @return [Boolean] whether the timeout was set or not
154
+ def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
+ args = [:pexpireat, key, ms_unix_time]
156
+ args << "NX" if nx
157
+ args << "XX" if xx
158
+ args << "GT" if gt
159
+ args << "LT" if lt
160
+
161
+ send_command(args, &Boolify)
162
+ end
163
+
164
+ # Get the time to live (in milliseconds) for a key.
165
+ #
166
+ # @param [String] key
167
+ # @return [Integer] remaining time to live in milliseconds
168
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
169
+ # the key exist but has no associated expire.
170
+ #
171
+ # Starting with Redis 2.8 the return value in case of error changed:
172
+ #
173
+ # - The command returns -2 if the key does not exist.
174
+ # - The command returns -1 if the key exists but has no associated expire.
175
+ def pttl(key)
176
+ send_command([:pttl, key])
177
+ end
178
+
179
+ # Return a serialized version of the value stored at a key.
180
+ #
181
+ # @param [String] key
182
+ # @return [String] serialized_value
183
+ def dump(key)
184
+ send_command([:dump, key])
185
+ end
186
+
187
+ # Create a key using the serialized value, previously obtained using DUMP.
188
+ #
189
+ # @param [String] key
190
+ # @param [String] ttl
191
+ # @param [String] serialized_value
192
+ # @param [Hash] options
193
+ # - `:replace => Boolean`: if false, raises an error if key already exists
194
+ # @raise [Redis::CommandError]
195
+ # @return [String] `"OK"`
196
+ def restore(key, ttl, serialized_value, replace: nil)
197
+ args = [:restore, key, ttl, serialized_value]
198
+ args << 'REPLACE' if replace
199
+
200
+ send_command(args)
201
+ end
202
+
203
+ # Transfer a key from the connected instance to another instance.
204
+ #
205
+ # @param [String, Array<String>] key
206
+ # @param [Hash] options
207
+ # - `:host => String`: host of instance to migrate to
208
+ # - `:port => Integer`: port of instance to migrate to
209
+ # - `:db => Integer`: database to migrate to (default: same as source)
210
+ # - `:timeout => Integer`: timeout (default: same as connection timeout)
211
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
212
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
213
+ # @return [String] `"OK"`
214
+ def migrate(key, options)
215
+ args = [:migrate]
216
+ args << (options[:host] || raise(':host not specified'))
217
+ args << (options[:port] || raise(':port not specified'))
218
+ args << (key.is_a?(String) ? key : '')
219
+ args << (options[:db] || @client.db).to_i
220
+ args << (options[:timeout] || @client.timeout).to_i
221
+ args << 'COPY' if options[:copy]
222
+ args << 'REPLACE' if options[:replace]
223
+ args += ['KEYS', *key] if key.is_a?(Array)
224
+
225
+ send_command(args)
226
+ end
227
+
228
+ # Delete one or more keys.
229
+ #
230
+ # @param [String, Array<String>] keys
231
+ # @return [Integer] number of keys that were deleted
232
+ def del(*keys)
233
+ keys.flatten!(1)
234
+ return 0 if keys.empty?
235
+
236
+ send_command([:del] + keys)
237
+ end
238
+
239
+ # Unlink one or more keys.
240
+ #
241
+ # @param [String, Array<String>] keys
242
+ # @return [Integer] number of keys that were unlinked
243
+ def unlink(*keys)
244
+ send_command([:unlink] + keys)
245
+ end
246
+
247
+ # Determine how many of the keys exists.
248
+ #
249
+ # @param [String, Array<String>] keys
250
+ # @return [Integer]
251
+ def exists(*keys)
252
+ if !Redis.exists_returns_integer && keys.size == 1
253
+ if Redis.exists_returns_integer.nil?
254
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
255
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
256
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
257
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
258
+ "(#{::Kernel.caller(1, 1).first})\n"
259
+
260
+ ::Redis.deprecate!(message)
261
+ end
262
+
263
+ exists?(*keys)
264
+ else
265
+ _exists(*keys)
266
+ end
267
+ end
268
+
269
+ def _exists(*keys)
270
+ send_command([:exists, *keys])
271
+ end
272
+
273
+ # Determine if any of the keys exists.
274
+ #
275
+ # @param [String, Array<String>] keys
276
+ # @return [Boolean]
277
+ def exists?(*keys)
278
+ send_command([:exists, *keys]) do |value|
279
+ value > 0
280
+ end
281
+ end
282
+
283
+ # Find all keys matching the given pattern.
284
+ #
285
+ # @param [String] pattern
286
+ # @return [Array<String>]
287
+ def keys(pattern = "*")
288
+ send_command([:keys, pattern]) do |reply|
289
+ if reply.is_a?(String)
290
+ reply.split(" ")
291
+ else
292
+ reply
293
+ end
294
+ end
295
+ end
296
+
297
+ # Move a key to another database.
298
+ #
299
+ # @example Move a key to another database
300
+ # redis.set "foo", "bar"
301
+ # # => "OK"
302
+ # redis.move "foo", 2
303
+ # # => true
304
+ # redis.exists "foo"
305
+ # # => false
306
+ # redis.select 2
307
+ # # => "OK"
308
+ # redis.exists "foo"
309
+ # # => true
310
+ # redis.get "foo"
311
+ # # => "bar"
312
+ #
313
+ # @param [String] key
314
+ # @param [Integer] db
315
+ # @return [Boolean] whether the key was moved or not
316
+ def move(key, db)
317
+ send_command([:move, key, db], &Boolify)
318
+ end
319
+
320
+ # Copy a value from one key to another.
321
+ #
322
+ # @example Copy a value to another key
323
+ # redis.set "foo", "value"
324
+ # # => "OK"
325
+ # redis.copy "foo", "bar"
326
+ # # => true
327
+ # redis.get "bar"
328
+ # # => "value"
329
+ #
330
+ # @example Copy a value to a key in another database
331
+ # redis.set "foo", "value"
332
+ # # => "OK"
333
+ # redis.copy "foo", "bar", db: 2
334
+ # # => true
335
+ # redis.select 2
336
+ # # => "OK"
337
+ # redis.get "bar"
338
+ # # => "value"
339
+ #
340
+ # @param [String] source
341
+ # @param [String] destination
342
+ # @param [Integer] db
343
+ # @param [Boolean] replace removes the `destination` key before copying value to it
344
+ # @return [Boolean] whether the key was copied or not
345
+ def copy(source, destination, db: nil, replace: false)
346
+ command = [:copy, source, destination]
347
+ command << "DB" << db if db
348
+ command << "REPLACE" if replace
349
+
350
+ send_command(command, &Boolify)
351
+ end
352
+
353
+ def object(*args)
354
+ send_command([:object] + args)
355
+ end
356
+
357
+ # Return a random key from the keyspace.
358
+ #
359
+ # @return [String]
360
+ def randomkey
361
+ send_command([:randomkey])
362
+ end
363
+
364
+ # Rename a key. If the new key already exists it is overwritten.
365
+ #
366
+ # @param [String] old_name
367
+ # @param [String] new_name
368
+ # @return [String] `OK`
369
+ def rename(old_name, new_name)
370
+ send_command([:rename, old_name, new_name])
371
+ end
372
+
373
+ # Rename a key, only if the new key does not exist.
374
+ #
375
+ # @param [String] old_name
376
+ # @param [String] new_name
377
+ # @return [Boolean] whether the key was renamed or not
378
+ def renamenx(old_name, new_name)
379
+ send_command([:renamenx, old_name, new_name], &Boolify)
380
+ end
381
+
382
+ # Sort the elements in a list, set or sorted set.
383
+ #
384
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
385
+ # redis.sort("list", :order => "alpha", :limit => [0, 2])
386
+ # # => ["a", "b"]
387
+ # @example Store an alphabetically descending list in "target"
388
+ # redis.sort("list", :order => "desc alpha", :store => "target")
389
+ # # => 26
390
+ #
391
+ # @param [String] key
392
+ # @param [Hash] options
393
+ # - `:by => String`: use external key to sort elements by
394
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
395
+ # of `count` elements
396
+ # - `:get => [String, Array<String>]`: single key or array of keys to
397
+ # retrieve per element in the result
398
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
399
+ # - `:store => String`: key to store the result at
400
+ #
401
+ # @return [Array<String>, Array<Array<String>>, Integer]
402
+ # - when `:get` is not specified, or holds a single element, an array of elements
403
+ # - when `:get` is specified, and holds more than one element, an array of
404
+ # elements where every element is an array with the result for every
405
+ # element specified in `:get`
406
+ # - when `:store` is specified, the number of elements in the stored result
407
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
408
+ args = [:sort, key]
409
+ args << "BY" << by if by
410
+
411
+ if limit
412
+ args << "LIMIT"
413
+ args.concat(limit)
414
+ end
415
+
416
+ get = Array(get)
417
+ get.each do |item|
418
+ args << "GET" << item
419
+ end
420
+
421
+ args.concat(order.split(" ")) if order
422
+ args << "STORE" << store if store
423
+
424
+ send_command(args) do |reply|
425
+ if get.size > 1 && !store
426
+ reply.each_slice(get.size).to_a if reply
427
+ else
428
+ reply
429
+ end
430
+ end
431
+ end
432
+
433
+ # Determine the type stored at key.
434
+ #
435
+ # @param [String] key
436
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
437
+ def type(key)
438
+ send_command([:type, key])
439
+ end
440
+
441
+ private
442
+
443
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
444
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
445
+
446
+ args << cursor
447
+ args << "MATCH" << match if match
448
+ args << "COUNT" << count if count
449
+ args << "TYPE" << type if type
450
+
451
+ send_command([command] + args, &block)
452
+ end
453
+ end
454
+ end
455
+ end