redis 3.3.5 → 4.7.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 (147) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +220 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +176 -99
  5. data/lib/redis/cluster/command.rb +79 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +120 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +34 -0
  11. data/lib/redis/cluster/option.rb +100 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +46 -0
  14. data/lib/redis/cluster.rb +315 -0
  15. data/lib/redis/commands/bitmaps.rb +63 -0
  16. data/lib/redis/commands/cluster.rb +45 -0
  17. data/lib/redis/commands/connection.rb +58 -0
  18. data/lib/redis/commands/geo.rb +84 -0
  19. data/lib/redis/commands/hashes.rb +251 -0
  20. data/lib/redis/commands/hyper_log_log.rb +37 -0
  21. data/lib/redis/commands/keys.rb +411 -0
  22. data/lib/redis/commands/lists.rb +289 -0
  23. data/lib/redis/commands/pubsub.rb +72 -0
  24. data/lib/redis/commands/scripting.rb +114 -0
  25. data/lib/redis/commands/server.rb +188 -0
  26. data/lib/redis/commands/sets.rb +207 -0
  27. data/lib/redis/commands/sorted_sets.rb +812 -0
  28. data/lib/redis/commands/streams.rb +382 -0
  29. data/lib/redis/commands/strings.rb +313 -0
  30. data/lib/redis/commands/transactions.rb +139 -0
  31. data/lib/redis/commands.rb +242 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +130 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +231 -72
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +30 -73
  41. data/lib/redis/pipeline.rb +178 -13
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +173 -2661
  45. metadata +66 -202
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -89
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -87
  52. data/benchmarking/logging.rb +0 -71
  53. data/benchmarking/pipeline.rb +0 -51
  54. data/benchmarking/speed.rb +0 -21
  55. data/benchmarking/suite.rb +0 -24
  56. data/benchmarking/worker.rb +0 -71
  57. data/examples/basic.rb +0 -15
  58. data/examples/consistency.rb +0 -114
  59. data/examples/dist_redis.rb +0 -43
  60. data/examples/incr-decr.rb +0 -17
  61. data/examples/list.rb +0 -26
  62. data/examples/pubsub.rb +0 -37
  63. data/examples/sentinel/sentinel.conf +0 -9
  64. data/examples/sentinel/start +0 -49
  65. data/examples/sentinel.rb +0 -41
  66. data/examples/sets.rb +0 -36
  67. data/examples/unicorn/config.ru +0 -3
  68. data/examples/unicorn/unicorn.rb +0 -20
  69. data/redis.gemspec +0 -44
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/client_test.rb +0 -59
  73. data/test/command_map_test.rb +0 -30
  74. data/test/commands_on_hashes_test.rb +0 -21
  75. data/test/commands_on_hyper_log_log_test.rb +0 -21
  76. data/test/commands_on_lists_test.rb +0 -20
  77. data/test/commands_on_sets_test.rb +0 -77
  78. data/test/commands_on_sorted_sets_test.rb +0 -137
  79. data/test/commands_on_strings_test.rb +0 -101
  80. data/test/commands_on_value_types_test.rb +0 -133
  81. data/test/connection_handling_test.rb +0 -277
  82. data/test/connection_test.rb +0 -57
  83. data/test/db/.gitkeep +0 -0
  84. data/test/distributed_blocking_commands_test.rb +0 -46
  85. data/test/distributed_commands_on_hashes_test.rb +0 -10
  86. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  87. data/test/distributed_commands_on_lists_test.rb +0 -22
  88. data/test/distributed_commands_on_sets_test.rb +0 -83
  89. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  90. data/test/distributed_commands_on_strings_test.rb +0 -59
  91. data/test/distributed_commands_on_value_types_test.rb +0 -95
  92. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  93. data/test/distributed_connection_handling_test.rb +0 -23
  94. data/test/distributed_internals_test.rb +0 -79
  95. data/test/distributed_key_tags_test.rb +0 -52
  96. data/test/distributed_persistence_control_commands_test.rb +0 -26
  97. data/test/distributed_publish_subscribe_test.rb +0 -92
  98. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  99. data/test/distributed_scripting_test.rb +0 -102
  100. data/test/distributed_sorting_test.rb +0 -20
  101. data/test/distributed_test.rb +0 -58
  102. data/test/distributed_transactions_test.rb +0 -32
  103. data/test/encoding_test.rb +0 -18
  104. data/test/error_replies_test.rb +0 -59
  105. data/test/fork_safety_test.rb +0 -65
  106. data/test/helper.rb +0 -232
  107. data/test/helper_test.rb +0 -24
  108. data/test/internals_test.rb +0 -417
  109. data/test/lint/blocking_commands.rb +0 -150
  110. data/test/lint/hashes.rb +0 -162
  111. data/test/lint/hyper_log_log.rb +0 -60
  112. data/test/lint/lists.rb +0 -143
  113. data/test/lint/sets.rb +0 -140
  114. data/test/lint/sorted_sets.rb +0 -316
  115. data/test/lint/strings.rb +0 -260
  116. data/test/lint/value_types.rb +0 -122
  117. data/test/persistence_control_commands_test.rb +0 -26
  118. data/test/pipelining_commands_test.rb +0 -242
  119. data/test/publish_subscribe_test.rb +0 -282
  120. data/test/remote_server_control_commands_test.rb +0 -118
  121. data/test/scanning_test.rb +0 -413
  122. data/test/scripting_test.rb +0 -78
  123. data/test/sentinel_command_test.rb +0 -80
  124. data/test/sentinel_test.rb +0 -255
  125. data/test/sorting_test.rb +0 -59
  126. data/test/ssl_test.rb +0 -73
  127. data/test/support/connection/hiredis.rb +0 -1
  128. data/test/support/connection/ruby.rb +0 -1
  129. data/test/support/connection/synchrony.rb +0 -17
  130. data/test/support/redis_mock.rb +0 -130
  131. data/test/support/ssl/gen_certs.sh +0 -31
  132. data/test/support/ssl/trusted-ca.crt +0 -25
  133. data/test/support/ssl/trusted-ca.key +0 -27
  134. data/test/support/ssl/trusted-cert.crt +0 -81
  135. data/test/support/ssl/trusted-cert.key +0 -28
  136. data/test/support/ssl/untrusted-ca.crt +0 -26
  137. data/test/support/ssl/untrusted-ca.key +0 -27
  138. data/test/support/ssl/untrusted-cert.crt +0 -82
  139. data/test/support/ssl/untrusted-cert.key +0 -28
  140. data/test/support/wire/synchrony.rb +0 -24
  141. data/test/support/wire/thread.rb +0 -5
  142. data/test/synchrony_driver.rb +0 -88
  143. data/test/test.conf.erb +0 -9
  144. data/test/thread_safety_test.rb +0 -62
  145. data/test/transactions_test.rb +0 -264
  146. data/test/unknown_commands_test.rb +0 -14
  147. data/test/url_param_test.rb +0 -138
data/lib/redis.rb CHANGED
@@ -1,65 +1,95 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "monitor"
2
4
  require "redis/errors"
5
+ require "redis/commands"
3
6
 
4
7
  class Redis
8
+ BASE_PATH = __dir__
9
+ @exists_returns_integer = true
10
+
11
+ Deprecated = Class.new(StandardError)
12
+
13
+ class << self
14
+ attr_reader :exists_returns_integer
15
+ attr_accessor :silence_deprecations, :raise_deprecations
16
+
17
+ def exists_returns_integer=(value)
18
+ unless value
19
+ deprecate!(
20
+ "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
21
+ "disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
22
+ "`exists?` instead."
23
+ )
24
+ end
5
25
 
6
- def self.deprecate(message, trace = caller[0])
7
- $stderr.puts "\n#{message} (in #{trace})"
8
- end
9
-
10
- attr :client
26
+ @exists_returns_integer = value
27
+ end
11
28
 
12
- # @deprecated The preferred way to create a new client object is using `#new`.
13
- # This method does not actually establish a connection to Redis,
14
- # in contrary to what you might expect.
15
- def self.connect(options = {})
16
- new(options)
17
- end
29
+ def deprecate!(message)
30
+ unless silence_deprecations
31
+ if raise_deprecations
32
+ raise Deprecated, message
33
+ else
34
+ ::Kernel.warn(message)
35
+ end
36
+ end
37
+ end
18
38
 
19
- def self.current
20
- @current ||= Redis.new
21
- end
39
+ def current
40
+ deprecate!("`Redis.current` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
41
+ @current ||= Redis.new
42
+ end
22
43
 
23
- def self.current=(redis)
24
- @current = redis
44
+ def current=(redis)
45
+ deprecate!("`Redis.current=` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
46
+ @current = redis
47
+ end
25
48
  end
26
49
 
27
- include MonitorMixin
50
+ include Commands
28
51
 
29
52
  # Create a new client instance
30
53
  #
31
54
  # @param [Hash] options
32
- # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options.
55
+ # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection:
56
+ # `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket
57
+ # connection: `unix://[path to Redis socket]`. This overrides all other options.
33
58
  # @option options [String] :host ("127.0.0.1") server hostname
34
- # @option options [Fixnum] :port (6379) server port
59
+ # @option options [Integer] :port (6379) server port
35
60
  # @option options [String] :path path to server socket (overrides host and port)
36
61
  # @option options [Float] :timeout (5.0) timeout in seconds
37
62
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
63
+ # @option options [String] :username Username to authenticate against server
38
64
  # @option options [String] :password Password to authenticate against server
39
- # @option options [Fixnum] :db (0) Database to select after initial connect
65
+ # @option options [Integer] :db (0) Database to select after initial connect
40
66
  # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
41
- # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
42
- # @option options [Hash, Fixnum] :tcp_keepalive Keepalive values, if Fixnum `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Fixnum
43
- # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect
67
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending
68
+ # `CLIENT SETNAME`
69
+ # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated
70
+ # based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
71
+ # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
44
72
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45
73
  # @option options [Array] :sentinels List of sentinels to contact
46
74
  # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
75
+ # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
76
+ # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
77
+ # @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
78
+ # client has to connect nodes via single endpoint with SSL/TLS
79
+ # @option options [Class] :connector Class of custom connector
47
80
  #
48
81
  # @return [Redis] a new client instance
49
82
  def initialize(options = {})
50
83
  @options = options.dup
51
- @original_client = @client = Client.new(options)
84
+ @cluster_mode = options.key?(:cluster)
85
+ client = @cluster_mode ? Cluster : Client
86
+ @original_client = @client = client.new(options)
52
87
  @queue = Hash.new { |h, k| h[k] = [] }
53
-
54
- super() # Monitor#initialize
55
- end
56
-
57
- def synchronize
58
- mon_synchronize { yield(@client) }
88
+ @monitor = Monitor.new
59
89
  end
60
90
 
61
91
  # Run code with the client reconnecting
62
- def with_reconnect(val=true, &blk)
92
+ def with_reconnect(val = true, &blk)
63
93
  synchronize do |client|
64
94
  client.with_reconnect(val, &blk)
65
95
  end
@@ -81,2708 +111,190 @@ class Redis
81
111
  end
82
112
  alias disconnect! close
83
113
 
84
- # Sends a command to Redis and returns its reply.
85
- #
86
- # Replies are converted to Ruby objects according to the RESP protocol, so
87
- # you can expect a Ruby array, integer or nil when Redis sends one. Higher
88
- # level transformations, such as converting an array of pairs into a Ruby
89
- # hash, are up to consumers.
90
- #
91
- # Redis error replies are raised as Ruby exceptions.
92
- def call(*command)
93
- synchronize do |client|
94
- client.call(command)
95
- end
114
+ def with
115
+ yield self
96
116
  end
97
117
 
98
- # Queues a command for pipelining.
118
+ # @deprecated Queues a command for pipelining.
99
119
  #
100
120
  # Commands in the queue are executed with the Redis#commit method.
101
121
  #
102
122
  # See http://redis.io/topics/pipelining for more details.
103
123
  #
104
124
  def queue(*command)
105
- @queue[Thread.current.object_id] << command
125
+ ::Redis.deprecate!(
126
+ "Redis#queue is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead." \
127
+ "(called from: #{caller(1, 1).first})"
128
+ )
129
+
130
+ synchronize do
131
+ @queue[Thread.current.object_id] << command
132
+ end
106
133
  end
107
134
 
108
- # Sends all commands in the queue.
135
+ # @deprecated Sends all commands in the queue.
109
136
  #
110
137
  # See http://redis.io/topics/pipelining for more details.
111
138
  #
112
139
  def commit
140
+ ::Redis.deprecate!(
141
+ "Redis#commit is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead. " \
142
+ "(called from: #{Kernel.caller(1, 1).first})"
143
+ )
144
+
113
145
  synchronize do |client|
114
146
  begin
115
- client.call_pipelined(@queue[Thread.current.object_id])
147
+ pipeline = Pipeline.new(client)
148
+ @queue[Thread.current.object_id].each do |command|
149
+ pipeline.call(command)
150
+ end
151
+
152
+ client.call_pipelined(pipeline)
116
153
  ensure
117
154
  @queue.delete(Thread.current.object_id)
118
155
  end
119
156
  end
120
157
  end
121
158
 
122
- # Authenticate to the server.
123
- #
124
- # @param [String] password must match the password specified in the
125
- # `requirepass` directive in the configuration file
126
- # @return [String] `OK`
127
- def auth(password)
128
- synchronize do |client|
129
- client.call([:auth, password])
130
- end
131
- end
132
-
133
- # Change the selected database for the current connection.
134
- #
135
- # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
136
- # @return [String] `OK`
137
- def select(db)
138
- synchronize do |client|
139
- client.db = db
140
- client.call([:select, db])
141
- end
142
- end
143
-
144
- # Ping the server.
145
- #
146
- # @return [String] `PONG`
147
- def ping
148
- synchronize do |client|
149
- client.call([:ping])
150
- end
159
+ def _client
160
+ @client
151
161
  end
152
162
 
153
- # Echo the given string.
154
- #
155
- # @param [String] value
156
- # @return [String]
157
- def echo(value)
158
- synchronize do |client|
159
- client.call([:echo, value])
163
+ def pipelined(&block)
164
+ deprecation_displayed = false
165
+ if block&.arity == 0
166
+ Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 5))
167
+ deprecation_displayed = true
160
168
  end
161
- end
162
169
 
163
- # Close the connection.
164
- #
165
- # @return [String] `OK`
166
- def quit
167
- synchronize do |client|
170
+ synchronize do |prior_client|
168
171
  begin
169
- client.call([:quit])
170
- rescue ConnectionError
172
+ pipeline = Pipeline.new(prior_client)
173
+ @client = deprecation_displayed ? pipeline : DeprecatedPipeline.new(pipeline)
174
+ pipelined_connection = PipelinedConnection.new(pipeline)
175
+ yield pipelined_connection
176
+ prior_client.call_pipeline(pipeline)
171
177
  ensure
172
- client.disconnect
178
+ @client = prior_client
173
179
  end
174
180
  end
175
181
  end
176
182
 
177
- # Asynchronously rewrite the append-only file.
178
- #
179
- # @return [String] `OK`
180
- def bgrewriteaof
181
- synchronize do |client|
182
- client.call([:bgrewriteaof])
183
- end
184
- end
185
-
186
- # Asynchronously save the dataset to disk.
183
+ # Mark the start of a transaction block.
187
184
  #
188
- # @return [String] `OK`
189
- def bgsave
190
- synchronize do |client|
191
- client.call([:bgsave])
192
- end
193
- end
194
-
195
- # Get or set server configuration parameters.
185
+ # Passing a block is optional.
196
186
  #
197
- # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat`
198
- # @return [String, Hash] string reply, or hash when retrieving more than one
199
- # property with `CONFIG GET`
200
- def config(action, *args)
201
- synchronize do |client|
202
- client.call([:config, action] + args) do |reply|
203
- if reply.kind_of?(Array) && action == :get
204
- Hashify.call(reply)
205
- else
206
- reply
207
- end
208
- end
209
- end
210
- end
211
-
212
- # Return the number of keys in the selected database.
187
+ # @example With a block
188
+ # redis.multi do |multi|
189
+ # multi.set("key", "value")
190
+ # multi.incr("counter")
191
+ # end # => ["OK", 6]
213
192
  #
214
- # @return [Fixnum]
215
- def dbsize
216
- synchronize do |client|
217
- client.call([:dbsize])
218
- end
219
- end
220
-
221
- def debug(*args)
222
- synchronize do |client|
223
- client.call([:debug] + args)
224
- end
225
- end
226
-
227
- # Remove all keys from all databases.
193
+ # @example Without a block
194
+ # redis.multi
195
+ # # => "OK"
196
+ # redis.set("key", "value")
197
+ # # => "QUEUED"
198
+ # redis.incr("counter")
199
+ # # => "QUEUED"
200
+ # redis.exec
201
+ # # => ["OK", 6]
228
202
  #
229
- # @return [String] `OK`
230
- def flushall
231
- synchronize do |client|
232
- client.call([:flushall])
233
- end
234
- end
235
-
236
- # Remove all keys from the current database.
203
+ # @yield [multi] the commands that are called inside this block are cached
204
+ # and written to the server upon returning from it
205
+ # @yieldparam [Redis] multi `self`
237
206
  #
238
- # @return [String] `OK`
239
- def flushdb
240
- synchronize do |client|
241
- client.call([:flushdb])
242
- end
243
- end
244
-
245
- # Get information and statistics about the server.
207
+ # @return [String, Array<...>]
208
+ # - when a block is not given, `OK`
209
+ # - when a block is given, an array with replies
246
210
  #
247
- # @param [String, Symbol] cmd e.g. "commandstats"
248
- # @return [Hash<String, String>]
249
- def info(cmd = nil)
250
- synchronize do |client|
251
- client.call([:info, cmd].compact) do |reply|
252
- if reply.kind_of?(String)
253
- reply = Hash[reply.split("\r\n").map do |line|
254
- line.split(":", 2) unless line =~ /^(#|$)/
255
- end.compact]
256
-
257
- if cmd && cmd.to_s == "commandstats"
258
- # Extract nested hashes for INFO COMMANDSTATS
259
- reply = Hash[reply.map do |k, v|
260
- v = v.split(",").map { |e| e.split("=") }
261
- [k[/^cmdstat_(.*)$/, 1], Hash[v]]
262
- end]
263
- end
264
- end
265
-
266
- reply
211
+ # @see #watch
212
+ # @see #unwatch
213
+ def multi(&block)
214
+ if block_given?
215
+ deprecation_displayed = false
216
+ if block&.arity == 0
217
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
218
+ deprecation_displayed = true
267
219
  end
268
- end
269
- end
270
-
271
- # Get the UNIX time stamp of the last successful save to disk.
272
- #
273
- # @return [Fixnum]
274
- def lastsave
275
- synchronize do |client|
276
- client.call([:lastsave])
277
- end
278
- end
279
-
280
- # Listen for all requests received by the server in real time.
281
- #
282
- # There is no way to interrupt this command.
283
- #
284
- # @yield a block to be called for every line of output
285
- # @yieldparam [String] line timestamp and command that was executed
286
- def monitor(&block)
287
- synchronize do |client|
288
- client.call_loop([:monitor], &block)
289
- end
290
- end
291
-
292
- # Synchronously save the dataset to disk.
293
- #
294
- # @return [String]
295
- def save
296
- synchronize do |client|
297
- client.call([:save])
298
- end
299
- end
300
220
 
301
- # Synchronously save the dataset to disk and then shut down the server.
302
- def shutdown
303
- synchronize do |client|
304
- client.with_reconnect(false) do
221
+ synchronize do |prior_client|
305
222
  begin
306
- client.call([:shutdown])
307
- rescue ConnectionError
308
- # This means Redis has probably exited.
309
- nil
223
+ pipeline = Pipeline::Multi.new(prior_client)
224
+ @client = deprecation_displayed ? pipeline : DeprecatedMulti.new(pipeline)
225
+ pipelined_connection = PipelinedConnection.new(pipeline)
226
+ yield pipelined_connection
227
+ prior_client.call_pipeline(pipeline)
228
+ ensure
229
+ @client = prior_client
310
230
  end
311
231
  end
232
+ else
233
+ send_command([:multi])
312
234
  end
313
235
  end
314
236
 
315
- # Make the server a slave of another instance, or promote it as master.
316
- def slaveof(host, port)
317
- synchronize do |client|
318
- client.call([:slaveof, host, port])
319
- end
320
- end
321
-
322
- # Interact with the slowlog (get, len, reset)
323
- #
324
- # @param [String] subcommand e.g. `get`, `len`, `reset`
325
- # @param [Fixnum] length maximum number of entries to return
326
- # @return [Array<String>, Fixnum, String] depends on subcommand
327
- def slowlog(subcommand, length=nil)
328
- synchronize do |client|
329
- args = [:slowlog, subcommand]
330
- args << length if length
331
- client.call args
332
- end
333
- end
334
-
335
- # Internal command used for replication.
336
- def sync
337
- synchronize do |client|
338
- client.call([:sync])
339
- end
340
- end
341
-
342
- # Return the server time.
343
- #
344
- # @example
345
- # r.time # => [ 1333093196, 606806 ]
346
- #
347
- # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and
348
- # microseconds in the current second
349
- def time
350
- synchronize do |client|
351
- client.call([:time]) do |reply|
352
- reply.map(&:to_i) if reply
353
- end
354
- end
355
- end
356
-
357
- # Remove the expiration from a key.
358
- #
359
- # @param [String] key
360
- # @return [Boolean] whether the timeout was removed or not
361
- def persist(key)
362
- synchronize do |client|
363
- client.call([:persist, key], &Boolify)
364
- end
365
- end
366
-
367
- # Set a key's time to live in seconds.
368
- #
369
- # @param [String] key
370
- # @param [Fixnum] seconds time to live
371
- # @return [Boolean] whether the timeout was set or not
372
- def expire(key, seconds)
373
- synchronize do |client|
374
- client.call([:expire, key, seconds], &Boolify)
375
- end
376
- end
377
-
378
- # Set the expiration for a key as a UNIX timestamp.
379
- #
380
- # @param [String] key
381
- # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
382
- # @return [Boolean] whether the timeout was set or not
383
- def expireat(key, unix_time)
384
- synchronize do |client|
385
- client.call([:expireat, key, unix_time], &Boolify)
386
- end
237
+ def id
238
+ @original_client.id
387
239
  end
388
240
 
389
- # Get the time to live (in seconds) for a key.
390
- #
391
- # @param [String] key
392
- # @return [Fixnum] remaining time to live in seconds.
393
- #
394
- # In Redis 2.6 or older the command returns -1 if the key does not exist or if
395
- # the key exist but has no associated expire.
396
- #
397
- # Starting with Redis 2.8 the return value in case of error changed:
398
- #
399
- # - The command returns -2 if the key does not exist.
400
- # - The command returns -1 if the key exists but has no associated expire.
401
- def ttl(key)
402
- synchronize do |client|
403
- client.call([:ttl, key])
404
- end
241
+ def inspect
242
+ "#<Redis client v#{Redis::VERSION} for #{id}>"
405
243
  end
406
244
 
407
- # Set a key's time to live in milliseconds.
408
- #
409
- # @param [String] key
410
- # @param [Fixnum] milliseconds time to live
411
- # @return [Boolean] whether the timeout was set or not
412
- def pexpire(key, milliseconds)
413
- synchronize do |client|
414
- client.call([:pexpire, key, milliseconds], &Boolify)
415
- end
245
+ def dup
246
+ self.class.new(@options)
416
247
  end
417
248
 
418
- # Set the expiration for a key as number of milliseconds from UNIX Epoch.
419
- #
420
- # @param [String] key
421
- # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
422
- # @return [Boolean] whether the timeout was set or not
423
- def pexpireat(key, ms_unix_time)
424
- synchronize do |client|
425
- client.call([:pexpireat, key, ms_unix_time], &Boolify)
426
- end
427
- end
249
+ def connection
250
+ return @original_client.connection_info if @cluster_mode
428
251
 
429
- # Get the time to live (in milliseconds) for a key.
430
- #
431
- # @param [String] key
432
- # @return [Fixnum] remaining time to live in milliseconds
433
- # In Redis 2.6 or older the command returns -1 if the key does not exist or if
434
- # the key exist but has no associated expire.
435
- #
436
- # Starting with Redis 2.8 the return value in case of error changed:
437
- #
438
- # - The command returns -2 if the key does not exist.
439
- # - The command returns -1 if the key exists but has no associated expire.
440
- def pttl(key)
441
- synchronize do |client|
442
- client.call([:pttl, key])
443
- end
252
+ {
253
+ host: @original_client.host,
254
+ port: @original_client.port,
255
+ db: @original_client.db,
256
+ id: @original_client.id,
257
+ location: @original_client.location
258
+ }
444
259
  end
445
260
 
446
- # Return a serialized version of the value stored at a key.
447
- #
448
- # @param [String] key
449
- # @return [String] serialized_value
450
- def dump(key)
451
- synchronize do |client|
452
- client.call([:dump, key])
453
- end
454
- end
261
+ private
455
262
 
456
- # Create a key using the serialized value, previously obtained using DUMP.
457
- #
458
- # @param [String] key
459
- # @param [String] ttl
460
- # @param [String] serialized_value
461
- # @return [String] `"OK"`
462
- def restore(key, ttl, serialized_value)
463
- synchronize do |client|
464
- client.call([:restore, key, ttl, serialized_value])
465
- end
263
+ def synchronize
264
+ @monitor.synchronize { yield(@client) }
466
265
  end
467
266
 
468
- # Transfer a key from the connected instance to another instance.
469
- #
470
- # @param [String] key
471
- # @param [Hash] options
472
- # - `:host => String`: host of instance to migrate to
473
- # - `:port => Integer`: port of instance to migrate to
474
- # - `:db => Integer`: database to migrate to (default: same as source)
475
- # - `:timeout => Integer`: timeout (default: same as connection timeout)
476
- # @return [String] `"OK"`
477
- def migrate(key, options)
478
- host = options[:host] || raise(RuntimeError, ":host not specified")
479
- port = options[:port] || raise(RuntimeError, ":port not specified")
480
- db = (options[:db] || client.db).to_i
481
- timeout = (options[:timeout] || client.timeout).to_i
482
-
483
- synchronize do |client|
484
- client.call([:migrate, host, port, key, db, timeout])
267
+ def send_command(command, &block)
268
+ @monitor.synchronize do
269
+ @client.call(command, &block)
485
270
  end
486
271
  end
487
272
 
488
- # Delete one or more keys.
489
- #
490
- # @param [String, Array<String>] keys
491
- # @return [Fixnum] number of keys that were deleted
492
- def del(*keys)
493
- synchronize do |client|
494
- client.call([:del] + keys)
273
+ def send_blocking_command(command, timeout, &block)
274
+ @monitor.synchronize do
275
+ @client.call_with_timeout(command, timeout, &block)
495
276
  end
496
277
  end
497
278
 
498
- # Determine if a key exists.
499
- #
500
- # @param [String] key
501
- # @return [Boolean]
502
- def exists(key)
503
- synchronize do |client|
504
- client.call([:exists, key], &Boolify)
505
- end
506
- end
279
+ def _subscription(method, timeout, channels, block)
280
+ return @client.call([method] + channels) if subscribed?
507
281
 
508
- # Find all keys matching the given pattern.
509
- #
510
- # @param [String] pattern
511
- # @return [Array<String>]
512
- def keys(pattern = "*")
513
- synchronize do |client|
514
- client.call([:keys, pattern]) do |reply|
515
- if reply.kind_of?(String)
516
- reply.split(" ")
517
- else
518
- reply
519
- end
282
+ begin
283
+ original, @client = @client, SubscribedClient.new(@client)
284
+ if timeout > 0
285
+ @client.send(method, timeout, *channels, &block)
286
+ else
287
+ @client.send(method, *channels, &block)
520
288
  end
289
+ ensure
290
+ @client = original
521
291
  end
522
292
  end
523
-
524
- # Move a key to another database.
525
- #
526
- # @example Move a key to another database
527
- # redis.set "foo", "bar"
528
- # # => "OK"
529
- # redis.move "foo", 2
530
- # # => true
531
- # redis.exists "foo"
532
- # # => false
533
- # redis.select 2
534
- # # => "OK"
535
- # redis.exists "foo"
536
- # # => true
537
- # redis.get "foo"
538
- # # => "bar"
539
- #
540
- # @param [String] key
541
- # @param [Fixnum] db
542
- # @return [Boolean] whether the key was moved or not
543
- def move(key, db)
544
- synchronize do |client|
545
- client.call([:move, key, db], &Boolify)
546
- end
547
- end
548
-
549
- def object(*args)
550
- synchronize do |client|
551
- client.call([:object] + args)
552
- end
553
- end
554
-
555
- # Return a random key from the keyspace.
556
- #
557
- # @return [String]
558
- def randomkey
559
- synchronize do |client|
560
- client.call([:randomkey])
561
- end
562
- end
563
-
564
- # Rename a key. If the new key already exists it is overwritten.
565
- #
566
- # @param [String] old_name
567
- # @param [String] new_name
568
- # @return [String] `OK`
569
- def rename(old_name, new_name)
570
- synchronize do |client|
571
- client.call([:rename, old_name, new_name])
572
- end
573
- end
574
-
575
- # Rename a key, only if the new key does not exist.
576
- #
577
- # @param [String] old_name
578
- # @param [String] new_name
579
- # @return [Boolean] whether the key was renamed or not
580
- def renamenx(old_name, new_name)
581
- synchronize do |client|
582
- client.call([:renamenx, old_name, new_name], &Boolify)
583
- end
584
- end
585
-
586
- # Sort the elements in a list, set or sorted set.
587
- #
588
- # @example Retrieve the first 2 elements from an alphabetically sorted "list"
589
- # redis.sort("list", :order => "alpha", :limit => [0, 2])
590
- # # => ["a", "b"]
591
- # @example Store an alphabetically descending list in "target"
592
- # redis.sort("list", :order => "desc alpha", :store => "target")
593
- # # => 26
594
- #
595
- # @param [String] key
596
- # @param [Hash] options
597
- # - `:by => String`: use external key to sort elements by
598
- # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
599
- # of `count` elements
600
- # - `:get => [String, Array<String>]`: single key or array of keys to
601
- # retrieve per element in the result
602
- # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
603
- # - `:store => String`: key to store the result at
604
- #
605
- # @return [Array<String>, Array<Array<String>>, Fixnum]
606
- # - when `:get` is not specified, or holds a single element, an array of elements
607
- # - when `:get` is specified, and holds more than one element, an array of
608
- # elements where every element is an array with the result for every
609
- # element specified in `:get`
610
- # - when `:store` is specified, the number of elements in the stored result
611
- def sort(key, options = {})
612
- args = []
613
-
614
- by = options[:by]
615
- args.concat(["BY", by]) if by
616
-
617
- limit = options[:limit]
618
- args.concat(["LIMIT"] + limit) if limit
619
-
620
- get = Array(options[:get])
621
- args.concat(["GET"].product(get).flatten) unless get.empty?
622
-
623
- order = options[:order]
624
- args.concat(order.split(" ")) if order
625
-
626
- store = options[:store]
627
- args.concat(["STORE", store]) if store
628
-
629
- synchronize do |client|
630
- client.call([:sort, key] + args) do |reply|
631
- if get.size > 1 && !store
632
- if reply
633
- reply.each_slice(get.size).to_a
634
- end
635
- else
636
- reply
637
- end
638
- end
639
- end
640
- end
641
-
642
- # Determine the type stored at key.
643
- #
644
- # @param [String] key
645
- # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
646
- def type(key)
647
- synchronize do |client|
648
- client.call([:type, key])
649
- end
650
- end
651
-
652
- # Decrement the integer value of a key by one.
653
- #
654
- # @example
655
- # redis.decr("value")
656
- # # => 4
657
- #
658
- # @param [String] key
659
- # @return [Fixnum] value after decrementing it
660
- def decr(key)
661
- synchronize do |client|
662
- client.call([:decr, key])
663
- end
664
- end
665
-
666
- # Decrement the integer value of a key by the given number.
667
- #
668
- # @example
669
- # redis.decrby("value", 5)
670
- # # => 0
671
- #
672
- # @param [String] key
673
- # @param [Fixnum] decrement
674
- # @return [Fixnum] value after decrementing it
675
- def decrby(key, decrement)
676
- synchronize do |client|
677
- client.call([:decrby, key, decrement])
678
- end
679
- end
680
-
681
- # Increment the integer value of a key by one.
682
- #
683
- # @example
684
- # redis.incr("value")
685
- # # => 6
686
- #
687
- # @param [String] key
688
- # @return [Fixnum] value after incrementing it
689
- def incr(key)
690
- synchronize do |client|
691
- client.call([:incr, key])
692
- end
693
- end
694
-
695
- # Increment the integer value of a key by the given integer number.
696
- #
697
- # @example
698
- # redis.incrby("value", 5)
699
- # # => 10
700
- #
701
- # @param [String] key
702
- # @param [Fixnum] increment
703
- # @return [Fixnum] value after incrementing it
704
- def incrby(key, increment)
705
- synchronize do |client|
706
- client.call([:incrby, key, increment])
707
- end
708
- end
709
-
710
- # Increment the numeric value of a key by the given float number.
711
- #
712
- # @example
713
- # redis.incrbyfloat("value", 1.23)
714
- # # => 1.23
715
- #
716
- # @param [String] key
717
- # @param [Float] increment
718
- # @return [Float] value after incrementing it
719
- def incrbyfloat(key, increment)
720
- synchronize do |client|
721
- client.call([:incrbyfloat, key, increment], &Floatify)
722
- end
723
- end
724
-
725
- # Set the string value of a key.
726
- #
727
- # @param [String] key
728
- # @param [String] value
729
- # @param [Hash] options
730
- # - `:ex => Fixnum`: Set the specified expire time, in seconds.
731
- # - `:px => Fixnum`: Set the specified expire time, in milliseconds.
732
- # - `:nx => true`: Only set the key if it does not already exist.
733
- # - `:xx => true`: Only set the key if it already exist.
734
- # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
735
- def set(key, value, options = {})
736
- args = []
737
-
738
- ex = options[:ex]
739
- args.concat(["EX", ex]) if ex
740
-
741
- px = options[:px]
742
- args.concat(["PX", px]) if px
743
-
744
- nx = options[:nx]
745
- args.concat(["NX"]) if nx
746
-
747
- xx = options[:xx]
748
- args.concat(["XX"]) if xx
749
-
750
- synchronize do |client|
751
- if nx || xx
752
- client.call([:set, key, value.to_s] + args, &BoolifySet)
753
- else
754
- client.call([:set, key, value.to_s] + args)
755
- end
756
- end
757
- end
758
-
759
- alias :[]= :set
760
-
761
- # Set the time to live in seconds of a key.
762
- #
763
- # @param [String] key
764
- # @param [Fixnum] ttl
765
- # @param [String] value
766
- # @return [String] `"OK"`
767
- def setex(key, ttl, value)
768
- synchronize do |client|
769
- client.call([:setex, key, ttl, value.to_s])
770
- end
771
- end
772
-
773
- # Set the time to live in milliseconds of a key.
774
- #
775
- # @param [String] key
776
- # @param [Fixnum] ttl
777
- # @param [String] value
778
- # @return [String] `"OK"`
779
- def psetex(key, ttl, value)
780
- synchronize do |client|
781
- client.call([:psetex, key, ttl, value.to_s])
782
- end
783
- end
784
-
785
- # Set the value of a key, only if the key does not exist.
786
- #
787
- # @param [String] key
788
- # @param [String] value
789
- # @return [Boolean] whether the key was set or not
790
- def setnx(key, value)
791
- synchronize do |client|
792
- client.call([:setnx, key, value.to_s], &Boolify)
793
- end
794
- end
795
-
796
- # Set one or more values.
797
- #
798
- # @example
799
- # redis.mset("key1", "v1", "key2", "v2")
800
- # # => "OK"
801
- #
802
- # @param [Array<String>] args array of keys and values
803
- # @return [String] `"OK"`
804
- #
805
- # @see #mapped_mset
806
- def mset(*args)
807
- synchronize do |client|
808
- client.call([:mset] + args)
809
- end
810
- end
811
-
812
- # Set one or more values.
813
- #
814
- # @example
815
- # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" })
816
- # # => "OK"
817
- #
818
- # @param [Hash] hash keys mapping to values
819
- # @return [String] `"OK"`
820
- #
821
- # @see #mset
822
- def mapped_mset(hash)
823
- mset(hash.to_a.flatten)
824
- end
825
-
826
- # Set one or more values, only if none of the keys exist.
827
- #
828
- # @example
829
- # redis.msetnx("key1", "v1", "key2", "v2")
830
- # # => true
831
- #
832
- # @param [Array<String>] args array of keys and values
833
- # @return [Boolean] whether or not all values were set
834
- #
835
- # @see #mapped_msetnx
836
- def msetnx(*args)
837
- synchronize do |client|
838
- client.call([:msetnx] + args, &Boolify)
839
- end
840
- end
841
-
842
- # Set one or more values, only if none of the keys exist.
843
- #
844
- # @example
845
- # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
846
- # # => true
847
- #
848
- # @param [Hash] hash keys mapping to values
849
- # @return [Boolean] whether or not all values were set
850
- #
851
- # @see #msetnx
852
- def mapped_msetnx(hash)
853
- msetnx(hash.to_a.flatten)
854
- end
855
-
856
- # Get the value of a key.
857
- #
858
- # @param [String] key
859
- # @return [String]
860
- def get(key)
861
- synchronize do |client|
862
- client.call([:get, key])
863
- end
864
- end
865
-
866
- alias :[] :get
867
-
868
- # Get the values of all the given keys.
869
- #
870
- # @example
871
- # redis.mget("key1", "key1")
872
- # # => ["v1", "v2"]
873
- #
874
- # @param [Array<String>] keys
875
- # @return [Array<String>] an array of values for the specified keys
876
- #
877
- # @see #mapped_mget
878
- def mget(*keys, &blk)
879
- synchronize do |client|
880
- client.call([:mget] + keys, &blk)
881
- end
882
- end
883
-
884
- # Get the values of all the given keys.
885
- #
886
- # @example
887
- # redis.mapped_mget("key1", "key2")
888
- # # => { "key1" => "v1", "key2" => "v2" }
889
- #
890
- # @param [Array<String>] keys array of keys
891
- # @return [Hash] a hash mapping the specified keys to their values
892
- #
893
- # @see #mget
894
- def mapped_mget(*keys)
895
- mget(*keys) do |reply|
896
- if reply.kind_of?(Array)
897
- Hash[keys.zip(reply)]
898
- else
899
- reply
900
- end
901
- end
902
- end
903
-
904
- # Overwrite part of a string at key starting at the specified offset.
905
- #
906
- # @param [String] key
907
- # @param [Fixnum] offset byte offset
908
- # @param [String] value
909
- # @return [Fixnum] length of the string after it was modified
910
- def setrange(key, offset, value)
911
- synchronize do |client|
912
- client.call([:setrange, key, offset, value.to_s])
913
- end
914
- end
915
-
916
- # Get a substring of the string stored at a key.
917
- #
918
- # @param [String] key
919
- # @param [Fixnum] start zero-based start offset
920
- # @param [Fixnum] stop zero-based end offset. Use -1 for representing
921
- # the end of the string
922
- # @return [Fixnum] `0` or `1`
923
- def getrange(key, start, stop)
924
- synchronize do |client|
925
- client.call([:getrange, key, start, stop])
926
- end
927
- end
928
-
929
- # Sets or clears the bit at offset in the string value stored at key.
930
- #
931
- # @param [String] key
932
- # @param [Fixnum] offset bit offset
933
- # @param [Fixnum] value bit value `0` or `1`
934
- # @return [Fixnum] the original bit value stored at `offset`
935
- def setbit(key, offset, value)
936
- synchronize do |client|
937
- client.call([:setbit, key, offset, value])
938
- end
939
- end
940
-
941
- # Returns the bit value at offset in the string value stored at key.
942
- #
943
- # @param [String] key
944
- # @param [Fixnum] offset bit offset
945
- # @return [Fixnum] `0` or `1`
946
- def getbit(key, offset)
947
- synchronize do |client|
948
- client.call([:getbit, key, offset])
949
- end
950
- end
951
-
952
- # Append a value to a key.
953
- #
954
- # @param [String] key
955
- # @param [String] value value to append
956
- # @return [Fixnum] length of the string after appending
957
- def append(key, value)
958
- synchronize do |client|
959
- client.call([:append, key, value])
960
- end
961
- end
962
-
963
- # Count the number of set bits in a range of the string value stored at key.
964
- #
965
- # @param [String] key
966
- # @param [Fixnum] start start index
967
- # @param [Fixnum] stop stop index
968
- # @return [Fixnum] the number of bits set to 1
969
- def bitcount(key, start = 0, stop = -1)
970
- synchronize do |client|
971
- client.call([:bitcount, key, start, stop])
972
- end
973
- end
974
-
975
- # Perform a bitwise operation between strings and store the resulting string in a key.
976
- #
977
- # @param [String] operation e.g. `and`, `or`, `xor`, `not`
978
- # @param [String] destkey destination key
979
- # @param [String, Array<String>] keys one or more source keys to perform `operation`
980
- # @return [Fixnum] the length of the string stored in `destkey`
981
- def bitop(operation, destkey, *keys)
982
- synchronize do |client|
983
- client.call([:bitop, operation, destkey] + keys)
984
- end
985
- end
986
-
987
- # Return the position of the first bit set to 1 or 0 in a string.
988
- #
989
- # @param [String] key
990
- # @param [Fixnum] bit whether to look for the first 1 or 0 bit
991
- # @param [Fixnum] start start index
992
- # @param [Fixnum] stop stop index
993
- # @return [Fixnum] the position of the first 1/0 bit.
994
- # -1 if looking for 1 and it is not found or start and stop are given.
995
- def bitpos(key, bit, start=nil, stop=nil)
996
- if stop and not start
997
- raise(ArgumentError, 'stop parameter specified without start parameter')
998
- end
999
-
1000
- synchronize do |client|
1001
- command = [:bitpos, key, bit]
1002
- command << start if start
1003
- command << stop if stop
1004
- client.call(command)
1005
- end
1006
- end
1007
-
1008
- # Set the string value of a key and return its old value.
1009
- #
1010
- # @param [String] key
1011
- # @param [String] value value to replace the current value with
1012
- # @return [String] the old value stored in the key, or `nil` if the key
1013
- # did not exist
1014
- def getset(key, value)
1015
- synchronize do |client|
1016
- client.call([:getset, key, value.to_s])
1017
- end
1018
- end
1019
-
1020
- # Get the length of the value stored in a key.
1021
- #
1022
- # @param [String] key
1023
- # @return [Fixnum] the length of the value stored in the key, or 0
1024
- # if the key does not exist
1025
- def strlen(key)
1026
- synchronize do |client|
1027
- client.call([:strlen, key])
1028
- end
1029
- end
1030
-
1031
- # Get the length of a list.
1032
- #
1033
- # @param [String] key
1034
- # @return [Fixnum]
1035
- def llen(key)
1036
- synchronize do |client|
1037
- client.call([:llen, key])
1038
- end
1039
- end
1040
-
1041
- # Prepend one or more values to a list, creating the list if it doesn't exist
1042
- #
1043
- # @param [String] key
1044
- # @param [String, Array] value string value, or array of string values to push
1045
- # @return [Fixnum] the length of the list after the push operation
1046
- def lpush(key, value)
1047
- synchronize do |client|
1048
- client.call([:lpush, key, value])
1049
- end
1050
- end
1051
-
1052
- # Prepend a value to a list, only if the list exists.
1053
- #
1054
- # @param [String] key
1055
- # @param [String] value
1056
- # @return [Fixnum] the length of the list after the push operation
1057
- def lpushx(key, value)
1058
- synchronize do |client|
1059
- client.call([:lpushx, key, value])
1060
- end
1061
- end
1062
-
1063
- # Append one or more values to a list, creating the list if it doesn't exist
1064
- #
1065
- # @param [String] key
1066
- # @param [String] value
1067
- # @return [Fixnum] the length of the list after the push operation
1068
- def rpush(key, value)
1069
- synchronize do |client|
1070
- client.call([:rpush, key, value])
1071
- end
1072
- end
1073
-
1074
- # Append a value to a list, only if the list exists.
1075
- #
1076
- # @param [String] key
1077
- # @param [String] value
1078
- # @return [Fixnum] the length of the list after the push operation
1079
- def rpushx(key, value)
1080
- synchronize do |client|
1081
- client.call([:rpushx, key, value])
1082
- end
1083
- end
1084
-
1085
- # Remove and get the first element in a list.
1086
- #
1087
- # @param [String] key
1088
- # @return [String]
1089
- def lpop(key)
1090
- synchronize do |client|
1091
- client.call([:lpop, key])
1092
- end
1093
- end
1094
-
1095
- # Remove and get the last element in a list.
1096
- #
1097
- # @param [String] key
1098
- # @return [String]
1099
- def rpop(key)
1100
- synchronize do |client|
1101
- client.call([:rpop, key])
1102
- end
1103
- end
1104
-
1105
- # Remove the last element in a list, append it to another list and return it.
1106
- #
1107
- # @param [String] source source key
1108
- # @param [String] destination destination key
1109
- # @return [nil, String] the element, or nil when the source key does not exist
1110
- def rpoplpush(source, destination)
1111
- synchronize do |client|
1112
- client.call([:rpoplpush, source, destination])
1113
- end
1114
- end
1115
-
1116
- def _bpop(cmd, args)
1117
- options = {}
1118
-
1119
- case args.last
1120
- when Hash
1121
- options = args.pop
1122
- when Integer
1123
- # Issue deprecation notice in obnoxious mode...
1124
- options[:timeout] = args.pop
1125
- end
1126
-
1127
- if args.size > 1
1128
- # Issue deprecation notice in obnoxious mode...
1129
- end
1130
-
1131
- keys = args.flatten
1132
- timeout = options[:timeout] || 0
1133
-
1134
- synchronize do |client|
1135
- command = [cmd, keys, timeout]
1136
- timeout += client.timeout if timeout > 0
1137
- client.call_with_timeout(command, timeout)
1138
- end
1139
- end
1140
-
1141
- # Remove and get the first element in a list, or block until one is available.
1142
- #
1143
- # @example With timeout
1144
- # list, element = redis.blpop("list", :timeout => 5)
1145
- # # => nil on timeout
1146
- # # => ["list", "element"] on success
1147
- # @example Without timeout
1148
- # list, element = redis.blpop("list")
1149
- # # => ["list", "element"]
1150
- # @example Blocking pop on multiple lists
1151
- # list, element = redis.blpop(["list", "another_list"])
1152
- # # => ["list", "element"]
1153
- #
1154
- # @param [String, Array<String>] keys one or more keys to perform the
1155
- # blocking pop on
1156
- # @param [Hash] options
1157
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1158
- #
1159
- # @return [nil, [String, String]]
1160
- # - `nil` when the operation timed out
1161
- # - tuple of the list that was popped from and element was popped otherwise
1162
- def blpop(*args)
1163
- _bpop(:blpop, args)
1164
- end
1165
-
1166
- # Remove and get the last element in a list, or block until one is available.
1167
- #
1168
- # @param [String, Array<String>] keys one or more keys to perform the
1169
- # blocking pop on
1170
- # @param [Hash] options
1171
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1172
- #
1173
- # @return [nil, [String, String]]
1174
- # - `nil` when the operation timed out
1175
- # - tuple of the list that was popped from and element was popped otherwise
1176
- #
1177
- # @see #blpop
1178
- def brpop(*args)
1179
- _bpop(:brpop, args)
1180
- end
1181
-
1182
- # Pop a value from a list, push it to another list and return it; or block
1183
- # until one is available.
1184
- #
1185
- # @param [String] source source key
1186
- # @param [String] destination destination key
1187
- # @param [Hash] options
1188
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1189
- #
1190
- # @return [nil, String]
1191
- # - `nil` when the operation timed out
1192
- # - the element was popped and pushed otherwise
1193
- def brpoplpush(source, destination, options = {})
1194
- case options
1195
- when Integer
1196
- # Issue deprecation notice in obnoxious mode...
1197
- options = { :timeout => options }
1198
- end
1199
-
1200
- timeout = options[:timeout] || 0
1201
-
1202
- synchronize do |client|
1203
- command = [:brpoplpush, source, destination, timeout]
1204
- timeout += client.timeout if timeout > 0
1205
- client.call_with_timeout(command, timeout)
1206
- end
1207
- end
1208
-
1209
- # Get an element from a list by its index.
1210
- #
1211
- # @param [String] key
1212
- # @param [Fixnum] index
1213
- # @return [String]
1214
- def lindex(key, index)
1215
- synchronize do |client|
1216
- client.call([:lindex, key, index])
1217
- end
1218
- end
1219
-
1220
- # Insert an element before or after another element in a list.
1221
- #
1222
- # @param [String] key
1223
- # @param [String, Symbol] where `BEFORE` or `AFTER`
1224
- # @param [String] pivot reference element
1225
- # @param [String] value
1226
- # @return [Fixnum] length of the list after the insert operation, or `-1`
1227
- # when the element `pivot` was not found
1228
- def linsert(key, where, pivot, value)
1229
- synchronize do |client|
1230
- client.call([:linsert, key, where, pivot, value])
1231
- end
1232
- end
1233
-
1234
- # Get a range of elements from a list.
1235
- #
1236
- # @param [String] key
1237
- # @param [Fixnum] start start index
1238
- # @param [Fixnum] stop stop index
1239
- # @return [Array<String>]
1240
- def lrange(key, start, stop)
1241
- synchronize do |client|
1242
- client.call([:lrange, key, start, stop])
1243
- end
1244
- end
1245
-
1246
- # Remove elements from a list.
1247
- #
1248
- # @param [String] key
1249
- # @param [Fixnum] count number of elements to remove. Use a positive
1250
- # value to remove the first `count` occurrences of `value`. A negative
1251
- # value to remove the last `count` occurrences of `value`. Or zero, to
1252
- # remove all occurrences of `value` from the list.
1253
- # @param [String] value
1254
- # @return [Fixnum] the number of removed elements
1255
- def lrem(key, count, value)
1256
- synchronize do |client|
1257
- client.call([:lrem, key, count, value])
1258
- end
1259
- end
1260
-
1261
- # Set the value of an element in a list by its index.
1262
- #
1263
- # @param [String] key
1264
- # @param [Fixnum] index
1265
- # @param [String] value
1266
- # @return [String] `OK`
1267
- def lset(key, index, value)
1268
- synchronize do |client|
1269
- client.call([:lset, key, index, value])
1270
- end
1271
- end
1272
-
1273
- # Trim a list to the specified range.
1274
- #
1275
- # @param [String] key
1276
- # @param [Fixnum] start start index
1277
- # @param [Fixnum] stop stop index
1278
- # @return [String] `OK`
1279
- def ltrim(key, start, stop)
1280
- synchronize do |client|
1281
- client.call([:ltrim, key, start, stop])
1282
- end
1283
- end
1284
-
1285
- # Get the number of members in a set.
1286
- #
1287
- # @param [String] key
1288
- # @return [Fixnum]
1289
- def scard(key)
1290
- synchronize do |client|
1291
- client.call([:scard, key])
1292
- end
1293
- end
1294
-
1295
- # Add one or more members to a set.
1296
- #
1297
- # @param [String] key
1298
- # @param [String, Array<String>] member one member, or array of members
1299
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1300
- # holding whether or not adding the member succeeded, or `Fixnum` when an
1301
- # array of members is specified, holding the number of members that were
1302
- # successfully added
1303
- def sadd(key, member)
1304
- synchronize do |client|
1305
- client.call([:sadd, key, member]) do |reply|
1306
- if member.is_a? Array
1307
- # Variadic: return integer
1308
- reply
1309
- else
1310
- # Single argument: return boolean
1311
- Boolify.call(reply)
1312
- end
1313
- end
1314
- end
1315
- end
1316
-
1317
- # Remove one or more members from a set.
1318
- #
1319
- # @param [String] key
1320
- # @param [String, Array<String>] member one member, or array of members
1321
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1322
- # holding whether or not removing the member succeeded, or `Fixnum` when an
1323
- # array of members is specified, holding the number of members that were
1324
- # successfully removed
1325
- def srem(key, member)
1326
- synchronize do |client|
1327
- client.call([:srem, key, member]) do |reply|
1328
- if member.is_a? Array
1329
- # Variadic: return integer
1330
- reply
1331
- else
1332
- # Single argument: return boolean
1333
- Boolify.call(reply)
1334
- end
1335
- end
1336
- end
1337
- end
1338
-
1339
- # Remove and return one or more random member from a set.
1340
- #
1341
- # @param [String] key
1342
- # @return [String]
1343
- # @param [Fixnum] count
1344
- def spop(key, count = nil)
1345
- synchronize do |client|
1346
- if count.nil?
1347
- client.call([:spop, key])
1348
- else
1349
- client.call([:spop, key, count])
1350
- end
1351
- end
1352
- end
1353
-
1354
- # Get one or more random members from a set.
1355
- #
1356
- # @param [String] key
1357
- # @param [Fixnum] count
1358
- # @return [String]
1359
- def srandmember(key, count = nil)
1360
- synchronize do |client|
1361
- if count.nil?
1362
- client.call([:srandmember, key])
1363
- else
1364
- client.call([:srandmember, key, count])
1365
- end
1366
- end
1367
- end
1368
-
1369
- # Move a member from one set to another.
1370
- #
1371
- # @param [String] source source key
1372
- # @param [String] destination destination key
1373
- # @param [String] member member to move from `source` to `destination`
1374
- # @return [Boolean]
1375
- def smove(source, destination, member)
1376
- synchronize do |client|
1377
- client.call([:smove, source, destination, member], &Boolify)
1378
- end
1379
- end
1380
-
1381
- # Determine if a given value is a member of a set.
1382
- #
1383
- # @param [String] key
1384
- # @param [String] member
1385
- # @return [Boolean]
1386
- def sismember(key, member)
1387
- synchronize do |client|
1388
- client.call([:sismember, key, member], &Boolify)
1389
- end
1390
- end
1391
-
1392
- # Get all the members in a set.
1393
- #
1394
- # @param [String] key
1395
- # @return [Array<String>]
1396
- def smembers(key)
1397
- synchronize do |client|
1398
- client.call([:smembers, key])
1399
- end
1400
- end
1401
-
1402
- # Subtract multiple sets.
1403
- #
1404
- # @param [String, Array<String>] keys keys pointing to sets to subtract
1405
- # @return [Array<String>] members in the difference
1406
- def sdiff(*keys)
1407
- synchronize do |client|
1408
- client.call([:sdiff] + keys)
1409
- end
1410
- end
1411
-
1412
- # Subtract multiple sets and store the resulting set in a key.
1413
- #
1414
- # @param [String] destination destination key
1415
- # @param [String, Array<String>] keys keys pointing to sets to subtract
1416
- # @return [Fixnum] number of elements in the resulting set
1417
- def sdiffstore(destination, *keys)
1418
- synchronize do |client|
1419
- client.call([:sdiffstore, destination] + keys)
1420
- end
1421
- end
1422
-
1423
- # Intersect multiple sets.
1424
- #
1425
- # @param [String, Array<String>] keys keys pointing to sets to intersect
1426
- # @return [Array<String>] members in the intersection
1427
- def sinter(*keys)
1428
- synchronize do |client|
1429
- client.call([:sinter] + keys)
1430
- end
1431
- end
1432
-
1433
- # Intersect multiple sets and store the resulting set in a key.
1434
- #
1435
- # @param [String] destination destination key
1436
- # @param [String, Array<String>] keys keys pointing to sets to intersect
1437
- # @return [Fixnum] number of elements in the resulting set
1438
- def sinterstore(destination, *keys)
1439
- synchronize do |client|
1440
- client.call([:sinterstore, destination] + keys)
1441
- end
1442
- end
1443
-
1444
- # Add multiple sets.
1445
- #
1446
- # @param [String, Array<String>] keys keys pointing to sets to unify
1447
- # @return [Array<String>] members in the union
1448
- def sunion(*keys)
1449
- synchronize do |client|
1450
- client.call([:sunion] + keys)
1451
- end
1452
- end
1453
-
1454
- # Add multiple sets and store the resulting set in a key.
1455
- #
1456
- # @param [String] destination destination key
1457
- # @param [String, Array<String>] keys keys pointing to sets to unify
1458
- # @return [Fixnum] number of elements in the resulting set
1459
- def sunionstore(destination, *keys)
1460
- synchronize do |client|
1461
- client.call([:sunionstore, destination] + keys)
1462
- end
1463
- end
1464
-
1465
- # Get the number of members in a sorted set.
1466
- #
1467
- # @example
1468
- # redis.zcard("zset")
1469
- # # => 4
1470
- #
1471
- # @param [String] key
1472
- # @return [Fixnum]
1473
- def zcard(key)
1474
- synchronize do |client|
1475
- client.call([:zcard, key])
1476
- end
1477
- end
1478
-
1479
- # Add one or more members to a sorted set, or update the score for members
1480
- # that already exist.
1481
- #
1482
- # @example Add a single `[score, member]` pair to a sorted set
1483
- # redis.zadd("zset", 32.0, "member")
1484
- # @example Add an array of `[score, member]` pairs to a sorted set
1485
- # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
1486
- #
1487
- # @param [String] key
1488
- # @param [[Float, String], Array<[Float, String]>] args
1489
- # - a single `[score, member]` pair
1490
- # - an array of `[score, member]` pairs
1491
- # @param [Hash] options
1492
- # - `:xx => true`: Only update elements that already exist (never
1493
- # add elements)
1494
- # - `:nx => true`: Don't update already existing elements (always
1495
- # add new elements)
1496
- # - `:ch => true`: Modify the return value from the number of new
1497
- # elements added, to the total number of elements changed (CH is an
1498
- # abbreviation of changed); changed elements are new elements added
1499
- # and elements already existing for which the score was updated
1500
- # - `:incr => true`: When this option is specified ZADD acts like
1501
- # ZINCRBY; only one score-element pair can be specified in this mode
1502
- #
1503
- # @return [Boolean, Fixnum, Float]
1504
- # - `Boolean` when a single pair is specified, holding whether or not it was
1505
- # **added** to the sorted set.
1506
- # - `Fixnum` when an array of pairs is specified, holding the number of
1507
- # pairs that were **added** to the sorted set.
1508
- # - `Float` when option :incr is specified, holding the score of the member
1509
- # after incrementing it.
1510
- def zadd(key, *args) #, options
1511
- zadd_options = []
1512
- if args.last.is_a?(Hash)
1513
- options = args.pop
1514
-
1515
- nx = options[:nx]
1516
- zadd_options << "NX" if nx
1517
-
1518
- xx = options[:xx]
1519
- zadd_options << "XX" if xx
1520
-
1521
- ch = options[:ch]
1522
- zadd_options << "CH" if ch
1523
-
1524
- incr = options[:incr]
1525
- zadd_options << "INCR" if incr
1526
- end
1527
-
1528
- synchronize do |client|
1529
- if args.size == 1 && args[0].is_a?(Array)
1530
- # Variadic: return float if INCR, integer if !INCR
1531
- client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil))
1532
- elsif args.size == 2
1533
- # Single pair: return float if INCR, boolean if !INCR
1534
- client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify))
1535
- else
1536
- raise ArgumentError, "wrong number of arguments"
1537
- end
1538
- end
1539
- end
1540
-
1541
- # Increment the score of a member in a sorted set.
1542
- #
1543
- # @example
1544
- # redis.zincrby("zset", 32.0, "a")
1545
- # # => 64.0
1546
- #
1547
- # @param [String] key
1548
- # @param [Float] increment
1549
- # @param [String] member
1550
- # @return [Float] score of the member after incrementing it
1551
- def zincrby(key, increment, member)
1552
- synchronize do |client|
1553
- client.call([:zincrby, key, increment, member], &Floatify)
1554
- end
1555
- end
1556
-
1557
- # Remove one or more members from a sorted set.
1558
- #
1559
- # @example Remove a single member from a sorted set
1560
- # redis.zrem("zset", "a")
1561
- # @example Remove an array of members from a sorted set
1562
- # redis.zrem("zset", ["a", "b"])
1563
- #
1564
- # @param [String] key
1565
- # @param [String, Array<String>] member
1566
- # - a single member
1567
- # - an array of members
1568
- #
1569
- # @return [Boolean, Fixnum]
1570
- # - `Boolean` when a single member is specified, holding whether or not it
1571
- # was removed from the sorted set
1572
- # - `Fixnum` when an array of pairs is specified, holding the number of
1573
- # members that were removed to the sorted set
1574
- def zrem(key, member)
1575
- synchronize do |client|
1576
- client.call([:zrem, key, member]) do |reply|
1577
- if member.is_a? Array
1578
- # Variadic: return integer
1579
- reply
1580
- else
1581
- # Single argument: return boolean
1582
- Boolify.call(reply)
1583
- end
1584
- end
1585
- end
1586
- end
1587
-
1588
- # Get the score associated with the given member in a sorted set.
1589
- #
1590
- # @example Get the score for member "a"
1591
- # redis.zscore("zset", "a")
1592
- # # => 32.0
1593
- #
1594
- # @param [String] key
1595
- # @param [String] member
1596
- # @return [Float] score of the member
1597
- def zscore(key, member)
1598
- synchronize do |client|
1599
- client.call([:zscore, key, member], &Floatify)
1600
- end
1601
- end
1602
-
1603
- # Return a range of members in a sorted set, by index.
1604
- #
1605
- # @example Retrieve all members from a sorted set
1606
- # redis.zrange("zset", 0, -1)
1607
- # # => ["a", "b"]
1608
- # @example Retrieve all members and their scores from a sorted set
1609
- # redis.zrange("zset", 0, -1, :with_scores => true)
1610
- # # => [["a", 32.0], ["b", 64.0]]
1611
- #
1612
- # @param [String] key
1613
- # @param [Fixnum] start start index
1614
- # @param [Fixnum] stop stop index
1615
- # @param [Hash] options
1616
- # - `:with_scores => true`: include scores in output
1617
- #
1618
- # @return [Array<String>, Array<[String, Float]>]
1619
- # - when `:with_scores` is not specified, an array of members
1620
- # - when `:with_scores` is specified, an array with `[member, score]` pairs
1621
- def zrange(key, start, stop, options = {})
1622
- args = []
1623
-
1624
- with_scores = options[:with_scores] || options[:withscores]
1625
-
1626
- if with_scores
1627
- args << "WITHSCORES"
1628
- block = FloatifyPairs
1629
- end
1630
-
1631
- synchronize do |client|
1632
- client.call([:zrange, key, start, stop] + args, &block)
1633
- end
1634
- end
1635
-
1636
- # Return a range of members in a sorted set, by index, with scores ordered
1637
- # from high to low.
1638
- #
1639
- # @example Retrieve all members from a sorted set
1640
- # redis.zrevrange("zset", 0, -1)
1641
- # # => ["b", "a"]
1642
- # @example Retrieve all members and their scores from a sorted set
1643
- # redis.zrevrange("zset", 0, -1, :with_scores => true)
1644
- # # => [["b", 64.0], ["a", 32.0]]
1645
- #
1646
- # @see #zrange
1647
- def zrevrange(key, start, stop, options = {})
1648
- args = []
1649
-
1650
- with_scores = options[:with_scores] || options[:withscores]
1651
-
1652
- if with_scores
1653
- args << "WITHSCORES"
1654
- block = FloatifyPairs
1655
- end
1656
-
1657
- synchronize do |client|
1658
- client.call([:zrevrange, key, start, stop] + args, &block)
1659
- end
1660
- end
1661
-
1662
- # Determine the index of a member in a sorted set.
1663
- #
1664
- # @param [String] key
1665
- # @param [String] member
1666
- # @return [Fixnum]
1667
- def zrank(key, member)
1668
- synchronize do |client|
1669
- client.call([:zrank, key, member])
1670
- end
1671
- end
1672
-
1673
- # Determine the index of a member in a sorted set, with scores ordered from
1674
- # high to low.
1675
- #
1676
- # @param [String] key
1677
- # @param [String] member
1678
- # @return [Fixnum]
1679
- def zrevrank(key, member)
1680
- synchronize do |client|
1681
- client.call([:zrevrank, key, member])
1682
- end
1683
- end
1684
-
1685
- # Remove all members in a sorted set within the given indexes.
1686
- #
1687
- # @example Remove first 5 members
1688
- # redis.zremrangebyrank("zset", 0, 4)
1689
- # # => 5
1690
- # @example Remove last 5 members
1691
- # redis.zremrangebyrank("zset", -5, -1)
1692
- # # => 5
1693
- #
1694
- # @param [String] key
1695
- # @param [Fixnum] start start index
1696
- # @param [Fixnum] stop stop index
1697
- # @return [Fixnum] number of members that were removed
1698
- def zremrangebyrank(key, start, stop)
1699
- synchronize do |client|
1700
- client.call([:zremrangebyrank, key, start, stop])
1701
- end
1702
- end
1703
-
1704
- # Return a range of members with the same score in a sorted set, by lexicographical ordering
1705
- #
1706
- # @example Retrieve members matching a
1707
- # redis.zrangebylex("zset", "[a", "[a\xff")
1708
- # # => ["aaren", "aarika", "abagael", "abby"]
1709
- # @example Retrieve the first 2 members matching a
1710
- # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1711
- # # => ["aaren", "aarika"]
1712
- #
1713
- # @param [String] key
1714
- # @param [String] min
1715
- # - inclusive minimum is specified by prefixing `(`
1716
- # - exclusive minimum is specified by prefixing `[`
1717
- # @param [String] max
1718
- # - inclusive maximum is specified by prefixing `(`
1719
- # - exclusive maximum is specified by prefixing `[`
1720
- # @param [Hash] options
1721
- # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1722
- # `count` members
1723
- #
1724
- # @return [Array<String>, Array<[String, Float]>]
1725
- def zrangebylex(key, min, max, options = {})
1726
- args = []
1727
-
1728
- limit = options[:limit]
1729
- args.concat(["LIMIT"] + limit) if limit
1730
-
1731
- synchronize do |client|
1732
- client.call([:zrangebylex, key, min, max] + args)
1733
- end
1734
- end
1735
-
1736
- # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
1737
- # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
1738
- #
1739
- # @example Retrieve members matching a
1740
- # redis.zrevrangebylex("zset", "[a", "[a\xff")
1741
- # # => ["abbygail", "abby", "abagael", "aaren"]
1742
- # @example Retrieve the last 2 members matching a
1743
- # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1744
- # # => ["abbygail", "abby"]
1745
- #
1746
- # @see #zrangebylex
1747
- def zrevrangebylex(key, max, min, options = {})
1748
- args = []
1749
-
1750
- limit = options[:limit]
1751
- args.concat(["LIMIT"] + limit) if limit
1752
-
1753
- synchronize do |client|
1754
- client.call([:zrevrangebylex, key, max, min] + args)
1755
- end
1756
- end
1757
-
1758
- # Return a range of members in a sorted set, by score.
1759
- #
1760
- # @example Retrieve members with score `>= 5` and `< 100`
1761
- # redis.zrangebyscore("zset", "5", "(100")
1762
- # # => ["a", "b"]
1763
- # @example Retrieve the first 2 members with score `>= 0`
1764
- # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
1765
- # # => ["a", "b"]
1766
- # @example Retrieve members and their scores with scores `> 5`
1767
- # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
1768
- # # => [["a", 32.0], ["b", 64.0]]
1769
- #
1770
- # @param [String] key
1771
- # @param [String] min
1772
- # - inclusive minimum score is specified verbatim
1773
- # - exclusive minimum score is specified by prefixing `(`
1774
- # @param [String] max
1775
- # - inclusive maximum score is specified verbatim
1776
- # - exclusive maximum score is specified by prefixing `(`
1777
- # @param [Hash] options
1778
- # - `:with_scores => true`: include scores in output
1779
- # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1780
- # `count` members
1781
- #
1782
- # @return [Array<String>, Array<[String, Float]>]
1783
- # - when `:with_scores` is not specified, an array of members
1784
- # - when `:with_scores` is specified, an array with `[member, score]` pairs
1785
- def zrangebyscore(key, min, max, options = {})
1786
- args = []
1787
-
1788
- with_scores = options[:with_scores] || options[:withscores]
1789
-
1790
- if with_scores
1791
- args << "WITHSCORES"
1792
- block = FloatifyPairs
1793
- end
1794
-
1795
- limit = options[:limit]
1796
- args.concat(["LIMIT"] + limit) if limit
1797
-
1798
- synchronize do |client|
1799
- client.call([:zrangebyscore, key, min, max] + args, &block)
1800
- end
1801
- end
1802
-
1803
- # Return a range of members in a sorted set, by score, with scores ordered
1804
- # from high to low.
1805
- #
1806
- # @example Retrieve members with score `< 100` and `>= 5`
1807
- # redis.zrevrangebyscore("zset", "(100", "5")
1808
- # # => ["b", "a"]
1809
- # @example Retrieve the first 2 members with score `<= 0`
1810
- # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
1811
- # # => ["b", "a"]
1812
- # @example Retrieve members and their scores with scores `> 5`
1813
- # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
1814
- # # => [["b", 64.0], ["a", 32.0]]
1815
- #
1816
- # @see #zrangebyscore
1817
- def zrevrangebyscore(key, max, min, options = {})
1818
- args = []
1819
-
1820
- with_scores = options[:with_scores] || options[:withscores]
1821
-
1822
- if with_scores
1823
- args << ["WITHSCORES"]
1824
- block = FloatifyPairs
1825
- end
1826
-
1827
- limit = options[:limit]
1828
- args.concat(["LIMIT"] + limit) if limit
1829
-
1830
- synchronize do |client|
1831
- client.call([:zrevrangebyscore, key, max, min] + args, &block)
1832
- end
1833
- end
1834
-
1835
- # Remove all members in a sorted set within the given scores.
1836
- #
1837
- # @example Remove members with score `>= 5` and `< 100`
1838
- # redis.zremrangebyscore("zset", "5", "(100")
1839
- # # => 2
1840
- # @example Remove members with scores `> 5`
1841
- # redis.zremrangebyscore("zset", "(5", "+inf")
1842
- # # => 2
1843
- #
1844
- # @param [String] key
1845
- # @param [String] min
1846
- # - inclusive minimum score is specified verbatim
1847
- # - exclusive minimum score is specified by prefixing `(`
1848
- # @param [String] max
1849
- # - inclusive maximum score is specified verbatim
1850
- # - exclusive maximum score is specified by prefixing `(`
1851
- # @return [Fixnum] number of members that were removed
1852
- def zremrangebyscore(key, min, max)
1853
- synchronize do |client|
1854
- client.call([:zremrangebyscore, key, min, max])
1855
- end
1856
- end
1857
-
1858
- # Count the members in a sorted set with scores within the given values.
1859
- #
1860
- # @example Count members with score `>= 5` and `< 100`
1861
- # redis.zcount("zset", "5", "(100")
1862
- # # => 2
1863
- # @example Count members with scores `> 5`
1864
- # redis.zcount("zset", "(5", "+inf")
1865
- # # => 2
1866
- #
1867
- # @param [String] key
1868
- # @param [String] min
1869
- # - inclusive minimum score is specified verbatim
1870
- # - exclusive minimum score is specified by prefixing `(`
1871
- # @param [String] max
1872
- # - inclusive maximum score is specified verbatim
1873
- # - exclusive maximum score is specified by prefixing `(`
1874
- # @return [Fixnum] number of members in within the specified range
1875
- def zcount(key, min, max)
1876
- synchronize do |client|
1877
- client.call([:zcount, key, min, max])
1878
- end
1879
- end
1880
-
1881
- # Intersect multiple sorted sets and store the resulting sorted set in a new
1882
- # key.
1883
- #
1884
- # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
1885
- # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1886
- # # => 4
1887
- #
1888
- # @param [String] destination destination key
1889
- # @param [Array<String>] keys source keys
1890
- # @param [Hash] options
1891
- # - `:weights => [Float, Float, ...]`: weights to associate with source
1892
- # sorted sets
1893
- # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1894
- # @return [Fixnum] number of elements in the resulting sorted set
1895
- def zinterstore(destination, keys, options = {})
1896
- args = []
1897
-
1898
- weights = options[:weights]
1899
- args.concat(["WEIGHTS"] + weights) if weights
1900
-
1901
- aggregate = options[:aggregate]
1902
- args.concat(["AGGREGATE", aggregate]) if aggregate
1903
-
1904
- synchronize do |client|
1905
- client.call([:zinterstore, destination, keys.size] + keys + args)
1906
- end
1907
- end
1908
-
1909
- # Add multiple sorted sets and store the resulting sorted set in a new key.
1910
- #
1911
- # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
1912
- # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1913
- # # => 8
1914
- #
1915
- # @param [String] destination destination key
1916
- # @param [Array<String>] keys source keys
1917
- # @param [Hash] options
1918
- # - `:weights => [Float, Float, ...]`: weights to associate with source
1919
- # sorted sets
1920
- # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1921
- # @return [Fixnum] number of elements in the resulting sorted set
1922
- def zunionstore(destination, keys, options = {})
1923
- args = []
1924
-
1925
- weights = options[:weights]
1926
- args.concat(["WEIGHTS"] + weights) if weights
1927
-
1928
- aggregate = options[:aggregate]
1929
- args.concat(["AGGREGATE", aggregate]) if aggregate
1930
-
1931
- synchronize do |client|
1932
- client.call([:zunionstore, destination, keys.size] + keys + args)
1933
- end
1934
- end
1935
-
1936
- # Get the number of fields in a hash.
1937
- #
1938
- # @param [String] key
1939
- # @return [Fixnum] number of fields in the hash
1940
- def hlen(key)
1941
- synchronize do |client|
1942
- client.call([:hlen, key])
1943
- end
1944
- end
1945
-
1946
- # Set the string value of a hash field.
1947
- #
1948
- # @param [String] key
1949
- # @param [String] field
1950
- # @param [String] value
1951
- # @return [Boolean] whether or not the field was **added** to the hash
1952
- def hset(key, field, value)
1953
- synchronize do |client|
1954
- client.call([:hset, key, field, value], &Boolify)
1955
- end
1956
- end
1957
-
1958
- # Set the value of a hash field, only if the field does not exist.
1959
- #
1960
- # @param [String] key
1961
- # @param [String] field
1962
- # @param [String] value
1963
- # @return [Boolean] whether or not the field was **added** to the hash
1964
- def hsetnx(key, field, value)
1965
- synchronize do |client|
1966
- client.call([:hsetnx, key, field, value], &Boolify)
1967
- end
1968
- end
1969
-
1970
- # Set one or more hash values.
1971
- #
1972
- # @example
1973
- # redis.hmset("hash", "f1", "v1", "f2", "v2")
1974
- # # => "OK"
1975
- #
1976
- # @param [String] key
1977
- # @param [Array<String>] attrs array of fields and values
1978
- # @return [String] `"OK"`
1979
- #
1980
- # @see #mapped_hmset
1981
- def hmset(key, *attrs)
1982
- synchronize do |client|
1983
- client.call([:hmset, key] + attrs)
1984
- end
1985
- end
1986
-
1987
- # Set one or more hash values.
1988
- #
1989
- # @example
1990
- # redis.mapped_hmset("hash", { "f1" => "v1", "f2" => "v2" })
1991
- # # => "OK"
1992
- #
1993
- # @param [String] key
1994
- # @param [Hash] hash a non-empty hash with fields mapping to values
1995
- # @return [String] `"OK"`
1996
- #
1997
- # @see #hmset
1998
- def mapped_hmset(key, hash)
1999
- hmset(key, hash.to_a.flatten)
2000
- end
2001
-
2002
- # Get the value of a hash field.
2003
- #
2004
- # @param [String] key
2005
- # @param [String] field
2006
- # @return [String]
2007
- def hget(key, field)
2008
- synchronize do |client|
2009
- client.call([:hget, key, field])
2010
- end
2011
- end
2012
-
2013
- # Get the values of all the given hash fields.
2014
- #
2015
- # @example
2016
- # redis.hmget("hash", "f1", "f2")
2017
- # # => ["v1", "v2"]
2018
- #
2019
- # @param [String] key
2020
- # @param [Array<String>] fields array of fields
2021
- # @return [Array<String>] an array of values for the specified fields
2022
- #
2023
- # @see #mapped_hmget
2024
- def hmget(key, *fields, &blk)
2025
- synchronize do |client|
2026
- client.call([:hmget, key] + fields, &blk)
2027
- end
2028
- end
2029
-
2030
- # Get the values of all the given hash fields.
2031
- #
2032
- # @example
2033
- # redis.mapped_hmget("hash", "f1", "f2")
2034
- # # => { "f1" => "v1", "f2" => "v2" }
2035
- #
2036
- # @param [String] key
2037
- # @param [Array<String>] fields array of fields
2038
- # @return [Hash] a hash mapping the specified fields to their values
2039
- #
2040
- # @see #hmget
2041
- def mapped_hmget(key, *fields)
2042
- hmget(key, *fields) do |reply|
2043
- if reply.kind_of?(Array)
2044
- Hash[fields.zip(reply)]
2045
- else
2046
- reply
2047
- end
2048
- end
2049
- end
2050
-
2051
- # Delete one or more hash fields.
2052
- #
2053
- # @param [String] key
2054
- # @param [String, Array<String>] field
2055
- # @return [Fixnum] the number of fields that were removed from the hash
2056
- def hdel(key, field)
2057
- synchronize do |client|
2058
- client.call([:hdel, key, field])
2059
- end
2060
- end
2061
-
2062
- # Determine if a hash field exists.
2063
- #
2064
- # @param [String] key
2065
- # @param [String] field
2066
- # @return [Boolean] whether or not the field exists in the hash
2067
- def hexists(key, field)
2068
- synchronize do |client|
2069
- client.call([:hexists, key, field], &Boolify)
2070
- end
2071
- end
2072
-
2073
- # Increment the integer value of a hash field by the given integer number.
2074
- #
2075
- # @param [String] key
2076
- # @param [String] field
2077
- # @param [Fixnum] increment
2078
- # @return [Fixnum] value of the field after incrementing it
2079
- def hincrby(key, field, increment)
2080
- synchronize do |client|
2081
- client.call([:hincrby, key, field, increment])
2082
- end
2083
- end
2084
-
2085
- # Increment the numeric value of a hash field by the given float number.
2086
- #
2087
- # @param [String] key
2088
- # @param [String] field
2089
- # @param [Float] increment
2090
- # @return [Float] value of the field after incrementing it
2091
- def hincrbyfloat(key, field, increment)
2092
- synchronize do |client|
2093
- client.call([:hincrbyfloat, key, field, increment], &Floatify)
2094
- end
2095
- end
2096
-
2097
- # Get all the fields in a hash.
2098
- #
2099
- # @param [String] key
2100
- # @return [Array<String>]
2101
- def hkeys(key)
2102
- synchronize do |client|
2103
- client.call([:hkeys, key])
2104
- end
2105
- end
2106
-
2107
- # Get all the values in a hash.
2108
- #
2109
- # @param [String] key
2110
- # @return [Array<String>]
2111
- def hvals(key)
2112
- synchronize do |client|
2113
- client.call([:hvals, key])
2114
- end
2115
- end
2116
-
2117
- # Get all the fields and values in a hash.
2118
- #
2119
- # @param [String] key
2120
- # @return [Hash<String, String>]
2121
- def hgetall(key)
2122
- synchronize do |client|
2123
- client.call([:hgetall, key], &Hashify)
2124
- end
2125
- end
2126
-
2127
- # Post a message to a channel.
2128
- def publish(channel, message)
2129
- synchronize do |client|
2130
- client.call([:publish, channel, message])
2131
- end
2132
- end
2133
-
2134
- def subscribed?
2135
- synchronize do |client|
2136
- client.kind_of? SubscribedClient
2137
- end
2138
- end
2139
-
2140
- # Listen for messages published to the given channels.
2141
- def subscribe(*channels, &block)
2142
- synchronize do |client|
2143
- _subscription(:subscribe, 0, channels, block)
2144
- end
2145
- end
2146
-
2147
- # Listen for messages published to the given channels. Throw a timeout error if there is no messages for a timeout period.
2148
- def subscribe_with_timeout(timeout, *channels, &block)
2149
- synchronize do |client|
2150
- _subscription(:subscribe_with_timeout, timeout, channels, block)
2151
- end
2152
- end
2153
-
2154
- # Stop listening for messages posted to the given channels.
2155
- def unsubscribe(*channels)
2156
- synchronize do |client|
2157
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2158
- client.unsubscribe(*channels)
2159
- end
2160
- end
2161
-
2162
- # Listen for messages published to channels matching the given patterns.
2163
- def psubscribe(*channels, &block)
2164
- synchronize do |client|
2165
- _subscription(:psubscribe, 0, channels, block)
2166
- end
2167
- end
2168
-
2169
- # Listen for messages published to channels matching the given patterns. Throw a timeout error if there is no messages for a timeout period.
2170
- def psubscribe_with_timeout(timeout, *channels, &block)
2171
- synchronize do |client|
2172
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
2173
- end
2174
- end
2175
-
2176
- # Stop listening for messages posted to channels matching the given patterns.
2177
- def punsubscribe(*channels)
2178
- synchronize do |client|
2179
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2180
- client.punsubscribe(*channels)
2181
- end
2182
- end
2183
-
2184
- # Inspect the state of the Pub/Sub subsystem.
2185
- # Possible subcommands: channels, numsub, numpat.
2186
- def pubsub(subcommand, *args)
2187
- synchronize do |client|
2188
- client.call([:pubsub, subcommand] + args)
2189
- end
2190
- end
2191
-
2192
- # Watch the given keys to determine execution of the MULTI/EXEC block.
2193
- #
2194
- # Using a block is optional, but is necessary for thread-safety.
2195
- #
2196
- # An `#unwatch` is automatically issued if an exception is raised within the
2197
- # block that is a subclass of StandardError and is not a ConnectionError.
2198
- #
2199
- # @example With a block
2200
- # redis.watch("key") do
2201
- # if redis.get("key") == "some value"
2202
- # redis.multi do |multi|
2203
- # multi.set("key", "other value")
2204
- # multi.incr("counter")
2205
- # end
2206
- # else
2207
- # redis.unwatch
2208
- # end
2209
- # end
2210
- # # => ["OK", 6]
2211
- #
2212
- # @example Without a block
2213
- # redis.watch("key")
2214
- # # => "OK"
2215
- #
2216
- # @param [String, Array<String>] keys one or more keys to watch
2217
- # @return [Object] if using a block, returns the return value of the block
2218
- # @return [String] if not using a block, returns `OK`
2219
- #
2220
- # @see #unwatch
2221
- # @see #multi
2222
- def watch(*keys)
2223
- synchronize do |client|
2224
- res = client.call([:watch] + keys)
2225
-
2226
- if block_given?
2227
- begin
2228
- yield(self)
2229
- rescue ConnectionError
2230
- raise
2231
- rescue StandardError
2232
- unwatch
2233
- raise
2234
- end
2235
- else
2236
- res
2237
- end
2238
- end
2239
- end
2240
-
2241
- # Forget about all watched keys.
2242
- #
2243
- # @return [String] `OK`
2244
- #
2245
- # @see #watch
2246
- # @see #multi
2247
- def unwatch
2248
- synchronize do |client|
2249
- client.call([:unwatch])
2250
- end
2251
- end
2252
-
2253
- def pipelined
2254
- synchronize do |client|
2255
- begin
2256
- original, @client = @client, Pipeline.new
2257
- yield(self)
2258
- original.call_pipeline(@client)
2259
- ensure
2260
- @client = original
2261
- end
2262
- end
2263
- end
2264
-
2265
- # Mark the start of a transaction block.
2266
- #
2267
- # Passing a block is optional.
2268
- #
2269
- # @example With a block
2270
- # redis.multi do |multi|
2271
- # multi.set("key", "value")
2272
- # multi.incr("counter")
2273
- # end # => ["OK", 6]
2274
- #
2275
- # @example Without a block
2276
- # redis.multi
2277
- # # => "OK"
2278
- # redis.set("key", "value")
2279
- # # => "QUEUED"
2280
- # redis.incr("counter")
2281
- # # => "QUEUED"
2282
- # redis.exec
2283
- # # => ["OK", 6]
2284
- #
2285
- # @yield [multi] the commands that are called inside this block are cached
2286
- # and written to the server upon returning from it
2287
- # @yieldparam [Redis] multi `self`
2288
- #
2289
- # @return [String, Array<...>]
2290
- # - when a block is not given, `OK`
2291
- # - when a block is given, an array with replies
2292
- #
2293
- # @see #watch
2294
- # @see #unwatch
2295
- def multi
2296
- synchronize do |client|
2297
- if !block_given?
2298
- client.call([:multi])
2299
- else
2300
- begin
2301
- pipeline = Pipeline::Multi.new
2302
- original, @client = @client, pipeline
2303
- yield(self)
2304
- original.call_pipeline(pipeline)
2305
- ensure
2306
- @client = original
2307
- end
2308
- end
2309
- end
2310
- end
2311
-
2312
- # Execute all commands issued after MULTI.
2313
- #
2314
- # Only call this method when `#multi` was called **without** a block.
2315
- #
2316
- # @return [nil, Array<...>]
2317
- # - when commands were not executed, `nil`
2318
- # - when commands were executed, an array with their replies
2319
- #
2320
- # @see #multi
2321
- # @see #discard
2322
- def exec
2323
- synchronize do |client|
2324
- client.call([:exec])
2325
- end
2326
- end
2327
-
2328
- # Discard all commands issued after MULTI.
2329
- #
2330
- # Only call this method when `#multi` was called **without** a block.
2331
- #
2332
- # @return [String] `"OK"`
2333
- #
2334
- # @see #multi
2335
- # @see #exec
2336
- def discard
2337
- synchronize do |client|
2338
- client.call([:discard])
2339
- end
2340
- end
2341
-
2342
- # Control remote script registry.
2343
- #
2344
- # @example Load a script
2345
- # sha = redis.script(:load, "return 1")
2346
- # # => <sha of this script>
2347
- # @example Check if a script exists
2348
- # redis.script(:exists, sha)
2349
- # # => true
2350
- # @example Check if multiple scripts exist
2351
- # redis.script(:exists, [sha, other_sha])
2352
- # # => [true, false]
2353
- # @example Flush the script registry
2354
- # redis.script(:flush)
2355
- # # => "OK"
2356
- # @example Kill a running script
2357
- # redis.script(:kill)
2358
- # # => "OK"
2359
- #
2360
- # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill`
2361
- # @param [Array<String>] args depends on subcommand
2362
- # @return [String, Boolean, Array<Boolean>, ...] depends on subcommand
2363
- #
2364
- # @see #eval
2365
- # @see #evalsha
2366
- def script(subcommand, *args)
2367
- subcommand = subcommand.to_s.downcase
2368
-
2369
- if subcommand == "exists"
2370
- synchronize do |client|
2371
- arg = args.first
2372
-
2373
- client.call([:script, :exists, arg]) do |reply|
2374
- reply = reply.map { |r| Boolify.call(r) }
2375
-
2376
- if arg.is_a?(Array)
2377
- reply
2378
- else
2379
- reply.first
2380
- end
2381
- end
2382
- end
2383
- else
2384
- synchronize do |client|
2385
- client.call([:script, subcommand] + args)
2386
- end
2387
- end
2388
- end
2389
-
2390
- def _eval(cmd, args)
2391
- script = args.shift
2392
- options = args.pop if args.last.is_a?(Hash)
2393
- options ||= {}
2394
-
2395
- keys = args.shift || options[:keys] || []
2396
- argv = args.shift || options[:argv] || []
2397
-
2398
- synchronize do |client|
2399
- client.call([cmd, script, keys.length] + keys + argv)
2400
- end
2401
- end
2402
-
2403
- # Evaluate Lua script.
2404
- #
2405
- # @example EVAL without KEYS nor ARGV
2406
- # redis.eval("return 1")
2407
- # # => 1
2408
- # @example EVAL with KEYS and ARGV as array arguments
2409
- # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"])
2410
- # # => [["k1", "k2"], ["a1", "a2"]]
2411
- # @example EVAL with KEYS and ARGV in a hash argument
2412
- # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2413
- # # => [["k1", "k2"], ["a1", "a2"]]
2414
- #
2415
- # @param [Array<String>] keys optional array with keys to pass to the script
2416
- # @param [Array<String>] argv optional array with arguments to pass to the script
2417
- # @param [Hash] options
2418
- # - `:keys => Array<String>`: optional array with keys to pass to the script
2419
- # - `:argv => Array<String>`: optional array with arguments to pass to the script
2420
- # @return depends on the script
2421
- #
2422
- # @see #script
2423
- # @see #evalsha
2424
- def eval(*args)
2425
- _eval(:eval, args)
2426
- end
2427
-
2428
- # Evaluate Lua script by its SHA.
2429
- #
2430
- # @example EVALSHA without KEYS nor ARGV
2431
- # redis.evalsha(sha)
2432
- # # => <depends on script>
2433
- # @example EVALSHA with KEYS and ARGV as array arguments
2434
- # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"])
2435
- # # => <depends on script>
2436
- # @example EVALSHA with KEYS and ARGV in a hash argument
2437
- # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2438
- # # => <depends on script>
2439
- #
2440
- # @param [Array<String>] keys optional array with keys to pass to the script
2441
- # @param [Array<String>] argv optional array with arguments to pass to the script
2442
- # @param [Hash] options
2443
- # - `:keys => Array<String>`: optional array with keys to pass to the script
2444
- # - `:argv => Array<String>`: optional array with arguments to pass to the script
2445
- # @return depends on the script
2446
- #
2447
- # @see #script
2448
- # @see #eval
2449
- def evalsha(*args)
2450
- _eval(:evalsha, args)
2451
- end
2452
-
2453
- def _scan(command, cursor, args, options = {}, &block)
2454
- # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2455
-
2456
- args << cursor
2457
-
2458
- if match = options[:match]
2459
- args.concat(["MATCH", match])
2460
- end
2461
-
2462
- if count = options[:count]
2463
- args.concat(["COUNT", count])
2464
- end
2465
-
2466
- synchronize do |client|
2467
- client.call([command] + args, &block)
2468
- end
2469
- end
2470
-
2471
- # Scan the keyspace
2472
- #
2473
- # @example Retrieve the first batch of keys
2474
- # redis.scan(0)
2475
- # # => ["4", ["key:21", "key:47", "key:42"]]
2476
- # @example Retrieve a batch of keys matching a pattern
2477
- # redis.scan(4, :match => "key:1?")
2478
- # # => ["92", ["key:13", "key:18"]]
2479
- #
2480
- # @param [String, Integer] cursor the cursor of the iteration
2481
- # @param [Hash] options
2482
- # - `:match => String`: only return keys matching the pattern
2483
- # - `:count => Integer`: return count keys at most per iteration
2484
- #
2485
- # @return [String, Array<String>] the next cursor and all found keys
2486
- def scan(cursor, options={})
2487
- _scan(:scan, cursor, [], options)
2488
- end
2489
-
2490
- # Scan the keyspace
2491
- #
2492
- # @example Retrieve all of the keys (with possible duplicates)
2493
- # redis.scan_each.to_a
2494
- # # => ["key:21", "key:47", "key:42"]
2495
- # @example Execute block for each key matching a pattern
2496
- # redis.scan_each(:match => "key:1?") {|key| puts key}
2497
- # # => key:13
2498
- # # => key:18
2499
- #
2500
- # @param [Hash] options
2501
- # - `:match => String`: only return keys matching the pattern
2502
- # - `:count => Integer`: return count keys at most per iteration
2503
- #
2504
- # @return [Enumerator] an enumerator for all found keys
2505
- def scan_each(options={}, &block)
2506
- return to_enum(:scan_each, options) unless block_given?
2507
- cursor = 0
2508
- loop do
2509
- cursor, keys = scan(cursor, options)
2510
- keys.each(&block)
2511
- break if cursor == "0"
2512
- end
2513
- end
2514
-
2515
- # Scan a hash
2516
- #
2517
- # @example Retrieve the first batch of key/value pairs in a hash
2518
- # redis.hscan("hash", 0)
2519
- #
2520
- # @param [String, Integer] cursor the cursor of the iteration
2521
- # @param [Hash] options
2522
- # - `:match => String`: only return keys matching the pattern
2523
- # - `:count => Integer`: return count keys at most per iteration
2524
- #
2525
- # @return [String, Array<[String, String]>] the next cursor and all found keys
2526
- def hscan(key, cursor, options={})
2527
- _scan(:hscan, cursor, [key], options) do |reply|
2528
- [reply[0], reply[1].each_slice(2).to_a]
2529
- end
2530
- end
2531
-
2532
- # Scan a hash
2533
- #
2534
- # @example Retrieve all of the key/value pairs in a hash
2535
- # redis.hscan_each("hash").to_a
2536
- # # => [["key70", "70"], ["key80", "80"]]
2537
- #
2538
- # @param [Hash] options
2539
- # - `:match => String`: only return keys matching the pattern
2540
- # - `:count => Integer`: return count keys at most per iteration
2541
- #
2542
- # @return [Enumerator] an enumerator for all found keys
2543
- def hscan_each(key, options={}, &block)
2544
- return to_enum(:hscan_each, key, options) unless block_given?
2545
- cursor = 0
2546
- loop do
2547
- cursor, values = hscan(key, cursor, options)
2548
- values.each(&block)
2549
- break if cursor == "0"
2550
- end
2551
- end
2552
-
2553
- # Scan a sorted set
2554
- #
2555
- # @example Retrieve the first batch of key/value pairs in a hash
2556
- # redis.zscan("zset", 0)
2557
- #
2558
- # @param [String, Integer] cursor the cursor of the iteration
2559
- # @param [Hash] options
2560
- # - `:match => String`: only return keys matching the pattern
2561
- # - `:count => Integer`: return count keys at most per iteration
2562
- #
2563
- # @return [String, Array<[String, Float]>] the next cursor and all found
2564
- # members and scores
2565
- def zscan(key, cursor, options={})
2566
- _scan(:zscan, cursor, [key], options) do |reply|
2567
- [reply[0], FloatifyPairs.call(reply[1])]
2568
- end
2569
- end
2570
-
2571
- # Scan a sorted set
2572
- #
2573
- # @example Retrieve all of the members/scores in a sorted set
2574
- # redis.zscan_each("zset").to_a
2575
- # # => [["key70", "70"], ["key80", "80"]]
2576
- #
2577
- # @param [Hash] options
2578
- # - `:match => String`: only return keys matching the pattern
2579
- # - `:count => Integer`: return count keys at most per iteration
2580
- #
2581
- # @return [Enumerator] an enumerator for all found scores and members
2582
- def zscan_each(key, options={}, &block)
2583
- return to_enum(:zscan_each, key, options) unless block_given?
2584
- cursor = 0
2585
- loop do
2586
- cursor, values = zscan(key, cursor, options)
2587
- values.each(&block)
2588
- break if cursor == "0"
2589
- end
2590
- end
2591
-
2592
- # Scan a set
2593
- #
2594
- # @example Retrieve the first batch of keys in a set
2595
- # redis.sscan("set", 0)
2596
- #
2597
- # @param [String, Integer] cursor the cursor of the iteration
2598
- # @param [Hash] options
2599
- # - `:match => String`: only return keys matching the pattern
2600
- # - `:count => Integer`: return count keys at most per iteration
2601
- #
2602
- # @return [String, Array<String>] the next cursor and all found members
2603
- def sscan(key, cursor, options={})
2604
- _scan(:sscan, cursor, [key], options)
2605
- end
2606
-
2607
- # Scan a set
2608
- #
2609
- # @example Retrieve all of the keys in a set
2610
- # redis.sscan_each("set").to_a
2611
- # # => ["key1", "key2", "key3"]
2612
- #
2613
- # @param [Hash] options
2614
- # - `:match => String`: only return keys matching the pattern
2615
- # - `:count => Integer`: return count keys at most per iteration
2616
- #
2617
- # @return [Enumerator] an enumerator for all keys in the set
2618
- def sscan_each(key, options={}, &block)
2619
- return to_enum(:sscan_each, key, options) unless block_given?
2620
- cursor = 0
2621
- loop do
2622
- cursor, keys = sscan(key, cursor, options)
2623
- keys.each(&block)
2624
- break if cursor == "0"
2625
- end
2626
- end
2627
-
2628
- # Add one or more members to a HyperLogLog structure.
2629
- #
2630
- # @param [String] key
2631
- # @param [String, Array<String>] member one member, or array of members
2632
- # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise.
2633
- def pfadd(key, member)
2634
- synchronize do |client|
2635
- client.call([:pfadd, key, member], &Boolify)
2636
- end
2637
- end
2638
-
2639
- # Get the approximate cardinality of members added to HyperLogLog structure.
2640
- #
2641
- # If called with multiple keys, returns the approximate cardinality of the
2642
- # union of the HyperLogLogs contained in the keys.
2643
- #
2644
- # @param [String, Array<String>] keys
2645
- # @return [Fixnum]
2646
- def pfcount(*keys)
2647
- synchronize do |client|
2648
- client.call([:pfcount] + keys)
2649
- end
2650
- end
2651
-
2652
- # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
2653
- # the observed Sets of the source HyperLogLog structures.
2654
- #
2655
- # @param [String] dest_key destination key
2656
- # @param [String, Array<String>] source_key source key, or array of keys
2657
- # @return [Boolean]
2658
- def pfmerge(dest_key, *source_key)
2659
- synchronize do |client|
2660
- client.call([:pfmerge, dest_key, *source_key], &BoolifySet)
2661
- end
2662
- end
2663
-
2664
- # Interact with the sentinel command (masters, master, slaves, failover)
2665
- #
2666
- # @param [String] subcommand e.g. `masters`, `master`, `slaves`
2667
- # @param [Array<String>] args depends on subcommand
2668
- # @return [Array<String>, Hash<String, String>, String] depends on subcommand
2669
- def sentinel(subcommand, *args)
2670
- subcommand = subcommand.to_s.downcase
2671
- synchronize do |client|
2672
- client.call([:sentinel, subcommand] + args) do |reply|
2673
- case subcommand
2674
- when "get-master-addr-by-name"
2675
- reply
2676
- else
2677
- if reply.kind_of?(Array)
2678
- if reply[0].kind_of?(Array)
2679
- reply.map(&Hashify)
2680
- else
2681
- Hashify.call(reply)
2682
- end
2683
- else
2684
- reply
2685
- end
2686
- end
2687
- end
2688
- end
2689
- end
2690
-
2691
- def id
2692
- @original_client.id
2693
- end
2694
-
2695
- def inspect
2696
- "#<Redis client v#{Redis::VERSION} for #{id}>"
2697
- end
2698
-
2699
- def dup
2700
- self.class.new(@options)
2701
- end
2702
-
2703
- def connection
2704
- {
2705
- :host => @original_client.host,
2706
- :port => @original_client.port,
2707
- :db => @original_client.db,
2708
- :id => @original_client.id,
2709
- :location => @original_client.location
2710
- }
2711
- end
2712
-
2713
- def method_missing(command, *args)
2714
- synchronize do |client|
2715
- client.call([command] + args)
2716
- end
2717
- end
2718
-
2719
- private
2720
-
2721
- # Commands returning 1 for true and 0 for false may be executed in a pipeline
2722
- # where the method call will return nil. Propagate the nil instead of falsely
2723
- # returning false.
2724
- Boolify =
2725
- lambda { |value|
2726
- value == 1 if value
2727
- }
2728
-
2729
- BoolifySet =
2730
- lambda { |value|
2731
- if value && "OK" == value
2732
- true
2733
- else
2734
- false
2735
- end
2736
- }
2737
-
2738
- Hashify =
2739
- lambda { |array|
2740
- hash = Hash.new
2741
- array.each_slice(2) do |field, value|
2742
- hash[field] = value
2743
- end
2744
- hash
2745
- }
2746
-
2747
- Floatify =
2748
- lambda { |str|
2749
- if str
2750
- if (inf = str.match(/^(-)?inf/i))
2751
- (inf[1] ? -1.0 : 1.0) / 0.0
2752
- else
2753
- Float(str)
2754
- end
2755
- end
2756
- }
2757
-
2758
- FloatifyPairs =
2759
- lambda { |array|
2760
- if array
2761
- array.each_slice(2).map do |member, score|
2762
- [member, Floatify.call(score)]
2763
- end
2764
- end
2765
- }
2766
-
2767
- def _subscription(method, timeout, channels, block)
2768
- return @client.call([method] + channels) if subscribed?
2769
-
2770
- begin
2771
- original, @client = @client, SubscribedClient.new(@client)
2772
- if timeout > 0
2773
- @client.send(method, timeout, *channels, &block)
2774
- else
2775
- @client.send(method, *channels, &block)
2776
- end
2777
- ensure
2778
- @client = original
2779
- end
2780
- end
2781
-
2782
293
  end
2783
294
 
2784
295
  require "redis/version"
2785
296
  require "redis/connection"
2786
297
  require "redis/client"
298
+ require "redis/cluster"
2787
299
  require "redis/pipeline"
2788
300
  require "redis/subscribe"