redis 4.3.1 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Transactions
6
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
7
+ #
8
+ # Using a block is optional, but is necessary for thread-safety.
9
+ #
10
+ # An `#unwatch` is automatically issued if an exception is raised within the
11
+ # block that is a subclass of StandardError and is not a ConnectionError.
12
+ #
13
+ # @example With a block
14
+ # redis.watch("key") do
15
+ # if redis.get("key") == "some value"
16
+ # redis.multi do |multi|
17
+ # multi.set("key", "other value")
18
+ # multi.incr("counter")
19
+ # end
20
+ # else
21
+ # redis.unwatch
22
+ # end
23
+ # end
24
+ # # => ["OK", 6]
25
+ #
26
+ # @example Without a block
27
+ # redis.watch("key")
28
+ # # => "OK"
29
+ #
30
+ # @param [String, Array<String>] keys one or more keys to watch
31
+ # @return [Object] if using a block, returns the return value of the block
32
+ # @return [String] if not using a block, returns `OK`
33
+ #
34
+ # @see #unwatch
35
+ # @see #multi
36
+ def watch(*keys)
37
+ synchronize do |client|
38
+ res = client.call([:watch, *keys])
39
+
40
+ if block_given?
41
+ begin
42
+ yield(self)
43
+ rescue ConnectionError
44
+ raise
45
+ rescue StandardError
46
+ unwatch
47
+ raise
48
+ end
49
+ else
50
+ res
51
+ end
52
+ end
53
+ end
54
+
55
+ # Forget about all watched keys.
56
+ #
57
+ # @return [String] `OK`
58
+ #
59
+ # @see #watch
60
+ # @see #multi
61
+ def unwatch
62
+ send_command([:unwatch])
63
+ end
64
+
65
+ # Execute all commands issued after MULTI.
66
+ #
67
+ # Only call this method when `#multi` was called **without** a block.
68
+ #
69
+ # @return [nil, Array<...>]
70
+ # - when commands were not executed, `nil`
71
+ # - when commands were executed, an array with their replies
72
+ #
73
+ # @see #multi
74
+ # @see #discard
75
+ def exec
76
+ send_command([:exec])
77
+ end
78
+
79
+ # Discard all commands issued after MULTI.
80
+ #
81
+ # Only call this method when `#multi` was called **without** a block.
82
+ #
83
+ # @return [String] `"OK"`
84
+ #
85
+ # @see #multi
86
+ # @see #exec
87
+ def discard
88
+ send_command([:discard])
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/commands/bitmaps"
4
+ require "redis/commands/cluster"
5
+ require "redis/commands/connection"
6
+ require "redis/commands/geo"
7
+ require "redis/commands/hashes"
8
+ require "redis/commands/hyper_log_log"
9
+ require "redis/commands/keys"
10
+ require "redis/commands/lists"
11
+ require "redis/commands/pubsub"
12
+ require "redis/commands/scripting"
13
+ require "redis/commands/server"
14
+ require "redis/commands/sets"
15
+ require "redis/commands/sorted_sets"
16
+ require "redis/commands/streams"
17
+ require "redis/commands/strings"
18
+ require "redis/commands/transactions"
19
+
20
+ class Redis
21
+ module Commands
22
+ include Bitmaps
23
+ include Cluster
24
+ include Connection
25
+ include Geo
26
+ include Hashes
27
+ include HyperLogLog
28
+ include Keys
29
+ include Lists
30
+ include Pubsub
31
+ include Scripting
32
+ include Server
33
+ include Sets
34
+ include SortedSets
35
+ include Streams
36
+ include Strings
37
+ include Transactions
38
+
39
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
40
+ # where the method call will return nil. Propagate the nil instead of falsely
41
+ # returning false.
42
+ Boolify = lambda { |value|
43
+ case value
44
+ when 1
45
+ true
46
+ when 0
47
+ false
48
+ else
49
+ value
50
+ end
51
+ }
52
+
53
+ BoolifySet = lambda { |value|
54
+ case value
55
+ when "OK"
56
+ true
57
+ when nil
58
+ false
59
+ else
60
+ value
61
+ end
62
+ }
63
+
64
+ Hashify = lambda { |value|
65
+ if value.respond_to?(:each_slice)
66
+ value.each_slice(2).to_h
67
+ else
68
+ value
69
+ end
70
+ }
71
+
72
+ Pairify = lambda { |value|
73
+ if value.respond_to?(:each_slice)
74
+ value.each_slice(2).to_a
75
+ else
76
+ value
77
+ end
78
+ }
79
+
80
+ Floatify = lambda { |value|
81
+ case value
82
+ when "inf"
83
+ Float::INFINITY
84
+ when "-inf"
85
+ -Float::INFINITY
86
+ when String
87
+ Float(value)
88
+ else
89
+ value
90
+ end
91
+ }
92
+
93
+ FloatifyPairs = lambda { |value|
94
+ return value unless value.respond_to?(:each_slice)
95
+
96
+ value.each_slice(2).map do |member, score|
97
+ [member, Floatify.call(score)]
98
+ end
99
+ }
100
+
101
+ HashifyInfo = lambda { |reply|
102
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
103
+ lines.map! { |line| line.split(':', 2) }
104
+ lines.compact!
105
+ lines.to_h
106
+ }
107
+
108
+ HashifyStreams = lambda { |reply|
109
+ case reply
110
+ when nil
111
+ {}
112
+ else
113
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
114
+ end
115
+ }
116
+
117
+ EMPTY_STREAM_RESPONSE = [nil].freeze
118
+ private_constant :EMPTY_STREAM_RESPONSE
119
+
120
+ HashifyStreamEntries = lambda { |reply|
121
+ reply.compact.map do |entry_id, values|
122
+ [entry_id, values&.each_slice(2)&.to_h]
123
+ end
124
+ }
125
+
126
+ HashifyStreamAutoclaim = lambda { |reply|
127
+ {
128
+ 'next' => reply[0],
129
+ 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
130
+ }
131
+ }
132
+
133
+ HashifyStreamAutoclaimJustId = lambda { |reply|
134
+ {
135
+ 'next' => reply[0],
136
+ 'entries' => reply[1]
137
+ }
138
+ }
139
+
140
+ HashifyStreamPendings = lambda { |reply|
141
+ {
142
+ 'size' => reply[0],
143
+ 'min_entry_id' => reply[1],
144
+ 'max_entry_id' => reply[2],
145
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
146
+ }
147
+ }
148
+
149
+ HashifyStreamPendingDetails = lambda { |reply|
150
+ reply.map do |arr|
151
+ {
152
+ 'entry_id' => arr[0],
153
+ 'consumer' => arr[1],
154
+ 'elapsed' => arr[2],
155
+ 'count' => arr[3]
156
+ }
157
+ end
158
+ }
159
+
160
+ HashifyClusterNodeInfo = lambda { |str|
161
+ arr = str.split(' ')
162
+ {
163
+ 'node_id' => arr[0],
164
+ 'ip_port' => arr[1],
165
+ 'flags' => arr[2].split(','),
166
+ 'master_node_id' => arr[3],
167
+ 'ping_sent' => arr[4],
168
+ 'pong_recv' => arr[5],
169
+ 'config_epoch' => arr[6],
170
+ 'link_state' => arr[7],
171
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
172
+ }
173
+ }
174
+
175
+ HashifyClusterSlots = lambda { |reply|
176
+ reply.map do |arr|
177
+ first_slot, last_slot = arr[0..1]
178
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
179
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
180
+ {
181
+ 'start_slot' => first_slot,
182
+ 'end_slot' => last_slot,
183
+ 'master' => master,
184
+ 'replicas' => replicas
185
+ }
186
+ end
187
+ }
188
+
189
+ HashifyClusterNodes = lambda { |reply|
190
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
191
+ }
192
+
193
+ HashifyClusterSlaves = lambda { |reply|
194
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
195
+ }
196
+
197
+ Noop = ->(reply) { reply }
198
+
199
+ # Sends a command to Redis and returns its reply.
200
+ #
201
+ # Replies are converted to Ruby objects according to the RESP protocol, so
202
+ # you can expect a Ruby array, integer or nil when Redis sends one. Higher
203
+ # level transformations, such as converting an array of pairs into a Ruby
204
+ # hash, are up to consumers.
205
+ #
206
+ # Redis error replies are raised as Ruby exceptions.
207
+ def call(*command)
208
+ send_command(command)
209
+ end
210
+
211
+ # Interact with the sentinel command (masters, master, slaves, failover)
212
+ #
213
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
214
+ # @param [Array<String>] args depends on subcommand
215
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
216
+ def sentinel(subcommand, *args)
217
+ subcommand = subcommand.to_s.downcase
218
+ send_command([:sentinel, subcommand] + args) do |reply|
219
+ case subcommand
220
+ when "get-master-addr-by-name"
221
+ reply
222
+ else
223
+ if reply.is_a?(Array)
224
+ if reply[0].is_a?(Array)
225
+ reply.map(&Hashify)
226
+ else
227
+ Hashify.call(reply)
228
+ end
229
+ else
230
+ reply
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def method_missing(*command) # rubocop:disable Style/MissingRespondToMissing
239
+ send_command(command)
240
+ end
241
+ end
242
+ end
@@ -12,11 +12,13 @@ class Redis
12
12
  if i.is_a? Array
