fakeredis 0.7.0 → 0.9.2

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
@@ -118,7 +122,7 @@ class Redis
118
122
  "OK"
119
123
  end
120
124
 
121
- def auth(password)
125
+ def auth(*args)
122
126
  "OK"
123
127
  end
124
128
 
@@ -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,21 @@ 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
+ if Redis.sadd_returns_boolean && !should_return_int
566
+ return result == 1
567
+ end
568
+
547
569
  result
548
570
  end
549
571
 
572
+ def sadd?(key, value)
573
+ sadd(key, value) == 1
574
+ end
575
+
550
576
  def srem(key, value)
551
577
  data_type_check(key, ::Set)
578
+ raise_argument_error('srem') if Array(value).empty?
552
579
  return false unless data[key]
553
580
 
554
581
  if value.is_a?(Array)
@@ -575,7 +602,7 @@ class Redis
575
602
  data_type_check(key, ::Set)
576
603
  results = (count || 1).times.map do
577
604
  elem = srandmember(key)
578
- srem(key, elem)
605
+ srem(key, elem) if elem
579
606
  elem
580
607
  end.compact
581
608
  count.nil? ? results.first : results
@@ -635,6 +662,8 @@ class Redis
635
662
  data_type_check(destination, ::Set)
636
663
  result = sdiff(key1, *keys)
637
664
  data[destination] = ::Set.new(result)
665
+
666
+ result.size
638
667
  end
639
668
 
640
669
  def srandmember(key, number=nil)
@@ -680,18 +709,15 @@ class Redis
680
709
  end
681
710
 
682
711
  def del(*keys)
683
- keys = keys.flatten(1)
684
- raise_argument_error('del') if keys.empty?
712
+ delete_keys(keys, 'del')
713
+ end
685
714
 
686
- old_count = data.keys.size
687
- keys.each do |key|
688
- data.delete(key)
689
- end
690
- old_count - data.keys.size
715
+ def unlink(*keys)
716
+ delete_keys(keys, 'unlink')
691
717
  end
692
718
 
693
719
  def setnx(key, value)
694
- if exists(key)
720
+ if exists(key) > 0
695
721
  0
696
722
  else
697
723
  set(key, value)
@@ -707,7 +733,7 @@ class Redis
707
733
  end
708
734
 
709
735
  def renamenx(key, new_key)
710
- if exists(new_key)
736
+ if exists(new_key) > 0
711
737
  false
712
738
  else
713
739
  rename(key, new_key)
@@ -731,7 +757,7 @@ class Redis
731
757
  if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
732
758
  ttl
733
759
  else
734
- exists(key) ? -1 : -2
760
+ exists(key) > 0 ? -1 : -2
735
761
  end
736
762
  end
737
763
 
@@ -739,7 +765,7 @@ class Redis
739
765
  if data.expires.include?(key) && (ttl = data.expires[key].to_f - Time.now.to_f) > 0
740
766
  ttl * 1000
741
767
  else
742
- exists(key) ? -1 : -2
768
+ exists(key) > 0 ? -1 : -2
743
769
  end
744
770
  end
745
771
 
@@ -752,17 +778,32 @@ class Redis
752
778
  !!data.expires.delete(key)
753
779
  end
754
780
 
755
- def hset(key, field, value)
781
+ def hset(key, *fields)
782
+ fields = fields.first if fields.size == 1 && fields.first.is_a?(Hash)
783
+ raise_argument_error('hset') if fields.empty?
784
+
785
+ is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)}
786
+
787
+ raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays
788
+ raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2}
789
+
756
790
  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
791
+ insert_count = 0
792
+ data[key] ||= {}
793
+
794
+ if fields.is_a?(Hash)
795
+ insert_count = fields.keys.size - (data[key].keys & fields.keys).size
796
+
797
+ data[key].merge!(fields)
762
798
  else
763
- data[key] = { field => value.to_s }
764
- 1
799
+ fields.each_slice(2) do |field|
800
+ insert_count += 1 if data[key][field[0].to_s].nil?
801
+
802
+ data[key][field[0].to_s] = field[1].to_s
803
+ end
765
804
  end
