fakeredis 0.7.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +4 -3
- data/fakeredis.gemspec +2 -2
- 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 +261 -62
- 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 +23 -2
- data/spec/sorted_sets_spec.rb +180 -2
- data/spec/spec_helper.rb +5 -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 +20 -21
- 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
|
@@ -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,15 @@ class Redis
|
|
542
561
|
data[key].size
|
543
562
|
end
|
544
563
|
|
545
|
-
# 0 = false, 1 = true
|
546
|
-
return result == 1
|
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
|
684
|
-
|
706
|
+
delete_keys(keys, 'del')
|
707
|
+
end
|
685
708
|
|
686
|
-
|
687
|
-
keys
|
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,
|
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
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
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
|
-
|
764
|
-
|
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
|
901
|
+
return nil if option_nx && option_xx
|
869
902
|
|
870
|
-
return
|
871
|
-
return
|
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
|
-
|
1024
|
-
|
1025
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
1623
|
+
arr.sort do |(k1, v1), (k2, v2)|
|
1425
1624
|
if v1 == v2
|
1426
1625
|
k1 <=> k2
|
1427
1626
|
else
|