redis 4.7.1 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +88 -168
  4. data/lib/redis/client.rb +84 -623
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +53 -27
  12. data/lib/redis/commands/lists.rb +73 -23
  13. data/lib/redis/commands/pubsub.rb +7 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +43 -36
  16. data/lib/redis/commands/sorted_sets.rb +84 -12
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +4 -9
  21. data/lib/redis/distributed.rb +128 -67
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +90 -182
  28. metadata +10 -54
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
data/lib/redis.rb CHANGED
@@ -6,26 +6,11 @@ require "redis/commands"
6
6
 
7
7
  class Redis
8
8
  BASE_PATH = __dir__
9
- @exists_returns_integer = true
10
-
11
9
  Deprecated = Class.new(StandardError)
12
10
 
13
11
  class << self
14
- attr_reader :exists_returns_integer
15
12
  attr_accessor :silence_deprecations, :raise_deprecations
16
13
 
17
- def exists_returns_integer=(value)
18
- unless value
19
- deprecate!(
20
- "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
21
- "disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
22
- "`exists?` instead."
23
- )
24
- end
25
-
26
- @exists_returns_integer = value
27
- end
28
-
29
14
  def deprecate!(message)
30
15
  unless silence_deprecations
31
16
  if raise_deprecations
@@ -35,20 +20,22 @@ class Redis
35
20
  end
36
21
  end
37
22
  end
23
+ end
38
24
 
