fakeredis 0.4.3 → 0.5.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 +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
|