redis 4.0.0 → 5.0.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 (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +252 -1
  3. data/README.md +126 -88
  4. data/lib/redis/client.rb +79 -543
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +285 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +818 -0
  18. data/lib/redis/commands/streams.rb +384 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +235 -0
  22. data/lib/redis/distributed.rb +301 -109
  23. data/lib/redis/errors.rb +19 -1
  24. data/lib/redis/hash_ring.rb +34 -33
  25. data/lib/redis/pipeline.rb +69 -77
  26. data/lib/redis/subscribe.rb +26 -19
  27. data/lib/redis/version.rb +3 -1
  28. data/lib/redis.rb +109 -2728
  29. metadata +37 -229
  30. data/.gitignore +0 -16
  31. data/.travis/Gemfile +0 -13
  32. data/.travis.yml +0 -73
  33. data/.yardopts +0 -3
  34. data/Gemfile +0 -3
  35. data/benchmarking/logging.rb +0 -71
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/bors.toml +0 -14
  41. data/examples/basic.rb +0 -15
  42. data/examples/consistency.rb +0 -114
  43. data/examples/dist_redis.rb +0 -43
  44. data/examples/incr-decr.rb +0 -17
  45. data/examples/list.rb +0 -26
  46. data/examples/pubsub.rb +0 -37
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sentinel.rb +0 -41
  50. data/examples/sets.rb +0 -36
  51. data/examples/unicorn/config.ru +0 -3
  52. data/examples/unicorn/unicorn.rb +0 -20
  53. data/lib/redis/connection/command_helper.rb +0 -38
  54. data/lib/redis/connection/hiredis.rb +0 -66
  55. data/lib/redis/connection/registry.rb +0 -12
  56. data/lib/redis/connection/ruby.rb +0 -409
  57. data/lib/redis/connection/synchrony.rb +0 -141
  58. data/lib/redis/connection.rb +0 -9
  59. data/makefile +0 -42
  60. data/redis.gemspec +0 -42
  61. data/test/bitpos_test.rb +0 -63
  62. data/test/blocking_commands_test.rb +0 -40
  63. data/test/client_test.rb +0 -59
  64. data/test/command_map_test.rb +0 -28
  65. data/test/commands_on_hashes_test.rb +0 -19
  66. data/test/commands_on_hyper_log_log_test.rb +0 -19
  67. data/test/commands_on_lists_test.rb +0 -18
  68. data/test/commands_on_sets_test.rb +0 -75
  69. data/test/commands_on_sorted_sets_test.rb +0 -150
  70. data/test/commands_on_strings_test.rb +0 -99
  71. data/test/commands_on_value_types_test.rb +0 -131
  72. data/test/connection_handling_test.rb +0 -275
  73. data/test/db/.gitkeep +0 -0
  74. data/test/distributed_blocking_commands_test.rb +0 -44
  75. data/test/distributed_commands_on_hashes_test.rb +0 -8
  76. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -31
  77. data/test/distributed_commands_on_lists_test.rb +0 -20
  78. data/test/distributed_commands_on_sets_test.rb +0 -81
  79. data/test/distributed_commands_on_sorted_sets_test.rb +0 -16
  80. data/test/distributed_commands_on_strings_test.rb +0 -57
  81. data/test/distributed_commands_on_value_types_test.rb +0 -93
  82. data/test/distributed_commands_requiring_clustering_test.rb +0 -162
  83. data/test/distributed_connection_handling_test.rb +0 -21
  84. data/test/distributed_internals_test.rb +0 -68
  85. data/test/distributed_key_tags_test.rb +0 -50
  86. data/test/distributed_persistence_control_commands_test.rb +0 -24
  87. data/test/distributed_publish_subscribe_test.rb +0 -90
  88. data/test/distributed_remote_server_control_commands_test.rb +0 -64
  89. data/test/distributed_scripting_test.rb +0 -100
  90. data/test/distributed_sorting_test.rb +0 -18
  91. data/test/distributed_test.rb +0 -56
  92. data/test/distributed_transactions_test.rb +0 -30
  93. data/test/encoding_test.rb +0 -14
  94. data/test/error_replies_test.rb +0 -57
  95. data/test/fork_safety_test.rb +0 -60
  96. data/test/helper.rb +0 -201
  97. data/test/helper_test.rb +0 -22
  98. data/test/internals_test.rb +0 -429
  99. data/test/lint/blocking_commands.rb +0 -150
  100. data/test/lint/hashes.rb +0 -162
  101. data/test/lint/hyper_log_log.rb +0 -60
  102. data/test/lint/lists.rb +0 -143
  103. data/test/lint/sets.rb +0 -140
  104. data/test/lint/sorted_sets.rb +0 -316
  105. data/test/lint/strings.rb +0 -246
  106. data/test/lint/value_types.rb +0 -130
  107. data/test/persistence_control_commands_test.rb +0 -24
  108. data/test/pipelining_commands_test.rb +0 -238
  109. data/test/publish_subscribe_test.rb +0 -280
  110. data/test/remote_server_control_commands_test.rb +0 -175
  111. data/test/scanning_test.rb +0 -407
  112. data/test/scripting_test.rb +0 -76
  113. data/test/sentinel_command_test.rb +0 -78
  114. data/test/sentinel_test.rb +0 -253
  115. data/test/sorting_test.rb +0 -57
  116. data/test/ssl_test.rb +0 -69
  117. data/test/support/connection/hiredis.rb +0 -1
  118. data/test/support/connection/ruby.rb +0 -1
  119. data/test/support/connection/synchrony.rb +0 -17
  120. data/test/support/redis_mock.rb +0 -130
  121. data/test/support/ssl/gen_certs.sh +0 -31
  122. data/test/support/ssl/trusted-ca.crt +0 -25
  123. data/test/support/ssl/trusted-ca.key +0 -27
  124. data/test/support/ssl/trusted-cert.crt +0 -81
  125. data/test/support/ssl/trusted-cert.key +0 -28
  126. data/test/support/ssl/untrusted-ca.crt +0 -26
  127. data/test/support/ssl/untrusted-ca.key +0 -27
  128. data/test/support/ssl/untrusted-cert.crt +0 -82
  129. data/test/support/ssl/untrusted-cert.key +0 -28
  130. data/test/support/wire/synchrony.rb +0 -24
  131. data/test/support/wire/thread.rb +0 -5
  132. data/test/synchrony_driver.rb +0 -85
  133. data/test/test.conf.erb +0 -9
  134. data/test/thread_safety_test.rb +0 -60
  135. data/test/transactions_test.rb +0 -262
  136. data/test/unknown_commands_test.rb +0 -12
  137. data/test/url_param_test.rb +0 -136
data/lib/redis/errors.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  # Base error for all redis-rb errors.
3
- class BaseError < RuntimeError
5
+ class BaseError < StandardError
4
6
  end
5
7
 
6
8
  # Raised by the connection when a protocol error occurs.
@@ -18,6 +20,15 @@ class Redis
18
20
  class CommandError < BaseError
19
21
  end
20
22
 
23
+ class PermissionError < CommandError
24
+ end
25
+
26
+ class WrongTypeError < CommandError
27
+ end
28
+
29
+ class ReadOnlyError < CommandError
30
+ end
31
+
21
32
  # Base error for connection related errors.
22
33
  class BaseConnectionError < BaseError
23
34
  end
@@ -37,4 +48,11 @@ class Redis
37
48
  # Raised when the connection was inherited by a child process.
38
49
  class InheritedError < BaseConnectionError
39
50
  end
51
+
52
+ # Raised when client options are invalid.
53
+ class InvalidClientOptionError < BaseError
54
+ end
55
+
56
+ class SubscriptionError < BaseError
57
+ end
40
58
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
4
+ require 'digest/md5'
2
5
 
3
6
  class Redis
4
7
  class HashRing
5
-
6
8
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
9
 
8
10
  attr_reader :ring, :sorted_keys, :replicas, :nodes
@@ -10,7 +12,7 @@ class Redis
10
12
  # nodes is a list of objects that have a proper to_s representation.
11
13
  # replicas indicates how many virtual points should be used pr. node,
12
14
  # replicas are required to improve the distribution.
13
- def initialize(nodes=[], replicas=POINTS_PER_SERVER)
15
+ def initialize(nodes = [], replicas = POINTS_PER_SERVER)
14
16
  @replicas = replicas
15
17
  @ring = {}
16
18
  @nodes = []
@@ -24,7 +26,7 @@ class Redis
24
26
  def add_node(node)
25
27
  @nodes << node
26
28
  @replicas.times do |i|
27
- key = Zlib.crc32("#{node.id}:#{i}")
29
+ key = server_hash_for("#{node.id}:#{i}")
28
30
  @ring[key] = node
29
31
  @sorted_keys << key
30
32
  end
@@ -32,57 +34,56 @@ class Redis
32
34
  end
33
35
 
34
36
  def remove_node(node)
35
- @nodes.reject!{|n| n.id == node.id}
37
+ @nodes.reject! { |n| n.id == node.id }
36
38
  @replicas.times do |i|
37
- key = Zlib.crc32("#{node.id}:#{i}")
39
+ key = server_hash_for("#{node.id}:#{i}")
38
40
  @ring.delete(key)
39
- @sorted_keys.reject! {|k| k == key}
41
+ @sorted_keys.reject! { |k| k == key }
40
42
  end
41
43
  end
42
44
 
43
45
  # get the node in the hash ring for this key
44
46
  def get_node(key)
45
- get_node_pos(key)[0]
46
- end
47
-
48
- def get_node_pos(key)
49
- return [nil,nil] if @ring.size == 0
50
- crc = Zlib.crc32(key)
51
- idx = HashRing.binary_search(@sorted_keys, crc)
52
- return [@ring[@sorted_keys[idx]], idx]
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
53
50
  end
54
51
 
55
52
  def iter_nodes(key)
56
- return [nil,nil] if @ring.size == 0
57
- _, pos = get_node_pos(key)
53
+ return [nil, nil] if @ring.empty?
54
+
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
58
57
  @ring.size.times do |n|
59
- yield @ring[@sorted_keys[(pos+n) % @ring.size]]
58
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
60
59
  end
61
60
  end
62
61
 
62
+ private
63
+
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
67
+
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
71
+
63
72
  # Find the closest index in HashRing with value <= the given value
64
- def self.binary_search(ary, value, &block)
65
- upper = ary.size - 1
73
+ def binary_search(ary, value)
74
+ upper = ary.size
66
75
  lower = 0
67
- idx = 0
68
-
69
- while(lower <= upper) do
70
- idx = (lower + upper) / 2
71
- comp = ary[idx] <=> value
72
76
 
73
- if comp == 0
74
- return idx
75
- elsif comp > 0
76
- upper = idx - 1
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
77
81
  else
78
- lower = idx + 1
82
+ lower = mid + 1
79
83
  end
80
84
  end
81
85
 
82
- if upper < 0
83
- upper = ary.size - 1
84
- end
85
- return upper
86
+ upper - 1
86
87
  end
87
88
  end
88
89
  end
@@ -1,93 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
1
5
  class Redis
2
- class Pipeline
6
+ class PipelinedConnection
3
7
  attr_accessor :db
4
8
 
5
- attr :futures
6
-
7
- def initialize
8
- @with_reconnect = true
9
- @shutdown = false
10
- @futures = []
9
+ def initialize(pipeline, futures = [])
10
+ @pipeline = pipeline
11
+ @futures = futures
11
12
  end
12
13
 
13
- def with_reconnect?
14
- @with_reconnect
14
+ include Commands
15
+
16
+ def pipelined
17
+ yield self
15
18
  end
16
19
 
17
- def without_reconnect?
18
- !@with_reconnect
20
+ def multi
21
+ transaction = MultiConnection.new(@pipeline, @futures)
22
+ send_command([:multi])
23
+ size = @futures.size
24
+ yield transaction
25
+ multi_future = MultiFuture.new(@futures[size..-1])
26
+ @pipeline.call_v([:exec]) do |result|
27
+ multi_future._set(result)
28
+ end
29
+ @futures << multi_future
30
+ multi_future
19
31
  end
20
32
 
21
- def shutdown?
22
- @shutdown
33
+ private
34
+
35
+ def synchronize
36
+ yield self
23
37
  end
24
38
 
25
- def call(command, &block)
26
- # A pipeline that contains a shutdown should not raise ECONNRESET when
27
- # the connection is gone.
28
- @shutdown = true if command.first == :shutdown
39
+ def send_command(command, &block)
29
40
  future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
43
+ end
30
44
  @futures << future
31
45
  future
32
46
  end
33
47
 
34
- def call_pipeline(pipeline)
35
- @shutdown = true if pipeline.shutdown?
36
- @futures.concat(pipeline.futures)
37
- @db = pipeline.db
38
- nil
39
- end
40
-
41
- def commands
42
- @futures.map { |f| f._command }
43
- end
44
-
45
- def with_reconnect(val=true)
46
- @with_reconnect = false unless val
47
- yield
48
- end
49
-
50
- def without_reconnect(&blk)
51
- with_reconnect(false, &blk)
52
- end
53
-
54
- def finish(replies, &blk)
55
- if blk
56
- futures.each_with_index.map do |future, i|
57
- future._set(blk.call(replies[i]))
58
- end
59
- else
60
- futures.each_with_index.map do |future, i|
61
- future._set(replies[i])
62
- end
48
+ def send_blocking_command(command, timeout, &block)
49
+ future = Future.new(command, block)
50
+ @pipeline.blocking_call_v(timeout, command) do |result|
51
+ future._set(result)
63
52
  end
53
+ @futures << future
54
+ future
64
55
  end
56
+ end
65
57
 
66
- class Multi < self
67
- def finish(replies)
68
- exec = replies.last
69
-
70
- return if exec.nil? # The transaction failed because of WATCH.
71
-
72
- # EXEC command failed.
73
- raise exec if exec.is_a?(CommandError)
74
-
75
- if exec.size < futures.size
76
- # Some command wasn't recognized by Redis.
77
- raise replies.detect { |r| r.is_a?(CommandError) }
78
- end
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
61
+ end
79
62
 
80
- super(exec) do |reply|
81
- # Because an EXEC returns nested replies, hiredis won't be able to
82
- # convert an error reply to a CommandError instance itself. This is
83
- # specific to MULTI/EXEC, so we solve this here.
84
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
85
- end
86
- end
63
+ private
87
64
 
88
- def commands
89
- [[:multi]] + super + [[:exec]]
90
- end
65
+ # Blocking commands inside transaction behave like non-blocking.
66
+ # It shouldn't be done though.
67
+ # https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
68
+ def send_blocking_command(command, _timeout, &block)
69
+ send_command(command, &block)
91
70
  end
92
71
  end
93
72
 
@@ -100,10 +79,10 @@ class Redis
100
79
  class Future < BasicObject
101
80
  FutureNotReady = ::Redis::FutureNotReady.new
102
81
 
103
- def initialize(command, transformation)
82
+ def initialize(command, coerce)
104
83
  @command = command
105
- @transformation = transformation
106
84
  @object = FutureNotReady
85
+ @coerce = coerce
107
86
  end
108
87
 
109
88
  def inspect
@@ -111,16 +90,12 @@ class Redis
111
90
  end
112
91
 
113
92
  def _set(object)
114
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
115
94
  value
116
95
  end
117
96
 
118
- def _command
119
- @command
120
- end
121
-
122
97
  def value
123
- ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
124
99
  @object
125
100
  end
126
101
 
@@ -132,4 +107,21 @@ class Redis
132
107
  Future
133
108
  end
134
109
  end
110
+
111
+ class MultiFuture < Future
112
+ def initialize(futures)
113
+ @futures = futures
114
+ @command = [:exec]
115
+ @object = FutureNotReady
116
+ end
117
+
118
+ def _set(replies)
119
+ if replies
120
+ @futures.each_with_index do |future, index|
121
+ future._set(replies[index])
122
+ end
123
+ end
124
+ @object = replies
125
+ end
126
+ end
135
127
  end
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  class SubscribedClient
3
5
  def initialize(client)
4
6
  @client = client
7
+ @write_monitor = Monitor.new
5
8
  end
6
9
 
7
- def call(command)
8
- @client.process([command])
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
9
14
  end
10
15
 
11
16
  def subscribe(*channels, &block)
@@ -25,31 +30,36 @@ class Redis
25
30
  end
26
31
 
27
32
  def unsubscribe(*channels)
28
- call([:unsubscribe, *channels])
33
+ call_v([:unsubscribe, *channels])
29
34
  end
30
35
 
31
36
  def punsubscribe(*channels)
32
- call([:punsubscribe, *channels])
37
+ call_v([:punsubscribe, *channels])
38
+ end
39
+
40
+ def close
41
+ @client.close
33
42
  end
34
43
 
35
- protected
44
+ protected
36
45
 
37
46
  def subscription(start, stop, channels, block, timeout = 0)
38
47
  sub = Subscription.new(&block)
39
48
 
40
- unsubscribed = false
49
+ call_v([start, *channels])
50
+ while event = @client.next_event(timeout)
51
+ if event.is_a?(::RedisClient::CommandError)
52
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
53
+ end
41
54
 
42
- begin
43
- @client.call_loop([start, *channels], timeout) do |line|
44
- type, *rest = line
45
- sub.callbacks[type].call(*rest)
46
- unsubscribed = type == stop && rest.last == 0
47
- break if unsubscribed
55
+ type, *rest = event
56
+ if callback = sub.callbacks[type]
57
+ callback.call(*rest)
48
58
  end
49
- ensure
50
- # No need to unsubscribe here. The real client closes the connection
51
- # whenever an exception is raised (see #ensure_connected).
59
+ break if type == stop && rest.last == 0
52
60
  end
61
+ # No need to unsubscribe here. The real client closes the connection
62
+ # whenever an exception is raised (see #ensure_connected).
53
63
  end
54
64
  end
55
65
 
@@ -57,10 +67,7 @@ class Redis
57
67
  attr :callbacks
58
68
 
59
69
  def initialize
60
- @callbacks = Hash.new do |hash, key|
61
- hash[key] = lambda { |*_| }
62
- end
63
-
70
+ @callbacks = {}
64
71
  yield(self)
65
72
  end
66
73
 
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
- VERSION = "4.0.0"
4
+ VERSION = '5.0.0'
3
5
  end