39
- def current
40
- deprecate!("`Redis.current` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
41
- @current ||= Redis.new
42
- end
43
-
44
- def current=(redis)
45
- deprecate!("`Redis.current=` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
46
- @current = redis
25
+ # soft-deprecated
26
+ # We added this back for older sidekiq releases
27
+ module Connection
28
+ class << self
29
+ def drivers
30
+ [RedisClient.default_driver]
31
+ end
47
32
  end
48
33
  end
49
34
 
50
35
  include Commands
51
36
 
37
+ SERVER_URL_OPTIONS = %i(url host port path).freeze
38
+
52
39
  # Create a new client instance
53
40
  #
54
41
  # @param [Hash] options
@@ -62,52 +49,45 @@ class Redis
62
49
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
63
50
  # @option options [String] :username Username to authenticate against server
64
51
  # @option options [String] :password Password to authenticate against server
65
- # @option options [Integer] :db (0) Database to select after initial connect
66
- # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
52
+ # @option options [Integer] :db (0) Database to select after connect and on reconnects
53
+ # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`
67
54
  # @option options [String] :id ID for the client connection, assigns name to current connection by sending
68
55
  # `CLIENT SETNAME`
69
- # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated
70
- # based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
71
- # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
56
+ # @option options [Integer, Array<Integer, Float>] :reconnect_attempts Number of attempts trying to connect,
57
+ # or a list of sleep duration between attempts.
72
58
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
59
+ # @option options [String] :name The name of the server group to connect to.
73
60
  # @option options [Array] :sentinels List of sentinels to contact
74
- # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
75
- # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
76
- # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
77
- # @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
78
- # client has to connect nodes via single endpoint with SSL/TLS
79
- # @option options [Class] :connector Class of custom connector
80
61
  #
81
62
  # @return [Redis] a new client instance
82
63
  def initialize(options = {})
83
- @options = options.dup
84
- @cluster_mode = options.key?(:cluster)
85
- client = @cluster_mode ? Cluster : Client
86
- @original_client = @client = client.new(options)
87
- @queue = Hash.new { |h, k| h[k] = [] }
88
64
  @monitor = Monitor.new
89
- end
90
-
91
- # Run code with the client reconnecting
92
- def with_reconnect(val = true, &blk)
93
- synchronize do |client|
94
- client.with_reconnect(val, &blk)
65
+ @options = options.dup
66
+ @options[:reconnect_attempts] = 1 unless @options.key?(:reconnect_attempts)
67
+ if ENV["REDIS_URL"] && SERVER_URL_OPTIONS.none? { |o| @options.key?(o) }
68
+ @options[:url] = ENV["REDIS_URL"]
95
69
  end
70
+ inherit_socket = @options.delete(:inherit_socket)
71
+ @subscription_client = nil
72
+
73
+ @client = initialize_client(@options)
74
+ @client.inherit_socket! if inherit_socket
96
75
  end
97
76
 
98
77
  # Run code without the client reconnecting
99
- def without_reconnect(&blk)
100
- with_reconnect(false, &blk)
78
+ def without_reconnect(&block)
79
+ @client.disable_reconnection(&block)
101
80
  end
102
81
 
103
82
  # Test whether or not the client is connected
104
83
  def connected?
105
- @original_client.connected?
84
+ @client.connected? || @subscription_client&.connected?
106
85
  end
107
86
 
108
87
  # Disconnect the client as quickly and silently as possible.
109
88
  def close
110
- @original_client.disconnect
89
+ @client.close
90
+ @subscription_client&.close
111
91
  end
112
92
  alias disconnect! close
113
93
 
@@ -115,127 +95,20 @@ class Redis
115
95
  yield self
116
96
  end
117
97
 
118
- # @deprecated Queues a command for pipelining.
119
- #
120
- # Commands in the queue are executed with the Redis#commit method.
121
- #
122
- # See http://redis.io/topics/pipelining for more details.
123
- #
124
- def queue(*command)
125
- ::Redis.deprecate!(
126
- "Redis#queue is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead." \
127
- "(called from: #{caller(1, 1).first})"
128
- )
129
-
130
- synchronize do
131
- @queue[Thread.current.object_id] << command
132
- end
133
- end
134
-
135
- # @deprecated Sends all commands in the queue.
136
- #
137
- # See http://redis.io/topics/pipelining for more details.
138
- #
139
- def commit
140
- ::Redis.deprecate!(
141
- "Redis#commit is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead. " \
142
- "(called from: #{Kernel.caller(1, 1).first})"
143
- )
144
-
145
- synchronize do |client|
146
- begin
147
- pipeline = Pipeline.new(client)
148
- @queue[Thread.current.object_id].each do |command|
149
- pipeline.call(command)
150
- end
151
-
152
- client.call_pipelined(pipeline)
153
- ensure
154
- @queue.delete(Thread.current.object_id)
155
- end
156
- end
157
- end
158
-
159
98
  def _client
160
99
  @client
161
100
  end
162
101
 
163
- def pipelined(&block)
164
- deprecation_displayed = false
165
- if block&.arity == 0
166
- Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 5))
167
- deprecation_displayed = true
168
- end
169
-
170
- synchronize do |prior_client|
171
- begin
172
- pipeline = Pipeline.new(prior_client)
173
- @client = deprecation_displayed ? pipeline : DeprecatedPipeline.new(pipeline)
174
- pipelined_connection = PipelinedConnection.new(pipeline)
175
- yield pipelined_connection
176
- prior_client.call_pipeline(pipeline)
177
- ensure
178
- @client = prior_client
179
- end
180
- end
181
- end
182
-
183
- # Mark the start of a transaction block.
184
- #
185
- # Passing a block is optional.
186
- #
187
- # @example With a block
188
- # redis.multi do |multi|
189
- # multi.set("key", "value")
190
- # multi.incr("counter")
191
- # end # => ["OK", 6]
192
- #
193
- # @example Without a block
194
- # redis.multi
195
- # # => "OK"
196
- # redis.set("key", "value")
197
- # # => "QUEUED"
198
- # redis.incr("counter")
199
- # # => "QUEUED"
200
- # redis.exec
201
- # # => ["OK", 6]
202
- #
203
- # @yield [multi] the commands that are called inside this block are cached
204
- # and written to the server upon returning from it
205
- # @yieldparam [Redis] multi `self`
206
- #
207
- # @return [String, Array<...>]
208
- # - when a block is not given, `OK`
209
- # - when a block is given, an array with replies
210
- #
211
- # @see #watch
212
- # @see #unwatch
213
- def multi(&block)
214
- if block_given?
215
- deprecation_displayed = false
216
- if block&.arity == 0
217
- Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
218
- deprecation_displayed = true
219
- end
220
-
221
- synchronize do |prior_client|
222
- begin
223
- pipeline = Pipeline::Multi.new(prior_client)
224
- @client = deprecation_displayed ? pipeline : DeprecatedMulti.new(pipeline)
225
- pipelined_connection = PipelinedConnection.new(pipeline)
226
- yield pipelined_connection
227
- prior_client.call_pipeline(pipeline)
228
- ensure
229
- @client = prior_client
230
- end
102
+ def pipelined
103
+ synchronize do |client|
104
+ client.pipelined do |raw_pipeline|
105
+ yield PipelinedConnection.new(raw_pipeline)
231
106
  end
