fakeredis 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -82,20 +86,20 @@ class Redis
82
86
  attr_writer :replies
83
87
 
84
88
  def connected?
85
- true
89
+ defined?(@disconnected) ? false : true
86
90
  end
87
91
 
88
92
  def connect_unix(path, timeout)
89
93
  end
90
94
 
91
95
  def disconnect
96
+ @disconnected = true
92
97
  end
93
98
 
94
99
  def client(command, _options = {})
95
100
  case command
96
- when :setname then true
101
+ when :setname then "OK"
97
102
  when :getname then nil
98
- when :client then true
99
103
  else
100
104
  raise Redis::CommandError, "ERR unknown command '#{command}'"
101
105
  end
@@ -130,7 +134,7 @@ class Redis
130
134
 
131
135
  def info
132
136
  {
133
- "redis_version" => "2.6.16",
137
+ "redis_version" => options[:version] || DEFAULT_REDIS_VERSION,
134
138
  "connected_clients" => "1",
135
139
  "connected_slaves" => "0",
136
140
  "used_memory" => "3187",
@@ -147,9 +151,13 @@ class Redis
147
151
 
148
152
  def save; end
149
153
 
150
- def bgsave ; end
154
+ def bgsave; end
155
+
156
+ def bgrewriteaof; end
157
+
158
+ def evalsha; end
151
159
 
152
- def bgrewriteaof ; end
160
+ def eval; end
153
161
 
154
162
  def move key, destination_id
155
163
  raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
@@ -161,7 +169,7 @@ class Redis
161
169
  end
162
170
 
163
171
  def dump(key)
164
- return nil unless exists(key)
172
+ return nil if exists(key).zero?
165
173
 
166
174
  value = data[key]
167
175
 
@@ -172,7 +180,7 @@ class Redis
172
180
  end
173
181
 
174
182
  def restore(key, ttl, serialized_value)
175
- raise Redis::CommandError, "ERR Target key name is busy." if exists(key)
183
+ raise Redis::CommandError, "ERR Target key name is busy." if exists(key) > 0
176
184
 
177
185
  raise Redis::CommandError, "ERR DUMP payload version or checksum are wrong" if serialized_value.nil?
178
186
 
@@ -214,6 +222,11 @@ class Redis
214
222
  data[key][start_index..end_index].unpack('B*')[0].count("1")
215
223
  end
216
224
 
225
+ def bitpos(key, bit, start_index = 0, end_index = -1)
226
+ value = data[key] || ""
227
+ value[0..end_index].unpack('B*')[0].index(bit.to_s, start_index * 8) || -1
228
+ end
229
+
217
230
  def getrange(key, start, ending)
218
231
  return unless data[key]
219
232
  data[key][start..ending]
@@ -228,9 +241,9 @@ class Redis
228
241
  end
229
242
 
230
243
  def mget(*keys)
244
+ keys = keys[0] if flatten?(keys)
231
245
  raise_argument_error('mget') if keys.empty?
232
- # We work with either an array, or list of arguments
233
- keys = keys.first if keys.size == 1
246
+ keys.each { |key| data_type_check(key, String) }
234
247
  data.values_at(*keys)
235
248
  end
236
249
 
@@ -346,7 +359,11 @@ class Redis
346
359
  data.keys.count
347
360
  end
348
361
 
349
- def exists(key)
362
+ def exists(*keys)
363
+ keys.count { |key| data.key?(key) }
364
+ end
365
+
366
+ def exists?(key)
350
367
  data.key?(key)
351
368
  end
352
369
 
@@ -493,9 +510,9 @@ class Redis
493
510
 
494
511
  def brpoplpush(key1, key2, opts={})
495
512
  data_type_check(key1, Array)
496
- brpop(key1).tap do |elem|
497
- lpush(key2, elem) unless elem.nil?
498
- end
513
+ _key, elem = brpop(key1)
514
+ lpush(key2, elem) unless elem.nil?
515
+ elem
499
516
  end
500
517
 
501
518
  def lpop(key)
@@ -530,6 +547,8 @@ class Redis
530
547
 
531
548
  def sadd(key, value)
532
549
  data_type_check(key, ::Set)
550
+ should_return_int = value.is_a? Array
551
+
533
552
  value = Array(value)
534
553
  raise_argument_error('sadd') if value.empty?
535
554
 
@@ -542,13 +561,15 @@ class Redis
542
561
  data[key].size
543
562
  end
544
563
 
545
- # 0 = false, 1 = true, 2+ untouched
546
- return result == 1 if result < 2
564
+ # 0 = false, 1 = true unless an array was passed in
565
+ return result == 1 unless should_return_int
566
+
547
567
  result
548
568
  end
549
569
 
550
570
  def srem(key, value)
551
571
  data_type_check(key, ::Set)
572
+ raise_argument_error('srem') if Array(value).empty?
552
573
  return false unless data[key]
553
574
 
554
575
  if value.is_a?(Array)
@@ -575,7 +596,7 @@ class Redis
575
596
  data_type_check(key, ::Set)
576
597
  results = (count || 1).times.map do
577
598
  elem = srandmember(key)
578
- srem(key, elem)
599
+ srem(key, elem) if elem
579
600
  elem
580
601
  end.compact
581
602
  count.nil? ? results.first : results
@@ -635,6 +656,8 @@ class Redis
635
656
  data_type_check(destination, ::Set)
636
657
  result = sdiff(key1, *keys)
637
658
  data[destination] = ::Set.new(result)
659
+
660
+ result.size
638
661
  end
639
662
 
640
663
  def srandmember(key, number=nil)
@@ -680,18 +703,15 @@ class Redis
680
703
  end
681
704
 
682
705
  def del(*keys)
683
- keys = keys.flatten(1)
684
- raise_argument_error('del') if keys.empty?
706
+ delete_keys(keys, 'del')
707
+ end
685
708
 
686
- old_count = data.keys.size
687
- keys.each do |key|
688
- data.delete(key)
689
- end
690
- old_count - data.keys.size
709
+ def unlink(*keys)
710
+ delete_keys(keys, 'unlink')
691
711
  end
692
712
 
693
713
  def setnx(key, value)
694
- if exists(key)
714
+ if exists(key) > 0
695
715
  0
696
716
  else
697
717
  set(key, value)
@@ -707,7 +727,7 @@ class Redis
707
727
  end
708
728
 
709
729
  def renamenx(key, new_key)
710
- if exists(new_key)
730
+ if exists(new_key) > 0
711
731
  false
712
732
  else
713
733
  rename(key, new_key)
@@ -731,7 +751,7 @@ class Redis
731
751
  if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
732
752
  ttl
733
753
  else
734
- exists(key) ? -1 : -2
754
+ exists(key) > 0 ? -1 : -2
735
755
  end
736
756
  end
737
757
 
@@ -739,7 +759,7 @@ class Redis
739
759
  if data.expires.include?(key) && (ttl = data.expires[key].to_f - Time.now.to_f) > 0
740
760
  ttl * 1000
741
761
  else
742
- exists(key) ? -1 : -2
762
+ exists(key) > 0 ? -1 : -2
743
763
  end
744
764
  end
745
765
 
@@ -752,17 +772,32 @@ class Redis
752
772
  !!data.expires.delete(key)
753
773
  end
754
774
 
755
- def hset(key, field, value)
775
+ def hset(key, *fields)
776
+ fields = fields.first if fields.size == 1 && fields.first.is_a?(Hash)
777
+ raise_argument_error('hset') if fields.empty?
778
+
779
+ is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)}
780
+
781
+ raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays
782
+ raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2}
783
+
756
784
  data_type_check(key, Hash)