805
+
806
+ insert_count
766
807
  end
767
808
 
768
809
  def hsetnx(key, field, value)
@@ -798,7 +839,7 @@ class Redis
798
839
  end
799
840
 
800
841
  def hmget(key, *fields)
801
- raise_argument_error('hmget') if fields.empty?
842
+ raise_argument_error('hmget') if fields.empty? || fields.flatten.empty?
802
843
 
803
844
  data_type_check(key, Hash)
804
845
  fields.flatten.map do |field|
@@ -817,6 +858,12 @@ class Redis
817
858
  data[key].size
818
859
  end
819
860
 
861
+ def hstrlen(key, field)
862
+ data_type_check(key, Hash)
863
+ return 0 if data[key].nil? || data[key][field].nil?
864
+ data[key][field].size
865
+ end
866
+
820
867
  def hvals(key)
821
868
  data_type_check(key, Hash)
822
869
  return [] unless data[key]
@@ -853,26 +900,19 @@ class Redis
853
900
 
854
901
  def sync ; end
855
902
 
856
- def [](key)
857
- get(key)
858
- end
859
-
860
- def []=(key, value)
861
- set(key, value)
862
- end
863
-
864
903
  def set(key, value, *array_options)
865
904
  option_nx = array_options.delete("NX")
866
905
  option_xx = array_options.delete("XX")
867
906
 
868
- return false if option_nx && option_xx
907
+ return nil if option_nx && option_xx
869
908
 
870
- return false if option_nx && exists(key)
871
- return false if option_xx && !exists(key)
909
+ return nil if option_nx && exists(key) > 0
910
+ return nil if option_xx && exists(key).zero?
872
911
 
873
912
  data[key] = value.to_s
874
913
 
875
914
  options = Hash[array_options.each_slice(2).to_a]
915
+ raise_command_error('ERR value is not an integer or out of range') if non_integer_expirations?(options)
876
916
  ttl_in_seconds = options["EX"] if options["EX"]
877
917
  ttl_in_seconds = options["PX"] / 1000.0 if options["PX"]
878
918
 
@@ -881,6 +921,11 @@ class Redis
881
921
  "OK"
882
922
  end
883
923
 
924
+ def non_integer_expirations?(options)
925
+ (options["EX"] && !options["EX"].is_a?(Integer)) ||
926
+ (options["PX"] && !options["PX"].is_a?(Integer))
927
+ end
928
+
884
929
  def setbit(key, offset, bit)
885
930
  old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
886
931
  size_increment = [((offset/8)+1)*8-old_val.length, 0].max
@@ -894,11 +939,19 @@ class Redis
894
939
  end
895
940
 
896
941
  def setex(key, seconds, value)
942
+ raise_command_error('ERR value is not an integer or out of range') unless seconds.is_a?(Integer)
897
943
  data[key] = value.to_s
898
944
  expire(key, seconds)
899
945
  "OK"
900
946
  end
901
947
 
948
+ def psetex(key, milliseconds, value)
949
+ raise_command_error('ERR value is not an integer or out of range') unless milliseconds.is_a?(Integer)
950
+ data[key] = value.to_s
951
+ expire(key, milliseconds / 1000.0)
952
+ "OK"
953
+ end
954
+
902
955
  def setrange(key, offset, value)
903
956
  return unless data[key]
904
957
  s = data[key][offset,value.size]
@@ -1005,6 +1058,19 @@ class Redis
1005
1058
  end
1006
1059
 
1007
1060
  def zadd(key, *args)
1061
+ option_xx = args.delete("XX")
1062
+ option_nx = args.delete("NX")
1063
+ option_ch = args.delete("CH")
1064
+ option_incr = args.delete("INCR")
1065
+
1066
+ if option_xx && option_nx
1067
+ raise_options_error("XX", "NX")
1068
+ end
1069
+
1070
+ if option_incr && args.size > 2
1071
+ raise_options_error("INCR")
1072
+ end
1073
+
1008
1074
  if !args.first.is_a?(Array)
1009
1075
  if args.size < 2
1010
1076
  raise_argument_error('zadd')
