redis 4.1.0 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +158 -0
- data/README.md +91 -27
- data/lib/redis/client.rb +148 -92
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +6 -7
- data/lib/redis/cluster/node.rb +17 -1
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +30 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/cluster.rb +46 -17
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +411 -0
- data/lib/redis/commands/lists.rb +289 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +207 -0
- data/lib/redis/commands/sorted_sets.rb +804 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +92 -0
- data/lib/redis/commands.rb +242 -0
- data/lib/redis/connection/command_helper.rb +5 -2
- data/lib/redis/connection/hiredis.rb +7 -5
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +129 -110
- data/lib/redis/connection/synchrony.rb +17 -10
- data/lib/redis/connection.rb +3 -1
- data/lib/redis/distributed.rb +209 -70
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +139 -8
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +167 -3377
- metadata +32 -25
data/lib/redis/cluster/option.rb
CHANGED
@@ -15,36 +15,36 @@ class Redis
|
|
15
15
|
def initialize(options)
|
16
16
|
options = options.dup
|
17
17
|
node_addrs = options.delete(:cluster)
|
18
|
-
@
|
18
|
+
@node_opts = build_node_options(node_addrs)
|
19
19
|
@replica = options.delete(:replica) == true
|
20
|
+
add_common_node_option_if_needed(options, @node_opts, :scheme)
|
21
|
+
add_common_node_option_if_needed(options, @node_opts, :username)
|
22
|
+
add_common_node_option_if_needed(options, @node_opts, :password)
|
20
23
|
@options = options
|
21
24
|
end
|
22
25
|
|
23
26
|
def per_node_key
|
24
|
-
@
|
27
|
+
@node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
|
25
28
|
.to_h
|
26
29
|
end
|
27
30
|
|
28
|
-
def secure?
|
29
|
-
@node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false
|
30
|
-
end
|
31
|
-
|
32
31
|
def use_replica?
|
33
32
|
@replica
|
34
33
|
end
|
35
34
|
|
36
35
|
def update_node(addrs)
|
37
|
-
@
|
36
|
+
@node_opts = build_node_options(addrs)
|
38
37
|
end
|
39
38
|
|
40
39
|
def add_node(host, port)
|
41
|
-
@
|
40
|
+
@node_opts << { host: host, port: port }
|
42
41
|
end
|
43
42
|
|
44
43
|
private
|
45
44
|
|
46
|
-
def
|
45
|
+
def build_node_options(addrs)
|
47
46
|
raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
|
47
|
+
|
48
48
|
addrs.map { |addr| parse_node_addr(addr) }
|
49
49
|
end
|
50
50
|
|
@@ -53,7 +53,7 @@ class Redis
|
|
53
53
|
when String
|
54
54
|
parse_node_url(addr)
|
55
55
|
when Hash
|
56
|
-
|
56
|
+
parse_node_option(addr)
|
57
57
|
else
|
58
58
|
raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
|
59
59
|
end
|
@@ -62,15 +62,31 @@ class Redis
|
|
62
62
|
def parse_node_url(addr)
|
63
63
|
uri = URI(addr)
|
64
64
|
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
65
|
-
|
65
|
+
|
66
|
+
db = uri.path.split('/')[1]&.to_i
|
67
|
+
|
68
|
+
{ scheme: uri.scheme, username: uri.user, password: uri.password, host: uri.host, port: uri.port, db: db }
|
69
|
+
.reject { |_, v| v.nil? || v == '' }
|
66
70
|
rescue URI::InvalidURIError => err
|
67
71
|
raise InvalidClientOptionError, err.message
|
68
72
|
end
|
69
73
|
|
70
|
-
def
|
74
|
+
def parse_node_option(addr)
|
71
75
|
addr = addr.map { |k, v| [k.to_sym, v] }.to_h
|
72
|
-
|
73
|
-
|
76
|
+
if addr.values_at(:host, :port).any?(&:nil?)
|
77
|
+
raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
|
78
|
+
end
|
79
|
+
|
80
|
+
addr
|
81
|
+
end
|
82
|
+
|
83
|
+
# Redis cluster node returns only host and port information.
|
84
|
+
# So we should complement additional information such as:
|
85
|
+
# scheme, username, password and so on.
|
86
|
+
def add_common_node_option_if_needed(options, node_opts, key)
|
87
|
+
return options if options[key].nil? && node_opts.first[key].nil?
|
88
|
+
|
89
|
+
options[key] ||= node_opts.first[key]
|
74
90
|
end
|
75
91
|
end
|
76
92
|
end
|
data/lib/redis/cluster/slot.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'set'
|
4
|
-
|
5
3
|
class Redis
|
6
4
|
class Cluster
|
7
5
|
# Keep slot and node key map for Redis Cluster Client
|
@@ -28,11 +26,20 @@ class Redis
|
|
28
26
|
return nil unless exists?(slot)
|
29
27
|
return find_node_key_of_master(slot) if replica_disabled?
|
30
28
|
|
31
|
-
@map[slot][:slaves].
|
29
|
+
@map[slot][:slaves].sample
|
32
30
|
end
|
33
31
|
|
34
32
|
def put(slot, node_key)
|
35
|
-
|
33
|
+
# Since we're sharing a hash for build_slot_node_key_map, duplicate it
|
34
|
+
# if it already exists instead of preserving as-is.
|
35
|
+
@map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
|
36
|
+
|
37
|
+
if master?(node_key)
|
38
|
+
@map[slot][:master] = node_key
|
39
|
+
elsif !@map[slot][:slaves].include?(node_key)
|
40
|
+
@map[slot][:slaves] << node_key
|
41
|
+
end
|
42
|
+
|
36
43
|
nil
|
37
44
|
end
|
38
45
|
|
@@ -50,19 +57,29 @@ class Redis
|
|
50
57
|
@node_flags[node_key] == ROLE_SLAVE
|
51
58
|
end
|
52
59
|
|
60
|
+
# available_slots is mapping of node_key to list of slot ranges
|
53
61
|
def build_slot_node_key_map(available_slots)
|
54
|
-
|
55
|
-
|
62
|
+
by_ranges = {}
|
63
|
+
available_slots.each do |node_key, slots_arr|
|
64
|
+
by_ranges[slots_arr] ||= { master: nil, slaves: [] }
|
65
|
+
|
66
|
+
if master?(node_key)
|
67
|
+
by_ranges[slots_arr][:master] = node_key
|
68
|
+
elsif !by_ranges[slots_arr][:slaves].include?(node_key)
|
69
|
+
by_ranges[slots_arr][:slaves] << node_key
|
70
|
+
end
|
56
71
|
end
|
57
|
-
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
73
|
+
by_slot = {}
|
74
|
+
by_ranges.each do |slots_arr, nodes|
|
75
|
+
slots_arr.each do |slots|
|
76
|
+
slots.each do |slot|
|
77
|
+
by_slot[slot] = nodes
|
78
|
+
end
|
79
|
+
end
|
65
80
|
end
|
81
|
+
|
82
|
+
by_slot
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -13,7 +13,7 @@ class Redis
|
|
13
13
|
info = {}
|
14
14
|
|
15
15
|
nodes.each do |node|
|
16
|
-
info =
|
16
|
+
info = fetch_slot_info(node)
|
17
17
|
info.empty? ? next : break
|
18
18
|
end
|
19
19
|
|
@@ -23,9 +23,10 @@ class Redis
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
26
27
|
node.call(%i[cluster slots])
|
27
|
-
.
|
28
|
-
.
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
29
30
|
rescue CannotConnectError, ConnectionError, CommandError
|
30
31
|
{} # can retry on another node
|
31
32
|
end
|
@@ -34,7 +35,6 @@ class Redis
|
|
34
35
|
first_slot, last_slot = arr[0..1]
|
35
36
|
slot_range = (first_slot..last_slot).freeze
|
36
37
|
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
-
.flatten
|
38
38
|
end
|
39
39
|
|
40
40
|
def stringify_node_key(arr, default_ip)
|
data/lib/redis/cluster.rb
CHANGED
@@ -78,10 +78,13 @@ class Redis
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def call_pipeline(pipeline)
|
81
|
-
node_keys
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
|
82
|
+
if node_keys.size > 1
|
83
|
+
raise(CrossSlotPipeliningError,
|
84
|
+
pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
|
85
|
+
end
|
86
|
+
|
87
|
+
try_send(find_node(node_keys.first), :call_pipeline, pipeline)
|
85
88
|
end
|
86
89
|
|
87
90
|
def call_with_timeout(command, timeout, &block)
|
@@ -112,12 +115,11 @@ class Redis
|
|
112
115
|
node = Node.new(option.per_node_key)
|
113
116
|
available_slots = SlotLoader.load(node)
|
114
117
|
node_flags = NodeLoader.load_flags(node)
|
115
|
-
|
116
|
-
option.update_node(available_node_urls)
|
118
|
+
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
|
117
119
|
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
118
120
|
Slot.new(available_slots, node_flags, option.use_replica?)]
|
119
121
|
ensure
|
120
|
-
node
|
122
|
+
node&.each(&:disconnect)
|
121
123
|
end
|
122
124
|
|
123
125
|
def fetch_command_details(nodes)
|
@@ -128,13 +130,14 @@ class Redis
|
|
128
130
|
def send_command(command, &block)
|
129
131
|
cmd = command.first.to_s.downcase
|
130
132
|
case cmd
|
131
|
-
when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
133
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
132
134
|
@node.call_all(command, &block).first
|
133
135
|
when 'flushall', 'flushdb'
|
134
136
|
@node.call_master(command, &block).first
|
135
137
|
when 'wait' then @node.call_master(command, &block).reduce(:+)
|
136
138
|
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
137
139
|
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
140
|
+
when 'scan' then _scan(command, &block)
|
138
141
|
when 'lastsave' then @node.call_all(command, &block).sort
|
139
142
|
when 'role' then @node.call_all(command, &block)
|
140
143
|
when 'config' then send_config_command(command, &block)
|
@@ -216,9 +219,14 @@ class Redis
|
|
216
219
|
node.public_send(method_name, *args, &block)
|
217
220
|
rescue CommandError => err
|
218
221
|
if err.message.start_with?('MOVED')
|
219
|
-
|
222
|
+
raise if retry_count <= 0
|
223
|
+
|
224
|
+
node = assign_redirection_node(err.message)
|
225
|
+
retry_count -= 1
|
226
|
+
retry
|
220
227
|
elsif err.message.start_with?('ASK')
|
221
228
|
raise if retry_count <= 0
|
229
|
+
|
222
230
|
node = assign_asking_node(err.message)
|
223
231
|
node.call(%i[asking])
|
224
232
|
retry_count -= 1
|
@@ -226,6 +234,32 @@ class Redis
|
|
226
234
|
else
|
227
235
|
raise
|
228
236
|
end
|
237
|
+
rescue CannotConnectError
|
238
|
+
update_cluster_info!
|
239
|
+
raise
|
240
|
+
end
|
241
|
+
|
242
|
+
def _scan(command, &block)
|
243
|
+
input_cursor = Integer(command[1])
|
244
|
+
|
245
|
+
client_index = input_cursor % 256
|
246
|
+
raw_cursor = input_cursor >> 8
|
247
|
+
|
248
|
+
clients = @node.scale_reading_clients
|
249
|
+
|
250
|
+
client = clients[client_index]
|
251
|
+
return ['0', []] unless client
|
252
|
+
|
253
|
+
command[1] = raw_cursor.to_s
|
254
|
+
|
255
|
+
result_cursor, result_keys = client.call(command, &block)
|
256
|
+
result_cursor = Integer(result_cursor)
|
257
|
+
|
258
|
+
if result_cursor == 0
|
259
|
+
client_index += 1
|
260
|
+
end
|
261
|
+
|
262
|
+
[((result_cursor << 8) + client_index).to_s, result_keys]
|
229
263
|
end
|
230
264
|
|
231
265
|
def assign_redirection_node(err_msg)
|
@@ -245,14 +279,14 @@ class Redis
|
|
245
279
|
find_node(node_key)
|
246
280
|
end
|
247
281
|
|
248
|
-
def find_node_key(command)
|
282
|
+
def find_node_key(command, primary_only: false)
|
249
283
|
key = @command.extract_first_key(command)
|
250
284
|
return if key.empty?
|
251
285
|
|
252
286
|
slot = KeySlotConverter.convert(key)
|
253
287
|
return unless @slot.exists?(slot)
|
254
288
|
|
255
|
-
if @command.should_send_to_master?(command)
|
289
|
+
if @command.should_send_to_master?(command) || primary_only
|
256
290
|
@slot.find_node_key_of_master(slot)
|
257
291
|
else
|
258
292
|
@slot.find_node_key_of_slave(slot)
|
@@ -261,6 +295,7 @@ class Redis
|
|
261
295
|
|
262
296
|
def find_node(node_key)
|
263
297
|
return @node.sample if node_key.nil?
|
298
|
+
|
264
299
|
@node.find_by(node_key)
|
265
300
|
rescue Node::ReloadNeeded
|
266
301
|
update_cluster_info!(node_key)
|
@@ -276,11 +311,5 @@ class Redis
|
|
276
311
|
@node.map(&:disconnect)
|
277
312
|
@node, @slot = fetch_cluster_info!(@option)
|
278
313
|
end
|
279
|
-
|
280
|
-
def extract_keys_in_pipeline(pipeline)
|
281
|
-
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
282
|
-
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
283
|
-
[node_keys, command_keys]
|
284
|
-
end
|
285
314
|
end
|
286
315
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Bitmaps
|
6
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
7
|
+
#
|
8
|
+
# @param [String] key
|
9
|
+
# @param [Integer] offset bit offset
|
10
|
+
# @param [Integer] value bit value `0` or `1`
|
11
|
+
# @return [Integer] the original bit value stored at `offset`
|
12
|
+
def setbit(key, offset, value)
|
13
|
+
send_command([:setbit, key, offset, value])
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the bit value at offset in the string value stored at key.
|
17
|
+
#
|
18
|
+
# @param [String] key
|
19
|
+
# @param [Integer] offset bit offset
|
20
|
+
# @return [Integer] `0` or `1`
|
21
|
+
def getbit(key, offset)
|
22
|
+
send_command([:getbit, key, offset])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Count the number of set bits in a range of the string value stored at key.
|
26
|
+
#
|
27
|
+
# @param [String] key
|
28
|
+
# @param [Integer] start start index
|
29
|
+
# @param [Integer] stop stop index
|
30
|
+
# @return [Integer] the number of bits set to 1
|
31
|
+
def bitcount(key, start = 0, stop = -1)
|
32
|
+
send_command([:bitcount, key, start, stop])
|
33
|
+
end
|
34
|
+
|
35
|
+
# Perform a bitwise operation between strings and store the resulting string in a key.
|
36
|
+
#
|
37
|
+
# @param [String] operation e.g. `and`, `or`, `xor`, `not`
|
38
|
+
# @param [String] destkey destination key
|
39
|
+
# @param [String, Array<String>] keys one or more source keys to perform `operation`
|
40
|
+
# @return [Integer] the length of the string stored in `destkey`
|
41
|
+
def bitop(operation, destkey, *keys)
|
42
|
+
send_command([:bitop, operation, destkey, *keys])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the position of the first bit set to 1 or 0 in a string.
|
46
|
+
#
|
47
|
+
# @param [String] key
|
48
|
+
# @param [Integer] bit whether to look for the first 1 or 0 bit
|
49
|
+
# @param [Integer] start start index
|
50
|
+
# @param [Integer] stop stop index
|
51
|
+
# @return [Integer] the position of the first 1/0 bit.
|
52
|
+
# -1 if looking for 1 and it is not found or start and stop are given.
|
53
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
54
|
+
raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
|
55
|
+
|
56
|
+
command = [:bitpos, key, bit]
|
57
|
+
command << start if start
|
58
|
+
command << stop if stop
|
59
|
+
send_command(command)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Cluster
|
6
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
7
|
+
#
|
8
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
9
|
+
#
|
10
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
11
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
12
|
+
#
|
13
|
+
# @return [Object] depends on the subcommand
|
14
|
+
def cluster(subcommand, *args)
|
15
|
+
subcommand = subcommand.to_s.downcase
|
16
|
+
block = case subcommand
|
17
|
+
when 'slots'
|
18
|
+
HashifyClusterSlots
|
19
|
+
when 'nodes'
|
20
|
+
HashifyClusterNodes
|
21
|
+
when 'slaves'
|
22
|
+
HashifyClusterSlaves
|
23
|
+
when 'info'
|
24
|
+
HashifyInfo
|
25
|
+
else
|
26
|
+
Noop
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
|
30
|
+
block = Noop unless @cluster_mode
|
31
|
+
|
32
|
+
send_command([:cluster, subcommand] + args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sends `ASKING` command to random node and returns its reply.
|
36
|
+
#
|
37
|
+
# @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
|
38
|
+
#
|
39
|
+
# @return [String] `'OK'`
|
40
|
+
def asking
|
41
|
+
send_command(%i[asking])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Connection
|
6
|
+
# Authenticate to the server.
|
7
|
+
#
|
8
|
+
# @param [Array<String>] args includes both username and password
|
9
|
+
# or only password
|
10
|
+
# @return [String] `OK`
|
11
|
+
# @see https://redis.io/commands/auth AUTH command
|
12
|
+
def auth(*args)
|
13
|
+
send_command([:auth, *args])
|
14
|
+
end
|
15
|
+
|
16
|
+
# Ping the server.
|
17
|
+
#
|
18
|
+
# @param [optional, String] message
|
19
|
+
# @return [String] `PONG`
|
20
|
+
def ping(message = nil)
|
21
|
+
send_command([:ping, message].compact)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Echo the given string.
|
25
|
+
#
|
26
|
+
# @param [String] value
|
27
|
+
# @return [String]
|
28
|
+
def echo(value)
|
29
|
+
send_command([:echo, value])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Change the selected database for the current connection.
|
33
|
+
#
|
34
|
+
# @param [Integer] db zero-based index of the DB to use (0 to 15)
|
35
|
+
# @return [String] `OK`
|
36
|
+
def select(db)
|
37
|
+
synchronize do |client|
|
38
|
+
client.db = db
|
39
|
+
client.call([:select, db])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Close the connection.
|
44
|
+
#
|
45
|
+
# @return [String] `OK`
|
46
|
+
def quit
|
47
|
+
synchronize do |client|
|
48
|
+
begin
|
49
|
+
client.call([:quit])
|
50
|
+
rescue ConnectionError
|
51
|
+
ensure
|
52
|
+
client.disconnect
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Geo
|
6
|
+
# Adds the specified geospatial items (latitude, longitude, name) to the specified key
|
7
|
+
#
|
8
|
+
# @param [String] key
|
9
|
+
# @param [Array] member arguemnts for member or members: longitude, latitude, name
|
10
|
+
# @return [Integer] number of elements added to the sorted set
|
11
|
+
def geoadd(key, *member)
|
12
|
+
send_command([:geoadd, key, *member])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns geohash string representing position for specified members of the specified key.
|
16
|
+
#
|
17
|
+
# @param [String] key
|
18
|
+
# @param [String, Array<String>] member one member or array of members
|
19
|
+
# @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
|
20
|
+
def geohash(key, member)
|
21
|
+
send_command([:geohash, key, member])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
25
|
+
# given maximum distance from a point
|
26
|
+
#
|
27
|
+
# @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
|
28
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
|
29
|
+
# or the farthest to the nearest relative to the center
|
30
|
+
# @param [Integer] count limit the results to the first N matching items
|
31
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
32
|
+
# @return [Array<String>] may be changed with `options`
|
33
|
+
def georadius(*args, **geoptions)
|
34
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
35
|
+
|
36
|
+
send_command([:georadius, *geoarguments])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
40
|
+
# given maximum distance from an already existing member
|
41
|
+
#
|
42
|
+
# @param [Array] args key, member, radius, unit(m|km|ft|mi)
|
43
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
|
44
|
+
# to the nearest relative to the center
|
45
|
+
# @param [Integer] count limit the results to the first N matching items
|
46
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
47
|
+
# @return [Array<String>] may be changed with `options`
|
48
|
+
def georadiusbymember(*args, **geoptions)
|
49
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
50
|
+
|
51
|
+
send_command([:georadiusbymember, *geoarguments])
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns longitude and latitude of members of a geospatial index
|
55
|
+
#
|
56
|
+
# @param [String] key
|
57
|
+
# @param [String, Array<String>] member one member or array of members
|
58
|
+
# @return [Array<Array<String>, nil>] returns array of elements, where each
|
59
|
+
# element is either array of longitude and latitude or nil
|
60
|
+
def geopos(key, member)
|
61
|
+
send_command([:geopos, key, member])
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the distance between two members of a geospatial index
|
65
|
+
#
|
66
|
+
# @param [String ]key
|
67
|
+
# @param [Array<String>] members
|
68
|
+
# @param ['m', 'km', 'mi', 'ft'] unit
|
69
|
+
# @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
|
70
|
+
def geodist(key, member1, member2, unit = 'm')
|
71
|
+
send_command([:geodist, key, member1, member2, unit])
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def _geoarguments(*args, options: nil, sort: nil, count: nil)
|
77
|
+
args.push sort if sort
|
78
|
+
args.push 'count', count if count
|
79
|
+
args.push options if options
|
80
|
+
args
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|