redis-client 0.1.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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +190 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +67 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +347 -0
  8. data/Rakefile +86 -0
  9. data/ext/redis_client/hiredis/extconf.rb +54 -0
  10. data/ext/redis_client/hiredis/hiredis_connection.c +696 -0
  11. data/ext/redis_client/hiredis/vendor/.gitignore +9 -0
  12. data/ext/redis_client/hiredis/vendor/.travis.yml +131 -0
  13. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +364 -0
  14. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +165 -0
  15. data/ext/redis_client/hiredis/vendor/COPYING +29 -0
  16. data/ext/redis_client/hiredis/vendor/Makefile +308 -0
  17. data/ext/redis_client/hiredis/vendor/README.md +664 -0
  18. data/ext/redis_client/hiredis/vendor/adapters/ae.h +130 -0
  19. data/ext/redis_client/hiredis/vendor/adapters/glib.h +156 -0
  20. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +84 -0
  21. data/ext/redis_client/hiredis/vendor/adapters/libev.h +179 -0
  22. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +175 -0
  23. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +117 -0
  24. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +115 -0
  25. data/ext/redis_client/hiredis/vendor/adapters/qt.h +135 -0
  26. data/ext/redis_client/hiredis/vendor/alloc.c +86 -0
  27. data/ext/redis_client/hiredis/vendor/alloc.h +91 -0
  28. data/ext/redis_client/hiredis/vendor/appveyor.yml +24 -0
  29. data/ext/redis_client/hiredis/vendor/async.c +887 -0
  30. data/ext/redis_client/hiredis/vendor/async.h +147 -0
  31. data/ext/redis_client/hiredis/vendor/async_private.h +75 -0
  32. data/ext/redis_client/hiredis/vendor/dict.c +352 -0
  33. data/ext/redis_client/hiredis/vendor/dict.h +126 -0
  34. data/ext/redis_client/hiredis/vendor/fmacros.h +12 -0
  35. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +13 -0
  36. data/ext/redis_client/hiredis/vendor/hiredis.c +1174 -0
  37. data/ext/redis_client/hiredis/vendor/hiredis.h +336 -0
  38. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +12 -0
  39. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +13 -0
  40. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +157 -0
  41. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +12 -0
  42. data/ext/redis_client/hiredis/vendor/net.c +612 -0
  43. data/ext/redis_client/hiredis/vendor/net.h +56 -0
  44. data/ext/redis_client/hiredis/vendor/read.c +739 -0
  45. data/ext/redis_client/hiredis/vendor/read.h +129 -0
  46. data/ext/redis_client/hiredis/vendor/sds.c +1289 -0
  47. data/ext/redis_client/hiredis/vendor/sds.h +278 -0
  48. data/ext/redis_client/hiredis/vendor/sdsalloc.h +44 -0
  49. data/ext/redis_client/hiredis/vendor/sockcompat.c +248 -0
  50. data/ext/redis_client/hiredis/vendor/sockcompat.h +92 -0
  51. data/ext/redis_client/hiredis/vendor/ssl.c +544 -0
  52. data/ext/redis_client/hiredis/vendor/test.c +1401 -0
  53. data/ext/redis_client/hiredis/vendor/test.sh +78 -0
  54. data/ext/redis_client/hiredis/vendor/win32.h +56 -0
  55. data/lib/redis-client.rb +3 -0
  56. data/lib/redis_client/buffered_io.rb +149 -0
  57. data/lib/redis_client/config.rb +174 -0
  58. data/lib/redis_client/connection.rb +86 -0
  59. data/lib/redis_client/hiredis_connection.rb +78 -0
  60. data/lib/redis_client/pooled.rb +86 -0
  61. data/lib/redis_client/resp3.rb +225 -0
  62. data/lib/redis_client/sentinel_config.rb +134 -0
  63. data/lib/redis_client/version.rb +5 -0
  64. data/lib/redis_client.rb +438 -0
  65. data/redis-client.gemspec +34 -0
  66. metadata +125 -0
