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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1be2134927d3b7838ee3e2fd4376791bea516561
4
- data.tar.gz: 2b3e0c64b79df1358151bbd1405000395fa06e86
2
+ SHA256:
3
+ metadata.gz: 7c0b77a04c1c761edb3a6544aca213f373d57bb485d17f9e7e0f2c63bfd7e660
4
+ data.tar.gz: 57975993a6664bc469ee297a6a36d75ac2e4e429972aee6e50a2f518f93ca580
5
5
  SHA512:
6
- metadata.gz: cfb37be84fd30d941fadc44a177e2029cd2de063fc090bc3004166a72f5dba6e7378e9c918aa1d156d44b8277f830dfedbd34aad57142770512f420f4f12fae3
7
- data.tar.gz: 2f841badb4c16e9ff6d3c35b697b9ee262022bc8286553e7187a47173af742114eacd5cccdb3d0477c111457fc9a7f50fdb7de970a25c04ba1be33318c84a1ac
6
+ metadata.gz: ba35de9b7e297eca69cb7652d7817f257c447d7533d8c259c47215b15355ec49e5c2584907f0726f97327c443de122bd240cbca6fa194222f114bc99ffd0dbb9
7
+ data.tar.gz: 8c4e340be842d1fe966a3a4c6f63987bc8af346da74ced914cdc77488e08a6d15101ee6625097337287ec55c19f41118da4a79892b7fdd00bbebff1d103a813a
data/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
1
  *.gem
2
2
  .bundle
3
3
  Gemfile.lock
4
- gemfiles/*.gemfile.lock
5
4
  pkg/*
6
5
  .rvmrc
7
6
  *.rbc
@@ -5,18 +5,13 @@ cache: bundler
5
5
  sudo: false
6
6
 
7
7
  rvm:
8
- - 2.2
9
- - 2.3.6
10
8
  - 2.4
11
9
  - 2.5
10
+ - 2.6
12
11
  - ruby-head
13
12
  - jruby
14
13
  - rbx-2
15
14
 
16
- gemfile:
17
- - Gemfile
18
- - gemfiles/redisrb-master.gemfile
19
-
20
15
  matrix:
21
16
  allow_failures:
22
17
  - rvm: rbx-2
@@ -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>, [">= 3.2", "< 5.0"])
21
+ s.add_runtime_dependency(%q<redis>, ["~> 4.1"])
22
22
  s.add_development_dependency(%q<rspec>, ["~> 3.0"])
23
23
  end
@@ -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
@@ -15,12 +15,6 @@ module FakeRedis
15
15
  raise Redis::CommandError, "ERR unknown command '#{meffod}'"
16
16
  end
17
17
 
18
- if reply == true
19
- reply = 1
20
- elsif reply == false
21
- reply = 0
22
- end
23
-
24
18
  replies << reply
25
19
  nil
26
20
  end
@@ -44,10 +44,8 @@ module FakeRedis
44
44
  end
45
45
 
46
46
  def values_at(*keys)
47
- keys.each do |key|
48
- key = normalize(key)
49
- delete(key) if expired?(key)
50
- end
47
+ keys = keys.map { |key| normalize(key) }
48
+ keys.each { |key| delete(key) if expired?(key) }
51
49
  super
52
50
  end
53
51
 
@@ -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
@@ -15,8 +15,8 @@ require 'fakeredis'
15
15
  module FakeRedis
16
16
  module Minitest
17
17
  def setup
18
- super
19
18
  Redis::Connection::Memory.reset_all_databases
19
+ super
20
20
  end
21
21
 
22
22
  ::Minitest::Test.send(:include, self)
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module FakeRedis
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -17,7 +17,7 @@ module FakeRedis
17
17
  def select_by_score min, max
18
18
  min = _floatify(min, true)
19
19
  max = _floatify(max, false)
20
- reject {|_,v| v < min || v > max }
20
+ select {|_,v| v >= min && v <= max }
21
21
  end
22
22
 
23
23
  private
@@ -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 true
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" => "2.6.16",
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 ; end
153
+ def bgsave; end
154
+
155
+ def bgrewriteaof; end
156
+
157
+ def evalsha; end
151
158
 
152
- def bgrewriteaof ; end
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).tap do |elem|
497
- lpush(key2, elem) unless elem.nil?
498
- end
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 = keys.flatten(1)
684
- raise_argument_error('del') if keys.empty?
697
+ delete_keys(keys, 'del')
698
+ end
685
699
 
686
- old_count = data.keys.size
687
- keys.each do |key|
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 false if option_nx && option_xx
877
+ return nil if option_nx && option_xx
869
878
 
870
- return false if option_nx && exists(key)
871
- return false if option_xx && !exists(key)
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
- if args.size == 2 && !(Array === args.first)
1024
- score, value = args
1025
- exists = !data[key].key?(value.to_s)
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
- exists
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 && value.to_s
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
- data[key][value.to_s].to_s
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
- sorted_keys = arr.sort do |(k1, v1), (k2, v2)|
1589
+ arr.sort do |(k1, v1), (k2, v2)|
1425
1590
  if v1 == v2
1426
1591
  k1 <=> k2
1427
1592
  else