13
13
  i.each do |j|
14
14
  j = j.to_s
15
+ j = j.encoding == Encoding::BINARY ? j : j.b
15
16
  command << "$#{j.bytesize}"
16
17
  command << j
17
18
  end
18
19
  else
19
20
  i = i.to_s
21
+ i = i.encoding == Encoding::BINARY ? i : i.b
20
22
  command << "$#{i.bytesize}"
21
23
  command << i
22
24
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "registry"
4
- require_relative "../errors"
3
+ require "redis/connection/registry"
4
+ require "redis/errors"
5
+
5
6
  require "hiredis/connection"
6
7
  require "timeout"
7
8
 
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "registry"
4
- require_relative "command_helper"
5
- require_relative "../errors"
3
+ require "redis/connection/registry"
4
+ require "redis/connection/command_helper"
5
+ require "redis/errors"
6
+
6
7
  require "socket"
7
8
  require "timeout"
8
9
 
@@ -21,7 +22,7 @@ class Redis
21
22
  super(*args)
22
23
 
23
24
  @timeout = @write_timeout = nil
24
- @buffer = "".dup
25
+ @buffer = "".b
25
26
  end
26
27
 
27
28
  def timeout=(timeout)
@@ -35,7 +36,8 @@ class Redis
35
36
  def read(nbytes)
