fakeredis 0.7.0 → 0.8.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/.gitignore +0 -1
- data/.travis.yml +1 -6
- data/fakeredis.gemspec +1 -1
- data/lib/fakeredis.rb +16 -0
- data/lib/fakeredis/command_executor.rb +0 -6
- data/lib/fakeredis/expiring_hash.rb +2 -4
- data/lib/fakeredis/geo_commands.rb +142 -0
- data/lib/fakeredis/geo_set.rb +84 -0
- data/lib/fakeredis/minitest.rb +1 -1
- data/lib/fakeredis/sort_method.rb +0 -1
- data/lib/fakeredis/version.rb +1 -1
- data/lib/fakeredis/zset.rb +1 -1
- data/lib/redis/connection/memory.rb +207 -42
- data/spec/bitop_command_spec.rb +71 -71
- data/spec/fakeredis_spec.rb +52 -0
- data/spec/geo_set_spec.rb +164 -0
- data/spec/hashes_spec.rb +11 -0
- data/spec/hyper_log_logs_spec.rb +50 -0
- data/spec/keys_spec.rb +41 -16
- data/spec/lists_spec.rb +18 -1
- data/spec/memory_spec.rb +3 -7
- data/spec/server_spec.rb +14 -0
- data/spec/sets_spec.rb +2 -1
- data/spec/sorted_sets_spec.rb +179 -1
- data/spec/spec_helper.rb +5 -19
- data/spec/strings_spec.rb +31 -2
- data/spec/subscription_spec.rb +31 -31
- data/spec/support/shared_examples/bitwise_operation.rb +12 -12
- data/spec/transactions_spec.rb +13 -0
- metadata +13 -15
- data/gemfiles/redisrb-master.gemfile +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7c0b77a04c1c761edb3a6544aca213f373d57bb485d17f9e7e0f2c63bfd7e660
|
4
|
+
data.tar.gz: 57975993a6664bc469ee297a6a36d75ac2e4e429972aee6e50a2f518f93ca580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba35de9b7e297eca69cb7652d7817f257c447d7533d8c259c47215b15355ec49e5c2584907f0726f97327c443de122bd240cbca6fa194222f114bc99ffd0dbb9
|
7
|
+
data.tar.gz: 8c4e340be842d1fe966a3a4c6f63987bc8af346da74ced914cdc77488e08a6d15101ee6625097337287ec55c19f41118da4a79892b7fdd00bbebff1d103a813a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/fakeredis.gemspec
CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_runtime_dependency(%q<redis>, ["
|
21
|
+
s.add_runtime_dependency(%q<redis>, ["~> 4.1"])
|
22
22
|
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
23
23
|
end
|
data/lib/fakeredis.rb
CHANGED
@@ -15,4 +15,20 @@ module FakeRedis
|
|
15
15
|
def self.disable
|
16
16
|
Redis::Connection.drivers.delete_if {|driver| Redis::Connection::Memory == driver }
|
17
17
|
end
|
18
|
+
|
19
|
+
def self.disabling
|
20
|
+
return yield unless enabled?
|
21
|
+
|
22
|
+
disable
|
23
|
+
yield
|
24
|
+
enable
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.enabling
|
28
|
+
return yield if enabled?
|
29
|
+
|
30
|
+
enable
|
31
|
+
yield
|
32
|
+
disable
|
33
|
+
end
|
18
34
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "fakeredis/geo_set"
|
2
|
+
|
3
|
+
module FakeRedis
|
4
|
+
module GeoCommands
|
5
|
+
DISTANCE_UNITS = {
|
6
|
+
"m" => 1,
|
7
|
+
"km" => 1000,
|
8
|
+
"ft" => 0.3048,
|
9
|
+
"mi" => 1609.34
|
10
|
+
}
|
11
|
+
|
12
|
+
REDIS_DOUBLE_PRECISION = 4
|
13
|
+
REDIS_GEOHASH_SIZE = 10
|
14
|
+
|
15
|
+
def geoadd(key, *members)
|
16
|
+
raise_argument_error("geoadd") if members.empty? || members.size % 3 != 0
|
17
|
+
|
18
|
+
set = (data[key] ||= GeoSet.new)
|
19
|
+
prev_size = set.size
|
20
|
+
members.each_slice(3) do |member|
|
21
|
+
set.add(*member)
|
22
|
+
end
|
23
|
+
set.size - prev_size
|
24
|
+
end
|
25
|
+
|
26
|
+
def geodist(key, member1, member2, unit = "m")
|
27
|
+
unit = unit.to_s
|
28
|
+
raise_command_error("ERR unsupported unit provided. please use #{DISTANCE_UNITS.keys.join(', ')}") unless DISTANCE_UNITS.include?(unit)
|
29
|
+
|
30
|
+
set = (data[key] || GeoSet.new)
|
31
|
+
point1 = set.get(member1)
|
32
|
+
point2 = set.get(member2)
|
33
|
+
if point1 && point2
|
34
|
+
distance = point1.distance_to(point2)
|
35
|
+
distance_in_units = distance / DISTANCE_UNITS[unit]
|
36
|
+
distance_in_units.round(REDIS_DOUBLE_PRECISION).to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def geohash(key, member)
|
41
|
+
members = Array(member)
|
42
|
+
raise_argument_error("geohash") if members.empty?
|
43
|
+
set = (data[key] || GeoSet.new)
|
44
|
+
members.map do |member|
|
45
|
+
point = set.get(member)
|
46
|
+
point.geohash(REDIS_GEOHASH_SIZE) if point
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def geopos(key, member)
|
51
|
+
return nil unless data[key]
|
52
|
+
|
53
|
+
members = Array(member)
|
54
|
+
set = (data[key] || GeoSet.new)
|
55
|
+
members.map do |member|
|
56
|
+
point = set.get(member)
|
57
|
+
[point.lon.to_s, point.lat.to_s] if point
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def georadius(*args)
|
62
|
+
args = args.dup
|
63
|
+
raise_argument_error("georadius") if args.size < 5
|
64
|
+
key, lon, lat, radius, unit, *rest = args
|
65
|
+
raise_argument_error("georadius") unless DISTANCE_UNITS.has_key?(unit)
|
66
|
+
radius *= DISTANCE_UNITS[unit]
|
67
|
+
|
68
|
+
set = (data[key] || GeoSet.new)
|
69
|
+
center = GeoSet::Point.new(lon, lat, nil)
|
70
|
+
|
71
|
+
do_georadius(set, center, radius, unit, rest)
|
72
|
+
end
|
73
|
+
|
74
|
+
def georadiusbymember(*args)
|
75
|
+
args = args.dup
|
76
|
+
raise_argument_error("georadiusbymember") if args.size < 4
|
77
|
+
key, member, radius, unit, *rest = args
|
78
|
+
raise_argument_error("georadiusbymember") unless DISTANCE_UNITS.has_key?(unit)
|
79
|
+
radius *= DISTANCE_UNITS[unit]
|
80
|
+
|
81
|
+
set = (data[key] || GeoSet.new)
|
82
|
+
center = set.get(member)
|
83
|
+
raise_command_error("ERR could not decode requested zset member") unless center
|
84
|
+
|
85
|
+
do_georadius(set, center, radius, unit, args)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def do_georadius(set, center, radius, unit, args)
|
91
|
+
points = set.points_within_radius(center, radius)
|
92
|
+
|
93
|
+
options = georadius_options(args)
|
94
|
+
|
95
|
+
if options[:asc]
|
96
|
+
points.sort_by! { |p| p.distance_to(center) }
|
97
|
+
elsif options[:desc]
|
98
|
+
points.sort_by! { |p| -p.distance_to(center) }
|
99
|
+
end
|
100
|
+
|
101
|
+
points = points.take(options[:count]) if options[:count]
|
102
|
+
extras = options[:extras]
|
103
|
+
return points.map(&:name) if extras.empty?
|
104
|
+
|
105
|
+
points.map do |point|
|
106
|
+
member = [point.name]
|
107
|
+
|
108
|
+
extras.each do |extra|
|
109
|
+
case extra
|
110
|
+
when "WITHCOORD"
|
111
|
+
member << [point.lon.to_s, point.lat.to_s]
|
112
|
+
when "WITHDIST"
|
113
|
+
distance = point.distance_to(center)
|
114
|
+
distance_in_units = distance / DISTANCE_UNITS[unit]
|
115
|
+
member << distance_in_units.round(REDIS_DOUBLE_PRECISION).to_s
|
116
|
+
when "WITHHASH"
|
117
|
+
member << point.geohash(REDIS_GEOHASH_SIZE)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
member
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def georadius_options(args)
|
126
|
+
options = {}
|
127
|
+
args = args.map { |arg| arg.to_s.upcase }
|
128
|
+
|
129
|
+
if idx = args.index("COUNT")
|
130
|
+
options[:count] = Integer(args[idx + 1])
|
131
|
+
end
|
132
|
+
|
133
|
+
options[:asc] = true if args.include?("ASC")
|
134
|
+
options[:desc] = true if args.include?("DESC")
|
135
|
+
|
136
|
+
extras = args & ["WITHCOORD", "WITHDIST", "WITHHASH"]
|
137
|
+
options[:extras] = extras
|
138
|
+
|
139
|
+
options
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
class GeoSet
|
3
|
+
class Point
|
4
|
+
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz" # (geohash-specific) Base32 map
|
5
|
+
EARTH_RADIUS_IN_M = 6_378_100.0
|
6
|
+
|
7
|
+
attr_reader :lon, :lat, :name
|
8
|
+
|
9
|
+
def initialize(lon, lat, name)
|
10
|
+
@lon = Float(lon)
|
11
|
+
@lat = Float(lat)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def geohash(precision = 10)
|
16
|
+
latlon = [@lat, @lon]
|
17
|
+
ranges = [[-90.0, 90.0], [-180.0, 180.0]]
|
18
|
+
coordinate = 1
|
19
|
+
|
20
|
+
(0...precision).map do
|
21
|
+
index = 0 # index into base32 map
|
22
|
+
|
23
|
+
5.times do |bit|
|
24
|
+
mid = (ranges[coordinate][0] + ranges[coordinate][1]) / 2
|
25
|
+
if latlon[coordinate] >= mid
|
26
|
+
index = index * 2 + 1
|
27
|
+
ranges[coordinate][0] = mid
|
28
|
+
else
|
29
|
+
index *= 2
|
30
|
+
ranges[coordinate][1] = mid
|
31
|
+
end
|
32
|
+
|
33
|
+
coordinate ^= 1
|
34
|
+
end
|
35
|
+
|
36
|
+
BASE32[index]
|
37
|
+
end.join
|
38
|
+
end
|
39
|
+
|
40
|
+
def distance_to(other)
|
41
|
+
lat1 = deg_to_rad(@lat)
|
42
|
+
lon1 = deg_to_rad(@lon)
|
43
|
+
lat2 = deg_to_rad(other.lat)
|
44
|
+
lon2 = deg_to_rad(other.lon)
|
45
|
+
haversine_distance(lat1, lon1, lat2, lon2)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def deg_to_rad(deg)
|
51
|
+
deg * Math::PI / 180.0
|
52
|
+
end
|
53
|
+
|
54
|
+
def haversine_distance(lat1, lon1, lat2, lon2)
|
55
|
+
h = Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) *
|
56
|
+
Math.sin((lon2 - lon1) / 2) ** 2
|
57
|
+
|
58
|
+
2 * EARTH_RADIUS_IN_M * Math.asin(Math.sqrt(h))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
@points = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def size
|
67
|
+
@points.size
|
68
|
+
end
|
69
|
+
|
70
|
+
def add(lon, lat, name)
|
71
|
+
@points[name] = Point.new(lon, lat, name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def get(name)
|
75
|
+
@points[name]
|
76
|
+
end
|
77
|
+
|
78
|
+
def points_within_radius(center, radius)
|
79
|
+
@points.values.select do |point|
|
80
|
+
point.distance_to(center) <= radius
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/fakeredis/minitest.rb
CHANGED
@@ -6,7 +6,6 @@ module FakeRedis
|
|
6
6
|
return [] if type(key) == 'none'
|
7
7
|
|
8
8
|
unless %w(list set zset).include? type(key)
|
9
|
-
warn "Operation against a key holding the wrong kind of value: Expected list, set or zset at #{key}."
|
10
9
|
raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value")
|
11
10
|
end
|
12
11
|
|
data/lib/fakeredis/version.rb
CHANGED
data/lib/fakeredis/zset.rb
CHANGED
@@ -9,16 +9,20 @@ require "fakeredis/sorted_set_store"
|
|
9
9
|
require "fakeredis/transaction_commands"
|
10
10
|
require "fakeredis/zset"
|
11
11
|
require "fakeredis/bitop_command"
|
12
|
+
require "fakeredis/geo_commands"
|
12
13
|
require "fakeredis/version"
|
13
14
|
|
14
15
|
class Redis
|
15
16
|
module Connection
|
17
|
+
DEFAULT_REDIS_VERSION = '3.3.5'
|
18
|
+
|
16
19
|
class Memory
|
17
20
|
include Redis::Connection::CommandHelper
|
18
21
|
include FakeRedis
|
19
22
|
include SortMethod
|
20
23
|
include TransactionCommands
|
21
24
|
include BitopCommand
|
25
|
+
include GeoCommands
|
22
26
|
include CommandExecutor
|
23
27
|
|
24
28
|
attr_accessor :options
|
@@ -51,7 +55,7 @@ class Redis
|
|
51
55
|
end
|
52
56
|
|
53
57
|
def initialize(options = {})
|
54
|
-
self.options = options
|
58
|
+
self.options = self.options ? self.options.merge(options) : options
|
55
59
|
end
|
56
60
|
|
57
61
|
def database_id
|
@@ -93,9 +97,8 @@ class Redis
|
|
93
97
|
|
94
98
|
def client(command, _options = {})
|
95
99
|
case command
|
96
|
-
when :setname then
|
100
|
+
when :setname then "OK"
|
97
101
|
when :getname then nil
|
98
|
-
when :client then true
|
99
102
|
else
|
100
103
|
raise Redis::CommandError, "ERR unknown command '#{command}'"
|
101
104
|
end
|
@@ -130,7 +133,7 @@ class Redis
|
|
130
133
|
|
131
134
|
def info
|
132
135
|
{
|
133
|
-
"redis_version" =>
|
136
|
+
"redis_version" => options[:version] || DEFAULT_REDIS_VERSION,
|
134
137
|
"connected_clients" => "1",
|
135
138
|
"connected_slaves" => "0",
|
136
139
|
"used_memory" => "3187",
|
@@ -147,9 +150,13 @@ class Redis
|
|
147
150
|
|
148
151
|
def save; end
|
149
152
|
|
150
|
-
def bgsave
|
153
|
+
def bgsave; end
|
154
|
+
|
155
|
+
def bgrewriteaof; end
|
156
|
+
|
157
|
+
def evalsha; end
|
151
158
|
|
152
|
-
def
|
159
|
+
def eval; end
|
153
160
|
|
154
161
|
def move key, destination_id
|
155
162
|
raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
|
@@ -214,6 +221,11 @@ class Redis
|
|
214
221
|
data[key][start_index..end_index].unpack('B*')[0].count("1")
|
215
222
|
end
|
216
223
|
|
224
|
+
def bitpos(key, bit, start_index = 0, end_index = -1)
|
225
|
+
value = data[key] || ""
|
226
|
+
value[0..end_index].unpack('B*')[0].index(bit.to_s, start_index * 8) || -1
|
227
|
+
end
|
228
|
+
|
217
229
|
def getrange(key, start, ending)
|
218
230
|
return unless data[key]
|
219
231
|
data[key][start..ending]
|
@@ -493,9 +505,9 @@ class Redis
|
|
493
505
|
|
494
506
|
def brpoplpush(key1, key2, opts={})
|
495
507
|
data_type_check(key1, Array)
|
496
|
-
brpop(key1)
|
497
|
-
|
498
|
-
|
508
|
+
_key, elem = brpop(key1)
|
509
|
+
lpush(key2, elem) unless elem.nil?
|
510
|
+
elem
|
499
511
|
end
|
500
512
|
|
501
513
|
def lpop(key)
|
@@ -549,6 +561,8 @@ class Redis
|
|
549
561
|
|
550
562
|
def srem(key, value)
|
551
563
|
data_type_check(key, ::Set)
|
564
|
+
value = Array(value)
|
565
|
+
raise_argument_error('srem') if value.empty?
|
552
566
|
return false unless data[key]
|
553
567
|
|
554
568
|
if value.is_a?(Array)
|
@@ -575,7 +589,7 @@ class Redis
|
|
575
589
|
data_type_check(key, ::Set)
|
576
590
|
results = (count || 1).times.map do
|
577
591
|
elem = srandmember(key)
|
578
|
-
srem(key, elem)
|
592
|
+
srem(key, elem) if elem
|
579
593
|
elem
|
580
594
|
end.compact
|
581
595
|
count.nil? ? results.first : results
|
@@ -680,14 +694,11 @@ class Redis
|
|
680
694
|
end
|
681
695
|
|
682
696
|
def del(*keys)
|
683
|
-
keys
|
684
|
-
|
697
|
+
delete_keys(keys, 'del')
|
698
|
+
end
|
685
699
|
|
686
|
-
|
687
|
-
keys
|
688
|
-
data.delete(key)
|
689
|
-
end
|
690
|
-
old_count - data.keys.size
|
700
|
+
def unlink(*keys)
|
701
|
+
delete_keys(keys, 'unlink')
|
691
702
|
end
|
692
703
|
|
693
704
|
def setnx(key, value)
|
@@ -798,7 +809,7 @@ class Redis
|
|
798
809
|
end
|
799
810
|
|
800
811
|
def hmget(key, *fields)
|
801
|
-
raise_argument_error('hmget') if fields.empty?
|
812
|
+
raise_argument_error('hmget') if fields.empty? || fields.flatten.empty?
|
802
813
|
|
803
814
|
data_type_check(key, Hash)
|
804
815
|
fields.flatten.map do |field|
|
@@ -817,6 +828,12 @@ class Redis
|
|
817
828
|
data[key].size
|
818
829
|
end
|
819
830
|
|
831
|
+
def hstrlen(key, field)
|
832
|
+
data_type_check(key, Hash)
|
833
|
+
return 0 if data[key].nil? || data[key][field].nil?
|
834
|
+
data[key][field].size
|
835
|
+
end
|
836
|
+
|
820
837
|
def hvals(key)
|
821
838
|
data_type_check(key, Hash)
|
822
839
|
return [] unless data[key]
|
@@ -853,22 +870,14 @@ class Redis
|
|
853
870
|
|
854
871
|
def sync ; end
|
855
872
|
|
856
|
-
def [](key)
|
857
|
-
get(key)
|
858
|
-
end
|
859
|
-
|
860
|
-
def []=(key, value)
|
861
|
-
set(key, value)
|
862
|
-
end
|
863
|
-
|
864
873
|
def set(key, value, *array_options)
|
865
874
|
option_nx = array_options.delete("NX")
|
866
875
|
option_xx = array_options.delete("XX")
|
867
876
|
|
868
|
-
return
|
877
|
+
return nil if option_nx && option_xx
|
869
878
|
|
870
|
-
return
|
871
|
-
return
|
879
|
+
return nil if option_nx && exists(key)
|
880
|
+
return nil if option_xx && !exists(key)
|
872
881
|
|
873
882
|
data[key] = value.to_s
|
874
883
|
|
@@ -899,6 +908,10 @@ class Redis
|
|
899
908
|
"OK"
|
900
909
|
end
|
901
910
|
|
911
|
+
def psetex(key, milliseconds, value)
|
912
|
+
setex(key, milliseconds / 1000.0, value)
|
913
|
+
end
|
914
|
+
|
902
915
|
def setrange(key, offset, value)
|
903
916
|
return unless data[key]
|
904
917
|
s = data[key][offset,value.size]
|
@@ -1005,6 +1018,19 @@ class Redis
|
|
1005
1018
|
end
|
1006
1019
|
|
1007
1020
|
def zadd(key, *args)
|
1021
|
+
option_xx = args.delete("XX")
|
1022
|
+
option_nx = args.delete("NX")
|
1023
|
+
option_ch = args.delete("CH")
|
1024
|
+
option_incr = args.delete("INCR")
|
1025
|
+
|
1026
|
+
if option_xx && option_nx
|
1027
|
+
raise_options_error("XX", "NX")
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
if option_incr && args.size > 2
|
1031
|
+
raise_options_error("INCR")
|
1032
|
+
end
|
1033
|
+
|
1008
1034
|
if !args.first.is_a?(Array)
|
1009
1035
|
if args.size < 2
|
1010
1036
|
raise_argument_error('zadd')
|
@@ -1020,18 +1046,39 @@ class Redis
|
|
1020
1046
|
data_type_check(key, ZSet)
|
1021
1047
|
data[key] ||= ZSet.new
|
1022
1048
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1049
|
+
# Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
|
1050
|
+
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
1051
|
+
|
1052
|
+
changed = 0
|
1053
|
+
exists = args.map(&:last).count { |el| !hexists(key, el.to_s) }
|
1054
|
+
|
1055
|
+
args.each do |score, value|
|
1056
|
+
if option_nx && hexists(key, value.to_s)
|
1057
|
+
next
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
if option_xx && !hexists(key, value.to_s)
|
1061
|
+
exists -= 1
|
1062
|
+
next
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
if option_incr
|
1066
|
+
data[key][value.to_s] ||= 0
|
1067
|
+
return data[key].increment(value, score).to_s
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
if option_ch && data[key][value.to_s] != score
|
1071
|
+
changed += 1
|
1072
|
+
end
|
1026
1073
|
data[key][value.to_s] = score
|
1027
|
-
else
|
1028
|
-
# Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
|
1029
|
-
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
1030
|
-
exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
|
1031
|
-
args.each { |s, v| data[key][v.to_s] = s }
|
1032
1074
|
end
|
1033
1075
|
|
1034
|
-
|
1076
|
+
if option_incr
|
1077
|
+
changed = changed.zero? ? nil : changed
|
1078
|
+
exists = exists.zero? ? nil : exists
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
option_ch ? changed : exists
|
1035
1082
|
end
|
1036
1083
|
|
1037
1084
|
def zrem(key, value)
|
@@ -1047,6 +1094,36 @@ class Redis
|
|
1047
1094
|
response
|
1048
1095
|
end
|
1049
1096
|
|
1097
|
+
def zpopmax(key, count = nil)
|
1098
|
+
data_type_check(key, ZSet)
|
1099
|
+
return [] unless data[key]
|
1100
|
+
sorted_members = sort_keys(data[key])
|
1101
|
+
results = sorted_members.last(count || 1).reverse!
|
1102
|
+
results.each do |member|
|
1103
|
+
zrem(key, member.first)
|
1104
|
+
end
|
1105
|
+
count.nil? ? results.first : results.flatten
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
def zpopmin(key, count = nil)
|
1109
|
+
data_type_check(key, ZSet)
|
1110
|
+
return [] unless data[key]
|
1111
|
+
sorted_members = sort_keys(data[key])
|
1112
|
+
results = sorted_members.first(count || 1)
|
1113
|
+
results.each do |member|
|
1114
|
+
zrem(key, member.first)
|
1115
|
+
end
|
1116
|
+
count.nil? ? results.first : results.flatten
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
def bzpopmax(*args)
|
1120
|
+
bzpop(:bzpopmax, args)
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
def bzpopmin(*args)
|
1124
|
+
bzpop(:bzpopmin, args)
|
1125
|
+
end
|
1126
|
+
|
1050
1127
|
def zcard(key)
|
1051
1128
|
data_type_check(key, ZSet)
|
1052
1129
|
data[key] ? data[key].size : 0
|
@@ -1055,7 +1132,13 @@ class Redis
|
|
1055
1132
|
def zscore(key, value)
|
1056
1133
|
data_type_check(key, ZSet)
|
1057
1134
|
value = data[key] && data[key][value.to_s]
|
1058
|
-
value
|
1135
|
+
if value == Float::INFINITY
|
1136
|
+
"inf"
|
1137
|
+
elsif value == -Float::INFINITY
|
1138
|
+
"-inf"
|
1139
|
+
elsif value
|
1140
|
+
value.to_s
|
1141
|
+
end
|
1059
1142
|
end
|
1060
1143
|
|
1061
1144
|
def zcount(key, min, max)
|
@@ -1069,7 +1152,14 @@ class Redis
|
|
1069
1152
|
data[key] ||= ZSet.new
|
1070
1153
|
data[key][value.to_s] ||= 0
|
1071
1154
|
data[key].increment(value.to_s, num)
|
1072
|
-
|
1155
|
+
|
1156
|
+
if num =~ /^\+?inf/
|
1157
|
+
"inf"
|
1158
|
+
elsif num == "-inf"
|
1159
|
+
"-inf"
|
1160
|
+
else
|
1161
|
+
data[key][value.to_s].to_s
|
1162
|
+
end
|
1073
1163
|
end
|
1074
1164
|
|
1075
1165
|
def zrank(key, value)
|
@@ -1093,6 +1183,7 @@ class Redis
|
|
1093
1183
|
results = sort_keys(data[key])
|
1094
1184
|
# Select just the keys unless we want scores
|
1095
1185
|
results = results.map(&:first) unless with_scores
|
1186
|
+
start = [start, -results.size].max
|
1096
1187
|
(results[start..stop] || []).flatten.map(&:to_s)
|
1097
1188
|
end
|
1098
1189
|
|
@@ -1208,6 +1299,33 @@ class Redis
|
|
1208
1299
|
data[out].size
|
1209
1300
|
end
|
1210
1301
|
|
1302
|
+
def pfadd(key, member)
|
1303
|
+
data_type_check(key, Set)
|
1304
|
+
data[key] ||= Set.new
|
1305
|
+
previous_size = data[key].size
|
1306
|
+
data[key] |= Array(member)
|
1307
|
+
data[key].size != previous_size
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
def pfcount(*keys)
|
1311
|
+
keys = keys.flatten
|
1312
|
+
raise_argument_error("pfcount") if keys.empty?
|
1313
|
+
keys.each { |key| data_type_check(key, Set) }
|
1314
|
+
if keys.count == 1
|
1315
|
+
(data[keys.first] || Set.new).size
|
1316
|
+
else
|
1317
|
+
union = keys.map { |key| data[key] }.compact.reduce(&:|)
|
1318
|
+
union.size
|
1319
|
+
end
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
def pfmerge(destination, *sources)
|
1323
|
+
sources.each { |source| data_type_check(source, Set) }
|
1324
|
+
union = sources.map { |source| data[source] || Set.new }.reduce(&:|)
|
1325
|
+
data[destination] = union
|
1326
|
+
"OK"
|
1327
|
+
end
|
1328
|
+
|
1211
1329
|
def subscribe(*channels)
|
1212
1330
|
raise_argument_error('subscribe') if channels.empty?()
|
1213
1331
|
|
@@ -1348,13 +1466,36 @@ class Redis
|
|
1348
1466
|
raise Redis::CommandError, "ERR syntax error"
|
1349
1467
|
end
|
1350
1468
|
|
1469
|
+
def raise_options_error(*options)
|
1470
|
+
if options.detect { |opt| opt.match(/incr/i) }
|
1471
|
+
error_message = "ERR INCR option supports a single increment-element pair"
|
1472
|
+
else
|
1473
|
+
error_message = "ERR #{options.join(" and ")} options at the same time are not compatible"
|
1474
|
+
end
|
1475
|
+
raise Redis::CommandError, error_message
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
def raise_command_error(message)
|
1479
|
+
raise Redis::CommandError, message
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
def delete_keys(keys, command)
|
1483
|
+
keys = keys.flatten(1)
|
1484
|
+
raise_argument_error(command) if keys.empty?
|
1485
|
+
|
1486
|
+
old_count = data.keys.size
|
1487
|
+
keys.each do |key|
|
1488
|
+
data.delete(key)
|
1489
|
+
end
|
1490
|
+
old_count - data.keys.size
|
1491
|
+
end
|
1492
|
+
|
1351
1493
|
def remove_key_for_empty_collection(key)
|
1352
1494
|
del(key) if data[key] && data[key].empty?
|
1353
1495
|
end
|
1354
1496
|
|
1355
1497
|
def data_type_check(key, klass)
|
1356
1498
|
if data[key] && !data[key].is_a?(klass)
|
1357
|
-
warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
1358
1499
|
raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value")
|
1359
1500
|
end
|
1360
1501
|
end
|
@@ -1419,9 +1560,33 @@ class Redis
|
|
1419
1560
|
end
|
1420
1561
|
end
|
1421
1562
|
|
1563
|
+
def bzpop(command, args)
|
1564
|
+
timeout =
|
1565
|
+
if args.last.is_a?(Hash)
|
1566
|
+
args.pop[:timeout]
|
1567
|
+
elsif args.last.respond_to?(:to_int)
|
1568
|
+
args.pop.to_int
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
timeout ||= 0
|
1572
|
+
single_pop_command = command.to_s[1..-1]
|
1573
|
+
keys = args.flatten
|
1574
|
+
keys.each do |key|
|
1575
|
+
if data[key]
|
1576
|
+
data_type_check(data[key], ZSet)
|
1577
|
+
if data[key].size > 0
|
1578
|
+
result = public_send(single_pop_command, key)
|
1579
|
+
return result.unshift(key)
|
1580
|
+
end
|
1581
|
+
end
|
1582
|
+
end
|
1583
|
+
sleep(timeout.to_f)
|
1584
|
+
nil
|
1585
|
+
end
|
1586
|
+
|
1422
1587
|
def sort_keys(arr)
|
1423
1588
|
# Sort by score, or if scores are equal, key alphanum
|
1424
|
-
|
1589
|
+
arr.sort do |(k1, v1), (k2, v2)|
|
1425
1590
|
if v1 == v2
|
1426
1591
|
k1 <=> k2
|
1427
1592
|
else
|