protocol-redis 0.9.0 → 0.11.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 (34) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -2
  3. data/context/getting-started.md +55 -0
  4. data/context/index.yaml +13 -0
  5. data/lib/protocol/redis/cluster/methods/generic.rb +94 -0
  6. data/lib/protocol/redis/cluster/methods/pubsub.rb +27 -0
  7. data/lib/protocol/redis/cluster/methods/scripting.rb +93 -0
  8. data/lib/protocol/redis/cluster/methods/streams.rb +204 -0
  9. data/lib/protocol/redis/cluster/methods/strings.rb +304 -0
  10. data/lib/protocol/redis/cluster/methods.rb +27 -0
  11. data/lib/protocol/redis/connection.rb +41 -12
  12. data/lib/protocol/redis/error.rb +6 -0
  13. data/lib/protocol/redis/methods/cluster.rb +9 -7
  14. data/lib/protocol/redis/methods/connection.rb +9 -8
  15. data/lib/protocol/redis/methods/counting.rb +9 -8
  16. data/lib/protocol/redis/methods/generic.rb +100 -99
  17. data/lib/protocol/redis/methods/geospatial.rb +42 -49
  18. data/lib/protocol/redis/methods/hashes.rb +84 -83
  19. data/lib/protocol/redis/methods/lists.rb +75 -74
  20. data/lib/protocol/redis/methods/pubsub.rb +5 -4
  21. data/lib/protocol/redis/methods/scripting.rb +19 -20
  22. data/lib/protocol/redis/methods/server.rb +13 -9
  23. data/lib/protocol/redis/methods/sets.rb +42 -41
  24. data/lib/protocol/redis/methods/sorted_sets.rb +110 -109
  25. data/lib/protocol/redis/methods/streams.rb +48 -47
  26. data/lib/protocol/redis/methods/strings.rb +112 -109
  27. data/lib/protocol/redis/methods.rb +16 -14
  28. data/lib/protocol/redis/version.rb +1 -1
  29. data/lib/protocol/redis.rb +9 -2
  30. data/readme.md +49 -21
  31. data/releases.md +98 -0
  32. data.tar.gz.sig +0 -0
  33. metadata +13 -9
  34. metadata.gz.sig +0 -0
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protocol
4
+ module Redis
5
+ module Cluster
6
+ module Methods
7
+ # Provides Redis String commands for cluster environments.
8
+ # String operations are routed to the appropriate shard based on the key.
9
+ module Strings
10
+ # Append a value to a key.
11
+ # @parameter key [String] The key to append to.
12
+ # @parameter value [String] The value to append.
13
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
14
+ # @returns [Integer] The length of the string after the append operation.
15
+ def append(key, value, role: :master)
16
+ slot = slot_for(key)
17
+ client = client_for(slot, role)
18
+
19
+ return client.call("APPEND", key, value)
20
+ end
21
+
22
+ # Count set bits in a string.
23
+ # @parameter key [String] The key to count bits in.
24
+ # @parameter range [Array] Optional start and end positions.
25
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
26
+ # @returns [Integer] The number of set bits.
27
+ def bitcount(key, *range, role: :master)
28
+ slot = slot_for(key)
29
+ client = client_for(slot, role)
30
+
31
+ return client.call("BITCOUNT", key, *range)
32
+ end
33
+
34
+ # Decrement the integer value of a key by one.
35
+ # @parameter key [String] The key to decrement.
36
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
37
+ # @returns [Integer] The value after decrementing.
38
+ def decr(key, role: :master)
39
+ slot = slot_for(key)
40
+ client = client_for(slot, role)
41
+
42
+ return client.call("DECR", key)
43
+ end
44
+
45
+ # Decrement the integer value of a key by the given number.
46
+ # @parameter key [String] The key to decrement.
47
+ # @parameter decrement [Integer] The amount to decrement by.
48
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
49
+ # @returns [Integer] The value after decrementing.
50
+ def decrby(key, decrement, role: :master)
51
+ slot = slot_for(key)
52
+ client = client_for(slot, role)
53
+
54
+ return client.call("DECRBY", key, decrement)
55
+ end
56
+
57
+ # Get the value of a key.
58
+ # @parameter key [String] The key to get.
59
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
60
+ # @returns [String | nil] The value, or `nil` if key doesn't exist.
61
+ def get(key, role: :master)
62
+ slot = slot_for(key)
63
+ client = client_for(slot, role)
64
+
65
+ return client.call("GET", key)
66
+ end
67
+
68
+ # Get the bit value at offset in the string value stored at key.
69
+ # @parameter key [String] The key to get bit from.
70
+ # @parameter offset [Integer] The bit offset.
71
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
72
+ # @returns [Integer] The bit value (0 or 1).
73
+ def getbit(key, offset, role: :master)
74
+ slot = slot_for(key)
75
+ client = client_for(slot, role)
76
+
77
+ return client.call("GETBIT", key, offset)
78
+ end
79
+
80
+ # Get a substring of the string stored at a key.
81
+ # @parameter key [String] The key to get range from.
82
+ # @parameter start_index [Integer] The start position.
83
+ # @parameter end_index [Integer] The end position.
84
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
85
+ # @returns [String] The substring.
86
+ def getrange(key, start_index, end_index, role: :master)
87
+ slot = slot_for(key)
88
+ client = client_for(slot, role)
89
+
90
+ return client.call("GETRANGE", key, start_index, end_index)
91
+ end
92
+
93
+ # Set the string value of a key and return its old value.
94
+ # @parameter key [String] The key to set.
95
+ # @parameter value [String] The new value.
96
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
97
+ # @returns [String | nil] The old value, or `nil` if key didn't exist.
98
+ def getset(key, value, role: :master)
99
+ slot = slot_for(key)
100
+ client = client_for(slot, role)
101
+
102
+ return client.call("GETSET", key, value)
103
+ end
104
+
105
+ # Increment the integer value of a key by one.
106
+ # @parameter key [String] The key to increment.
107
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
108
+ # @returns [Integer] The value after incrementing.
109
+ def incr(key, role: :master)
110
+ slot = slot_for(key)
111
+ client = client_for(slot, role)
112
+
113
+ return client.call("INCR", key)
114
+ end
115
+
116
+ # Increment the integer value of a key by the given amount.
117
+ # @parameter key [String] The key to increment.
118
+ # @parameter increment [Integer] The amount to increment by.
119
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
120
+ # @returns [Integer] The value after incrementing.
121
+ def incrby(key, increment, role: :master)
122
+ slot = slot_for(key)
123
+ client = client_for(slot, role)
124
+
125
+ return client.call("INCRBY", key, increment)
126
+ end
127
+
128
+ # Increment the float value of a key by the given amount.
129
+ # @parameter key [String] The key to increment.
130
+ # @parameter increment [Float] The amount to increment by.
131
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
132
+ # @returns [String] The value after incrementing (as string).
133
+ def incrbyfloat(key, increment, role: :master)
134
+ slot = slot_for(key)
135
+ client = client_for(slot, role)
136
+
137
+ return client.call("INCRBYFLOAT", key, increment)
138
+ end
139
+
140
+ # Get the values of all the given keys.
141
+ # Uses the cluster-aware multi-key handling from generic methods.
142
+ # @parameter keys [Array(String)] The keys to get.
143
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
144
+ # @returns [Array] The values for the given keys, in order.
145
+ def mget(*keys, role: :master)
146
+ return [] if keys.empty?
147
+
148
+ results = Array.new(keys.size)
149
+ key_to_index = keys.each_with_index.to_h
150
+
151
+ clients_for(*keys, role: role) do |client, grouped_keys|
152
+ values = client.call("MGET", *grouped_keys)
153
+ grouped_keys.each_with_index do |key, i|
154
+ results[key_to_index[key]] = values[i]
155
+ end
156
+ end
157
+
158
+ return results
159
+ end
160
+
161
+ # Set multiple keys to multiple values.
162
+ # Redis will return a CROSSSLOT error if keys span multiple slots.
163
+ # @parameter pairs [Hash] The key-value pairs to set.
164
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
165
+ # @returns [String] Status reply.
166
+ def mset(pairs, role: :master)
167
+ return if pairs.empty?
168
+
169
+ if pairs.is_a?(Hash)
170
+ pairs = pairs.to_a.flatten
171
+ end
172
+
173
+ slot = slot_for(pairs.first)
174
+ client = client_for(slot, role)
175
+
176
+ return client.call("MSET", *pairs)
177
+ end
178
+
179
+ # Set multiple keys to multiple values, only if none exist.
180
+ # Redis will return a CROSSSLOT error if keys span multiple slots.
181
+ # @parameter pairs [Hash] The key-value pairs to set.
182
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
183
+ # @returns [Integer] 1 if all keys were set, 0 otherwise.
184
+ def msetnx(pairs, role: :master)
185
+ keys = pairs.keys
186
+ return 0 if keys.empty?
187
+
188
+ flattened_pairs = pairs.keys.zip(pairs.values).flatten
189
+ slot = slot_for(keys.first)
190
+ client = client_for(slot, role)
191
+
192
+ return client.call("MSETNX", *flattened_pairs)
193
+ end
194
+
195
+ # Set the value and expiration in milliseconds of a key.
196
+ # @parameter key [String] The key to set.
197
+ # @parameter milliseconds [Integer] The expiration time in milliseconds.
198
+ # @parameter value [String] The value to set.
199
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
200
+ # @returns [String] Status reply.
201
+ def psetex(key, milliseconds, value, role: :master)
202
+ slot = slot_for(key)
203
+ client = client_for(slot, role)
204
+
205
+ return client.call("PSETEX", key, milliseconds, value)
206
+ end
207
+
208
+ # Set the string value of a key.
209
+ # @parameter key [String] The key to set.
210
+ # @parameter value [String] The value to set.
211
+ # @parameter update [Boolean | nil] If `true`, only update existing keys. If `false`, only set new keys.
212
+ # @parameter seconds [Integer | nil] Expiration time in seconds.
213
+ # @parameter milliseconds [Integer | nil] Expiration time in milliseconds.
214
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
215
+ # @returns [String] Status reply.
216
+ def set(key, value, update: nil, seconds: nil, milliseconds: nil, role: :master)
217
+ arguments = []
218
+
219
+ if seconds
220
+ arguments << "EX" << seconds
221
+ end
222
+
223
+ if milliseconds
224
+ arguments << "PX" << milliseconds
225
+ end
226
+
227
+ if update == true
228
+ arguments << "XX"
229
+ elsif update == false
230
+ arguments << "NX"
231
+ end
232
+
233
+ slot = slot_for(key)
234
+ client = client_for(slot, role)
235
+
236
+ return client.call("SET", key, value, *arguments)
237
+ end
238
+
239
+ # Set or clear the bit at offset in the string value stored at key.
240
+ # @parameter key [String] The key to modify.
241
+ # @parameter offset [Integer] The bit offset.
242
+ # @parameter value [Integer] The bit value (0 or 1).
243
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
244
+ # @returns [Integer] The original bit value.
245
+ def setbit(key, offset, value, role: :master)
246
+ slot = slot_for(key)
247
+ client = client_for(slot, role)
248
+
249
+ return client.call("SETBIT", key, offset, value)
250
+ end
251
+
252
+ # Set the value and expiration of a key.
253
+ # @parameter key [String] The key to set.
254
+ # @parameter seconds [Integer] The expiration time in seconds.
255
+ # @parameter value [String] The value to set.
256
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
257
+ # @returns [String] Status reply.
258
+ def setex(key, seconds, value, role: :master)
259
+ slot = slot_for(key)
260
+ client = client_for(slot, role)
261
+
262
+ return client.call("SETEX", key, seconds, value)
263
+ end
264
+
265
+ # Set the value of a key, only if the key does not exist.
266
+ # @parameter key [String] The key to set.
267
+ # @parameter value [String] The value to set.
268
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
269
+ # @returns [Boolean] `true` if the key was set, `false` otherwise.
270
+ def setnx(key, value, role: :master)
271
+ slot = slot_for(key)
272
+ client = client_for(slot, role)
273
+
274
+ return client.call("SETNX", key, value) == 1
275
+ end
276
+
277
+ # Overwrite part of a string at key starting at the specified offset.
278
+ # @parameter key [String] The key to modify.
279
+ # @parameter offset [Integer] The offset to start overwriting at.
280
+ # @parameter value [String] The value to write.
281
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
282
+ # @returns [Integer] The length of the string after modification.
283
+ def setrange(key, offset, value, role: :master)
284
+ slot = slot_for(key)
285
+ client = client_for(slot, role)
286
+
287
+ return client.call("SETRANGE", key, offset, value)
288
+ end
289
+
290
+ # Get the length of the value stored in a key.
291
+ # @parameter key [String] The key to get length of.
292
+ # @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
293
+ # @returns [Integer] The length of the string value, or 0 if key doesn't exist.
294
+ def strlen(key, role: :master)
295
+ slot = slot_for(key)
296
+ client = client_for(slot, role)
297
+
298
+ return client.call("STRLEN", key)
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "methods/generic"
7
+ require_relative "methods/pubsub"
8
+ require_relative "methods/streams"
9
+ require_relative "methods/strings"
10
+
11
+ module Protocol
12
+ module Redis
13
+ # @namespace
14
+ module Cluster
15
+ # A collection of methods for interacting with Redis in cluster mode.
16
+ module Methods
17
+ # Includes all Redis methods into the given class.
18
+ def self.included(klass)
19
+ klass.include Methods::Generic
20
+ klass.include Methods::Pubsub
21
+ klass.include Methods::Streams
22
+ klass.include Methods::Strings
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,13 +3,16 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2019-2023, by Samuel Williams.
5
5
 
6
- require_relative 'error'
6
+ require_relative "error"
7
7
 
8
8
  module Protocol
9
9
  module Redis
10
+ # Represents a Redis protocol connection handling low-level communication.
10
11
  class Connection
11
12
  CRLF = "\r\n".freeze
12
13
 
14
+ # Initialize a new connection with the provided stream.
15
+ # @parameter stream [IO] The underlying stream for communication.
13
16
  def initialize(stream)
14
17
  @stream = stream
15
18
 
@@ -22,18 +25,23 @@ module Protocol
22
25
  # @attr [Integer] Number of requests sent.
23
26
  attr :count
24
27
 
28
+ # Close the underlying stream connection.
25
29
  def close
26
30
  @stream.close
27
31
  end
28
32
 
29
33
  class << self
34
+ # Create a new client connection instance.
30
35
  alias client new
31
36
  end
32
37
 
38
+ # Flush any buffered data to the stream.
33
39
  def flush
34
40
  @stream.flush
35
41
  end
36
42
 
43
+ # Check if the connection is closed.
44
+ # @returns [Boolean] True if the connection is closed.
37
45
  def closed?
38
46
  @stream.closed?
39
47
  end
@@ -51,6 +59,8 @@ module Protocol
51
59
  end
52
60
  end
53
61
 
62
+ # Write a Redis object to the stream.
63
+ # @parameter object [Object] The object to write (String, Array, Integer, or object with to_redis method).
54
64
  def write_object(object)
55
65
  case object
56
66
  when String
@@ -59,11 +69,26 @@ module Protocol
59
69
  write_array(object)
60
70
  when Integer
61
71
  write_lines(":#{object}")
72
+ when nil
73
+ write_lines("$-1")
62
74
  else
63
75
  write_object(object.to_redis)
64
76
  end
65
77
  end
66
78
 
79
+ # Write a Redis array to the stream.
80
+ # @parameter array [Array] The array to write.
81
+ def write_array(array)
82
+ write_lines("*#{array.size}")
83
+
84
+ array.each do |element|
85
+ write_object(element)
86
+ end
87
+ end
88
+
89
+ # Read data of specified length from the stream.
90
+ # @parameter length [Integer] The number of bytes to read.
91
+ # @returns [String] The data read from the stream.
67
92
  def read_data(length)
68
93
  buffer = @stream.read(length) or @stream.eof!
69
94
 
@@ -73,13 +98,17 @@ module Protocol
73
98
  return buffer
74
99
  end
75
100
 
101
+ # Read and parse a Redis object from the stream.
102
+ # @returns [Object] The parsed Redis object (String, Array, Integer, or nil).
103
+ # @raises [ServerError] If the server returns an error response.
104
+ # @raises [EOFError] If the stream reaches end of file.
76
105
  def read_object