36
37
  result = @buffer.slice!(0, nbytes)
37
38
 
38
- result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
39
+ buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
40
+ result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
39
41
 
40
42
  result
41
43
  end
@@ -48,9 +50,9 @@ class Redis
48
50
  @buffer.slice!(0, crlf + CRLF.bytesize)
49
51
  end
50
52
 
51
- def _read_from_socket(nbytes)
53
+ def _read_from_socket(nbytes, buffer = nil)
52
54
  loop do
53
- case chunk = read_nonblock(nbytes, exception: false)
55
+ case chunk = read_nonblock(nbytes, buffer, exception: false)
54
56
  when :wait_readable
55
57
  unless wait_readable(@timeout)
56
58
  raise Redis::TimeoutError
@@ -132,7 +134,9 @@ class Redis
132
134
  # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
133
135
  # Use the blocking #readpartial method instead.
134
136
 
135
- def _read_from_socket(nbytes)
137
+ def _read_from_socket(nbytes, _buffer = nil)
138
+ # JRuby: Throw away the buffer as we won't need it
139
+ # but still need to support the max arity of 2
136
140
  readpartial(nbytes)
137
141
  rescue EOFError
138
142
  raise Errno::ECONNRESET
@@ -239,7 +243,7 @@ class Redis
239
243
  end
240
244
 
241
245
  def self.connect(host, port, timeout, ssl_params)