757
- field = field.to_s
758
- if data[key]
759
- result = !data[key].include?(field)
760
- data[key][field] = value.to_s
761
- result ? 1 : 0
785
+ insert_count = 0
786
+ data[key] ||= {}
787
+
788
+ if fields.is_a?(Hash)
789
+ insert_count = fields.keys.size - (data[key].keys & fields.keys).size
790
+
791
+ data[key].merge!(fields)
762
792
  else
763
- data[key] = { field => value.to_s }
764
- 1
793
+ fields.each_slice(2) do |field|
794
+ insert_count += 1 if data[key][field[0].to_s].nil?
795
+
796
+ data[key][field[0].to_s] = field[1].to_s
797
+ end
765
798
  end
799
+
800
+ insert_count
766
801
  end
767
802
 
768
803
  def hsetnx(key, field, value)
@@ -798,7 +833,7 @@ class Redis
798
833
  end
799
834
 
800
835
  def hmget(key, *fields)
801
- raise_argument_error('hmget') if fields.empty?
836
+ raise_argument_error('hmget') if fields.empty? || fields.flatten.empty?
802
837
 
803
838
  data_type_check(key, Hash)
804
839
  fields.flatten.map do |field|
@@ -817,6 +852,12 @@ class Redis
817
852
  data[key].size
