redis 4.2.5 → 4.8.1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +113 -0
  3. data/README.md +41 -21
  4. data/lib/redis/client.rb +52 -27
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +8 -9
  7. data/lib/redis/cluster/node.rb +14 -1
  8. data/lib/redis/cluster/node_loader.rb +8 -11
  9. data/lib/redis/cluster/option.rb +14 -4
  10. data/lib/redis/cluster/slot_loader.rb +9 -12
  11. data/lib/redis/cluster.rb +33 -13
  12. data/lib/redis/commands/bitmaps.rb +63 -0
  13. data/lib/redis/commands/cluster.rb +45 -0
  14. data/lib/redis/commands/connection.rb +58 -0
  15. data/lib/redis/commands/geo.rb +84 -0
  16. data/lib/redis/commands/hashes.rb +251 -0
  17. data/lib/redis/commands/hyper_log_log.rb +37 -0
  18. data/lib/redis/commands/keys.rb +455 -0
  19. data/lib/redis/commands/lists.rb +290 -0
  20. data/lib/redis/commands/pubsub.rb +72 -0
  21. data/lib/redis/commands/scripting.rb +114 -0
  22. data/lib/redis/commands/server.rb +188 -0
  23. data/lib/redis/commands/sets.rb +223 -0
  24. data/lib/redis/commands/sorted_sets.rb +812 -0
  25. data/lib/redis/commands/streams.rb +382 -0
  26. data/lib/redis/commands/strings.rb +313 -0
  27. data/lib/redis/commands/transactions.rb +139 -0
  28. data/lib/redis/commands.rb +240 -0
  29. data/lib/redis/connection/command_helper.rb +2 -0
  30. data/lib/redis/connection/hiredis.rb +3 -2
  31. data/lib/redis/connection/ruby.rb +19 -9
  32. data/lib/redis/connection/synchrony.rb +10 -8
  33. data/lib/redis/connection.rb +1 -1
  34. data/lib/redis/distributed.rb +124 -29
  35. data/lib/redis/errors.rb +9 -0
  36. data/lib/redis/pipeline.rb +128 -3
  37. data/lib/redis/version.rb +1 -1
  38. data/lib/redis.rb +139 -3377
  39. metadata +22 -5
data/lib/redis/cluster.rb CHANGED
@@ -78,11 +78,13 @@ class Redis
78
78
  end
79
79
 
80
80
  def call_pipeline(pipeline)