232
- else
233
- send_command([:multi])
234
107
  end
235
108
  end
236
109
 
237
110
  def id
238
- @original_client.id
111
+ @client.id || @client.server_url
239
112
  end
240
113
 
241
114
  def inspect
@@ -247,54 +120,89 @@ class Redis
247
120
  end
248
121
 
249
122
  def connection
250
- return @original_client.connection_info if @cluster_mode
251
-
252
123
  {
253
- host: @original_client.host,
254
- port: @original_client.port,
255
- db: @original_client.db,
256
- id: @original_client.id,
257
- location: @original_client.location
124
+ host: @client.host,
125
+ port: @client.port,
126
+ db: @client.db,
127
+ id: id,
128
+ location: "#{@client.host}:#{@client.port}"
258
129
  }
259
130
  end
260
131
 
261
132
  private
262
133
 
134
+ def initialize_client(options)
135
+ if options.key?(:cluster)
136
+ raise "Redis Cluster support was moved to the `redis-clustering` gem."
137
+ end
138
+
139
+ if options.key?(:sentinels)
140
+ if url = options.delete(:url)
141
+ uri = URI.parse(url)
142
+ if !options.key?(:name) && uri.host
143
+ options[:name] = uri.host
144
+ end
145
+
146
+ if !options.key?(:password) && uri.password && !uri.password.empty?
147
+ options[:password] = uri.password
148
+ end
149
+
150
+ if !options.key?(:username) && uri.user && !uri.user.empty?
151
+ options[:username] = uri.user
152
+ end
153
+ end
154
+
155
+ Client.sentinel(**options).new_client
156
+ else
157
+ Client.config(**options).new_client
158
+ end
159
+ end
160
+
263
161
  def synchronize
264
162
  @monitor.synchronize { yield(@client) }
265
163
  end
266
164
 
267
165
  def send_command(command, &block)
268
166
  @monitor.synchronize do
269
- @client.call(command, &block)
167
+ @client.call_v(command, &block)
270
168
  end
169
+ rescue ::RedisClient::Error => error
170
+ Client.translate_error!(error)
271
171
  end
272
172
 
273
173
  def send_blocking_command(command, timeout, &block)
274
174
  @monitor.synchronize do
275
- @client.call_with_timeout(command, timeout, &block)
175
+ @client.blocking_call_v(timeout, command, &block)
276
176
  end
277
177
  end
278
178
 
279
179
  def _subscription(method, timeout, channels, block)
280
- return @client.call([method] + channels) if subscribed?
281
-
282
- begin
283
- original, @client = @client, SubscribedClient.new(@client)
284
- if timeout > 0
285
- @client.send(method, timeout, *channels, &block)
286
- else
287
- @client.send(method, *channels, &block)
180
+ if block
181
+ if @subscription_client
182
+ raise SubscriptionError, "This client is already subscribed"
288
183
  end
289
- ensure
290
- @client = original
184
+
185
+ begin
186
+ @subscription_client = SubscribedClient.new(@client.pubsub)
187
+ if timeout > 0
188
+ @subscription_client.send(method, timeout, *channels, &block)
189
+ else
190
+ @subscription_client.send(method, *channels, &block)
191
+ end
192
+ ensure
193
+ @subscription_client = nil
194
+ end
195
+ else
196
+ unless @subscription_client
197
+ raise SubscriptionError, "This client is not subscribed"
198
+ end
199
+
200
+ @subscription_client.call_v([method].concat(channels))
291
201
  end
292
202
  end
293
203
  end
294
204
 
295
205
  require "redis/version"
296
- require "redis/connection"
297
206
  require "redis/client"
298
- require "redis/cluster"
299
207
  require "redis/pipeline"
300
208
  require "redis/subscribe"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.1