818
853
  end
819
854
 
855
+ def hstrlen(key, field)
856
+ data_type_check(key, Hash)
857
+ return 0 if data[key].nil? || data[key][field].nil?
858
+ data[key][field].size
859
+ end
860
+
820
861
  def hvals(key)
821
862
  data_type_check(key, Hash)
822
863
  return [] unless data[key]
@@ -853,26 +894,19 @@ class Redis
853
894
 
854
895
  def sync ; end
855
896
 
856
- def [](key)
857
- get(key)
858
- end
859
-
860
- def []=(key, value)
861
- set(key, value)
862
- end
863
-
864
897
  def set(key, value, *array_options)
865
898
  option_nx = array_options.delete("NX")
866
899
  option_xx = array_options.delete("XX")
867
900
 
868
- return false if option_nx && option_xx
901
+ return nil if option_nx && option_xx
869
902
 
870
- return false if option_nx && exists(key)
871
- return false if option_xx && !exists(key)
903
+ return nil if option_nx && exists(key) > 0
904
+ return nil if option_xx && exists(key).zero?
872
905
 
873
906
  data[key] = value.to_s
874
907
 
875
908
  options = Hash[array_options.each_slice(2).to_a]
909
+ raise_command_error('ERR value is not an integer or out of range') if non_integer_expirations?(options)
876
910
  ttl_in_seconds = options["EX"] if options["EX"]
877
911
  ttl_in_seconds = options["PX"] / 1000.0 if options["PX"]
878
912
 
@@ -881,6 +915,11 @@ class Redis
881
915
  "OK"
882
916
  end
883
917
 
918
+ def non_integer_expirations?(options)
919
+ (options["EX"] && !options["EX"].is_a?(Integer)) ||
920
+ (options["PX"] && !options["PX"].is_a?(Integer))
921
+ end
922
+
884
923
  def setbit(key, offset, bit)
885
924
  old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
886
925
  size_increment = [((offset/8)+1)*8-old_val.length, 0].max
@@ -894,11 +933,19 @@ class Redis
894
933
  end
895
934
 
896
935
  def setex(key, seconds, value)
936
+ raise_command_error('ERR value is not an integer or out of range') unless seconds.is_a?(Integer)
897
937
  data[key] = value.to_s
898
938
  expire(key, seconds)
899
939
  "OK"
900
940
  end
901
941
 
942
+ def psetex(key, milliseconds, value)
943
+ raise_command_error('ERR value is not an integer or out of range') unless milliseconds.is_a?(Integer)
944
+ data[key] = value.to_s
945
+ expire(key, milliseconds / 1000.0)
946
+ "OK"
947
+ end
948
+
902
949
  def setrange(key, offset, value)
903
950
  return unless data[key]
904
951
  s = data[key][offset,value.size]
@@ -1005,6 +1052,19 @@ class Redis
1005
1052
  end
1006
1053
 
1007
1054
  def zadd(key, *args)
1055
+ option_xx = args.delete("XX")
1056
+ option_nx = args.delete("NX")
1057
+ option_ch = args.delete("CH")
1058
+ option_incr = args.delete("INCR")
1059
+
1060
+ if option_xx && option_nx
1061
+ raise_options_error("XX", "NX")
1062
+ end
1063
+
1064
+ if option_incr && args.size > 2
1065
+ raise_options_error("INCR")
1066
+ end
1067
+
1008
1068
  if !args.first.is_a?(Array)
1009
1069
  if args.size < 2
1010
1070
  raise_argument_error('zadd')