77
106
  line = read_line or raise EOFError
78
107
 
79
108
  token = line.slice!(0, 1)
80
109
 
81
110
  case token
82
- when '$'
111
+ when "$"
83
112
  length = line.to_i
84
113
 
85
114
  if length == -1
@@ -87,7 +116,7 @@ module Protocol
87
116
  else
88
117
  return read_data(length)
89
118
  end
90
- when '*'
119
+ when "*"
91
120
  count = line.to_i
92
121
 
93
122
  # Null array (https://redis.io/topics/protocol#resp-arrays):
@@ -96,28 +125,28 @@ module Protocol
96
125
  array = Array.new(count) {read_object}
97
126
 
98
127
  return array
99
- when ':'
128
+ when ":"
100
129
  return line.to_i
101
-
102
- when '-'
130
+
131
+ when "-"
103
132
  raise ServerError.new(line)
104
-
105
- when '+'
133
+
134
+ when "+"
106
135
  return line
107
-
136
+
108
137
  else
109
138
  @stream.flush
110
139
 
111
- raise NotImplementedError, "Implementation for token #{token} missing"
140
+ raise UnknownTokenError, token.inspect
112
141
  end
113
142
 
114
143
  # TODO: If an exception (e.g. Async::TimeoutError) propagates out of this function, perhaps @stream should be closed? Otherwise it might be in a weird state.