4
+ version: 5.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,50 +16,22 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2022-07-01 00:00:00.000000000 Z
19
+ date: 2023-08-09 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: em-synchrony
22
+ name: redis-client
23
23
  requirement: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
28
- type: :development
27
+ version: 0.9.0
28
+ type: :runtime
29
29
  prerelease: false
30
30
  version_requirements: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
35
- - !ruby/object:Gem::Dependency
36
- name: hiredis
37
- requirement: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
- type: :development
43
- prerelease: false
44
- version_requirements: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '0'
49
- - !ruby/object:Gem::Dependency
50
- name: mocha
51
- requirement: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: '0'
56
- type: :development
57
- prerelease: false
58
- version_requirements: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
34
+ version: 0.9.0
63
35
  description: |2
64
36
  A Ruby client that tries to match Redis' API one-to-one, while still
65
37
  providing an idiomatic interface.
@@ -74,16 +46,6 @@ files:
74
46
  - README.md
75
47
  - lib/redis.rb
76
48
  - lib/redis/client.rb
77
- - lib/redis/cluster.rb
78
- - lib/redis/cluster/command.rb
79
- - lib/redis/cluster/command_loader.rb
80
- - lib/redis/cluster/key_slot_converter.rb
81
- - lib/redis/cluster/node.rb
82
- - lib/redis/cluster/node_key.rb
83
- - lib/redis/cluster/node_loader.rb
84
- - lib/redis/cluster/option.rb
85
- - lib/redis/cluster/slot.rb
86
- - lib/redis/cluster/slot_loader.rb
87
49
  - lib/redis/commands.rb
88
50
  - lib/redis/commands/bitmaps.rb
89
51
  - lib/redis/commands/cluster.rb
@@ -101,12 +63,6 @@ files:
101
63
  - lib/redis/commands/streams.rb
102
64
  - lib/redis/commands/strings.rb
103
65
  - lib/redis/commands/transactions.rb
104
- - lib/redis/connection.rb
105
- - lib/redis/connection/command_helper.rb
106
- - lib/redis/connection/hiredis.rb
107
- - lib/redis/connection/registry.rb
108
- - lib/redis/connection/ruby.rb
109
- - lib/redis/connection/synchrony.rb
110
66
  - lib/redis/distributed.rb
111
67
  - lib/redis/errors.rb
112
68
  - lib/redis/hash_ring.rb
@@ -119,9 +75,9 @@ licenses:
119
75
  metadata:
120
76
  bug_tracker_uri: https://github.com/redis/redis-rb/issues
121
77
  changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
122
- documentation_uri: https://www.rubydoc.info/gems/redis/4.7.1
78
+ documentation_uri: https://www.rubydoc.info/gems/redis/5.0.7
123
79
  homepage_uri: https://github.com/redis/redis-rb
124
- source_code_uri: https://github.com/redis/redis-rb/tree/v4.7.1
80
+ source_code_uri: https://github.com/redis/redis-rb/tree/v5.0.7
125
81
  post_install_message:
126
82
  rdoc_options: []
127
83
  require_paths:
@@ -130,14 +86,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
130
86
  requirements:
131
87
  - - ">="
132
88
  - !ruby/object:Gem::Version
133
- version: 2.4.0
89
+ version: 2.5.0
134
90
  required_rubygems_version: !ruby/object:Gem::Requirement
135
91
  requirements:
136
92
  - - ">="
137
93
  - !ruby/object:Gem::Version
138
94
  version: '0'
139
95
  requirements: []
140
- rubygems_version: 3.1.2
96
+ rubygems_version: 3.3.7
141
97
  signing_key:
142
98
  specification_version: 4