242
- # Note: this is using Redis::Connection::TCPSocket
246
+ # NOTE: this is using Redis::Connection::TCPSocket
243
247
  tcp_sock = TCPSocket.connect(host, port, timeout)
244
248
 
245
249
  ctx = OpenSSL::SSL::SSLContext.new
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "command_helper"
4
- require_relative "registry"
5
- require_relative "../errors"
3
+ require "redis/connection/registry"
4
+ require "redis/connection/command_helper"
5
+ require "redis/errors"
6
+
6
7
  require "em-synchrony"
7
8
  require "hiredis/reader"
8
9
 
9
- Kernel.warn(
10
- "The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
10
+ ::Redis.deprecate!(
11
+ "The redis synchrony driver is deprecated and will be removed in redis-rb 5.0.0. " \
11
12
  "We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
12
13
  )
13
14
 
@@ -129,11 +130,12 @@ class Redis
129
130
  def read
130
131
  type, payload = @connection.read
131
132
 
132
- if type == :reply
133
+ case type
134
+ when :reply
133
135
  payload
134
- elsif type == :error
136
+ when :error
135
137
  raise payload
136
- elsif type == :timeout
138
+ when :timeout
137
139
  raise TimeoutError
138
140
  else
139
141
  raise "Unknown type #{type.inspect}"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "connection/registry"
3
+ require "redis/connection/registry"
4
4
 
5
5
  # If a connection driver was required before this file, the array
6
6
  # Redis::Connection.drivers will contain one or more classes. The last driver
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "hash_ring"
3
+ require "redis/hash_ring"
4
4
 
5
5
  class Redis
6
6
  class Distributed
@@ -178,15 +178,11 @@ class Redis
178
178
  # Determine if a key exists.
179
179
  def exists(*args)
180
180
  if !Redis.exists_returns_integer && args.size == 1
181
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
181
+ ::Redis.deprecate!(
182
+ "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
182
183
  "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
183
184
  "(#{::Kernel.caller(1, 1).first})\n"
184
-
185
- if defined?(::Warning)
186
- ::Warning.warn(message)
187
- else
188
- warn(message)
189
- end
185
+ )
190
186
  exists?(*args)
191
187
  else
192
188
  keys_per_node = args.group_by { |key| node_for(key) }
@@ -215,6 +211,13 @@ class Redis
215
211
  node_for(key).move(key, db)
216
212
  end
217
213
 
214
+ # Copy a value from one key to another.
215
+ def copy(source, destination, **options)
216
+ ensure_same_node(:copy, [source, destination]) do |node|
217
+ node.copy(source, destination, **options)
218
+ end
219
+ end
220
+
218
221
  # Return a random key from the keyspace.
219
222
  def randomkey
220
223
  raise CannotDistribute, :randomkey
@@ -316,6 +319,16 @@ class Redis
316
319
  node_for(key).get(key)
317
320
  end
318
321
 
322
+ # Get the value of a key and delete it.
323
+ def getdel(key)
324
+ node_for(key).getdel(key)
325
+ end
326
+
327
+ # Get the value of a key and sets its time to live based on options.
328
+ def getex(key, **options)
329
+ node_for(key).getex(key, **options)
330
+ end
331
+
319
332
  # Get the values of all the given keys as an Array.
320
333
  def mget(*keys)
321
334
  mapped_mget(*keys).values_at(*keys)
@@ -393,6 +406,21 @@ class Redis
393
406
  node_for(key).llen(key)
394
407
  end
395
408
 
409
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
410
+ def lmove(source, destination, where_source, where_destination)
411
+ ensure_same_node(:lmove, [source, destination]) do |node|
412
+ node.lmove(source, destination, where_source, where_destination)
413
+ end
414
+ end
415
+
416
+ # Remove the first/last element in a list and append/prepend it
417
+ # to another list and return it, or block until one is available.
418
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
419
+ ensure_same_node(:lmove, [source, destination]) do |node|
420
+ node.blmove(source, destination, where_source, where_destination, timeout: timeout)
421
+ end
422
+ end
423
+
396
424
  # Prepend one or more values to a list.
397
425
  def lpush(key, value)
398
426
  node_for(key).lpush(key, value)
@@ -542,6 +570,11 @@ class Redis
542
570
  node_for(key).sismember(key, member)
543
571
  end
544
572
 
573
+ # Determine if multiple values are members of a set.
574
+ def smismember(key, *members)
575
+ node_for(key).smismember(key, *members)
576
+ end
577
+
545
578
  # Get all the members in a set.
546
579
  def smembers(key)
547
580
  node_for(key).smembers(key)
@@ -626,11 +659,29 @@ class Redis
626
659
  node_for(key).zscore(key, member)
627
660
  end
628
661
 
629
- # Return a range of members in a sorted set, by index.
662
+ # Get one or more random members from a sorted set.
663
+ def zrandmember(key, count = nil, **options)
664
+ node_for(key).zrandmember(key, count, **options)
665
+ end
666
+
667
+ # Get the scores associated with the given members in a sorted set.
668
+ def zmscore(key, *members)
669
+ node_for(key).zmscore(key, *members)
670
+ end
671
+
672
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
630
673
  def zrange(key, start, stop, **options)
631
674
  node_for(key).zrange(key, start, stop, **options)
632
675
  end
633
676
 
677
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
678
+ # and store the resulting sorted set in a new key.
679
+ def zrangestore(dest_key, src_key, start, stop, **options)
680
+ ensure_same_node(:zrangestore, [dest_key, src_key]) do |node|
681
+ node.zrangestore(dest_key, src_key, start, stop, **options)
682
+ end
683
+ end
684
+
634
685
  # Return a range of members in a sorted set, by index, with scores ordered
635
686
  # from high to low.
636
687
  def zrevrange(key, start, stop, **options)
@@ -674,6 +725,13 @@ class Redis
674
725
  node_for(key).zcount(key, min, max)
675
726
  end
676
727
 
728
+ # Get the intersection of multiple sorted sets
729
+ def zinter(*keys, **options)
730
+ ensure_same_node(:zinter, keys) do |node|
731
+ node.zinter(*keys, **options)
732
+ end
733
+ end
734
+
677
735
  # Intersect multiple sorted sets and store the resulting sorted set in a new
678
736
  # key.
679
737
  def zinterstore(destination, keys, **options)
@@ -682,6 +740,13 @@ class Redis
682
740
  end
683
741
  end
684
742
 
743
+ # Return the union of multiple sorted sets.
744
+ def zunion(*keys, **options)
745
+ ensure_same_node(:zunion, keys) do |node|
746
+ node.zunion(*keys, **options)
747
+ end
748
+ end
749
+
685
750
  # Add multiple sorted sets and store the resulting sorted set in a new key.
686
751
  def zunionstore(destination, keys, **options)
687
752
  ensure_same_node(:zunionstore, [destination] + keys) do |node|
@@ -689,6 +754,21 @@ class Redis
689
754
  end
690
755
  end
691
756
 
757
+ # Return the difference between the first and all successive input sorted sets.
758
+ def zdiff(*keys, **options)
759
+ ensure_same_node(:zdiff, keys) do |node|
760
+ node.zdiff(*keys, **options)
761
+ end
762
+ end
763
+
764
+ # Compute the difference between the first and all successive input sorted sets
765
+ # and store the resulting sorted set in a new key.
766
+ def zdiffstore(destination, keys, **options)
767
+ ensure_same_node(:zdiffstore, [destination] + keys) do |node|
768
+ node.zdiffstore(destination, keys, **options)
769
+ end
770
+ end
771
+
692
772
  # Get the number of fields in a hash.
693
773
  def hlen(key)
694
774
  node_for(key).hlen(key)
@@ -727,6 +807,10 @@ class Redis
727
807
  Hash[*fields.zip(hmget(key, *fields)).flatten]
728
808
  end
729
809
 
810
+ def hrandfield(key, count = nil, **options)
811
+ node_for(key).hrandfield(key, count, **options)
812
+ end
813
+
730
814
  # Delete one or more hash fields.
731
815
  def hdel(key, *fields)
732
816
  node_for(key).hdel(key, *fields)