115
144
  end
116
145
 
117
146
  alias read_response read_object
118
-
147
+
119
148
  private
120
-
149
+
121
150
  # In the case of Redis, we do not want to perform a flush in every line,
122
151
  # because each Redis command contains several lines. Flushing once per
123
152
  # command is more efficient because it avoids unnecessary writes to the
@@ -5,10 +5,16 @@
5
5
 
6
6
  module Protocol
7
7
  module Redis
8
+ # Represents a general Redis protocol error.
8
9
  class Error < StandardError
9
10
  end
10
11
 
12
+ # Represents an error response from the Redis server.
11
13
  class ServerError < Error
12
14
  end
15
+
16
+ # Represents an unknown token error in the Redis protocol.
17
+ class UnknownTokenError < Error
18
+ end
13
19
  end
14
20
  end
@@ -2,25 +2,27 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Nick Burwell.
5
+ # Copyright, 2024, by Samuel Williams.
5
6
 
6
7
  module Protocol
7
8
  module Redis
8
9
  module Methods
10
+ # Methods for managing Redis clusters.
9
11
  module Cluster
10
12
  # Sends the `CLUSTER *` command to random node and returns its reply.
11
- # @see https://redis.io/commands/cluster-addslots/
12
- # @param subcommand [String, Symbol] the subcommand of cluster command
13
+ # See <https://redis.io/commands/cluster-addslots/> for more details.
14
+ # @parameter subcommand [String, Symbol] the subcommand of cluster command
13
15
  # e.g. `:addslots`, `:delslots`, `:nodes`, `:replicas`, `:info`
14
16
  #
