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.
- checksums.yaml +5 -5
- data/.github/workflows/codeql.yml +74 -0
- data/.github/workflows/ruby.yml +33 -0
- data/.gitignore +0 -1
- data/Gemfile +0 -6
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/fakeredis.gemspec +3 -3
- 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 +2 -2
- data/lib/fakeredis.rb +16 -0
- data/lib/redis/connection/memory.rb +260 -63
- 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 +46 -5
- data/spec/hyper_log_logs_spec.rb +50 -0
- data/spec/keys_spec.rb +92 -34
- data/spec/lists_spec.rb +20 -3
- data/spec/memory_spec.rb +4 -8
- data/spec/server_spec.rb +18 -4
- data/spec/sets_spec.rb +25 -4
- data/spec/sorted_sets_spec.rb +180 -2
- data/spec/spec_helper.rb +8 -19
- data/spec/strings_spec.rb +42 -2
- data/spec/subscription_spec.rb +31 -31
- data/spec/support/shared_examples/bitwise_operation.rb +12 -12
- data/spec/transactions_spec.rb +14 -1
- metadata +21 -22
- data/.travis.yml +0 -22
- data/gemfiles/redisrb-master.gemfile +0 -14
@@ -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
|
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(
|
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" =>
|
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
|
154
|
+
def bgsave; end
|
155
|
+
|
156
|
+
def bgrewriteaof; end
|
157
|
+
|
158
|
+
def evalsha; end
|
151
159
|
|
152
|
-
def
|
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
|
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
|
-
|
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(
|
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)
|
497
|
-
|
498
|
-
|
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
|
546
|
-
|
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
|
684
|
-
|
712
|
+
delete_keys(keys, 'del')
|
713
|
+
end
|
685
714
|
|
686
|
-
|
687
|
-
keys
|
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,
|
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
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
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
|
-
|
764
|
-
|
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
|
907
|
+
return nil if option_nx && option_xx
|
869
908
|
|
870
|
-
return
|
871
|
-
return
|
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
|
-
|
1024
|
-
|
1025
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
1621
|
+
arr.sort do |(k1, v1), (k2, v2)|
|
1425
1622
|
if v1 == v2
|
1426
1623
|
k1 <=> k2
|
1427
1624
|
else
|