@@ -0,0 +1,438 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis_client/version"
4
+ require "redis_client/config"
5
+ require "redis_client/sentinel_config"
6
+ require "redis_client/connection"
7
+
8
+ class RedisClient
9
+ Error = Class.new(StandardError)
10
+
11
+ ConnectionError = Class.new(Error)
12
+
13
+ FailoverError = Class.new(ConnectionError)
14
+
15
+ TimeoutError = Class.new(ConnectionError)
16
+ ReadTimeoutError = Class.new(TimeoutError)
17
+ WriteTimeoutError = Class.new(TimeoutError)
18
+ ConnectTimeoutError = Class.new(TimeoutError)
19
+ CheckoutTimeoutError = Class.new(ConnectTimeoutError)
20
+
21
+ class CommandError < Error
22
+ class << self
23
+ def parse(error_message)
24
+ code = error_message.split(' ', 2).first
25
+ klass = ERRORS.fetch(code, self)
26
+ klass.new(error_message)
27
+ end
28
+ end
29
+ end
30
+
31
+ AuthenticationError = Class.new(CommandError)
32
+ PermissionError = Class.new(CommandError)
33
+
34
+ CommandError::ERRORS = {
35
+ "WRONGPASS" => AuthenticationError,
36
+ "NOPERM" => PermissionError,
37
+ }.freeze
38
+
39
+ class << self
40
+ def config(**kwargs)
41
+ Config.new(**kwargs)
42
+ end
43
+
44
+ def sentinel(**kwargs)
45
+ SentinelConfig.new(**kwargs)
46
+ end
47
+
48
+ def new(arg = nil, **kwargs)
49
+ if arg.is_a?(Config::Common)
50
+ super
51
+ else
52
+ super(config(**(arg || {}), **kwargs))
53
+ end
54
+ end
55
+ end
56
+
57
+ attr_reader :config, :id
58
+ attr_accessor :connect_timeout, :read_timeout, :write_timeout
59
+
60
+ def initialize(
61
+ config,
62
+ id: config.id,
63
+ connect_timeout: config.connect_timeout,
64
+ read_timeout: config.read_timeout,
65
+ write_timeout: config.write_timeout
66
+ )
67
+ @config = config
68
+ @id = id
69
+ @connect_timeout = connect_timeout
70
+ @read_timeout = read_timeout
71
+ @write_timeout = write_timeout
72
+ @raw_connection = nil
73
+ @disable_reconnection = false
74
+ end
75
+
76
+ def size
77
+ 1
78
+ end
79
+
80
+ def with(_options = nil)
81
+ yield self
82
+ end
83
+ alias_method :then, :with
84
+
85
+ def timeout=(timeout)
86
+ @connect_timeout = @read_timeout = @write_timeout = timeout
87
+ end
88
+
89
+ def pubsub
90
+ sub = PubSub.new(ensure_connected)
91
+ @raw_connection = nil
92
+ sub
93
+ end
94
+
95
+ def call(*command)
96
+ command = RESP3.coerce_command!(command)
97
+ result = ensure_connected do |connection|
98
+ connection.write(command)
99
+ connection.read
100
+ end
101
+
102
+ if result.is_a?(CommandError)
103
+ raise result
104
+ else
105
+ result
106
+ end
107
+ end
108
+
109
+ def call_once(*command)
110
+ command = RESP3.coerce_command!(command)
111
+ result = ensure_connected(retryable: false) do |connection|
112
+ connection.write(command)
113
+ connection.read
114
+ end
115
+
116
+ if result.is_a?(CommandError)
117
+ raise result
118
+ else
119
+ result
120
+ end
121
+ end
122
+
123
+ def blocking_call(timeout, *command)
124
+ command = RESP3.coerce_command!(command)
125
+ result = ensure_connected do |connection|
126
+ connection.write(command)
127
+ connection.read(timeout)
128
+ end
129
+
130
+ if result.is_a?(CommandError)
131
+ raise result
132
+ else
133
+ result
134
+ end
135
+ end
136
+
137
+ def scan(*args, &block)
138
+ unless block_given?
139
+ return to_enum(__callee__, *args)
140
+ end
141
+
142
+ scan_list(1, ["SCAN", 0, *args], &block)
143
+ end
144
+
145
+ def sscan(key, *args, &block)
146
+ unless block_given?
147
+ return to_enum(__callee__, key, *args)
148
+ end
149
+
150
+ scan_list(2, ["SSCAN", key, 0, *args], &block)
151
+ end
152
+
153
+ def hscan(key, *args, &block)
154
+ unless block_given?
155
+ return to_enum(__callee__, key, *args)
156
+ end
157
+
158
+ scan_pairs(2, ["HSCAN", key, 0, *args], &block)
159
+ end
160
+
161
+ def zscan(key, *args, &block)
162
+ unless block_given?
163
+ return to_enum(__callee__, key, *args)
164
+ end
165
+
166
+ scan_pairs(2, ["ZSCAN", key, 0, *args], &block)
167
+ end
168
+
169
+ def connected?
170
+ @raw_connection&.connected?
171
+ end
172
+
173
+ def close
174
+ @raw_connection&.close
175
+ @raw_connection = nil
176
+ self
177
+ end
178
+
179
+ def pipelined
180
+ pipeline = Pipeline.new
181
+ yield pipeline
182
+
183
+ if pipeline._size == 0
184
+ []
185
+ else
186
+ ensure_connected(retryable: pipeline._retryable?) do |connection|
187
+ call_pipelined(connection, pipeline._commands, pipeline._timeouts)
188
+ end
189
+ end
190
+ end
191
+
192
+ def multi(watch: nil, &block)
193
+ if watch
194
+ # WATCH is stateful, so we can't reconnect if it's used, the whole transaction
195
+ # has to be redone.
196
+ ensure_connected(retryable: false) do |connection|
197
+ call("WATCH", *watch)
198
+ begin
199
+ if transaction = build_transaction(&block)
200
+ call_pipelined(connection, transaction._commands).last
201
+ else
202
+ call("UNWATCH")
203
+ []
204
+ end
205
+ rescue
206
+ call("UNWATCH") if connected? && watch
207
+ raise
208
+ end
209
+ end
210
+ else
211
+ transaction = build_transaction(&block)
212
+ if transaction._empty?
213
+ []
214
+ else
215
+ ensure_connected(retryable: transaction._retryable?) do |connection|
216
+ call_pipelined(connection, transaction._commands).last
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ class PubSub
223
+ def initialize(raw_connection)
224
+ @raw_connection = raw_connection
225
+ end
226
+
227
+ def call(*command)
228
+ raw_connection.write(RESP3.coerce_command!(command))
229
+ nil
230
+ end
231
+
232
+ def close
233
+ raw_connection&.close
234
+ @raw_connection = nil
235
+ self
236
+ end
237
+
238
+ def next_event(timeout = nil)
239
+ unless raw_connection
240
+ raise ConnectionError, "Connection was closed or lost"
241
+ end
242
+
243
+ raw_connection.read(timeout)
244
+ rescue ReadTimeoutError
245
+ nil
246
+ end
247
+
248
+ private
249
+
250
+ attr_reader :raw_connection
251
+ end
252
+
253
+ class Multi
254
+ def initialize
255
+ @size = 0
256
+ @commands = []
257
+ @retryable = true
258
+ end
259
+
260
+ def call(*command)
261
+ @commands << RESP3.coerce_command!(command)
262
+ nil
263
+ end
264
+
265
+ def call_once(*command)
266
+ @retryable = false
267
+ @commands << RESP3.coerce_command!(command)
268
+ nil
269
+ end
270
+
271
+ def _commands
272
+ @commands
273
+ end
274
+
275
+ def _size
276
+ @commands.size
277
+ end
278
+
279
+ def _empty?
280
+ @commands.size <= 2
281
+ end
282
+
283
+ def _timeouts
284
+ nil
285
+ end
286
+
287
+ def _retryable?
288
+ @retryable
289
+ end
290
+ end
291
+
292
+ class Pipeline < Multi
293
+ def initialize
294
+ super
295
+ @timeouts = nil
296
+ end
297
+
298
+ def blocking_call(timeout, *command)
299
+ @timeouts ||= []
300
+ @timeouts[@commands.size] = timeout
301
+ @commands << RESP3.coerce_command!(command)
302
+ nil
303
+ end
304
+
305
+ def _timeouts
306
+ @timeouts
307
+ end
308
+
309
+ def _empty?
310
+ @commands.empty?
311
+ end
312
+ end
313
+
314
+ private
315
+
316
+ def build_transaction
317
+ transaction = Multi.new
318
+ transaction.call("MULTI")
319
+ yield transaction
320
+ transaction.call("EXEC")
321
+ transaction
322
+ end
323
+
324
+ def scan_list(cursor_index, command, &block)
325
+ cursor = 0
326
+ while cursor != "0"
327
+ command[cursor_index] = cursor
328
+ cursor, elements = call(*command)
329
+ elements.each(&block)
330
+ end
331
+ nil
332
+ end
333
+
334
+ def scan_pairs(cursor_index, command)
335
+ cursor = 0
336
+ while cursor != "0"
337
+ command[cursor_index] = cursor
338
+ cursor, elements = call(*command)
339
+
340
+ index = 0
341
+ size = elements.size
342
+ while index < size
343
+ yield elements[index], elements[index + 1]
344
+ index += 2
345
+ end
346
+ end
347
+ nil
348
+ end
349
+
350
+ def call_pipelined(connection, commands, timeouts = nil)
351
+ exception = nil
352
+
353
+ size = commands.size
354
+ results = Array.new(commands.size)
355
+ connection.write_multi(commands)
356
+
357
+ size.times do |index|
358
+ timeout = timeouts && timeouts[index]
359
+ result = connection.read(timeout)
360
+ if result.is_a?(CommandError)
361
+ exception ||= result
362
+ end
363
+ results[index] = result
364
+ end
365
+
366
+ if exception
367
+ raise exception
368
+ else
369
+ results
370
+ end
371
+ end
372
+
373
+ def ensure_connected(retryable: true)
374
+ if @disable_reconnection
375
+ yield @raw_connection
376
+ elsif retryable
377
+ tries = 0
378
+ connection = nil
379
+ begin
380
+ connection = raw_connection
381
+ if block_given?
382
+ yield connection
383
+ else
384
+ connection
385
+ end
386
+ rescue ConnectionError => error
387
+ connection&.close
388
+ close
389
+
390
+ if !@disable_reconnection && config.retry_connecting?(tries, error)
391
+ tries += 1
392
+ retry
393
+ else
394
+ raise
395
+ end
396
+ end
397
+ else
398
+ previous_disable_reconnection = @disable_reconnection
399
+ connection = ensure_connected
400
+ begin
401
+ @disable_reconnection = true
402
+ yield connection
403
+ ensure
404
+ @disable_reconnection = previous_disable_reconnection
405
+ end
406
+ end
407
+ end
408
+
409
+ def raw_connection
410
+ @raw_connection ||= begin
411
+ connection = config.driver.new(
412
+ config,
413
+ connect_timeout: connect_timeout,
414
+ read_timeout: read_timeout,
415
+ write_timeout: write_timeout,
416
+ )
417
+
418
+ prelude = config.connection_prelude.dup
419
+
420
+ if id
421
+ prelude << ["CLIENT", "SETNAME", id.to_s]
422
+ end
423
+
424
+ if config.sentinel?
425
+ prelude << ["ROLE"]
426
+ role, = call_pipelined(connection, prelude).last
427
+ config.check_role!(role)
428
+ else
429
+ call_pipelined(connection, prelude)
430
+ end
431
+
432
+ connection
433
+ end
434
+ end
435
+ end
436
+
437
+ require "redis_client/resp3"
438
+ require "redis_client/pooled"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/redis_client/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "redis-client"
7
+ spec.version = RedisClient::VERSION
8
+ spec.authors = ["Jean Boussier"]
9
+ spec.email = ["jean.boussier@gmail.com"]
10
+
11
+ spec.summary = "Simple low-level client for Redis 6+"
12
+ spec.homepage = "https://github.com/redis-rb/redis-client"
13
+ spec.required_ruby_version = ">= 2.5.0"
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = File.join(spec.homepage, "blob/master/CHANGELOG.md")
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|benchmark)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+ spec.extensions = ["ext/redis_client/hiredis/extconf.rb"]
32
+
33
+ spec.add_runtime_dependency "connection_pool"
34
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jean Boussier
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: connection_pool
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email:
29
+ - jean.boussier@gmail.com
30
+ executables: []
31
+ extensions:
32
+ - ext/redis_client/hiredis/extconf.rb
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rubocop.yml"
36
+ - CHANGELOG.md
37
+ - Gemfile
38
+ - Gemfile.lock
39
+ - LICENSE.md
40
+ - README.md
41
+ - Rakefile
42
+ - ext/redis_client/hiredis/extconf.rb
43
+ - ext/redis_client/hiredis/hiredis_connection.c
44
+ - ext/redis_client/hiredis/vendor/.gitignore
45
+ - ext/redis_client/hiredis/vendor/.travis.yml
46
+ - ext/redis_client/hiredis/vendor/CHANGELOG.md
47
+ - ext/redis_client/hiredis/vendor/CMakeLists.txt
48
+ - ext/redis_client/hiredis/vendor/COPYING
49
+ - ext/redis_client/hiredis/vendor/Makefile
50
+ - ext/redis_client/hiredis/vendor/README.md
51
+ - ext/redis_client/hiredis/vendor/adapters/ae.h
52
+ - ext/redis_client/hiredis/vendor/adapters/glib.h
53
+ - ext/redis_client/hiredis/vendor/adapters/ivykis.h
54
+ - ext/redis_client/hiredis/vendor/adapters/libev.h
55
+ - ext/redis_client/hiredis/vendor/adapters/libevent.h
56
+ - ext/redis_client/hiredis/vendor/adapters/libuv.h
57
+ - ext/redis_client/hiredis/vendor/adapters/macosx.h
58
+ - ext/redis_client/hiredis/vendor/adapters/qt.h
59
+ - ext/redis_client/hiredis/vendor/alloc.c
60
+ - ext/redis_client/hiredis/vendor/alloc.h
61
+ - ext/redis_client/hiredis/vendor/appveyor.yml
62
+ - ext/redis_client/hiredis/vendor/async.c
63
+ - ext/redis_client/hiredis/vendor/async.h
64
+ - ext/redis_client/hiredis/vendor/async_private.h
65
+ - ext/redis_client/hiredis/vendor/dict.c
66
+ - ext/redis_client/hiredis/vendor/dict.h
67
+ - ext/redis_client/hiredis/vendor/fmacros.h
68
+ - ext/redis_client/hiredis/vendor/hiredis-config.cmake.in
69
+ - ext/redis_client/hiredis/vendor/hiredis.c
70
+ - ext/redis_client/hiredis/vendor/hiredis.h
71
+ - ext/redis_client/hiredis/vendor/hiredis.pc.in
72
+ - ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in
73
+ - ext/redis_client/hiredis/vendor/hiredis_ssl.h
74
+ - ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in
75
+ - ext/redis_client/hiredis/vendor/net.c
76
+ - ext/redis_client/hiredis/vendor/net.h
77
+ - ext/redis_client/hiredis/vendor/read.c
78
+ - ext/redis_client/hiredis/vendor/read.h
79
+ - ext/redis_client/hiredis/vendor/sds.c
80
+ - ext/redis_client/hiredis/vendor/sds.h
81
+ - ext/redis_client/hiredis/vendor/sdsalloc.h
82
+ - ext/redis_client/hiredis/vendor/sockcompat.c
83
+ - ext/redis_client/hiredis/vendor/sockcompat.h
84
+ - ext/redis_client/hiredis/vendor/ssl.c
85
+ - ext/redis_client/hiredis/vendor/test.c
86
+ - ext/redis_client/hiredis/vendor/test.sh
87
+ - ext/redis_client/hiredis/vendor/win32.h
88
+ - lib/redis-client.rb
89
+ - lib/redis_client.rb
90
+ - lib/redis_client/buffered_io.rb
91
+ - lib/redis_client/config.rb
92
+ - lib/redis_client/connection.rb
93
+ - lib/redis_client/hiredis_connection.rb
94
+ - lib/redis_client/pooled.rb
95
+ - lib/redis_client/resp3.rb
96
+ - lib/redis_client/sentinel_config.rb
97
+ - lib/redis_client/version.rb
98
+ - redis-client.gemspec
99
+ homepage: https://github.com/redis-rb/redis-client
100
+ licenses: []
101
+ metadata:
102
+ allowed_push_host: https://rubygems.org
103
+ homepage_uri: https://github.com/redis-rb/redis-client
104
+ source_code_uri: https://github.com/redis-rb/redis-client
105
+ changelog_uri: https://github.com/redis-rb/redis-client/blob/master/CHANGELOG.md
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 2.5.0
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubygems_version: 3.3.7
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Simple low-level client for Redis 6+
125
+ test_files: []