15
- # @return [Object] depends on the subcommand provided
16
- def cluster(subcommand, *args)
17
- call("CLUSTER", subcommand.to_s, *args)
17
+ # @returns [Object] depends on the subcommand provided
18
+ def cluster(subcommand, *arguments)
19
+ call("CLUSTER", subcommand.to_s, *arguments)
18
20
  end
19
21
 
20
22
  # Sends `ASKING` command to random node and returns its reply.
21
- # @see https://redis.io/commands/asking/
23
+ # See <https://redis.io/commands/asking/> for more details.
22
24
  #
23
- # @return [String] `'OK'`
25
+ # @returns [String] `'OK'`
24
26
  def asking
25
27
  call("ASKING")
26
28
  end
@@ -6,31 +6,32 @@
6
6
  module Protocol
7
7
  module Redis
8
8
  module Methods
9
+ # Methods for managing Redis connections.
9
10
  module Connection
10
11
  # Authenticate to the server.
11
- # @see https://redis.io/commands/auth
12
- # @param username [String] Optional username, if Redis ACLs are used.
13
- # @param password [String] Required password.
12
+ # See <https://redis.io/commands/auth> for more details.
13
+ # @parameter username [String] Optional username, if Redis ACLs are used.
14
+ # @parameter password [String] Required password.
14
15
  def auth(*arguments)
15
16
  call("AUTH", *arguments)
16
17
  end
17
18
 
18
19
  # Echo the given string.
19
- # @see https://redis.io/commands/echo
20
- # @param message [String]
20
+ # See <https://redis.io/commands/echo> for more details.
21
+ # @parameter message [String]
21
22
  def echo(message)
22
23
  call("ECHO", message)
23
24
  end
24
25
 
25
26
  # Ping the server.
26
- # @see https://redis.io/commands/ping
27
- # @param message [String]
27
+ # See <https://redis.io/commands/ping> for more details.
28
+ # @parameter message [String]
28
29
  def ping(message)
29
30
  call("PING", message)
30
31
  end
31
32
 
32
33
  # Close the connection.
33
- # @see https://redis.io/commands/quit
34
+ # See <https://redis.io/commands/quit> for more details.
34
35
  def quit
35
36
  call("QUIT")
36
37
  end
@@ -6,26 +6,27 @@
6
6
  module Protocol
7
7
  module Redis
8
8
  module Methods
9
+ # Methods for managing Redis HyperLogLogs.
9
10
  module Counting
10
11
  # Adds the specified elements to the specified HyperLogLog. O(1) to add every element.
11
- # @see https://redis.io/commands/pfadd
12
- # @param key [Key]
13
- # @param element [String]
12
+ # See <https://redis.io/commands/pfadd> for more details.
13
+ # @parameter key [Key]
14
+ # @parameter element [String]
14
15
  def pfadd(key, element, *elements)
15
16
  call("PFADD", key, element, *elements)
16
17
  end
17
18
 
18
19
  # Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s). O(1) with a very small average constant time when called with a single key. O(N) with N being the number of keys, and much bigger constant times, when called with multiple keys.
19
- # @see https://redis.io/commands/pfcount
20
- # @param key [Key]
20
+ # See <https://redis.io/commands/pfcount> for more details.
21
+ # @parameter key [Key]
21
22
  def pfcount(key, *keys)
22
23
  call("PFCOUNT", key, *keys)
23
24
  end
24
25
 
25
26
  # Merge N different HyperLogLogs into a single one. O(N) to merge N HyperLogLogs, but with high constant times.
26
- # @see https://redis.io/commands/pfmerge
27
- # @param destkey [Key]
28
- # @param sourcekey [Key]
27
+ # See <https://redis.io/commands/pfmerge> for more details.
28
+ # @parameter destkey [Key]
29
+ # @parameter sourcekey [Key]
29
30
  def pfmerge(destkey, sourcekey, *sourcekeys)
30
31
  call("PFMERGE", destkey, sourcekey, *sourcekeys)
31
32
  end