redis 4.5.1 → 4.6.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.
@@ -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