@@ -1020,18 +1080,39 @@ class Redis
1020
1080
  data_type_check(key, ZSet)
1021
1081
  data[key] ||= ZSet.new
1022
1082
 
1023
- if args.size == 2 && !(Array === args.first)
1024
- score, value = args
1025
- exists = !data[key].key?(value.to_s)
1083
+ # Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
1084
+ args = args.each_slice(2).to_a unless args.first.is_a?(Array)
1085
+
1086
+ changed = 0
1087
+ exists = args.map(&:last).count { |el| !hexists(key, el.to_s) }
1088
+
1089
+ args.each do |score, value|
1090
+ if option_nx && hexists(key, value.to_s)
1091
+ next
1092
+ end
1093
+
1094
+ if option_xx && !hexists(key, value.to_s)
1095
+ exists -= 1
1096
+ next
1097
+ end
1098
+
1099
+ if option_incr
1100
+ data[key][value.to_s] ||= 0
1101
+ return data[key].increment(value, score).to_s
1102
+ end
1103
+
1104
+ if option_ch && data[key][value.to_s] != score
1105
+ changed += 1
1106
+ end
1026
1107
  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
1108
  end
1033
1109
 
1034
- exists
1110
+ if option_incr
1111
+ changed = changed.zero? ? nil : changed
1112
+ exists = exists.zero? ? nil : exists
1113
+ end
1114
+
1115
+ option_ch ? changed : exists
1035
1116
  end
1036
1117
 
1037
1118
  def zrem(key, value)
@@ -1047,6 +1128,36 @@ class Redis
1047
1128
  response
1048
1129
  end
1049
1130
 
1131
+ def zpopmax(key, count = nil)
1132
+ data_type_check(key, ZSet)
1133
+ return [] unless data[key]
1134
+ sorted_members = sort_keys(data[key])
1135
+ results = sorted_members.last(count || 1).reverse!
1136
+ results.each do |member|
1137
+ zrem(key, member.first)
1138
+ end
1139
+ count.nil? ? results.first : results.flatten
1140
+ end
1141
+
1142
+ def zpopmin(key, count = nil)
1143
+ data_type_check(key, ZSet)
1144
+ return [] unless data[key]
1145
+ sorted_members = sort_keys(data[key])
1146
+ results = sorted_members.first(count || 1)
1147
+ results.each do |member|
1148
+ zrem(key, member.first)
1149
+ end
1150
+ count.nil? ? results.first : results.flatten
1151
+ end
1152
+
1153
+ def bzpopmax(*args)
1154
+ bzpop(:bzpopmax, args)
1155
+ end
1156
+
1157
+ def bzpopmin(*args)
1158
+ bzpop(:bzpopmin, args)
1159
+ end
1160
+
1050
1161
  def zcard(key)
1051
1162
  data_type_check(key, ZSet)
1052
1163
  data[key] ? data[key].size : 0
@@ -1055,7 +1166,13 @@ class Redis
1055
1166
  def zscore(key, value)
1056
1167
  data_type_check(key, ZSet)
1057
1168
  value = data[key] && data[key][value.to_s]
1058
- value && value.to_s
1169
+ if value == Float::INFINITY
1170
+ "inf"
1171
+ elsif value == -Float::INFINITY
1172
+ "-inf"
1173
+ elsif value
1174
+ value.to_s
1175
+ end
1059
1176
  end
1060
1177
 
1061
1178
  def zcount(key, min, max)
@@ -1069,7 +1186,14 @@ class Redis
1069
1186
  data[key] ||= ZSet.new
1070
1187
  data[key][value.to_s] ||= 0
1071
1188
  data[key].increment(value.to_s, num)
1072
- data[key][value.to_s].to_s
1189
+
1190
+ if num.is_a?(String) && num =~ /^\+?inf/
1191
+ "inf"
1192
+ elsif num == "-inf"
1193
+ "-inf"
1194
+ else
1195
+ data[key][value.to_s].to_s
1196
+ end
1073
1197
  end
1074
1198
 
1075
1199
  def zrank(key, value)
@@ -1093,6 +1217,7 @@ class Redis
1093
1217
  results = sort_keys(data[key])
1094
1218
  # Select just the keys unless we want scores
