fakeredis 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/Gemfile +6 -2
- data/LICENSE +1 -1
- data/README.md +2 -2
- data/fakeredis.gemspec +2 -2
- data/lib/fakeredis/command_executor.rb +25 -0
- data/lib/fakeredis/sort_method.rb +116 -0
- data/lib/fakeredis/transaction_commands.rb +83 -0
- data/lib/fakeredis/version.rb +1 -1
- data/lib/fakeredis/zset.rb +7 -3
- data/lib/redis/connection/memory.rb +132 -69
- data/spec/hashes_spec.rb +21 -8
- data/spec/keys_spec.rb +107 -5
- data/spec/lists_spec.rb +9 -1
- data/spec/memory_spec.rb +28 -0
- data/spec/server_spec.rb +4 -4
- data/spec/sets_spec.rb +87 -3
- data/spec/sort_method_spec.rb +68 -0
- data/spec/sorted_sets_spec.rb +9 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/strings_spec.rb +52 -4
- data/spec/support/shared_examples/sortable.rb +69 -0
- data/spec/transactions_spec.rb +72 -7
- metadata +24 -16
- data/.rspec +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20688b94f039d268518697ede7cdfad15f26cf73
|
4
|
+
data.tar.gz: ab1982d7e9ed2f7c6b864d2beadd63d6041bb59a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 080b44a262bae01a09dcd2660cb35e1a52543cad7d3780fc58224854b6007adb8a0598a0b892560988f7a86c921a1ea1d3b36fa19248542017ffb97ce8caa0ef
|
7
|
+
data.tar.gz: 687896c96f26f389776216ef6397ce9bb025e9fb04008860dc377f8ba0965245185b02bb4381e7e560a5d76621b8c87ee4989878fbe0bd086e5fc30e2e3c6442
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
3
|
gem 'rake'
|
4
4
|
gem 'rdoc'
|
5
5
|
|
6
|
-
|
6
|
+
platforms :rbx do
|
7
|
+
gem 'racc'
|
8
|
+
gem 'rubysl', '~> 2.0'
|
9
|
+
gem 'psych'
|
10
|
+
end
|
7
11
|
|
8
12
|
# Specify your gem's dependencies in fakeredis.gemspec
|
9
13
|
gemspec
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Add it to your Gemfile:
|
|
15
15
|
|
16
16
|
## Versions
|
17
17
|
|
18
|
-
FakeRedis currently supports redis-rb v3.
|
18
|
+
FakeRedis currently supports redis-rb v3.x.y or later, if you are using
|
19
19
|
redis-rb v2.2.x install the version 0.3.x:
|
20
20
|
|
21
21
|
gem install fakeredis -v "~> 0.3.0"
|
@@ -82,5 +82,5 @@ Or:
|
|
82
82
|
|
83
83
|
## Copyright
|
84
84
|
|
85
|
-
Copyright (c) 2011-
|
85
|
+
Copyright (c) 2011-2014 Guillermo Iguaran. See LICENSE for
|
86
86
|
further details.
|
data/fakeredis.gemspec
CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_runtime_dependency(%q<redis>, ["~> 3.0
|
22
|
-
s.add_development_dependency(%q<rspec>, ["
|
21
|
+
s.add_runtime_dependency(%q<redis>, ["~> 3.0"])
|
22
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
23
23
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
module CommandExecutor
|
3
|
+
def write(command)
|
4
|
+
meffod = command.shift.to_s.downcase.to_sym
|
5
|
+
|
6
|
+
if in_multi && !(TRANSACTION_COMMANDS.include? meffod) # queue commands
|
7
|
+
queued_commands << [meffod, *command]
|
8
|
+
reply = 'QUEUED'
|
9
|
+
elsif respond_to?(meffod)
|
10
|
+
reply = send(meffod, *command)
|
11
|
+
else
|
12
|
+
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
13
|
+
end
|
14
|
+
|
15
|
+
if reply == true
|
16
|
+
reply = 1
|
17
|
+
elsif reply == false
|
18
|
+
reply = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
replies << reply
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Codes are mostly referenced from MockRedis' implementation.
|
2
|
+
module FakeRedis
|
3
|
+
module SortMethod
|
4
|
+
def sort(key, *redis_options_array)
|
5
|
+
return [] unless key
|
6
|
+
|
7
|
+
unless %w(list set zset).include? type(key)
|
8
|
+
warn "Operation against a key holding the wrong kind of value: Expected list, set or zset at #{key}."
|
9
|
+
raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value")
|
10
|
+
end
|
11
|
+
|
12
|
+
# redis_options is an array of format [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]
|
13
|
+
# Lets nibble it back into a hash
|
14
|
+
options = extract_options_from(redis_options_array)
|
15
|
+
|
16
|
+
# And now to actually do the work of this method
|
17
|
+
|
18
|
+
projected = project(data[key], options[:by], options[:get])
|
19
|
+
sorted = sort_by(projected, options[:order])
|
20
|
+
sliced = slice(sorted, options[:limit])
|
21
|
+
# We have to flatten it down as redis-rb adds back the array to the return value
|
22
|
+
result = sliced.flatten(1)
|
23
|
+
|
24
|
+
options[:store] ? rpush(options[:store], sliced) : sliced.flatten(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
ASCENDING_SORT = Proc.new { |a, b| a.first <=> b.first }
|
30
|
+
DESCENDING_SORT = Proc.new { |a, b| b.first <=> a.first }
|
31
|
+
|
32
|
+
def extract_options_from(options_array)
|
33
|
+
# Defaults
|
34
|
+
options = {
|
35
|
+
:limit => [],
|
36
|
+
:order => "ASC",
|
37
|
+
:get => []
|
38
|
+
}
|
39
|
+
|
40
|
+
if options_array.first == "BY"
|
41
|
+
options_array.shift
|
42
|
+
options[:by] = options_array.shift
|
43
|
+
end
|
44
|
+
|
45
|
+
if options_array.first == "LIMIT"
|
46
|
+
options_array.shift
|
47
|
+
options[:limit] = [options_array.shift, options_array.shift]
|
48
|
+
end
|
49
|
+
|
50
|
+
while options_array.first == "GET"
|
51
|
+
options_array.shift
|
52
|
+
options[:get] << options_array.shift
|
53
|
+
end
|
54
|
+
|
55
|
+
if %w(ASC DESC ALPHA).include?(options_array.first)
|
56
|
+
options[:order] = options_array.shift
|
57
|
+
options[:order] = "ASC" if options[:order] == "ALPHA"
|
58
|
+
end
|
59
|
+
|
60
|
+
if options_array.first == "STORE"
|
61
|
+
options_array.shift
|
62
|
+
options[:store] = options_array.shift
|
63
|
+
end
|
64
|
+
|
65
|
+
options
|
66
|
+
end
|
67
|
+
|
68
|
+
def project(enumerable, by, get_patterns)
|
69
|
+
enumerable.map do |*elements|
|
70
|
+
element = elements.flatten.first
|
71
|
+
weight = by ? lookup_from_pattern(by, element) : element
|
72
|
+
value = element
|
73
|
+
|
74
|
+
if get_patterns.length > 0
|
75
|
+
value = get_patterns.map do |pattern|
|
76
|
+
pattern == "#" ? element : lookup_from_pattern(pattern, element)
|
77
|
+
end
|
78
|
+
value = value.first if value.length == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
[weight, value]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def sort_by(projected, direction)
|
86
|
+
sorter =
|
87
|
+
case direction.upcase
|
88
|
+
when "DESC"
|
89
|
+
DESCENDING_SORT
|
90
|
+
when "ASC", "ALPHA"
|
91
|
+
ASCENDING_SORT
|
92
|
+
else
|
93
|
+
raise "Invalid direction '#{direction}'"
|
94
|
+
end
|
95
|
+
|
96
|
+
projected.sort(&sorter).map(&:last)
|
97
|
+
end
|
98
|
+
|
99
|
+
def slice(sorted, limit)
|
100
|
+
skip = limit.first || 0
|
101
|
+
take = limit.last || sorted.length
|
102
|
+
|
103
|
+
sorted[skip...(skip + take)] || sorted
|
104
|
+
end
|
105
|
+
|
106
|
+
def lookup_from_pattern(pattern, element)
|
107
|
+
key = pattern.sub('*', element)
|
108
|
+
|
109
|
+
if (hash_parts = key.split('->')).length > 1
|
110
|
+
hget hash_parts.first, hash_parts.last
|
111
|
+
else
|
112
|
+
get key
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
TRANSACTION_COMMANDS = [:discard, :exec, :multi, :watch, :unwatch]
|
3
|
+
|
4
|
+
module TransactionCommands
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
def self.queued_commands
|
8
|
+
@queued_commands ||= Hash.new {|h,k| h[k] = [] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.in_multi
|
12
|
+
@in_multi ||= Hash.new{|h,k| h[k] = false}
|
13
|
+
end
|
14
|
+
|
15
|
+
def queued_commands
|
16
|
+
self.class.queued_commands[database_instance_key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def queued_commands=(cmds)
|
20
|
+
self.class.queued_commands[database_instance_key] = cmds
|
21
|
+
end
|
22
|
+
|
23
|
+
def in_multi
|
24
|
+
self.class.in_multi[database_instance_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def in_multi=(multi_state)
|
28
|
+
self.class.in_multi[database_instance_key] = multi_state
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def discard
|
34
|
+
unless in_multi
|
35
|
+
raise Redis::CommandError, "ERR DISCARD without MULTI"
|
36
|
+
end
|
37
|
+
|
38
|
+
self.in_multi = false
|
39
|
+
self.queued_commands = []
|
40
|
+
|
41
|
+
'OK'
|
42
|
+
end
|
43
|
+
|
44
|
+
def exec
|
45
|
+
unless in_multi
|
46
|
+
raise Redis::CommandError, "ERR EXEC without MULTI"
|
47
|
+
end
|
48
|
+
|
49
|
+
responses = queued_commands.map do |cmd|
|
50
|
+
begin
|
51
|
+
send(*cmd)
|
52
|
+
rescue => e
|
53
|
+
e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
self.queued_commands = [] # reset queued_commands
|
58
|
+
self.in_multi = false # reset in_multi state
|
59
|
+
|
60
|
+
responses
|
61
|
+
end
|
62
|
+
|
63
|
+
def multi
|
64
|
+
if in_multi
|
65
|
+
raise Redis::CommandError, "ERR MULTI calls can not be nested"
|
66
|
+
end
|
67
|
+
|
68
|
+
self.in_multi = true
|
69
|
+
|
70
|
+
yield(self) if block_given?
|
71
|
+
|
72
|
+
"OK"
|
73
|
+
end
|
74
|
+
|
75
|
+
def watch(_)
|
76
|
+
"OK"
|
77
|
+
end
|
78
|
+
|
79
|
+
def unwatch
|
80
|
+
"OK"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/fakeredis/version.rb
CHANGED
data/lib/fakeredis/zset.rb
CHANGED
@@ -11,15 +11,19 @@ module FakeRedis
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def select_by_score min, max
|
14
|
-
min = _floatify(min)
|
15
|
-
max = _floatify(max)
|
14
|
+
min = _floatify(min, true)
|
15
|
+
max = _floatify(max, false)
|
16
16
|
reject {|_,v| v < min || v > max }
|
17
17
|
end
|
18
18
|
|
19
|
+
private
|
20
|
+
|
19
21
|
# Originally lifted from redis-rb
|
20
|
-
def _floatify(str)
|
22
|
+
def _floatify(str, increment = true)
|
21
23
|
if (( inf = str.to_s.match(/^([+-])?inf/i) ))
|
22
24
|
(inf[1] == "-" ? -1.0 : 1.0) / 0.0
|
25
|
+
elsif (( number = str.to_s.match(/^\((\d+)/i) ))
|
26
|
+
number[1].to_i + (increment ? 1 : -1)
|
23
27
|
else
|
24
28
|
Float str
|
25
29
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'redis/connection/registry'
|
3
3
|
require 'redis/connection/command_helper'
|
4
|
+
require "fakeredis/command_executor"
|
4
5
|
require "fakeredis/expiring_hash"
|
6
|
+
require "fakeredis/sort_method"
|
5
7
|
require "fakeredis/sorted_set_argument_handler"
|
6
8
|
require "fakeredis/sorted_set_store"
|
9
|
+
require "fakeredis/transaction_commands"
|
7
10
|
require "fakeredis/zset"
|
8
11
|
|
9
12
|
class Redis
|
@@ -11,8 +14,11 @@ class Redis
|
|
11
14
|
class Memory
|
12
15
|
include Redis::Connection::CommandHelper
|
13
16
|
include FakeRedis
|
17
|
+
include SortMethod
|
18
|
+
include TransactionCommands
|
19
|
+
include CommandExecutor
|
14
20
|
|
15
|
-
attr_accessor :
|
21
|
+
attr_accessor :options
|
16
22
|
|
17
23
|
# Tracks all databases for all instances across the current process.
|
18
24
|
# We have to be able to handle two clients with the same host/port accessing
|
@@ -76,25 +82,6 @@ class Redis
|
|
76
82
|
def timeout=(usecs)
|
77
83
|
end
|
78
84
|
|
79
|
-
def write(command)
|
80
|
-
meffod = command.shift.to_s.downcase.to_sym
|
81
|
-
if respond_to?(meffod)
|
82
|
-
reply = send(meffod, *command)
|
83
|
-
else
|
84
|
-
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
85
|
-
end
|
86
|
-
|
87
|
-
if reply == true
|
88
|
-
reply = 1
|
89
|
-
elsif reply == false
|
90
|
-
reply = 0
|
91
|
-
end
|
92
|
-
|
93
|
-
replies << reply
|
94
|
-
buffer << reply if buffer && meffod != :multi
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
85
|
def read
|
99
86
|
replies.shift
|
100
87
|
end
|
@@ -103,8 +90,6 @@ class Redis
|
|
103
90
|
# * blpop
|
104
91
|
# * brpop
|
105
92
|
# * brpoplpush
|
106
|
-
# * discard
|
107
|
-
# * sort
|
108
93
|
# * subscribe
|
109
94
|
# * psubscribe
|
110
95
|
# * publish
|
@@ -150,7 +135,7 @@ class Redis
|
|
150
135
|
|
151
136
|
def bgsave ; end
|
152
137
|
|
153
|
-
def
|
138
|
+
def bgrewriteaof ; end
|
154
139
|
|
155
140
|
def move key, destination_id
|
156
141
|
raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
|
@@ -171,6 +156,11 @@ class Redis
|
|
171
156
|
data[key].unpack('B*')[0].split("")[offset].to_i
|
172
157
|
end
|
173
158
|
|
159
|
+
def bitcount(key, start_index = 0, end_index = -1)
|
160
|
+
return 0 unless data[key]
|
161
|
+
data[key][start_index..end_index].unpack('B*')[0].count("1")
|
162
|
+
end
|
163
|
+
|
174
164
|
def getrange(key, start, ending)
|
175
165
|
return unless data[key]
|
176
166
|
data[key][start..ending]
|
@@ -214,8 +204,9 @@ class Redis
|
|
214
204
|
def hdel(key, field)
|
215
205
|
field = field.to_s
|
216
206
|
data_type_check(key, Hash)
|
217
|
-
data[key] && data[key].delete(field)
|
207
|
+
deleted = data[key] && data[key].delete(field)
|
218
208
|
remove_key_for_empty_collection(key)
|
209
|
+
deleted ? 1 : 0
|
219
210
|
end
|
220
211
|
|
221
212
|
def hkeys(key)
|
@@ -244,6 +235,11 @@ class Redis
|
|
244
235
|
Time.now.to_i
|
245
236
|
end
|
246
237
|
|
238
|
+
def time
|
239
|
+
microseconds = (Time.now.to_f * 1000000).to_i
|
240
|
+
[ microseconds / 1000000, microseconds % 1000000 ]
|
241
|
+
end
|
242
|
+
|
247
243
|
def dbsize
|
248
244
|
data.keys.count
|
249
245
|
end
|
@@ -267,17 +263,17 @@ class Redis
|
|
267
263
|
data_type_check(key, Array)
|
268
264
|
return unless data[key]
|
269
265
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
data[key]
|
278
|
-
else
|
266
|
+
# Example: we have a list of 3 elements and
|
267
|
+
# we give it a ltrim list, -5, -1. This means
|
268
|
+
# it should trim to a max of 5. Since 3 < 5
|
269
|
+
# we should not touch the list. This is consistent
|
270
|
+
# with behavior of real Redis's ltrim with a negative
|
271
|
+
# start argument.
|
272
|
+
unless start < 0 && data[key].count < start.abs
|
279
273
|
data[key] = data[key][start..stop]
|
280
274
|
end
|
275
|
+
|
276
|
+
"OK"
|
281
277
|
end
|
282
278
|
|
283
279
|
def lindex(key, index)
|
@@ -360,7 +356,7 @@ class Redis
|
|
360
356
|
def rpoplpush(key1, key2)
|
361
357
|
data_type_check(key1, Array)
|
362
358
|
rpop(key1).tap do |elem|
|
363
|
-
lpush(key2, elem)
|
359
|
+
lpush(key2, elem) unless elem.nil?
|
364
360
|
end
|
365
361
|
end
|
366
362
|
|
@@ -403,7 +399,17 @@ class Redis
|
|
403
399
|
|
404
400
|
def srem(key, value)
|
405
401
|
data_type_check(key, ::Set)
|
406
|
-
|
402
|
+
return false unless data[key]
|
403
|
+
|
404
|
+
if value.is_a?(Array)
|
405
|
+
old_size = data[key].size
|
406
|
+
values = value.map(&:to_s)
|
407
|
+
values.each { |value| data[key].delete(value) }
|
408
|
+
deleted = old_size - data[key].size
|
409
|
+
else
|
410
|
+
deleted = !!data[key].delete?(value.to_s)
|
411
|
+
end
|
412
|
+
|
407
413
|
remove_key_for_empty_collection(key)
|
408
414
|
deleted
|
409
415
|
end
|
@@ -473,10 +479,8 @@ class Redis
|
|
473
479
|
data[destination] = ::Set.new(result)
|
474
480
|
end
|
475
481
|
|
476
|
-
def srandmember(key)
|
477
|
-
|
478
|
-
return nil unless data[key]
|
479
|
-
data[key].to_a[rand(data[key].size)]
|
482
|
+
def srandmember(key, number=nil)
|
483
|
+
number.nil? ? srandmember_single(key) : srandmember_multiple(key, number)
|
480
484
|
end
|
481
485
|
|
482
486
|
def del(*keys)
|
@@ -516,9 +520,9 @@ class Redis
|
|
516
520
|
end
|
517
521
|
|
518
522
|
def expire(key, ttl)
|
519
|
-
return unless data[key]
|
523
|
+
return 0 unless data[key]
|
520
524
|
data.expires[key] = Time.now + ttl
|
521
|
-
|
525
|
+
1
|
522
526
|
end
|
523
527
|
|
524
528
|
def ttl(key)
|
@@ -586,7 +590,7 @@ class Redis
|
|
586
590
|
raise_argument_error('hmget') if fields.empty?
|
587
591
|
|
588
592
|
data_type_check(key, Hash)
|
589
|
-
fields.map do |field|
|
593
|
+
fields.flatten.map do |field|
|
590
594
|
field = field.to_s
|
591
595
|
if data[key]
|
592
596
|
data[key][field]
|
@@ -619,6 +623,17 @@ class Redis
|
|
619
623
|
data[key][field].to_i
|
620
624
|
end
|
621
625
|
|
626
|
+
def hincrbyfloat(key, field, increment)
|
627
|
+
data_type_check(key, Hash)
|
628
|
+
field = field.to_s
|
629
|
+
if data[key]
|
630
|
+
data[key][field] = (data[key][field].to_f + increment.to_f).to_s
|
631
|
+
else
|
632
|
+
data[key] = { field => increment.to_s }
|
633
|
+
end
|
634
|
+
data[key][field]
|
635
|
+
end
|
636
|
+
|
622
637
|
def hexists(key, field)
|
623
638
|
data_type_check(key, Hash)
|
624
639
|
return false unless data[key]
|
@@ -635,8 +650,23 @@ class Redis
|
|
635
650
|
set(key, value)
|
636
651
|
end
|
637
652
|
|
638
|
-
def set(key, value)
|
653
|
+
def set(key, value, *array_options)
|
654
|
+
option_nx = array_options.delete("NX")
|
655
|
+
option_xx = array_options.delete("XX")
|
656
|
+
|
657
|
+
return false if option_nx && option_xx
|
658
|
+
|
659
|
+
return false if option_nx && exists(key)
|
660
|
+
return false if option_xx && !exists(key)
|
661
|
+
|
639
662
|
data[key] = value.to_s
|
663
|
+
|
664
|
+
options = Hash[array_options.each_slice(2).to_a]
|
665
|
+
ttl_in_seconds = options["EX"] if options["EX"]
|
666
|
+
ttl_in_seconds = options["PX"] / 1000.0 if options["PX"]
|
667
|
+
|
668
|
+
expire(key, ttl_in_seconds) if ttl_in_seconds
|
669
|
+
|
640
670
|
"OK"
|
641
671
|
end
|
642
672
|
|
@@ -644,7 +674,7 @@ class Redis
|
|
644
674
|
old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
|
645
675
|
size_increment = [((offset/8)+1)*8-old_val.length, 0].max
|
646
676
|
old_val += Array.new(size_increment).map{"0"}
|
647
|
-
original_val = old_val[offset]
|
677
|
+
original_val = old_val[offset].to_i
|
648
678
|
old_val[offset] = bit.to_s
|
649
679
|
new_val = ""
|
650
680
|
old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
|
@@ -687,10 +717,6 @@ class Redis
|
|
687
717
|
true
|
688
718
|
end
|
689
719
|
|
690
|
-
def sort(key)
|
691
|
-
# TODO: Implement
|
692
|
-
end
|
693
|
-
|
694
720
|
def incr(key)
|
695
721
|
data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
|
696
722
|
data[key].to_i
|
@@ -728,22 +754,37 @@ class Redis
|
|
728
754
|
|
729
755
|
def slaveof(host, port) ; end
|
730
756
|
|
731
|
-
def
|
732
|
-
|
733
|
-
|
757
|
+
def scan(start_cursor, *args)
|
758
|
+
match = "*"
|
759
|
+
count = 10
|
734
760
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
"OK"
|
739
|
-
end
|
761
|
+
if args.size.odd?
|
762
|
+
raise_argument_error('scan')
|
763
|
+
end
|
740
764
|
|
741
|
-
|
742
|
-
|
743
|
-
|
765
|
+
if idx = args.index("MATCH")
|
766
|
+
match = args[idx + 1]
|
767
|
+
end
|
744
768
|
|
745
|
-
|
746
|
-
|
769
|
+
if idx = args.index("COUNT")
|
770
|
+
count = args[idx + 1]
|
771
|
+
end
|
772
|
+
|
773
|
+
start_cursor = start_cursor.to_i
|
774
|
+
data_type_check(start_cursor, Fixnum)
|
775
|
+
|
776
|
+
cursor = start_cursor
|
777
|
+
next_keys = []
|
778
|
+
|
779
|
+
if start_cursor + count >= data.length
|
780
|
+
next_keys = keys(match)[start_cursor..-1]
|
781
|
+
cursor = 0
|
782
|
+
else
|
783
|
+
cursor = start_cursor + 10
|
784
|
+
next_keys = keys(match)[start_cursor..cursor]
|
785
|
+
end
|
786
|
+
|
787
|
+
return "#{cursor}", next_keys
|
747
788
|
end
|
748
789
|
|
749
790
|
def zadd(key, *args)
|
@@ -900,6 +941,17 @@ class Redis
|
|
900
941
|
range.size
|
901
942
|
end
|
902
943
|
|
944
|
+
def zremrangebyrank(key, start, stop)
|
945
|
+
data_type_check(key, ZSet)
|
946
|
+
return 0 unless data[key]
|
947
|
+
|
948
|
+
sorted_elements = data[key].sort_by { |k, v| v }
|
949
|
+
start = sorted_elements.length if start > sorted_elements.length
|
950
|
+
elements_to_delete = sorted_elements[start..stop]
|
951
|
+
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
952
|
+
elements_to_delete.size
|
953
|
+
end
|
954
|
+
|
903
955
|
def zinterstore(out, *args)
|
904
956
|
data_type_check(out, ZSet)
|
905
957
|
args_handler = SortedSetArgumentHandler.new(args)
|
@@ -914,14 +966,6 @@ class Redis
|
|
914
966
|
data[out].size
|
915
967
|
end
|
916
968
|
|
917
|
-
def zremrangebyrank(key, start, stop)
|
918
|
-
sorted_elements = data[key].sort_by { |k, v| v }
|
919
|
-
start = sorted_elements.length if start > sorted_elements.length
|
920
|
-
elements_to_delete = sorted_elements[start..stop]
|
921
|
-
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
922
|
-
elements_to_delete.size
|
923
|
-
end
|
924
|
-
|
925
969
|
private
|
926
970
|
def raise_argument_error(command, match_string=command)
|
927
971
|
error_message = if %w(hmset mset_odd).include?(match_string.downcase)
|
@@ -964,6 +1008,25 @@ class Redis
|
|
964
1008
|
def mapped_param? param
|
965
1009
|
param.size == 1 && param[0].is_a?(Array)
|
966
1010
|
end
|
1011
|
+
|
1012
|
+
def srandmember_single(key)
|
1013
|
+
data_type_check(key, ::Set)
|
1014
|
+
return nil unless data[key]
|
1015
|
+
data[key].to_a[rand(data[key].size)]
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
def srandmember_multiple(key, number)
|
1019
|
+
return [] unless data[key]
|
1020
|
+
if number >= 0
|
1021
|
+
# replace with `data[key].to_a.sample(number)` when 1.8.7 is deprecated
|
1022
|
+
(1..number).inject([]) do |selected, _|
|
1023
|
+
available_elements = data[key].to_a - selected
|
1024
|
+
selected << available_elements[rand(available_elements.size)]
|
1025
|
+
end.compact
|
1026
|
+
else
|
1027
|
+
(1..-number).map { data[key].to_a[rand(data[key].size)] }.flatten
|
1028
|
+
end
|
1029
|
+
end
|
967
1030
|
end
|
968
1031
|
end
|
969
1032
|
end
|