redis-file 0.4.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.
@@ -0,0 +1,960 @@
1
+ require 'set'
2
+ require 'redis/connection/registry'
3
+ require 'redis/connection/command_helper'
4
+ require "redis-file/expiring_hash"
5
+ require "redis-file/sorted_set_argument_handler"
6
+ require "redis-file/sorted_set_store"
7
+ require "redis-file/zset"
8
+ require 'digest/md5'
9
+
10
+ class Redis
11
+ module Connection
12
+ class File
13
+
14
+ # try use "db" folder, if the folder not exists, use the same folder
15
+ FILENAME_SUFIX = "redis"
16
+ FILENAME_PREFIX = Rails.env + "." rescue "db."
17
+ DB_FOLDER = ::File.directory?("db") ? "db/" : "./"
18
+ DB_PATH = DB_FOLDER + FILENAME_PREFIX + FILENAME_SUFIX
19
+ ::File.open(DB_PATH, 'wb') { |file| Marshal.dump({}, file) } unless ::File.exists?(DB_PATH)
20
+
21
+ include Redis::Connection::CommandHelper
22
+ include RedisFile
23
+
24
+ attr_accessor :buffer, :options
25
+
26
+ # Used for resetting everything in specs
27
+ def self.reset_all_databases
28
+ ::File.open(DB_PATH, 'wb') { |file| Marshal.dump({}, file) }
29
+ end
30
+
31
+ def self.connect(options = {})
32
+ new(options)
33
+ end
34
+
35
+ def initialize(options = {})
36
+ self.options = options
37
+ end
38
+
39
+ def database_id
40
+ @database_id ||= 0
41
+ end
42
+ attr_writer :database_id
43
+
44
+ def database_instance_key
45
+ Digest::MD5.hexdigest([options[:host], options[:port]].to_s)
46
+ end
47
+
48
+ def find_database id=database_id
49
+ @databases[database_instance_key] ||= {}
50
+ @databases[database_instance_key][id] ||= ExpiringHash.new
51
+ end
52
+
53
+ def data
54
+ find_database
55
+ end
56
+
57
+ def replies
58
+ @replies ||= []
59
+ end
60
+ attr_writer :replies
61
+
62
+ def connected?
63
+ true
64
+ end
65
+
66
+ def connect_unix(path, timeout)
67
+ end
68
+
69
+ def disconnect
70
+ end
71
+
72
+ def timeout=(usecs)
73
+ end
74
+
75
+ def write(command)
76
+ meffod = command.shift.to_s.downcase.to_sym
77
+ if respond_to?(meffod)
78
+ @databases = ::File.open(DB_PATH) { |file| Marshal.load(file) }
79
+ reply = send(meffod, *command)
80
+ ::File.open(DB_PATH, 'wb') { |file| Marshal.dump(@databases, file) }
81
+ else
82
+ raise Redis::CommandError, "ERR unknown command '#{meffod}'"
83
+ end
84
+
85
+ if reply == true
86
+ reply = 1
87
+ elsif reply == false
88
+ reply = 0
89
+ end
90
+
91
+ replies << reply
92
+ buffer << reply if buffer && meffod != :multi
93
+ nil
94
+ end
95
+
96
+ def read
97
+ replies.shift
98
+ end
99
+
100
+ # NOT IMPLEMENTED:
101
+ # * blpop
102
+ # * brpop
103
+ # * brpoplpush
104
+ # * discard
105
+ # * sort
106
+ # * subscribe
107
+ # * psubscribe
108
+ # * publish
109
+
110
+ def flushdb
111
+ @databases[database_instance_key].delete(database_id)
112
+ "OK"
113
+ end
114
+
115
+ def flushall
116
+ @databases[database_instance_key] = {}
117
+ "OK"
118
+ end
119
+
120
+ def auth(password)
121
+ "OK"
122
+ end
123
+
124
+ def select(index)
125
+ data_type_check(index, Integer)
126
+ self.database_id = index
127
+ "OK"
128
+ end
129
+
130
+ def info
131
+ {
132
+ "redis_version" => "0.07",
133
+ "connected_clients" => "1",
134
+ "connected_slaves" => "0",
135
+ "used_memory" => "3187",
136
+ "changes_since_last_save" => "0",
137
+ "last_save_time" => "1237655729",
138
+ "total_connections_received" => "1",
139
+ "total_commands_processed" => "1",
140
+ "uptime_in_seconds" => "36000",
141
+ "uptime_in_days" => 0
142
+ }
143
+ end
144
+
145
+ def monitor; end
146
+
147
+ def save; end
148
+
149
+ def bgsave ; end
150
+
151
+ def bgreriteaof ; end
152
+
153
+ def move key, destination_id
154
+ raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
155
+ destination = find_database(destination_id)
156
+ return false unless data.has_key?(key)
157
+ return false if destination.has_key?(key)
158
+ destination[key] = data.delete(key)
159
+ true
160
+ end
161
+
162
+ def get(key)
163
+ data_type_check(key, String)
164
+ data[key]
165
+ end
166
+
167
+ def getbit(key, offset)
168
+ return unless data[key]
169
+ data[key].unpack('B*')[0].split("")[offset].to_i
170
+ end
171
+
172
+ def getrange(key, start, ending)
173
+ return unless data[key]
174
+ data[key][start..ending]
175
+ end
176
+ alias :substr :getrange
177
+
178
+ def getset(key, value)
179
+ data_type_check(key, String)
180
+ data[key].tap do
181
+ set(key, value)
182
+ end
183
+ end
184
+
185
+ def mget(*keys)
186
+ raise_argument_error('mget') if keys.empty?
187
+ # We work with either an array, or list of arguments
188
+ keys = keys.first if keys.size == 1
189
+ data.values_at(*keys)
190
+ end
191
+
192
+ def append(key, value)
193
+ data[key] = (data[key] || "")
194
+ data[key] = data[key] + value.to_s
195
+ end
196
+
197
+ def strlen(key)
198
+ return unless data[key]
199
+ data[key].size
200
+ end
201
+
202
+ def hgetall(key)
203
+ data_type_check(key, Hash)
204
+ data[key].to_a.flatten || {}
205
+ end
206
+
207
+ def hget(key, field)
208
+ data_type_check(key, Hash)
209
+ data[key] && data[key][field.to_s]
210
+ end
211
+
212
+ def hdel(key, field)
213
+ field = field.to_s
214
+ data_type_check(key, Hash)
215
+ data[key] && data[key].delete(field)
216
+ remove_key_for_empty_collection(key)
217
+ end
218
+
219
+ def hkeys(key)
220
+ data_type_check(key, Hash)
221
+ return [] if data[key].nil?
222
+ data[key].keys
223
+ end
224
+
225
+ def keys(pattern = "*")
226
+ regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
227
+ data.keys.select { |key| key =~ regexp }
228
+ end
229
+
230
+ def randomkey
231
+ data.keys[rand(dbsize)]
232
+ end
233
+
234
+ def echo(string)
235
+ string
236
+ end
237
+
238
+ def ping
239
+ "PONG"
240
+ end
241
+
242
+ def lastsave
243
+ Time.now.to_i
244
+ end
245
+
246
+ def dbsize
247
+ data.keys.count
248
+ end
249
+
250
+ def exists(key)
251
+ data.key?(key)
252
+ end
253
+
254
+ def llen(key)
255
+ data_type_check(key, Array)
256
+ return 0 unless data[key]
257
+ data[key].size
258
+ end
259
+
260
+ def lrange(key, startidx, endidx)
261
+ data_type_check(key, Array)
262
+ (data[key] && data[key][startidx..endidx]) || []
263
+ end
264
+
265
+ def ltrim(key, start, stop)
266
+ data_type_check(key, Array)
267
+ return unless data[key]
268
+
269
+ if start < 0 && data[key].count < start.abs
270
+ # Example: we have a list of 3 elements and
271
+ # we give it a ltrim list, -5, -1. This means
272
+ # it should trim to a max of 5. Since 3 < 5
273
+ # we should not touch the list. This is consistent
274
+ # with behavior of real Redis's ltrim with a negative
275
+ # start argument.
276
+ data[key]
277
+ else
278
+ data[key] = data[key][start..stop]
279
+ end
280
+ end
281
+
282
+ def lindex(key, index)
283
+ data_type_check(key, Array)
284
+ data[key] && data[key][index]
285
+ end
286
+
287
+ def linsert(key, where, pivot, value)
288
+ data_type_check(key, Array)
289
+ return unless data[key]
290
+ index = data[key].index(pivot)
291
+ case where
292
+ when :before then data[key].insert(index, value)
293
+ when :after then data[key].insert(index + 1, value)
294
+ else raise_syntax_error
295
+ end
296
+ end
297
+
298
+ def lset(key, index, value)
299
+ data_type_check(key, Array)
300
+ return unless data[key]
301
+ raise Redis::CommandError, "ERR index out of range" if index >= data[key].size
302
+ data[key][index] = value
303
+ end
304
+
305
+ def lrem(key, count, value)
306
+ data_type_check(key, Array)
307
+ return unless data[key]
308
+ old_size = data[key].size
309
+ diff =
310
+ if count == 0
311
+ data[key].delete(value)
312
+ old_size - data[key].size
313
+ else
314
+ array = count > 0 ? data[key].dup : data[key].reverse
315
+ count.abs.times{ array.delete_at(array.index(value) || array.length) }
316
+ data[key] = count > 0 ? array.dup : array.reverse
317
+ old_size - data[key].size
318
+ end
319
+ remove_key_for_empty_collection(key)
320
+ diff
321
+ end
322
+
323
+ def rpush(key, value)
324
+ data_type_check(key, Array)
325
+ data[key] ||= []
326
+ [value].flatten.each do |val|
327
+ data[key].push(val.to_s)
328
+ end
329
+ data[key].size
330
+ end
331
+
332
+ def rpushx(key, value)
333
+ data_type_check(key, Array)
334
+ return unless data[key]
335
+ rpush(key, value)
336
+ end
337
+
338
+ def lpush(key, value)
339
+ data_type_check(key, Array)
340
+ data[key] ||= []
341
+ [value].flatten.each do |val|
342
+ data[key].unshift(val.to_s)
343
+ end
344
+ data[key].size
345
+ end
346
+
347
+ def lpushx(key, value)
348
+ data_type_check(key, Array)
349
+ return unless data[key]
350
+ lpush(key, value)
351
+ end
352
+
353
+ def rpop(key)
354
+ data_type_check(key, Array)
355
+ return unless data[key]
356
+ data[key].pop
357
+ end
358
+
359
+ def rpoplpush(key1, key2)
360
+ data_type_check(key1, Array)
361
+ rpop(key1).tap do |elem|
362
+ lpush(key2, elem)
363
+ end
364
+ end
365
+
366
+ def lpop(key)
367
+ data_type_check(key, Array)
368
+ return unless data[key]
369
+ data[key].shift
370
+ end
371
+
372
+ def smembers(key)
373
+ data_type_check(key, ::Set)
374
+ return [] unless data[key]
375
+ data[key].to_a.reverse
376
+ end
377
+
378
+ def sismember(key, value)
379
+ data_type_check(key, ::Set)
380
+ return false unless data[key]
381
+ data[key].include?(value.to_s)
382
+ end
383
+
384
+ def sadd(key, value)
385
+ data_type_check(key, ::Set)
386
+ value = Array(value)
387
+ raise_argument_error('sadd') if value.empty?
388
+
389
+ result = if data[key]
390
+ old_set = data[key].dup
391
+ data[key].merge(value.map(&:to_s))
392
+ (data[key] - old_set).size
393
+ else
394
+ data[key] = ::Set.new(value.map(&:to_s))
395
+ data[key].size
396
+ end
397
+
398
+ # 0 = false, 1 = true, 2+ untouched
399
+ return result == 1 if result < 2
400
+ result
401
+ end
402
+
403
+ def srem(key, value)
404
+ data_type_check(key, ::Set)
405
+ deleted = !!(data[key] && data[key].delete?(value.to_s))
406
+ remove_key_for_empty_collection(key)
407
+ deleted
408
+ end
409
+
410
+ def smove(source, destination, value)
411
+ data_type_check(destination, ::Set)
412
+ result = self.srem(source, value)
413
+ self.sadd(destination, value) if result
414
+ result
415
+ end
416
+
417
+ def spop(key)
418
+ data_type_check(key, ::Set)
419
+ elem = srandmember(key)
420
+ srem(key, elem)
421
+ elem
422
+ end
423
+
424
+ def scard(key)
425
+ data_type_check(key, ::Set)
426
+ return 0 unless data[key]
427
+ data[key].size
428
+ end
429
+
430
+ def sinter(*keys)
431
+ raise_argument_error('sinter') if keys.empty?
432
+
433
+ keys.each { |k| data_type_check(k, ::Set) }
434
+ return ::Set.new if keys.any? { |k| data[k].nil? }
435
+ keys = keys.map { |k| data[k] || ::Set.new }
436
+ keys.inject do |set, key|
437
+ set & key
438
+ end.to_a
439
+ end
440
+
441
+ def sinterstore(destination, *keys)
442
+ data_type_check(destination, ::Set)
443
+ result = sinter(*keys)
444
+ data[destination] = ::Set.new(result)
445
+ end
446
+
447
+ def sunion(*keys)
448
+ keys.each { |k| data_type_check(k, ::Set) }
449
+ keys = keys.map { |k| data[k] || ::Set.new }
450
+ keys.inject(::Set.new) do |set, key|
451
+ set | key
452
+ end.to_a
453
+ end
454
+
455
+ def sunionstore(destination, *keys)
456
+ data_type_check(destination, ::Set)
457
+ result = sunion(*keys)
458
+ data[destination] = ::Set.new(result)
459
+ end
460
+
461
+ def sdiff(key1, *keys)
462
+ [key1, *keys].each { |k| data_type_check(k, ::Set) }
463
+ keys = keys.map { |k| data[k] || ::Set.new }
464
+ keys.inject(data[key1]) do |memo, set|
465
+ memo - set
466
+ end.to_a
467
+ end
468
+
469
+ def sdiffstore(destination, key1, *keys)
470
+ data_type_check(destination, ::Set)
471
+ result = sdiff(key1, *keys)
472
+ data[destination] = ::Set.new(result)
473
+ end
474
+
475
+ def srandmember(key)
476
+ data_type_check(key, ::Set)
477
+ return nil unless data[key]
478
+ data[key].to_a[rand(data[key].size)]
479
+ end
480
+
481
+ def del(*keys)
482
+ keys = keys.flatten(1)
483
+ raise_argument_error('del') if keys.empty?
484
+
485
+ old_count = data.keys.size
486
+ keys.each do |key|
487
+ data.delete(key)
488
+ end
489
+ old_count - data.keys.size
490
+ end
491
+
492
+ def setnx(key, value)
493
+ if exists(key)
494
+ false
495
+ else
496
+ set(key, value)
497
+ true
498
+ end
499
+ end
500
+
501
+ def rename(key, new_key)
502
+ return unless data[key]
503
+ data[new_key] = data[key]
504
+ data.expires[new_key] = data.expires[key] if data.expires.include?(key)
505
+ data.delete(key)
506
+ end
507
+
508
+ def renamenx(key, new_key)
509
+ if exists(new_key)
510
+ false
511
+ else
512
+ rename(key, new_key)
513
+ true
514
+ end
515
+ end
516
+
517
+ def expire(key, ttl)
518
+ return unless data[key]
519
+ data.expires[key] = Time.now + ttl
520
+ true
521
+ end
522
+
523
+ def ttl(key)
524
+ if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
525
+ ttl
526
+ else
527
+ -1
528
+ end
529
+ end
530
+
531
+ def expireat(key, timestamp)
532
+ data.expires[key] = Time.at(timestamp)
533
+ true
534
+ end
535
+
536
+ def persist(key)
537
+ !!data.expires.delete(key)
538
+ end
539
+
540
+ def hset(key, field, value)
541
+ data_type_check(key, Hash)
542
+ field = field.to_s
543
+ if data[key]
544
+ result = !data[key].include?(field)
545
+ data[key][field] = value.to_s
546
+ result
547
+ else
548
+ data[key] = { field => value.to_s }
549
+ true
550
+ end
551
+ end
552
+
553
+ def hsetnx(key, field, value)
554
+ data_type_check(key, Hash)
555
+ field = field.to_s
556
+ return false if data[key] && data[key][field]
557
+ hset(key, field, value)
558
+ end
559
+
560
+ def hmset(key, *fields)
561
+ # mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
562
+ fields = fields[0] if mapped_param?(fields)
563
+ raise_argument_error('hmset') if fields.empty?
564
+
565
+ is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)}
566
+
567
+ raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays
568
+ raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2}
569
+
570
+ data_type_check(key, Hash)
571
+ data[key] ||= {}
572
+
573
+ if is_list_of_arrays
574
+ fields.each do |pair|
575
+ data[key][pair[0].to_s] = pair[1].to_s
576
+ end
577
+ else
578
+ fields.each_slice(2) do |field|
579
+ data[key][field[0].to_s] = field[1].to_s
580
+ end
581
+ end
582
+ end
583
+
584
+ def hmget(key, *fields)
585
+ raise_argument_error('hmget') if fields.empty?
586
+
587
+ data_type_check(key, Hash)
588
+ fields.map do |field|
589
+ field = field.to_s
590
+ if data[key]
591
+ data[key][field]
592
+ else
593
+ nil
594
+ end
595
+ end
596
+ end
597
+
598
+ def hlen(key)
599
+ data_type_check(key, Hash)
600
+ return 0 unless data[key]
601
+ data[key].size
602
+ end
603
+
604
+ def hvals(key)
605
+ data_type_check(key, Hash)
606
+ return [] unless data[key]
607
+ data[key].values
608
+ end
609
+
610
+ def hincrby(key, field, increment)
611
+ data_type_check(key, Hash)
612
+ field = field.to_s
613
+ if data[key]
614
+ data[key][field] = (data[key][field].to_i + increment.to_i).to_s
615
+ else
616
+ data[key] = { field => increment.to_s }
617
+ end
618
+ data[key][field].to_i
619
+ end
620
+
621
+ def hexists(key, field)
622
+ data_type_check(key, Hash)
623
+ return false unless data[key]
624
+ data[key].key?(field.to_s)
625
+ end
626
+
627
+ def sync ; end
628
+
629
+ def [](key)
630
+ get(key)
631
+ end
632
+
633
+ def []=(key, value)
634
+ set(key, value)
635
+ end
636
+
637
+ def set(key, value)
638
+ data[key] = value.to_s
639
+ "OK"
640
+ end
641
+
642
+ def setbit(key, offset, bit)
643
+ old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
644
+ size_increment = [((offset/8)+1)*8-old_val.length, 0].max
645
+ old_val += Array.new(size_increment).map{"0"}
646
+ original_val = old_val[offset]
647
+ old_val[offset] = bit.to_s
648
+ new_val = ""
649
+ old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
650
+ data[key] = new_val
651
+ original_val
652
+ end
653
+
654
+ def setex(key, seconds, value)
655
+ data[key] = value.to_s
656
+ expire(key, seconds)
657
+ "OK"
658
+ end
659
+
660
+ def setrange(key, offset, value)
661
+ return unless data[key]
662
+ s = data[key][offset,value.size]
663
+ data[key][s] = value
664
+ end
665
+
666
+ def mset(*pairs)
667
+ # Handle pairs for mapped_mset command
668
+ pairs = pairs[0] if mapped_param?(pairs)
669
+ raise_argument_error('mset') if pairs.empty? || pairs.size.odd?
670
+
671
+ pairs.each_slice(2) do |pair|
672
+ data[pair[0].to_s] = pair[1].to_s
673
+ end
674
+ "OK"
675
+ end
676
+
677
+ def msetnx(*pairs)
678
+ # Handle pairs for mapped_msetnx command
679
+ pairs = pairs[0] if mapped_param?(pairs)
680
+ keys = []
681
+ pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
682
+ return false if keys.any?{|key| data.key?(key) }
683
+ mset(*pairs)
684
+ true
685
+ end
686
+
687
+ def sort(key)
688
+ # TODO: Implement
689
+ end
690
+
691
+ def incr(key)
692
+ data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
693
+ data[key].to_i
694
+ end
695
+
696
+ def incrby(key, by)
697
+ data.merge!({ key => (data[key].to_i + by.to_i).to_s || by })
698
+ data[key].to_i
699
+ end
700
+
701
+ def decr(key)
702
+ data.merge!({ key => (data[key].to_i - 1).to_s || "-1"})
703
+ data[key].to_i
704
+ end
705
+
706
+ def decrby(key, by)
707
+ data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
708
+ data[key].to_i
709
+ end
710
+
711
+ def type(key)
712
+ case data[key]
713
+ when nil then "none"
714
+ when String then "string"
715
+ when Hash then "hash"
716
+ when Array then "list"
717
+ when ::Set then "set"
718
+ end
719
+ end
720
+
721
+ def quit ; end
722
+
723
+ def shutdown; end
724
+
725
+ def slaveof(host, port) ; end
726
+
727
+ def exec
728
+ buffer.tap {|x| self.buffer = nil }
729
+ end
730
+
731
+ def multi
732
+ self.buffer = []
733
+ yield if block_given?
734
+ "OK"
735
+ end
736
+
737
+ def watch(_)
738
+ "OK"
739
+ end
740
+
741
+ def unwatch
742
+ "OK"
743
+ end
744
+
745
+ def zadd(key, *args)
746
+ if !args.first.is_a?(Array)
747
+ if args.size < 2
748
+ raise_argument_error('zadd')
749
+ elsif args.size.odd?
750
+ raise_syntax_error
751
+ end
752
+ else
753
+ unless args.all? {|pair| pair.size == 2 }
754
+ raise_syntax_error
755
+ end
756
+ end
757
+
758
+ data_type_check(key, ZSet)
759
+ data[key] ||= ZSet.new
760
+
761
+ if args.size == 2 && !(Array === args.first)
762
+ score, value = args
763
+ exists = !data[key].key?(value.to_s)
764
+ data[key][value.to_s] = score
765
+ else
766
+ # Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
767
+ args = args.each_slice(2).to_a unless args.first.is_a?(Array)
768
+ exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
769
+ args.each { |s, v| data[key][v.to_s] = s }
770
+ end
771
+
772
+ exists
773
+ end
774
+
775
+ def zrem(key, value)
776
+ data_type_check(key, ZSet)
777
+ values = Array(value)
778
+ return 0 unless data[key]
779
+
780
+ response = values.map do |v|
781
+ data[key].delete(v) if data[key].has_key?(v)
782
+ end.compact.size
783
+
784
+ remove_key_for_empty_collection(key)
785
+ response
786
+ end
787
+
788
+ def zcard(key)
789
+ data_type_check(key, ZSet)
790
+ data[key] ? data[key].size : 0
791
+ end
792
+
793
+ def zscore(key, value)
794
+ data_type_check(key, ZSet)
795
+ value = data[key] && data[key][value.to_s]
796
+ value && value.to_s
797
+ end
798
+
799
+ def zcount(key, min, max)
800
+ data_type_check(key, ZSet)
801
+ return 0 unless data[key]
802
+ data[key].select_by_score(min, max).size
803
+ end
804
+
805
+ def zincrby(key, num, value)
806
+ data_type_check(key, ZSet)
807
+ data[key] ||= ZSet.new
808
+ data[key][value.to_s] ||= 0
809
+ data[key].increment(value.to_s, num)
810
+ data[key][value.to_s].to_s
811
+ end
812
+
813
+ def zrank(key, value)
814
+ data_type_check(key, ZSet)
815
+ z = data[key]
816
+ return unless z
817
+ z.keys.sort_by {|k| z[k] }.index(value.to_s)
818
+ end
819
+
820
+ def zrevrank(key, value)
821
+ data_type_check(key, ZSet)
822
+ z = data[key]
823
+ return unless z
824
+ z.keys.sort_by {|k| -z[k] }.index(value.to_s)
825
+ end
826
+
827
+ def zrange(key, start, stop, with_scores = nil)
828
+ data_type_check(key, ZSet)
829
+ return [] unless data[key]
830
+
831
+ # Sort by score, or if scores are equal, key alphanum
832
+ results = data[key].sort do |(k1, v1), (k2, v2)|
833
+ if v1 == v2
834
+ k1 <=> k2
835
+ else
836
+ v1 <=> v2
837
+ end
838
+ end
839
+ # Select just the keys unless we want scores
840
+ results = results.map(&:first) unless with_scores
841
+ results[start..stop].flatten.map(&:to_s)
842
+ end
843
+
844
+ def zrevrange(key, start, stop, with_scores = nil)
845
+ data_type_check(key, ZSet)
846
+ return [] unless data[key]
847
+
848
+ if with_scores
849
+ data[key].sort_by {|_,v| -v }
850
+ else
851
+ data[key].keys.sort_by {|k| -data[key][k] }
852
+ end[start..stop].flatten.map(&:to_s)
853
+ end
854
+
855
+ def zrangebyscore(key, min, max, *opts)
856
+ data_type_check(key, ZSet)
857
+ return [] unless data[key]
858
+
859
+ range = data[key].select_by_score(min, max)
860
+ vals = if opts.include?('WITHSCORES')
861
+ range.sort_by {|_,v| v }
862
+ else
863
+ range.keys.sort_by {|k| range[k] }
864
+ end
865
+
866
+ limit = get_limit(opts, vals)
867
+ vals = vals[*limit] if limit
868
+
869
+ vals.flatten.map(&:to_s)
870
+ end
871
+
872
+ def zrevrangebyscore(key, max, min, *opts)
873
+ data_type_check(key, ZSet)
874
+ return [] unless data[key]
875
+
876
+ range = data[key].select_by_score(min, max)
877
+ vals = if opts.include?('WITHSCORES')
878
+ range.sort_by {|_,v| -v }
879
+ else
880
+ range.keys.sort_by {|k| -range[k] }
881
+ end
882
+
883
+ limit = get_limit(opts, vals)
884
+ vals = vals[*limit] if limit
885
+
886
+ vals.flatten.map(&:to_s)
887
+ end
888
+
889
+ def zremrangebyscore(key, min, max)
890
+ data_type_check(key, ZSet)
891
+ return 0 unless data[key]
892
+
893
+ range = data[key].select_by_score(min, max)
894
+ range.each {|k,_| data[key].delete(k) }
895
+ range.size
896
+ end
897
+
898
+ def zinterstore(out, *args)
899
+ data_type_check(out, ZSet)
900
+ args_handler = SortedSetArgumentHandler.new(args)
901
+ data[out] = SortedSetIntersectStore.new(args_handler, data).call
902
+ data[out].size
903
+ end
904
+
905
+ def zunionstore(out, *args)
906
+ data_type_check(out, ZSet)
907
+ args_handler = SortedSetArgumentHandler.new(args)
908
+ data[out] = SortedSetUnionStore.new(args_handler, data).call
909
+ data[out].size
910
+ end
911
+
912
+ def zremrangebyrank(key, start, stop)
913
+ sorted_elements = data[key].sort_by { |k, v| v }
914
+ start = sorted_elements.length if start > sorted_elements.length
915
+ elements_to_delete = sorted_elements[start..stop]
916
+ elements_to_delete.each { |elem, rank| data[key].delete(elem) }
917
+ elements_to_delete.size
918
+ end
919
+
920
+ private
921
+ def raise_argument_error command
922
+ raise Redis::CommandError, "ERR wrong number of arguments for '#{command}' command"
923
+ end
924
+
925
+ def raise_syntax_error
926
+ raise Redis::CommandError, "ERR syntax error"
927
+ end
928
+
929
+ def remove_key_for_empty_collection(key)
930
+ del(key) if data[key] && data[key].empty?
931
+ end
932
+
933
+ def data_type_check(key, klass)
934
+ if data[key] && !data[key].is_a?(klass)
935
+ warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
936
+ raise Redis::CommandError.new("ERR Operation against a key holding the wrong kind of value")
937
+ end
938
+ end
939
+
940
+ def get_limit(opts, vals)
941
+ index = opts.index('LIMIT')
942
+
943
+ if index
944
+ offset = opts[index + 1]
945
+
946
+ count = opts[index + 2]
947
+ count = vals.size if count < 0
948
+
949
+ [offset, count]
950
+ end
951
+ end
952
+
953
+ def mapped_param? param
954
+ param.size == 1 && param[0].is_a?(Array)
955
+ end
956
+ end
957
+ end
958
+ end
959
+
960
+ Redis::Connection.drivers << Redis::Connection::File