1095
1219
  results = results.map(&:first) unless with_scores
1220
+ start = [start, -results.size].max
1096
1221
  (results[start..stop] || []).flatten.map(&:to_s)
1097
1222
  end
1098
1223
 
@@ -1208,6 +1333,33 @@ class Redis
1208
1333
  data[out].size
1209
1334
  end
1210
1335
 
1336
+ def pfadd(key, member)
1337
+ data_type_check(key, Set)
1338
+ data[key] ||= Set.new
1339
+ previous_size = data[key].size
1340
+ data[key] |= Array(member)
1341
+ data[key].size != previous_size
1342
+ end
1343
+
1344
+ def pfcount(*keys)
1345
+ keys = keys.flatten
1346
+ raise_argument_error("pfcount") if keys.empty?
1347
+ keys.each { |key| data_type_check(key, Set) }
1348
+ if keys.count == 1
1349
+ (data[keys.first] || Set.new).size
1350
+ else
1351
+ union = keys.map { |key| data[key] }.compact.reduce(&:|)
1352
+ union.size
1353
+ end
1354
+ end
1355
+
1356
+ def pfmerge(destination, *sources)
1357
+ sources.each { |source| data_type_check(source, Set) }
1358
+ union = sources.map { |source| data[source] || Set.new }.reduce(&:|)
1359
+ data[destination] = union
1360
+ "OK"
1361
+ end
1362
+
1211
1363
  def subscribe(*channels)
1212
1364
  raise_argument_error('subscribe') if channels.empty?()
1213
1365
 
@@ -1348,13 +1500,36 @@ class Redis
1348
1500
  raise Redis::CommandError, "ERR syntax error"
1349
1501
  end
1350
1502
 
1503
+ def raise_options_error(*options)
1504
+ if options.detect { |opt| opt.match(/incr/i) }
1505
+ error_message = "ERR INCR option supports a single increment-element pair"
1506
+ else
1507
+ error_message = "ERR #{options.join(" and ")} options at the same time are not compatible"
1508
+ end
1509
+ raise Redis::CommandError, error_message
1510
+ end
1511
+
1512
+ def raise_command_error(message)
1513
+ raise Redis::CommandError, message
1514
+ end
1515
+
1516
+ def delete_keys(keys, command)
1517
+ keys = keys.flatten(1)
1518
+ raise_argument_error(command) if keys.empty?
1519
+
1520
+ old_count = data.keys.size
1521
+ keys.each do |key|
1522
+ data.delete(key)
1523
+ end
1524
+ old_count - data.keys.size
1525
+ end
1526
+
1351
1527
  def remove_key_for_empty_collection(key)
1352
1528
  del(key) if data[key] && data[key].empty?
1353
1529
  end
1354
1530
 
1355
1531
  def data_type_check(key, klass)
1356
1532
  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
1533
  raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value")
1359
1534
  end
1360
1535
  end
@@ -1419,9 +1594,33 @@ class Redis
1419
1594
  end
1420
1595
  end
1421
1596
 
1597
+ def bzpop(command, args)
1598
+ timeout =
1599
+ if args.last.is_a?(Hash)
1600
+ args.pop[:timeout]
1601
+ elsif args.last.respond_to?(:to_int)
1602
+ args.pop.to_int
1603
+ end
1604
+
1605
+ timeout ||= 0
1606
+ single_pop_command = command.to_s[1..-1]
1607
+ keys = args.flatten
1608
+ keys.each do |key|
1609
+ if data[key]
1610
+ data_type_check(data[key], ZSet)
1611
+ if data[key].size > 0
1612
+ result = public_send(single_pop_command, key)
1613
+ return result.unshift(key)
1614
+ end
1615
+ end
1616
+ end
1617
+ sleep(timeout.to_f)
1618
+ nil
1619
+ end
1620
+
1422
1621
  def sort_keys(arr)
1423
1622
  # Sort by score, or if scores are equal, key alphanum
1424
- sorted_keys = arr.sort do |(k1, v1), (k2, v2)|
1623
+ arr.sort do |(k1, v1), (k2, v2)|
1425
1624
  if v1 == v2
1426
1625
  k1 <=> k2
1427
1626
  else