143
99
  summary: A Ruby client library for Redis
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../errors'
4
-
5
- class Redis
6
- class Cluster
7
- # Keep details about Redis commands for Redis Cluster Client.
8
- # @see https://redis.io/commands/command
9
- class Command
10
- def initialize(details)
11
- @details = pick_details(details)
12
- end
13
-
14
- def extract_first_key(command)
15
- i = determine_first_key_position(command)
16
- return '' if i == 0
17
-
18
- key = command[i].to_s
19
- hash_tag = extract_hash_tag(key)
20
- hash_tag.empty? ? key : hash_tag
21
- end
22
-
23
- def should_send_to_master?(command)
24
- dig_details(command, :write)
25
- end
26
-
27
- def should_send_to_slave?(command)
28
- dig_details(command, :readonly)
29
- end
30
-
31
- private
32
-
33
- def pick_details(details)
34
- details.transform_values do |detail|
35
- {
36
- first_key_position: detail[:first],
37
- write: detail[:flags].include?('write'),
38
- readonly: detail[:flags].include?('readonly')
39
- }
40
- end
41
- end
42
-
43
- def dig_details(command, key)
44
- name = command.first.to_s
45
- return unless @details.key?(name)
46
-
47
- @details.fetch(name).fetch(key)
48
- end
49
-
50
- def determine_first_key_position(command)
51
- case command.first.to_s.downcase
52
- when 'eval', 'evalsha', 'migrate', 'zinterstore', 'zunionstore' then 3
53
- when 'object' then 2
54
- when 'memory'
55
- command[1].to_s.casecmp('usage').zero? ? 2 : 0
56
- when 'xread', 'xreadgroup'
57
- determine_optional_key_position(command, 'streams')
58
- else
59
- dig_details(command, :first_key_position).to_i
60
- end
61
- end
62
-
63
- def determine_optional_key_position(command, option_name)
64
- idx = command.map(&:to_s).map(&:downcase).index(option_name)
65
- idx.nil? ? 0 : idx + 1
66
- end
67
-
68
- # @see https://redis.io/topics/cluster-spec#keys-hash-tags Keys hash tags
69
- def extract_hash_tag(key)
70
- s = key.index('{')
71
- e = key.index('}', s.to_i + 1)
72
-
73
- return '' if s.nil? || e.nil?
74
-
75
- key[s + 1..e - 1]
76
- end
77
- end
78
- end
79
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'redis/errors'
4
-
5
- class Redis
6
- class Cluster
7
- # Load details about Redis commands for Redis Cluster Client
8
- # @see https://redis.io/commands/command
9
- module CommandLoader
10
- module_function
11
-
12
- def load(nodes)
13
- errors = nodes.map do |node|
14
- begin
15
- return fetch_command_details(node)
16
- rescue CannotConnectError, ConnectionError, CommandError => error
17
- error
18
- end
19
- end
20
-
21
- raise InitialSetupError, errors
22
- end
23
-
24
- def fetch_command_details(node)
25
- node.call(%i[command]).map do |reply|
26
- [reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
27
- end.to_h
28
- end
29
-
30
- private_class_method :fetch_command_details
31
- end
32
- end
33
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- class Cluster
5
- # Key to slot converter for Redis Cluster Client
6
- #
7
- # We can test it by `CLUSTER KEYSLOT` command.
8
- #
9
- # @see https://github.com/antirez/redis-rb-cluster
10
- # Reference implementation in Ruby
11
- # @see https://redis.io/topics/cluster-spec#appendix
12
- # Reference implementation in ANSI C
13
- # @see https://redis.io/commands/cluster-keyslot
14
- # CLUSTER KEYSLOT command reference
15
- #
16
- # Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
17
- module KeySlotConverter
18
- XMODEM_CRC16_LOOKUP = [
19
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
20
- 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
21
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
22
- 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
23
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
24
- 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
25
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
26
- 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
27
- 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
28
- 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
29
- 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
30
- 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
31
- 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
32
- 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
33
- 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
34
- 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
35
- 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
36
- 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
37
- 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
38
- 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
39
- 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
40
- 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
41
- 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
42
- 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
43
- 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
44
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
45
- 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
46
- 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
47
- 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
48
- 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
49
- 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
50
- 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
51
- ].freeze
52
-
53
- HASH_SLOTS = 16_384
54
-
55
- module_function
56
-
57
- # Convert key into slot.
58
- #
59
- # @param key [String] the key of the redis command
60
- #
61
- # @return [Integer] slot number
62
- def convert(key)
63
- crc = 0
64
- key.each_byte do |b|
65
- crc = ((crc << 8) & 0xffff) ^ XMODEM_CRC16_LOOKUP[((crc >> 8) ^ b) & 0xff]
66
- end
67
-
68
- crc % HASH_SLOTS
69
- end
70
- end
71
- end
72
- end