redis 4.3.1 → 4.6.0

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