redis-client 0.28.0 → 0.29.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fc78ecaeaed568c9917391585a7a185c90f8d7d569b8443c0fb2f0e918c2ca6
4
- data.tar.gz: f9c7cbcb9c39267853681e926a71c27c3619bf0683fa5cd86885c585f2dfa239
3
+ metadata.gz: dc26284bc8ddd661e51d52ecf7afcf6d853b566128baa4176908eed4ed7e6ce2
4
+ data.tar.gz: 4051332c5b82220c88cb43efc0257e6dc61cba6b2c61ace4429d197591ecbd53
5
5
  SHA512:
6
- metadata.gz: 3738437f5876f4f8c09f82a19e4e75462779dae006ed37ac06d3d0ca997bb0190fc676b705e5f6186a5854caff4d98dddc8b035c0fbaa854f9d77b179cb01c94
7
- data.tar.gz: 4f9788a5547647cee34ddf01f60400e9645ca2c73f5329155b58670019733ec945331674f94b5a7720754ea2dd12b85d62040060166145beaea9b462514e6500
6
+ metadata.gz: 7f200a254286ae8c63dd708f4cc1d945b33a8ec96e5466de8b6fe966ddb2ebdca727cb532d817a05021eabdba9809eb2450a0ad31dfc24e39ae2e4473ca9a36a
7
+ data.tar.gz: 192f624ea10bf819f8d4f98d980861b945cbe5fad8a079dbb0ed247d031b70646eb16cf9a34eb9f0b7b80abb7eee191a3b006bc46a7b0ac8d7b9a9924ee00d1a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.29.0
4
+
5
+ - Fix connecting to Redis 7.1 and older with `driver_info:`set.
6
+ - Added `RedisClient::Error#next_error` for an easier way to check all errors produced by a pipeline.
7
+
3
8
  # 0.28.0
4
9
 
5
10
  - Added `RedisClient::HashRing` for horizontal sharing (compatible with `Redis::Distributed` from `redis-rb`).
data/README.md CHANGED
@@ -145,6 +145,29 @@ If you specify a username and/or password at the top level for your main Redis i
145
145
 
146
146
  ```ruby
147
147
 
148
+ # Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
149
+ SENTINELS = [{ host: '127.0.0.1', port: 26380 },
150
+ { host: '127.0.0.1', port: 26381 }]
151
+
152
+ redis_config = RedisClient.sentinel(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret')
153
+ ```
154
+
155
+ So you have to provide Sentinel credential and Redis explicitly even they are the same
156
+
157
+ ```ruby
158
+ # Use 'mysecret' to authenticate against the mymaster instance and sentinel
159
+ SENTINELS = [{ host: '127.0.0.1', port: 26380 },
160
+ { host: '127.0.0.1', port: 26381 }]
161
+
162
+ redis_config = RedisClient.sentinel(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret', sentinel_password: 'mysecret')
163
+ ```
164
+
165
+ Also the `name`, `password`, `username` and `db` for Redis instance can be passed as an url:
166
+
167
+ ```ruby
168
+ redis_config = RedisClient.sentinel(url: "redis://appuser:mysecret@mymaster/10", sentinels: SENTINELS, role: :master)
169
+ ```
170
+
148
171
  ### Consistent Hashing
149
172
 
150
173
  To horizontally shard keys across multiple servers without relying on clustering, a `RedisClient::HashRing` class is provided:
@@ -169,29 +192,6 @@ end
169
192
 
170
193
  Note that regular clients do respond to `node_for`, `nodes_for` and `nodes`, so that code that support `RedisClient.ring` is also usable with a single server.
171
194
 
