fakeredis 0.7.0 → 0.9.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.
@@ -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