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.
- 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
|