172
- # Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
173
- SENTINELS = [{ host: '127.0.0.1', port: 26380 },
174
- { host: '127.0.0.1', port: 26381 }]
175
-
176
- redis_config = RedisClient.sentinel(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret')
177
- ```
178
-
179
- So you have to provide Sentinel credential and Redis explicitly even they are the same
180
-
181
- ```ruby
182
- # Use 'mysecret' to authenticate against the mymaster instance and sentinel
183
- SENTINELS = [{ host: '127.0.0.1', port: 26380 },
184
- { host: '127.0.0.1', port: 26381 }]
185
-
186
- redis_config = RedisClient.sentinel(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret', sentinel_password: 'mysecret')
187
- ```
188
-
189
- Also the `name`, `password`, `username` and `db` for Redis instance can be passed as an url:
190
-
191
- ```ruby
192
- redis_config = RedisClient.sentinel(url: "redis://appuser:mysecret@mymaster/10", sentinels: SENTINELS, role: :master)
193
- ```
194
-
195
195
  ### Type support
196
196
 
197
197
  Only a select few Ruby types are supported as arguments beside strings.
@@ -361,7 +361,11 @@ end
361
361
 
362
362
  #### Exception management
363
363
 
364
- The `exception` flag in the `#pipelined` method of `RedisClient` is a feature that modifies the pipeline execution
364
+ By default, when a pipeline produce multiple command errors, only the first encountered error is raised.
365
+
366
+ However, the raised exception have a `.next_error` method you can use like a linked list to check all encountered errors.
367
+
368
+ Alternatively, the `exception` flag in the `#pipelined` method of `RedisClient` is a feature that modifies the pipeline execution
365
369
  behavior. When set to `false`, it doesn't raise an exception when a command error occurs. Instead, it allows the
366
370
  pipeline to execute all commands, and any failed command will be available in the returned array. (Defaults to `true`)
367
371
 
@@ -47,7 +47,7 @@ class RedisClient
47
47
  end
48
48
 
49
49
  def call_pipelined(commands, timeouts, exception: true)
50
- first_exception = nil
50
+ first_error = last_error = nil
51
51
 
52
52
  size = commands.size
53
53
  results = Array.new(commands.size)
@@ -69,14 +69,16 @@ class RedisClient
69
69
  result._set_command(commands[index])
70
70
  result._set_config(config)
71
71
  result._set_retry_attempt(@retry_attempt)
72
- first_exception ||= result
72
+
73
+ last_error&._set_next_error(result)
74
+ first_error ||= last_error = result
73
75
  end
74
76
 
75
77
  results[index] = result
76
78
  end
77
79
 
78
- if first_exception && exception
79
- raise first_exception
80
+ if first_error && exception
81
+ raise first_error
80
82
  else
81
83
  results
82
84
  end
@@ -116,6 +116,10 @@ class RedisClient
116
116
  end
117
117
 
118
118
  def check_role!(role)
119
+ unless role.is_a?(String)
120
+ raise TypeError, "Expected role to be a string, got: #{role.inspect}"
121
+ end
122
+
119
123
  if @role == :master
120
124
  unless role == "master"
121
125
  sleep SENTINEL_DELAY
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.28.0"
4
+ VERSION = "0.29.0"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -148,11 +148,17 @@ class RedisClient
148
148
  include HasConfig
149
149
  include Retriable
150
150
 
151
+ attr_reader :next_error
152
+
151
153
  def self.with_config(message, config = nil)
152
154
  error = new(message)
153
155
  error._set_config(config)
154
156
  error
155
157
  end
158
+
159
+ def _set_next_error(error) # :nodoc:
160
+ @next_error = error
161
+ end
156
162
  end
157
163
 
158
164
  ProtocolError = Class.new(Error)
@@ -863,19 +869,26 @@ class RedisClient
863
869
  prelude << ["CLIENT", "SETNAME", id]
864
870
  end
865
871
 
866
- # The connection prelude is deliberately not sent to Middlewares
867
872
  if config.sentinel?
868
873
  prelude << ["ROLE"]
869
- role, = @middlewares.call_pipelined(prelude, config) do
870
- @raw_connection.call_pipelined(prelude, nil).last
874
+ end
875
+
876
+ unless prelude.empty?
877
+ results = @middlewares.call_pipelined(prelude, config) do
878
+ @raw_connection.call_pipelined(prelude, nil, exception: false)
871
879
  end
872
- config.check_role!(role)
873
- else
874
- unless prelude.empty?
875
- @middlewares.call_pipelined(prelude, config) do
876
- @raw_connection.call_pipelined(prelude, nil)
880
+
881
+ results.each do |result|
882
+ # CLIENT SETINFO is unsupported on Redis < 7.2. The pipeline drained all responses before raising,
883
+ # so if that's the only error, the socket is healthy: keep it.
884
+ if result.is_a?(CommandError) && !(result.command[0] == "CLIENT" && result.command[1] == "SETINFO")
885
+ raise result
877
886
  end
878
887
  end
888
+
889
+ if config.sentinel?
890
+ config.check_role!(results.last.first)
891
+ end
879
892
  end
880
893
  rescue FailoverError, CannotConnectError => error
881
894
  @raw_connection&.close
@@ -888,12 +901,10 @@ class RedisClient
888
901
  raise connect_error
889
902
  rescue CommandError => error
890
903
  @raw_connection&.close
891
- if error.message.match?(/ERR unknown command ['`]HELLO['`]/)
904
+
905
+ if error.command&.first == "HELLO" && error.message.match?(/ERR unknown command/)
892
906
  raise UnsupportedServer,
893
- "redis-client requires Redis 6+ with HELLO command available (#{config.server_url})"
894
- # Ignore CLIENT SETINFO errors (Redis < 7.2 doesn't support it)
895
- elsif error.message.match?(/unknown subcommand.*setinfo/i)
896
- # Silently ignore - CLIENT SETINFO is not supported on Redis < 7.2
907
+ "redis-client requires Redis 6+ with HELLO command available (#{config.server_url})", cause: error
897
908
  else
898
909
  raise
899
910
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
@@ -71,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0'
73
73
  requirements: []
74
- rubygems_version: 4.0.3
74
+ rubygems_version: 4.0.6
75
75
  specification_version: 4
76
76
  summary: Simple low-level client for Redis 6+
77
77
  test_files: []