@@ -1020,18 +1086,39 @@ class Redis
1020
1086
  data_type_check(key, ZSet)
1021
1087
  data[key] ||= ZSet.new
1022
1088
 
1023
- if args.size == 2 && !(Array === args.first)
1024
- score, value = args
1025
- exists = !data[key].key?(value.to_s)
1089
+ # Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
1090
+ args = args.each_slice(2).to_a unless args.first.is_a?(Array)
1091
+
1092
+ changed = 0
1093
+ exists = args.map(&:last).count { |el| !hexists(key, el.to_s) }
1094
+
1095
+ args.each do |score, value|
1096
+ if option_nx && hexists(key, value.to_s)
1097
+ next
1098
+ end
1099
+
1100
+ if option_xx && !hexists(key, value.to_s)
1101
+ exists -= 1
1102
+ next
1103
+ end
1104
+
1105
+ if option_incr
1106
+ data[key][value.to_s] ||= 0
1107
+ return data[key].increment(value, score).to_s
1108
+ end
1109
+
1110
+ if option_ch && data[key][value.to_s] != score
1111
+ changed += 1
1112
+ end
1026
1113
  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
1114
  end
1033
1115
 
1034
- exists
1116
+ if option_incr
1117
+ changed = changed.zero? ? nil : changed
1118
+ exists = exists.zero? ? nil : exists
1119
+ end
1120
+
1121
+ option_ch ? changed : exists
1035
1122
  end
1036
1123
 
1037
1124
  def zrem(key, value)
@@ -1047,6 +1134,36 @@ class Redis
1047
1134
  response
1048
1135
  end
1049
1136
 
1137
+ def zpopmax(key, count = nil)
1138
+ data_type_check(key, ZSet)
1139
+ return [] unless data[key]
1140
+ sorted_members = sort_keys(data[key])
1141
+ results = sorted_members.last(count || 1).reverse!
1142
+ results.each do |member|
1143
+ zrem(key, member.first)
1144
+ end
1145
+ count.nil? ? results.first : results.flatten
1146
+ end
1147
+
1148
+ def zpopmin(key, count = nil)
1149
+ data_type_check(key, ZSet)
1150
+ return [] unless data[key]
1151
+ sorted_members = sort_keys(data[key])
1152
+ results = sorted_members.first(count || 1)
1153
+ results.each do |member|
1154
+ zrem(key, member.first)
1155
+ end
1156
+ count.nil? ? results.first : results.flatten
1157
+ end
1158
+
1159
+ def bzpopmax(*args, timeout: 0)
1160
+ bzpop(:bzpopmax, args, timeout)
1161
+ end
1162
+
1163
+ def bzpopmin(*args, timeout: 0)
1164
+ bzpop(:bzpopmin, args, timeout)
1165
+ end
1166
+
1050
1167
  def zcard(key)
1051
1168
  data_type_check(key, ZSet)
1052
1169
  data[key] ? data[key].size : 0
@@ -1055,7 +1172,13 @@ class Redis
1055
1172
  def zscore(key, value)
1056
1173
  data_type_check(key, ZSet)
1057
1174
  value = data[key] && data[key][value.to_s]
1058
- value && value.to_s
1175
+ if value == Float::INFINITY
1176
+ "inf"
1177
+ elsif value == -Float::INFINITY
1178
+ "-inf"
1179
+ elsif value
1180
+ value.to_s
1181
+ end
1059
1182
  end
1060
1183
 
1061
1184
  def zcount(key, min, max)
@@ -1069,7 +1192,14 @@ class Redis
1069
1192
  data[key] ||= ZSet.new
1070
1193
  data[key][value.to_s] ||= 0
1071
1194
  data[key].increment(value.to_s, num)
1072
- data[key][value.to_s].to_s
1195
+
1196
+ if num.is_a?(String) && num =~ /^\+?inf/
1197
+ "inf"
1198
+ elsif num == "-inf"
1199
+ "-inf"
1200
+ else
1201
+ data[key][value.to_s].to_s
1202
+ end
1073
1203
  end
1074
1204
 
