redis-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []