redis 4.5.1 → 4.6.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.
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Lists
6
+ # Get the length of a list.
7
+ #
8
+ # @param [String] key
9
+ # @return [Integer]
10
+ def llen(key)
11
+ send_command([:llen, key])
12
+ end
13
+
14
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
15
+ #
16
+ # @param [String] source source key
17
+ # @param [String] destination destination key
18
+ # @param [String, Symbol] where_source from where to remove the element from the source list
19
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
20
+ # @param [String, Symbol] where_destination where to push the element to the source list
21
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
22
+ #
23
+ # @return [nil, String] the element, or nil when the source key does not exist
24
+ #
25
+ # @note This command comes in place of the now deprecated RPOPLPUSH.
26
+ # Doing LMOVE RIGHT LEFT is equivalent.
27
+ def lmove(source, destination, where_source, where_destination)
28
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
29
+
30
+ send_command([:lmove, source, destination, where_source, where_destination])
31
+ end
32
+
33
+ # Remove the first/last element in a list and append/prepend it
34
+ # to another list and return it, or block until one is available.
35
+ #
36
+ # @example With timeout
37
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT", timeout: 5)
38
+ # # => nil on timeout
39
+ # # => "element" on success
40
+ # @example Without timeout
41
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT")
42
+ # # => "element"
43
+ #
44
+ # @param [String] source source key
45
+ # @param [String] destination destination key
46
+ # @param [String, Symbol] where_source from where to remove the element from the source list
47
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
48
+ # @param [String, Symbol] where_destination where to push the element to the source list
49
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
50
+ # @param [Hash] options
51
+ # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
52
+ #
53
+ # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
54
+ #
55
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
56
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
57
+
58
+ command = [:blmove, source, destination, where_source, where_destination, timeout]
59
+ send_blocking_command(command, timeout)
60
+ end
61
+
62
+ # Prepend one or more values to a list, creating the list if it doesn't exist
63
+ #
64
+ # @param [String] key
65
+ # @param [String, Array<String>] value string value, or array of string values to push
66
+ # @return [Integer] the length of the list after the push operation
67
+ def lpush(key, value)
68
+ send_command([:lpush, key, value])
69
+ end
70
+
71
+ # Prepend a value to a list, only if the list exists.
72
+ #
73
+ # @param [String] key
74
+ # @param [String] value
75
+ # @return [Integer] the length of the list after the push operation
76
+ def lpushx(key, value)
77
+ send_command([:lpushx, key, value])
78
+ end
79
+
80
+ # Append one or more values to a list, creating the list if it doesn't exist
81
+ #
82
+ # @param [String] key
83
+ # @param [String, Array<String>] value string value, or array of string values to push
84
+ # @return [Integer] the length of the list after the push operation
85
+ def rpush(key, value)
86
+ send_command([:rpush, key, value])
87
+ end
88
+
89
+ # Append a value to a list, only if the list exists.
90
+ #
91
+ # @param [String] key
92
+ # @param [String] value
93
+ # @return [Integer] the length of the list after the push operation
94
+ def rpushx(key, value)
95
+ send_command([:rpushx, key, value])
96
+ end
97
+
98
+ # Remove and get the first elements in a list.
99
+ #
100
+ # @param [String] key
101
+ # @param [Integer] count number of elements to remove
102
+ # @return [String, Array<String>] the values of the first elements
103
+ def lpop(key, count = nil)
104
+ command = [:lpop, key]
105
+ command << count if count
106
+ send_command(command)
107
+ end
108
+
109
+ # Remove and get the last elements in a list.
110
+ #
111
+ # @param [String] key
112
+ # @param [Integer] count number of elements to remove
113
+ # @return [String, Array<String>] the values of the last elements
114
+ def rpop(key, count = nil)
115
+ command = [:rpop, key]
116
+ command << count if count
117
+ send_command(command)
118
+ end
119
+
120
+ # Remove the last element in a list, append it to another list and return it.
121
+ #
122
+ # @param [String] source source key
123
+ # @param [String] destination destination key
124
+ # @return [nil, String] the element, or nil when the source key does not exist
125
+ def rpoplpush(source, destination)
126
+ send_command([:rpoplpush, source, destination])
127
+ end
128
+
129
+ # Remove and get the first element in a list, or block until one is available.
130
+ #
131
+ # @example With timeout
132
+ # list, element = redis.blpop("list", :timeout => 5)
133
+ # # => nil on timeout
134
+ # # => ["list", "element"] on success
135
+ # @example Without timeout
136
+ # list, element = redis.blpop("list")
137
+ # # => ["list", "element"]
138
+ # @example Blocking pop on multiple lists
139
+ # list, element = redis.blpop(["list", "another_list"])
140
+ # # => ["list", "element"]
141
+ #
142
+ # @param [String, Array<String>] keys one or more keys to perform the
143
+ # blocking pop on
144
+ # @param [Hash] options
145
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
146
+ #
147
+ # @return [nil, [String, String]]
148
+ # - `nil` when the operation timed out
149
+ # - tuple of the list that was popped from and element was popped otherwise
150
+ def blpop(*args)
151
+ _bpop(:blpop, args)
152
+ end
153
+
154
+ # Remove and get the last element in a list, or block until one is available.
155
+ #
156
+ # @param [String, Array<String>] keys one or more keys to perform the
157
+ # blocking pop on
158
+ # @param [Hash] options
159
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
160
+ #
161
+ # @return [nil, [String, String]]
162
+ # - `nil` when the operation timed out
163
+ # - tuple of the list that was popped from and element was popped otherwise
164
+ #
165
+ # @see #blpop
166
+ def brpop(*args)
167
+ _bpop(:brpop, args)
168
+ end
169
+
170
+ # Pop a value from a list, push it to another list and return it; or block
171
+ # until one is available.
172
+ #
173
+ # @param [String] source source key
174
+ # @param [String] destination destination key
175
+ # @param [Hash] options
176
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
177
+ #
178
+ # @return [nil, String]
179
+ # - `nil` when the operation timed out
180
+ # - the element was popped and pushed otherwise
181
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
182
+ command = [:brpoplpush, source, destination, timeout]
183
+ send_blocking_command(command, timeout)
184
+ end
185
+
186
+ # Get an element from a list by its index.
187
+ #
188
+ # @param [String] key
189
+ # @param [Integer] index
190
+ # @return [String]
191
+ def lindex(key, index)
192
+ send_command([:lindex, key, index])
193
+ end
194
+
195
+ # Insert an element before or after another element in a list.
196
+ #
197
+ # @param [String] key
198
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
199
+ # @param [String] pivot reference element
200
+ # @param [String] value
201
+ # @return [Integer] length of the list after the insert operation, or `-1`
202
+ # when the element `pivot` was not found
203
+ def linsert(key, where, pivot, value)
204
+ send_command([:linsert, key, where, pivot, value])
205
+ end
206
+
207
+ # Get a range of elements from a list.
208
+ #
209
+ # @param [String] key
210
+ # @param [Integer] start start index
211
+ # @param [Integer] stop stop index
212
+ # @return [Array<String>]
213
+ def lrange(key, start, stop)
214
+ send_command([:lrange, key, start, stop])
215
+ end
216
+
217
+ # Remove elements from a list.
218
+ #
219
+ # @param [String] key
220
+ # @param [Integer] count number of elements to remove. Use a positive
221
+ # value to remove the first `count` occurrences of `value`. A negative
222
+ # value to remove the last `count` occurrences of `value`. Or zero, to
223
+ # remove all occurrences of `value` from the list.
224
+ # @param [String] value
225
+ # @return [Integer] the number of removed elements
226
+ def lrem(key, count, value)
227
+ send_command([:lrem, key, count, value])
228
+ end
229
+
230
+ # Set the value of an element in a list by its index.
231
+ #
232
+ # @param [String] key
233
+ # @param [Integer] index
234
+ # @param [String] value
235
+ # @return [String] `OK`
236
+ def lset(key, index, value)
237
+ send_command([:lset, key, index, value])
238
+ end
239
+
240
+ # Trim a list to the specified range.
241
+ #
242
+ # @param [String] key
243
+ # @param [Integer] start start index
244
+ # @param [Integer] stop stop index
245
+ # @return [String] `OK`
246
+ def ltrim(key, start, stop)
247
+ send_command([:ltrim, key, start, stop])
248
+ end
249
+
250
+ private
251
+
252
+ def _bpop(cmd, args, &blk)
253
+ timeout = if args.last.is_a?(Hash)
254
+ options = args.pop
255
+ options[:timeout]
256
+ elsif args.last.respond_to?(:to_int)
257
+ # Issue deprecation notice in obnoxious mode...
258
+ args.pop.to_int
259
+ end
260
+
261
+ timeout ||= 0
262
+
263
+ if args.size > 1
264
+ # Issue deprecation notice in obnoxious mode...
265
+ end
266
+
267
+ keys = args.flatten
268
+
269
+ command = [cmd, keys, timeout]
270
+ send_blocking_command(command, timeout, &blk)
271
+ end
272
+
273
+ def _normalize_move_wheres(where_source, where_destination)
274
+ where_source = where_source.to_s.upcase
275
+ where_destination = where_destination.to_s.upcase
276
+
277
+ if where_source != "LEFT" && where_source != "RIGHT"
278
+ raise ArgumentError, "where_source must be 'LEFT' or 'RIGHT'"
279
+ end
280
+
281
+ if where_destination != "LEFT" && where_destination != "RIGHT"
282
+ raise ArgumentError, "where_destination must be 'LEFT' or 'RIGHT'"
283
+ end
284
+
285
+ [where_source, where_destination]
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Pubsub
6
+ # Post a message to a channel.
7
+ def publish(channel, message)
8
+ send_command([:publish, channel, message])
9
+ end
10
+
11
+ def subscribed?
12
+ synchronize do |client|
13
+ client.is_a? SubscribedClient
14
+ end
15
+ end
16
+
17
+ # Listen for messages published to the given channels.
18
+ def subscribe(*channels, &block)
19
+ synchronize do |_client|
20
+ _subscription(:subscribe, 0, channels, block)
21
+ end
22
+ end
23
+
24
+ # Listen for messages published to the given channels. Throw a timeout error
25
+ # if there is no messages for a timeout period.
26
+ def subscribe_with_timeout(timeout, *channels, &block)
27
+ synchronize do |_client|
28
+ _subscription(:subscribe_with_timeout, timeout, channels, block)
29
+ end
30
+ end
31
+
32
+ # Stop listening for messages posted to the given channels.
33
+ def unsubscribe(*channels)
34
+ synchronize do |client|
35
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
36
+
37
+ client.unsubscribe(*channels)
38
+ end
39
+ end
40
+
41
+ # Listen for messages published to channels matching the given patterns.
42
+ def psubscribe(*channels, &block)
43
+ synchronize do |_client|
44
+ _subscription(:psubscribe, 0, channels, block)
45
+ end
46
+ end
47
+
48
+ # Listen for messages published to channels matching the given patterns.
49
+ # Throw a timeout error if there is no messages for a timeout period.
50
+ def psubscribe_with_timeout(timeout, *channels, &block)
51
+ synchronize do |_client|
52
+ _subscription(:psubscribe_with_timeout, timeout, channels, block)
53
+ end
54
+ end
55
+
56
+ # Stop listening for messages posted to channels matching the given patterns.
57
+ def punsubscribe(*channels)
58
+ synchronize do |client|
59
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
60
+
61
+ client.punsubscribe(*channels)
62
+ end
63
+ end
64
+
65
+ # Inspect the state of the Pub/Sub subsystem.
66
+ # Possible subcommands: channels, numsub, numpat.
67
+ def pubsub(subcommand, *args)
68
+ send_command([:pubsub, subcommand] + args)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Scripting
6
+ # Control remote script registry.
7
+ #
8
+ # @example Load a script
9
+ # sha = redis.script(:load, "return 1")
10
+ # # => <sha of this script>
11
+ # @example Check if a script exists
12
+ # redis.script(:exists, sha)
13
+ # # => true
14
+ # @example Check if multiple scripts exist
15
+ # redis.script(:exists, [sha, other_sha])
16
+ # # => [true, false]
17
+ # @example Flush the script registry
18
+ # redis.script(:flush)
19
+ # # => "OK"
20
+ # @example Kill a running script
21
+ # redis.script(:kill)
22
+ # # => "OK"
23
+ #
24
+ # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill`
25
+ # @param [Array<String>] args depends on subcommand
26
+ # @return [String, Boolean, Array<Boolean>, ...] depends on subcommand
27
+ #
28
+ # @see #eval
29
+ # @see #evalsha
30
+ def script(subcommand, *args)
31
+ subcommand = subcommand.to_s.downcase
32
+
33
+ if subcommand == "exists"
34
+ arg = args.first
35
+
36
+ send_command([:script, :exists, arg]) do |reply|
37
+ reply = reply.map { |r| Boolify.call(r) }
38
+
39
+ if arg.is_a?(Array)
40
+ reply
41
+ else
42
+ reply.first
43
+ end
44
+ end
45
+ else
46
+ send_command([:script, subcommand] + args)
47
+ end
48
+ end
49
+
50
+ # Evaluate Lua script.
51
+ #
52
+ # @example EVAL without KEYS nor ARGV
53
+ # redis.eval("return 1")
54
+ # # => 1
55
+ # @example EVAL with KEYS and ARGV as array arguments
56
+ # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"])
57
+ # # => [["k1", "k2"], ["a1", "a2"]]
58
+ # @example EVAL with KEYS and ARGV in a hash argument
59
+ # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"])
60
+ # # => [["k1", "k2"], ["a1", "a2"]]
61
+ #
62
+ # @param [Array<String>] keys optional array with keys to pass to the script
63
+ # @param [Array<String>] argv optional array with arguments to pass to the script
64
+ # @param [Hash] options
65
+ # - `:keys => Array<String>`: optional array with keys to pass to the script
66
+ # - `:argv => Array<String>`: optional array with arguments to pass to the script
67
+ # @return depends on the script
68
+ #
69
+ # @see #script
70
+ # @see #evalsha
71
+ def eval(*args)
72
+ _eval(:eval, args)
73
+ end
74
+
75
+ # Evaluate Lua script by its SHA.
76
+ #
77
+ # @example EVALSHA without KEYS nor ARGV
78
+ # redis.evalsha(sha)
79
+ # # => <depends on script>
80
+ # @example EVALSHA with KEYS and ARGV as array arguments
81
+ # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"])
82
+ # # => <depends on script>
83
+ # @example EVALSHA with KEYS and ARGV in a hash argument
84
+ # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"])
85
+ # # => <depends on script>
86
+ #
87
+ # @param [Array<String>] keys optional array with keys to pass to the script
88
+ # @param [Array<String>] argv optional array with arguments to pass to the script
89
+ # @param [Hash] options
90
+ # - `:keys => Array<String>`: optional array with keys to pass to the script
91
+ # - `:argv => Array<String>`: optional array with arguments to pass to the script
92
+ # @return depends on the script
93
+ #
94
+ # @see #script
95
+ # @see #eval
96
+ def evalsha(*args)
97
+ _eval(:evalsha, args)
98
+ end
99
+
100
+ private
101
+
102
+ def _eval(cmd, args)
103
+ script = args.shift
104
+ options = args.pop if args.last.is_a?(Hash)
105
+ options ||= {}
106
+
107
+ keys = args.shift || options[:keys] || []
108
+ argv = args.shift || options[:argv] || []
109
+
110
+ send_command([cmd, script, keys.length] + keys + argv)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Server
6
+ # Asynchronously rewrite the append-only file.
7
+ #
8
+ # @return [String] `OK`
9
+ def bgrewriteaof
10
+ send_command([:bgrewriteaof])
11
+ end
12
+
13
+ # Asynchronously save the dataset to disk.
14
+ #
15
+ # @return [String] `OK`
16
+ def bgsave
17
+ send_command([:bgsave])
18
+ end
19
+
20
+ # Get or set server configuration parameters.
21
+ #
22
+ # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat`
23
+ # @return [String, Hash] string reply, or hash when retrieving more than one
24
+ # property with `CONFIG GET`
25
+ def config(action, *args)
26
+ send_command([:config, action] + args) do |reply|
27
+ if reply.is_a?(Array) && action == :get
28
+ Hashify.call(reply)
29
+ else
30
+ reply
31
+ end
32
+ end
33
+ end
34
+
35
+ # Manage client connections.
36
+ #
37
+ # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
38
+ # @return [String, Hash] depends on subcommand
39
+ def client(subcommand = nil, *args)
40
+ send_command([:client, subcommand] + args) do |reply|
41
+ if subcommand.to_s == "list"
42
+ reply.lines.map do |line|
43
+ entries = line.chomp.split(/[ =]/)
44
+ Hash[entries.each_slice(2).to_a]
45
+ end
46
+ else
47
+ reply
48
+ end
49
+ end
50
+ end
51
+
52
+ # Return the number of keys in the selected database.
53
+ #
54
+ # @return [Integer]
55
+ def dbsize
56
+ send_command([:dbsize])
57
+ end
58
+
59
+ # Remove all keys from all databases.
60
+ #
61
+ # @param [Hash] options
62
+ # - `:async => Boolean`: async flush (default: false)
63
+ # @return [String] `OK`
64
+ def flushall(options = nil)
65
+ if options && options[:async]
66
+ send_command(%i[flushall async])
67
+ else
68
+ send_command([:flushall])
69
+ end
70
+ end
71
+
72
+ # Remove all keys from the current database.
73
+ #
74
+ # @param [Hash] options
75
+ # - `:async => Boolean`: async flush (default: false)
76
+ # @return [String] `OK`
77
+ def flushdb(options = nil)
78
+ if options && options[:async]
79
+ send_command(%i[flushdb async])
80
+ else
81
+ send_command([:flushdb])
82
+ end
83
+ end
84
+
85
+ # Get information and statistics about the server.
86
+ #
87
+ # @param [String, Symbol] cmd e.g. "commandstats"
88
+ # @return [Hash<String, String>]
89
+ def info(cmd = nil)
90
+ send_command([:info, cmd].compact) do |reply|
91
+ if reply.is_a?(String)
92
+ reply = HashifyInfo.call(reply)
93
+
94
+ if cmd && cmd.to_s == "commandstats"
95
+ # Extract nested hashes for INFO COMMANDSTATS
96
+ reply = Hash[reply.map do |k, v|
97
+ v = v.split(",").map { |e| e.split("=") }
98
+ [k[/^cmdstat_(.*)$/, 1], Hash[v]]
99
+ end]
100
+ end
101
+ end
102
+
103
+ reply
104
+ end
105
+ end
106
+
107
+ # Get the UNIX time stamp of the last successful save to disk.
108
+ #
109
+ # @return [Integer]
110
+ def lastsave
111
+ send_command([:lastsave])
112
+ end
113
+
114
+ # Listen for all requests received by the server in real time.
115
+ #
116
+ # There is no way to interrupt this command.
117
+ #
118
+ # @yield a block to be called for every line of output
119
+ # @yieldparam [String] line timestamp and command that was executed
120
+ def monitor(&block)
121
+ synchronize do |client|
122
+ client.call_loop([:monitor], &block)
123
+ end
124
+ end
125
+
126
+ # Synchronously save the dataset to disk.
127
+ #
128
+ # @return [String]
129
+ def save
130
+ send_command([:save])
131
+ end
132
+
133
+ # Synchronously save the dataset to disk and then shut down the server.
134
+ def shutdown
135
+ synchronize do |client|
136
+ client.with_reconnect(false) do
137
+ begin
138
+ client.call([:shutdown])
139
+ rescue ConnectionError
140
+ # This means Redis has probably exited.
141
+ nil
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # Make the server a slave of another instance, or promote it as master.
148
+ def slaveof(host, port)
149
+ send_command([:slaveof, host, port])
150
+ end
151
+
152
+ # Interact with the slowlog (get, len, reset)
153
+ #
154
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
155
+ # @param [Integer] length maximum number of entries to return
156
+ # @return [Array<String>, Integer, String] depends on subcommand
157
+ def slowlog(subcommand, length = nil)
158
+ synchronize do |client|
159
+ args = [:slowlog, subcommand]
160
+ args << length if length
161
+ client.call args
162
+ end
163
+ end
164
+
165
+ # Internal command used for replication.
166
+ def sync
167
+ send_command([:sync])
168
+ end
169
+
170
+ # Return the server time.
171
+ #
172
+ # @example
173
+ # r.time # => [ 1333093196, 606806 ]
174
+ #
175
+ # @return [Array<Integer>] tuple of seconds since UNIX epoch and
176
+ # microseconds in the current second
177
+ def time
178
+ send_command([:time]) do |reply|
179
+ reply&.map(&:to_i)
180
+ end
181
+ end
182
+
183
+ def debug(*args)
184
+ send_command([:debug] + args)
185
+ end
186
+ end
187
+ end
188
+ end