1075
1205
  def zrank(key, value)
@@ -1093,6 +1223,7 @@ class Redis
1093
1223
  results = sort_keys(data[key])
1094
1224
  # Select just the keys unless we want scores
1095
1225
  results = results.map(&:first) unless with_scores
1226
+ start = [start, -results.size].max
1096
1227
  (results[start..stop] || []).flatten.map(&:to_s)
1097
1228
  end
1098
1229
 
@@ -1208,6 +1339,33 @@ class Redis
1208
1339
  data[out].size
1209
1340
  end
1210
1341
 
1342
+ def pfadd(key, member)
1343
+ data_type_check(key, Set)
1344
+ data[key] ||= Set.new
1345
+ previous_size = data[key].size
1346
+ data[key] |= Array(member)
1347
+ data[key].size != previous_size
1348
+ end
1349
+
1350
+ def pfcount(*keys)
1351
+ keys = keys.flatten
1352
+ raise_argument_error("pfcount") if keys.empty?
1353
+ keys.each { |key| data_type_check(key, Set) }
1354
+ if keys.count == 1
1355
+ (data[keys.first] || Set.new).size
1356
+ else
1357
+ union = keys.map { |key| data[key] }.compact.reduce(&:|)
1358
+ union.size
1359
+ end
1360
+ end
1361
+
1362
+ def pfmerge(destination, *sources)
1363
+ sources.each { |source| data_type_check(source, Set) }
1364
+ union = sources.map { |source| data[source] || Set.new }.reduce(&:|)
1365
+ data[destination] = union
1366
+ "OK"
1367
+ end
1368
+
1211
1369
  def subscribe(*channels)
1212
1370
  raise_argument_error('subscribe') if channels.empty?()
1213
1371
 
@@ -1348,13 +1506,36 @@ class Redis
1348
1506
  raise Redis::CommandError, "ERR syntax error"
1349
1507
  end
1350
1508
 
1509
+ def raise_options_error(*options)
1510
+ if options.detect { |opt| opt.match(/incr/i) }
1511
+ error_message = "ERR INCR option supports a single increment-element pair"
1512
+ else
1513
+ error_message = "ERR #{options.join(" and ")} options at the same time are not compatible"
1514
+ end
1515
+ raise Redis::CommandError, error_message
1516
+ end
1517
+
1518
+ def raise_command_error(message)
1519
+ raise Redis::CommandError, message
1520
+ end
1521
+
1522
+ def delete_keys(keys, command)
1523
+ keys = keys.flatten(1)
1524
+ raise_argument_error(command) if keys.empty?
1525
+
1526
+ old_count = data.keys.size
1527
+ keys.each do |key|
1528
+ data.delete(key)
1529
+ end
1530
+ old_count - data.keys.size
1531
+ end
1532
+
1351
1533
  def remove_key_for_empty_collection(key)
1352
1534
  del(key) if data[key] && data[key].empty?
1353
1535
  end
1354
1536
 
1355
1537
  def data_type_check(key, klass)
1356
1538
  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
1539
  raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value")
1359
1540
  end
1360
1541
  end
@@ -1419,9 +1600,25 @@ class Redis
1419
1600
  end
1420
1601
  end
1421
1602
 
1603
+ def bzpop(command, args, timeout)
1604
+ single_pop_command = command.to_s[1..-1]
1605
+ keys = args.flatten
1606
+ keys.each do |key|
1607
+ if data[key]
1608
+ data_type_check(data[key], ZSet)
1609
+ if data[key].size > 0
1610
+ result = public_send(single_pop_command, key)
1611
+ return result.unshift(key)
1612
+ end
1613
+ end
1614
+ end
1615
+ sleep(timeout.to_f)
1616
+ nil
1617
+ end
1618
+
1422
1619
  def sort_keys(arr)
1423
1620
  # Sort by score, or if scores are equal, key alphanum
1424
- sorted_keys = arr.sort do |(k1, v1), (k2, v2)|
1621
+ arr.sort do |(k1, v1), (k2, v2)|
1425
1622
  if v1 == v2
1426
1623
  k1 <=> k2
1427
1624
  else