81
- node_keys, command_keys = extract_keys_in_pipeline(pipeline)
82
- raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
81
+ node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
82
+ if node_keys.size > 1
83
+ raise(CrossSlotPipeliningError,
84
+ pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
85
+ end
83
86
 
84
- node = find_node(node_keys.first)
85
- try_send(node, :call_pipeline, pipeline)
87
+ try_send(find_node(node_keys.first), :call_pipeline, pipeline)
86
88
  end
87
89
 
88
90
  def call_with_timeout(command, timeout, &block)
@@ -128,13 +130,14 @@ class Redis
128
130
  def send_command(command, &block)
129
131
  cmd = command.first.to_s.downcase
130
132
  case cmd
131
- when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
133
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
132
134
  @node.call_all(command, &block).first
133
135
  when 'flushall', 'flushdb'
134
136
  @node.call_master(command, &block).first
135
137
  when 'wait' then @node.call_master(command, &block).reduce(:+)
136
138
  when 'keys' then @node.call_slave(command, &block).flatten.sort
137
139
  when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
140
+ when 'scan' then _scan(command, &block)
138
141
  when 'lastsave' then @node.call_all(command, &block).sort
139
142
  when 'role' then @node.call_all(command, &block)
140
143
  when 'config' then send_config_command(command, &block)
@@ -236,6 +239,29 @@ class Redis
236
239
  raise
237
240
  end
238
241
 
242
+ def _scan(command, &block)
243
+ input_cursor = Integer(command[1])
244
+
245
+ client_index = input_cursor % 256
246
+ raw_cursor = input_cursor >> 8
247
+
248
+ clients = @node.scale_reading_clients
249
+
250
+ client = clients[client_index]
251
+ return ['0', []] unless client
252
+
253
+ command[1] = raw_cursor.to_s
254
+
255
+ result_cursor, result_keys = client.call(command, &block)
256
+ result_cursor = Integer(result_cursor)
257
+
258
+ if result_cursor == 0
259
+ client_index += 1
260
+ end
261
+
262
+ [((result_cursor << 8) + client_index).to_s, result_keys]
263
+ end
264
+
239
265
  def assign_redirection_node(err_msg)
240
266
  _, slot, node_key = err_msg.split(' ')
241
267
  slot = slot.to_i
@@ -253,14 +279,14 @@ class Redis
253
279
  find_node(node_key)
254
280
  end
255
281
 
256
- def find_node_key(command)
282
+ def find_node_key(command, primary_only: false)
257
283
  key = @command.extract_first_key(command)
258
284
  return if key.empty?
259
285
 
260
286
  slot = KeySlotConverter.convert(key)
261
287
  return unless @slot.exists?(slot)
262
288
 
263
- if @command.should_send_to_master?(command)
289
+ if @command.should_send_to_master?(command) || primary_only
264
290
  @slot.find_node_key_of_master(slot)
265
291
  else
266
292
  @slot.find_node_key_of_slave(slot)
@@ -285,11 +311,5 @@ class Redis
285
311
  @node.map(&:disconnect)
286
312
  @node, @slot = fetch_cluster_info!(@option)
287
313
  end
288
-
289
- def extract_keys_in_pipeline(pipeline)
290
- node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
291
- command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
292
- [node_keys, command_keys]
293
- end
294
314
  end
295
315
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Bitmaps
6
+ # Sets or clears the bit at offset in the string value stored at key.
7
+ #
8
+ # @param [String] key
9
+ # @param [Integer] offset bit offset
10
+ # @param [Integer] value bit value `0` or `1`
11
+ # @return [Integer] the original bit value stored at `offset`
12
+ def setbit(key, offset, value)
13
+ send_command([:setbit, key, offset, value])
14
+ end
15
+
16
+ # Returns the bit value at offset in the string value stored at key.
17
+ #
18
+ # @param [String] key
19
+ # @param [Integer] offset bit offset
20
+ # @return [Integer] `0` or `1`
21
+ def getbit(key, offset)
22
+ send_command([:getbit, key, offset])
23
+ end
24
+
25
+ # Count the number of set bits in a range of the string value stored at key.
26
+ #
27
+ # @param [String] key
28
+ # @param [Integer] start start index
29
+ # @param [Integer] stop stop index
30
+ # @return [Integer] the number of bits set to 1
31
+ def bitcount(key, start = 0, stop = -1)
32
+ send_command([:bitcount, key, start, stop])
33
+ end
34
+
35
+ # Perform a bitwise operation between strings and store the resulting string in a key.
36
+ #
37
+ # @param [String] operation e.g. `and`, `or`, `xor`, `not`
38
+ # @param [String] destkey destination key
39
+ # @param [String, Array<String>] keys one or more source keys to perform `operation`
40
+ # @return [Integer] the length of the string stored in `destkey`
41
+ def bitop(operation, destkey, *keys)
42
+ send_command([:bitop, operation, destkey, *keys])
43
+ end
44
+
45
+ # Return the position of the first bit set to 1 or 0 in a string.
46
+ #
47
+ # @param [String] key
48
+ # @param [Integer] bit whether to look for the first 1 or 0 bit
49
+ # @param [Integer] start start index
50
+ # @param [Integer] stop stop index
51
+ # @return [Integer] the position of the first 1/0 bit.
52
+ # -1 if looking for 1 and it is not found or start and stop are given.
53
+ def bitpos(key, bit, start = nil, stop = nil)
54
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
55
+
56
+ command = [:bitpos, key, bit]
57
+ command << start if start
58
+ command << stop if stop
59
+ send_command(command)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Cluster
6
+ # Sends `CLUSTER *` command to random node and returns its reply.
7
+ #
8
+ # @see https://redis.io/commands#cluster Reference of cluster command
9
+ #
10
+ # @param subcommand [String, Symbol] the subcommand of cluster command
11
+ # e.g. `:slots`, `:nodes`, `:slaves`, `:info`
12
+ #
13
+ # @return [Object] depends on the subcommand
14
+ def cluster(subcommand, *args)
15
+ subcommand = subcommand.to_s.downcase
16
+ block = case subcommand
17
+ when 'slots'
18
+ HashifyClusterSlots
19
+ when 'nodes'
20
+ HashifyClusterNodes
21
+ when 'slaves'
22
+ HashifyClusterSlaves
23
+ when 'info'
24
+ HashifyInfo
25
+ else
26
+ Noop
27
+ end
28
+
29
+ # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
30
+ block = Noop unless @cluster_mode
31
+
32
+ send_command([:cluster, subcommand] + args, &block)
33
+ end
34
+
35
+ # Sends `ASKING` command to random node and returns its reply.
36
+ #
37
+ # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
38
+ #
39
+ # @return [String] `'OK'`
40
+ def asking
41
+ send_command(%i[asking])
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Connection
6
+ # Authenticate to the server.
7
+ #
8
+ # @param [Array<String>] args includes both username and password
9
+ # or only password
10
+ # @return [String] `OK`
11
+ # @see https://redis.io/commands/auth AUTH command
12
+ def auth(*args)
13
+ send_command([:auth, *args])
14
+ end
15
+
16
+ # Ping the server.
17
+ #
18
+ # @param [optional, String] message
19
+ # @return [String] `PONG`
20
+ def ping(message = nil)
21
+ send_command([:ping, message].compact)
22
+ end
23
+
24
+ # Echo the given string.
25
+ #
26
+ # @param [String] value
27
+ # @return [String]
28
+ def echo(value)
29
+ send_command([:echo, value])
30
+ end
31
+
32
+ # Change the selected database for the current connection.
33
+ #
34
+ # @param [Integer] db zero-based index of the DB to use (0 to 15)
35
+ # @return [String] `OK`
36
+ def select(db)
37
+ synchronize do |client|
38
+ client.db = db
39
+ client.call([:select, db])
40
+ end
41
+ end
42
+
43
+ # Close the connection.
44
+ #
45
+ # @return [String] `OK`
46
+ def quit
47
+ synchronize do |client|
48
+ begin
49
+ client.call([:quit])
50
+ rescue ConnectionError
51
+ ensure
52
+ client.disconnect
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Geo
6
+ # Adds the specified geospatial items (latitude, longitude, name) to the specified key
7
+ #
8
+ # @param [String] key
9
+ # @param [Array] member arguemnts for member or members: longitude, latitude, name
10
+ # @return [Integer] number of elements added to the sorted set
11
+ def geoadd(key, *member)
12
+ send_command([:geoadd, key, *member])
13
+ end
14
+
15
+ # Returns geohash string representing position for specified members of the specified key.
16
+ #
17
+ # @param [String] key
18
+ # @param [String, Array<String>] member one member or array of members
19
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
20
+ def geohash(key, member)
21
+ send_command([:geohash, key, member])
22
+ end
23
+
24
+ # Query a sorted set representing a geospatial index to fetch members matching a
25
+ # given maximum distance from a point
26
+ #
27
+ # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
28
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
29
+ # or the farthest to the nearest relative to the center
30
+ # @param [Integer] count limit the results to the first N matching items
31
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
32
+ # @return [Array<String>] may be changed with `options`
33
+ def georadius(*args, **geoptions)
34
+ geoarguments = _geoarguments(*args, **geoptions)
35
+
36
+ send_command([:georadius, *geoarguments])
37
+ end
38
+
39
+ # Query a sorted set representing a geospatial index to fetch members matching a
40
+ # given maximum distance from an already existing member
41
+ #
42
+ # @param [Array] args key, member, radius, unit(m|km|ft|mi)
43
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
44
+ # to the nearest relative to the center
45
+ # @param [Integer] count limit the results to the first N matching items
46
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
47
+ # @return [Array<String>] may be changed with `options`
48
+ def georadiusbymember(*args, **geoptions)
49
+ geoarguments = _geoarguments(*args, **geoptions)
50
+
51
+ send_command([:georadiusbymember, *geoarguments])
52
+ end
53
+
54
+ # Returns longitude and latitude of members of a geospatial index
55
+ #
56
+ # @param [String] key
57
+ # @param [String, Array<String>] member one member or array of members
58
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
59
+ # element is either array of longitude and latitude or nil
60
+ def geopos(key, member)
61
+ send_command([:geopos, key, member])
62
+ end
63
+
64
+ # Returns the distance between two members of a geospatial index
65
+ #
66
+ # @param [String ]key
67
+ # @param [Array<String>] members
68
+ # @param ['m', 'km', 'mi', 'ft'] unit
69
+ # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
70
+ def geodist(key, member1, member2, unit = 'm')
71
+ send_command([:geodist, key, member1, member2, unit])
72
+ end
73
+
74
+ private
75
+
76
+ def _geoarguments(*args, options: nil, sort: nil, count: nil)
77
+ args.push sort if sort
78
+ args.push 'count', count if count
79
+ args.push options if options
80
+ args
81
+ end
82
+ end
83
+ end
84
+ end
@@ -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