fakeredis 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -2
- data/LICENSE +1 -1
- data/README.md +3 -2
- data/fakeredis.gemspec +2 -3
- data/lib/fake_redis.rb +1 -0
- data/lib/fakeredis/expiring_hash.rb +70 -0
- data/lib/fakeredis/rspec.rb +1 -1
- data/lib/fakeredis/sorted_set_argument_handler.rb +74 -0
- data/lib/fakeredis/sorted_set_store.rb +80 -0
- data/lib/fakeredis/version.rb +1 -1
- data/lib/fakeredis/zset.rb +29 -0
- data/lib/redis/connection/memory.rb +280 -279
- data/spec/compatibility_spec.rb +2 -2
- data/spec/connection_spec.rb +62 -1
- data/spec/hashes_spec.rb +40 -29
- data/spec/keys_spec.rb +105 -25
- data/spec/lists_spec.rb +34 -31
- data/spec/server_spec.rb +80 -2
- data/spec/sets_spec.rb +18 -18
- data/spec/sorted_sets_spec.rb +246 -62
- data/spec/spec_helper_live_redis.rb +1 -1
- data/spec/strings_spec.rb +79 -55
- data/spec/transactions_spec.rb +1 -1
- metadata +12 -7
data/.travis.yml
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# FakeRedis [![Build Status](
|
1
|
+
# FakeRedis [![Build Status](https://secure.travis-ci.org/guilleiguaran/fakeredis.png)](http://travis-ci.org/guilleiguaran/fakeredis)
|
2
2
|
This a fake implementation of redis-rb for machines without Redis or test environments
|
3
3
|
|
4
4
|
|
@@ -65,6 +65,7 @@ Or:
|
|
65
65
|
* [jredville](https://github.com/jredville)
|
66
66
|
* [redsquirrel](https://github.com/redsquirrel)
|
67
67
|
* [dpick](https://github.com/dpick)
|
68
|
+
* [caius](https://github.com/caius)
|
68
69
|
* [Travis-CI](http://travis-ci.org/) (Travis-CI also uses Fakeredis in its tests!!!)
|
69
70
|
|
70
71
|
|
@@ -81,5 +82,5 @@ Or:
|
|
81
82
|
|
82
83
|
## Copyright
|
83
84
|
|
84
|
-
Copyright (c) 2011-
|
85
|
+
Copyright (c) 2011-2013 Guillermo Iguaran. See LICENSE for
|
85
86
|
further details.
|
data/fakeredis.gemspec
CHANGED
@@ -8,12 +8,11 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Guillermo Iguaran"]
|
10
10
|
s.email = ["guilleiguaran@gmail.com"]
|
11
|
-
s.homepage = "https://github.com/
|
11
|
+
s.homepage = "https://guilleiguaran.github.com/fakeredis"
|
12
|
+
s.license = "MIT"
|
12
13
|
s.summary = %q{Fake (In-memory) driver for redis-rb.}
|
13
14
|
s.description = %q{Fake (In-memory) driver for redis-rb. Useful for testing environment and machines without Redis.}
|
14
15
|
|
15
|
-
s.rubyforge_project = "fakeredis"
|
16
|
-
|
17
16
|
s.files = `git ls-files`.split("\n")
|
18
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
data/lib/fake_redis.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "fakeredis"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
# Represents a normal hash with some additional expiration information
|
3
|
+
# associated with each key
|
4
|
+
class ExpiringHash < Hash
|
5
|
+
attr_reader :expires
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@expires = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
key = normalize key
|
14
|
+
delete(key) if expired?(key)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(key, val)
|
19
|
+
key = normalize key
|
20
|
+
expire(key)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
key = normalize key
|
26
|
+
expire(key)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def expire(key)
|
31
|
+
key = normalize key
|
32
|
+
expires.delete(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def expired?(key)
|
36
|
+
key = normalize key
|
37
|
+
expires.include?(key) && expires[key] < Time.now
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
key = normalize key
|
42
|
+
delete(key) if expired?(key)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def values_at(*keys)
|
47
|
+
keys.each do |key|
|
48
|
+
key = normalize(key)
|
49
|
+
delete(key) if expired?(key)
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def keys
|
55
|
+
super.select do |key|
|
56
|
+
key = normalize(key)
|
57
|
+
if expired?(key)
|
58
|
+
delete(key)
|
59
|
+
false
|
60
|
+
else
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def normalize key
|
67
|
+
key.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/fakeredis/rspec.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
# Takes in the variable length array of arguments for a zinterstore/zunionstore method
|
3
|
+
# and parses them into a few attributes for the method to access.
|
4
|
+
#
|
5
|
+
# Handles throwing errors for various scenarios (matches redis):
|
6
|
+
# * Custom weights specified, but not enough or too many given
|
7
|
+
# * Invalid aggregate value given
|
8
|
+
# * Multiple aggregate values given
|
9
|
+
class SortedSetArgumentHandler
|
10
|
+
# [Symbol] The aggregate method to use for the output values. One of %w(sum min max) expected
|
11
|
+
attr_reader :aggregate
|
12
|
+
# [Integer] Number of keys in the argument list
|
13
|
+
attr_accessor :number_of_keys
|
14
|
+
# [Array] The actual keys in the argument list
|
15
|
+
attr_accessor :keys
|
16
|
+
# [Array] integers for weighting the values of each key - one number per key expected
|
17
|
+
attr_accessor :weights
|
18
|
+
|
19
|
+
# Used internally
|
20
|
+
attr_accessor :type
|
21
|
+
|
22
|
+
# Expects all the argments for the method to be passed as an array
|
23
|
+
def initialize args
|
24
|
+
# Pull out known lengths of data
|
25
|
+
self.number_of_keys = args.shift
|
26
|
+
self.keys = args.shift(number_of_keys)
|
27
|
+
# Handle the variable lengths of data (WEIGHTS/AGGREGATE)
|
28
|
+
args.inject(self) {|handler, item| handler.handle(item) }
|
29
|
+
|
30
|
+
# Defaults for unspecified things
|
31
|
+
self.weights ||= Array.new(number_of_keys) { 1 }
|
32
|
+
self.aggregate ||= :sum
|
33
|
+
|
34
|
+
# Validate values
|
35
|
+
raise(Redis::CommandError, "ERR syntax error") unless weights.size == number_of_keys
|
36
|
+
raise(Redis::CommandError, "ERR syntax error") unless [:min, :max, :sum].include?(aggregate)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Only allows assigning a value *once* - raises Redis::CommandError if a second is given
|
40
|
+
def aggregate=(str)
|
41
|
+
raise(Redis::CommandError, "ERR syntax error") if (defined?(@aggregate) && @aggregate)
|
42
|
+
@aggregate = str.to_s.downcase.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decides how to handle an item, depending on where we are in the arguments
|
46
|
+
def handle(item)
|
47
|
+
case item
|
48
|
+
when "WEIGHTS"
|
49
|
+
self.type = :weights
|
50
|
+
self.weights = []
|
51
|
+
when "AGGREGATE"
|
52
|
+
self.type = :aggregate
|
53
|
+
when nil
|
54
|
+
# This should never be called, raise a syntax error if we manage to hit it
|
55
|
+
raise(Redis::CommandError, "ERR syntax error")
|
56
|
+
else
|
57
|
+
send "handle_#{type}", item
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_weights(item)
|
63
|
+
self.weights << item
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_aggregate(item)
|
67
|
+
self.aggregate = item
|
68
|
+
end
|
69
|
+
|
70
|
+
def inject_block
|
71
|
+
lambda { |handler, item| handler.handle(item) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
class SortedSetStore
|
3
|
+
attr_accessor :data, :weights, :aggregate, :keys
|
4
|
+
|
5
|
+
def initialize params, data
|
6
|
+
self.data = data
|
7
|
+
self.weights = params.weights
|
8
|
+
self.aggregate = params.aggregate
|
9
|
+
self.keys = params.keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def hashes
|
13
|
+
@hashes ||= keys.map do |src|
|
14
|
+
case data[src]
|
15
|
+
when ::Set
|
16
|
+
# Every value has a score of 1
|
17
|
+
Hash[data[src].map {|k,v| [k, 1]}]
|
18
|
+
when Hash
|
19
|
+
data[src]
|
20
|
+
else
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Apply the weightings to the hashes
|
27
|
+
def computed_values
|
28
|
+
unless defined?(@computed_values) && @computed_values
|
29
|
+
# Do nothing if all weights are 1, as n * 1 is n
|
30
|
+
@computed_values = hashes if weights.all? {|weight| weight == 1 }
|
31
|
+
# Otherwise, multiply the values in each hash by that hash's weighting
|
32
|
+
@computed_values ||= hashes.each_with_index.map do |hash, index|
|
33
|
+
weight = weights[index]
|
34
|
+
Hash[hash.map {|k, v| [k, (v * weight)]}]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@computed_values
|
38
|
+
end
|
39
|
+
|
40
|
+
def aggregate_sum out
|
41
|
+
selected_keys.each do |key|
|
42
|
+
out[key] = computed_values.inject(0) do |n, hash|
|
43
|
+
n + (hash[key] || 0)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def aggregate_min out
|
49
|
+
selected_keys.each do |key|
|
50
|
+
out[key] = computed_values.map {|h| h[key] }.compact.min
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def aggregate_max out
|
55
|
+
selected_keys.each do |key|
|
56
|
+
out[key] = computed_values.map {|h| h[key] }.compact.max
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def selected_keys
|
61
|
+
raise NotImplemented, "subclass needs to implement #selected_keys"
|
62
|
+
end
|
63
|
+
|
64
|
+
def call
|
65
|
+
ZSet.new.tap {|out| send("aggregate_#{aggregate}", out) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class SortedSetIntersectStore < SortedSetStore
|
70
|
+
def selected_keys
|
71
|
+
@values ||= hashes.inject([]) { |r, h| r.empty? ? h.keys : (r & h.keys) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SortedSetUnionStore < SortedSetStore
|
76
|
+
def selected_keys
|
77
|
+
@values ||= hashes.map(&:keys).flatten.uniq
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/fakeredis/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
class ZSet < Hash
|
3
|
+
|
4
|
+
def []=(key, val)
|
5
|
+
super(key, _floatify(val))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Increments the value of key by val
|
9
|
+
def increment(key, val)
|
10
|
+
self[key] += _floatify(val)
|
11
|
+
end
|
12
|
+
|
13
|
+
def select_by_score min, max
|
14
|
+
min = _floatify(min)
|
15
|
+
max = _floatify(max)
|
16
|
+
reject {|_,v| v < min || v > max }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Originally lifted from redis-rb
|
20
|
+
def _floatify(str)
|
21
|
+
if (( inf = str.to_s.match(/^([+-])?inf/i) ))
|
22
|
+
(inf[1] == "-" ? -1.0 : 1.0) / 0.0
|
23
|
+
else
|
24
|
+
Float str
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,104 +1,88 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'redis/connection/registry'
|
3
3
|
require 'redis/connection/command_helper'
|
4
|
+
require "fakeredis/expiring_hash"
|
5
|
+
require "fakeredis/sorted_set_argument_handler"
|
6
|
+
require "fakeredis/sorted_set_store"
|
7
|
+
require "fakeredis/zset"
|
4
8
|
|
5
9
|
class Redis
|
6
10
|
module Connection
|
7
11
|
class Memory
|
8
|
-
|
9
|
-
|
10
|
-
class ExpiringHash < Hash
|
11
|
-
attr_reader :expires
|
12
|
-
|
13
|
-
def initialize(*)
|
14
|
-
super
|
15
|
-
@expires = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def [](key)
|
19
|
-
delete(key) if expired?(key)
|
20
|
-
super
|
21
|
-
end
|
22
|
-
|
23
|
-
def []=(key, val)
|
24
|
-
expire(key)
|
25
|
-
super
|
26
|
-
end
|
12
|
+
include Redis::Connection::CommandHelper
|
13
|
+
include FakeRedis
|
27
14
|
|
28
|
-
|
29
|
-
expire(key)
|
30
|
-
super
|
31
|
-
end
|
15
|
+
attr_accessor :buffer, :options
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
# Tracks all databases for all instances across the current process.
|
18
|
+
# We have to be able to handle two clients with the same host/port accessing
|
19
|
+
# different databases at once without overwriting each other. So we store our
|
20
|
+
# "data" outside the client instances, in this class level instance method.
|
21
|
+
# Client instances access it with a key made up of their host/port, and then select
|
22
|
+
# which DB out of the array of them they want. Allows the access we need.
|
23
|
+
def self.databases
|
24
|
+
@databases ||= Hash.new {|h,k| h[k] = [] }
|
25
|
+
end
|
36
26
|
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
# Used for resetting everything in specs
|
28
|
+
def self.reset_all_databases
|
29
|
+
@databases = nil
|
30
|
+
end
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
32
|
+
def self.connect(options = {})
|
33
|
+
new(options)
|
34
|
+
end
|
45
35
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
36
|
+
def initialize(options = {})
|
37
|
+
self.options = options
|
38
|
+
end
|
50
39
|
|
51
|
-
|
52
|
-
|
53
|
-
if expired?(key)
|
54
|
-
delete(key)
|
55
|
-
false
|
56
|
-
else
|
57
|
-
true
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
40
|
+
def database_id
|
41
|
+
@database_id ||= 0
|
61
42
|
end
|
43
|
+
attr_writer :database_id
|
62
44
|
|
63
|
-
|
45
|
+
def database_instance_key
|
46
|
+
[options[:host], options[:port]].hash
|
64
47
|
end
|
65
48
|
|
66
|
-
|
49
|
+
def databases
|
50
|
+
self.class.databases[database_instance_key]
|
51
|
+
end
|
67
52
|
|
68
|
-
def
|
69
|
-
|
70
|
-
@connected = connected
|
71
|
-
@replies = []
|
72
|
-
@buffer = nil
|
53
|
+
def find_database id=database_id
|
54
|
+
databases[id] ||= ExpiringHash.new
|
73
55
|
end
|
74
56
|
|
75
|
-
def
|
76
|
-
|
57
|
+
def data
|
58
|
+
find_database
|
77
59
|
end
|
78
60
|
|
79
|
-
def
|
80
|
-
@
|
61
|
+
def replies
|
62
|
+
@replies ||= []
|
81
63
|
end
|
64
|
+
attr_writer :replies
|
82
65
|
|
83
|
-
def
|
84
|
-
|
66
|
+
def connected?
|
67
|
+
true
|
85
68
|
end
|
86
69
|
|
87
70
|
def connect_unix(path, timeout)
|
88
|
-
@connected = true
|
89
71
|
end
|
90
72
|
|
91
73
|
def disconnect
|
92
|
-
@connected = false
|
93
|
-
nil
|
94
74
|
end
|
95
75
|
|
96
76
|
def timeout=(usecs)
|
97
77
|
end
|
98
78
|
|
99
79
|
def write(command)
|
100
|
-
|
101
|
-
|
80
|
+
meffod = command.shift
|
81
|
+
if respond_to?(meffod)
|
82
|
+
reply = send(meffod, *command)
|
83
|
+
else
|
84
|
+
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
85
|
+
end
|
102
86
|
|
103
87
|
if reply == true
|
104
88
|
reply = 1
|
@@ -106,13 +90,13 @@ class Redis
|
|
106
90
|
reply = 0
|
107
91
|
end
|
108
92
|
|
109
|
-
|
110
|
-
|
93
|
+
replies << reply
|
94
|
+
buffer << reply if buffer && meffod != :multi
|
111
95
|
nil
|
112
96
|
end
|
113
97
|
|
114
98
|
def read
|
115
|
-
|
99
|
+
replies.shift
|
116
100
|
end
|
117
101
|
|
118
102
|
# NOT IMPLEMENTED:
|
@@ -120,25 +104,30 @@ class Redis
|
|
120
104
|
# * brpop
|
121
105
|
# * brpoplpush
|
122
106
|
# * discard
|
123
|
-
# *
|
107
|
+
# * sort
|
124
108
|
# * subscribe
|
125
109
|
# * psubscribe
|
126
110
|
# * publish
|
127
|
-
|
128
|
-
# * zunionstore
|
111
|
+
|
129
112
|
def flushdb
|
130
|
-
|
113
|
+
databases.delete_at(database_id)
|
114
|
+
"OK"
|
131
115
|
end
|
132
116
|
|
133
117
|
def flushall
|
134
|
-
|
118
|
+
self.class.databases[database_instance_key] = []
|
119
|
+
"OK"
|
135
120
|
end
|
136
121
|
|
137
122
|
def auth(password)
|
138
123
|
"OK"
|
139
124
|
end
|
140
125
|
|
141
|
-
def select(index)
|
126
|
+
def select(index)
|
127
|
+
data_type_check(index, Integer)
|
128
|
+
self.database_id = index
|
129
|
+
"OK"
|
130
|
+
end
|
142
131
|
|
143
132
|
def info
|
144
133
|
{
|
@@ -163,71 +152,84 @@ class Redis
|
|
163
152
|
|
164
153
|
def bgreriteaof ; end
|
165
154
|
|
155
|
+
def move key, destination_id
|
156
|
+
raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
|
157
|
+
destination = find_database(destination_id)
|
158
|
+
return false unless data.has_key?(key)
|
159
|
+
return false if destination.has_key?(key)
|
160
|
+
destination[key] = data.delete(key)
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
166
164
|
def get(key)
|
167
|
-
|
165
|
+
data_type_check(key, String)
|
166
|
+
data[key]
|
168
167
|
end
|
169
168
|
|
170
169
|
def getbit(key, offset)
|
171
|
-
return unless
|
172
|
-
|
170
|
+
return unless data[key]
|
171
|
+
data[key].unpack('B*')[0].split("")[offset].to_i
|
173
172
|
end
|
174
173
|
|
175
174
|
def getrange(key, start, ending)
|
176
|
-
return unless
|
177
|
-
|
175
|
+
return unless data[key]
|
176
|
+
data[key][start..ending]
|
178
177
|
end
|
179
178
|
alias :substr :getrange
|
180
179
|
|
181
180
|
def getset(key, value)
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
data_type_check(key, String)
|
182
|
+
data[key].tap do
|
183
|
+
set(key, value)
|
184
|
+
end
|
185
185
|
end
|
186
186
|
|
187
187
|
def mget(*keys)
|
188
188
|
raise Redis::CommandError, "wrong number of arguments for 'mget' command" if keys.empty?
|
189
|
-
|
189
|
+
# We work with either an array, or list of arguments
|
190
|
+
keys = keys.first if keys.size == 1
|
191
|
+
data.values_at(*keys)
|
190
192
|
end
|
191
193
|
|
192
194
|
def append(key, value)
|
193
|
-
|
194
|
-
|
195
|
+
data[key] = (data[key] || "")
|
196
|
+
data[key] = data[key] + value.to_s
|
195
197
|
end
|
196
198
|
|
197
199
|
def strlen(key)
|
198
|
-
return unless
|
199
|
-
|
200
|
+
return unless data[key]
|
201
|
+
data[key].size
|
200
202
|
end
|
201
203
|
|
202
204
|
def hgetall(key)
|
203
205
|
data_type_check(key, Hash)
|
204
|
-
|
206
|
+
data[key].to_a.flatten || {}
|
205
207
|
end
|
206
208
|
|
207
209
|
def hget(key, field)
|
208
210
|
data_type_check(key, Hash)
|
209
|
-
|
211
|
+
data[key] && data[key][field.to_s]
|
210
212
|
end
|
211
213
|
|
212
214
|
def hdel(key, field)
|
213
215
|
data_type_check(key, Hash)
|
214
|
-
|
216
|
+
data[key] && data[key].delete(field)
|
215
217
|
remove_key_for_empty_collection(key)
|
216
218
|
end
|
217
219
|
|
218
220
|
def hkeys(key)
|
219
221
|
data_type_check(key, Hash)
|
220
|
-
return [] if
|
221
|
-
|
222
|
+
return [] if data[key].nil?
|
223
|
+
data[key].keys
|
222
224
|
end
|
223
225
|
|
224
226
|
def keys(pattern = "*")
|
225
227
|
regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
|
226
|
-
|
228
|
+
data.keys.select { |key| key =~ regexp }
|
227
229
|
end
|
228
230
|
|
229
231
|
def randomkey
|
230
|
-
|
232
|
+
data.keys[rand(dbsize)]
|
231
233
|
end
|
232
234
|
|
233
235
|
def echo(string)
|
@@ -243,66 +245,66 @@ class Redis
|
|
243
245
|
end
|
244
246
|
|
245
247
|
def dbsize
|
246
|
-
|
248
|
+
data.keys.count
|
247
249
|
end
|
248
250
|
|
249
251
|
def exists(key)
|
250
|
-
|
252
|
+
data.key?(key)
|
251
253
|
end
|
252
254
|
|
253
255
|
def llen(key)
|
254
256
|
data_type_check(key, Array)
|
255
|
-
return 0 unless
|
256
|
-
|
257
|
+
return 0 unless data[key]
|
258
|
+
data[key].size
|
257
259
|
end
|
258
260
|
|
259
261
|
def lrange(key, startidx, endidx)
|
260
262
|
data_type_check(key, Array)
|
261
|
-
(
|
263
|
+
(data[key] && data[key][startidx..endidx]) || []
|
262
264
|
end
|
263
265
|
|
264
266
|
def ltrim(key, start, stop)
|
265
267
|
data_type_check(key, Array)
|
266
|
-
return unless
|
267
|
-
|
268
|
+
return unless data[key]
|
269
|
+
data[key] = data[key][start..stop]
|
268
270
|
end
|
269
271
|
|
270
272
|
def lindex(key, index)
|
271
273
|
data_type_check(key, Array)
|
272
|
-
|
274
|
+
data[key] && data[key][index]
|
273
275
|
end
|
274
276
|
|
275
277
|
def linsert(key, where, pivot, value)
|
276
278
|
data_type_check(key, Array)
|
277
|
-
return unless
|
278
|
-
index =
|
279
|
+
return unless data[key]
|
280
|
+
index = data[key].index(pivot)
|
279
281
|
case where
|
280
|
-
when :before then
|
281
|
-
when :after then
|
282
|
+
when :before then data[key].insert(index, value)
|
283
|
+
when :after then data[key].insert(index + 1, value)
|
282
284
|
else raise Redis::CommandError, "ERR syntax error"
|
283
285
|
end
|
284
286
|
end
|
285
287
|
|
286
288
|
def lset(key, index, value)
|
287
289
|
data_type_check(key, Array)
|
288
|
-
return unless
|
289
|
-
raise
|
290
|
-
|
290
|
+
return unless data[key]
|
291
|
+
raise(Redis::CommandError, "ERR index out of range") if index >= data[key].size
|
292
|
+
data[key][index] = value
|
291
293
|
end
|
292
294
|
|
293
295
|
def lrem(key, count, value)
|
294
296
|
data_type_check(key, Array)
|
295
|
-
return unless
|
296
|
-
old_size =
|
297
|
+
return unless data[key]
|
298
|
+
old_size = data[key].size
|
297
299
|
diff =
|
298
300
|
if count == 0
|
299
|
-
|
300
|
-
old_size -
|
301
|
+
data[key].delete(value)
|
302
|
+
old_size - data[key].size
|
301
303
|
else
|
302
|
-
array = count > 0 ?
|
304
|
+
array = count > 0 ? data[key].dup : data[key].reverse
|
303
305
|
count.abs.times{ array.delete_at(array.index(value) || array.length) }
|
304
|
-
|
305
|
-
old_size -
|
306
|
+
data[key] = count > 0 ? array.dup : array.reverse
|
307
|
+
old_size - data[key].size
|
306
308
|
end
|
307
309
|
remove_key_for_empty_collection(key)
|
308
310
|
diff
|
@@ -310,75 +312,76 @@ class Redis
|
|
310
312
|
|
311
313
|
def rpush(key, value)
|
312
314
|
data_type_check(key, Array)
|
313
|
-
|
315
|
+
data[key] ||= []
|
314
316
|
[value].flatten.each do |val|
|
315
|
-
|
317
|
+
data[key].push(val.to_s)
|
316
318
|
end
|
317
|
-
|
319
|
+
data[key].size
|
318
320
|
end
|
319
321
|
|
320
322
|
def rpushx(key, value)
|
321
323
|
data_type_check(key, Array)
|
322
|
-
return unless
|
324
|
+
return unless data[key]
|
323
325
|
rpush(key, value)
|
324
326
|
end
|
325
327
|
|
326
328
|
def lpush(key, value)
|
327
329
|
data_type_check(key, Array)
|
328
|
-
|
330
|
+
data[key] ||= []
|
329
331
|
[value].flatten.each do |val|
|
330
|
-
|
332
|
+
data[key].unshift(val.to_s)
|
331
333
|
end
|
332
|
-
|
334
|
+
data[key].size
|
333
335
|
end
|
334
336
|
|
335
337
|
def lpushx(key, value)
|
336
338
|
data_type_check(key, Array)
|
337
|
-
return unless
|
339
|
+
return unless data[key]
|
338
340
|
lpush(key, value)
|
339
341
|
end
|
340
342
|
|
341
343
|
def rpop(key)
|
342
344
|
data_type_check(key, Array)
|
343
|
-
return unless
|
344
|
-
|
345
|
+
return unless data[key]
|
346
|
+
data[key].pop
|
345
347
|
end
|
346
348
|
|
347
349
|
def rpoplpush(key1, key2)
|
348
350
|
data_type_check(key1, Array)
|
349
|
-
|
350
|
-
|
351
|
+
rpop(key1).tap do |elem|
|
352
|
+
lpush(key2, elem)
|
353
|
+
end
|
351
354
|
end
|
352
355
|
|
353
356
|
def lpop(key)
|
354
357
|
data_type_check(key, Array)
|
355
|
-
return unless
|
356
|
-
|
358
|
+
return unless data[key]
|
359
|
+
data[key].shift
|
357
360
|
end
|
358
361
|
|
359
362
|
def smembers(key)
|
360
363
|
data_type_check(key, ::Set)
|
361
|
-
return [] unless
|
362
|
-
|
364
|
+
return [] unless data[key]
|
365
|
+
data[key].to_a.reverse
|
363
366
|
end
|
364
367
|
|
365
368
|
def sismember(key, value)
|
366
369
|
data_type_check(key, ::Set)
|
367
|
-
return false unless
|
368
|
-
|
370
|
+
return false unless data[key]
|
371
|
+
data[key].include?(value.to_s)
|
369
372
|
end
|
370
373
|
|
371
374
|
def sadd(key, value)
|
372
375
|
data_type_check(key, ::Set)
|
373
376
|
value = Array(value)
|
374
377
|
|
375
|
-
result = if
|
376
|
-
old_set =
|
377
|
-
|
378
|
-
(
|
378
|
+
result = if data[key]
|
379
|
+
old_set = data[key].dup
|
380
|
+
data[key].merge(value.map(&:to_s))
|
381
|
+
(data[key] - old_set).size
|
379
382
|
else
|
380
|
-
|
381
|
-
|
383
|
+
data[key] = ::Set.new(value.map(&:to_s))
|
384
|
+
data[key].size
|
382
385
|
end
|
383
386
|
|
384
387
|
# 0 = false, 1 = true, 2+ untouched
|
@@ -388,7 +391,7 @@ class Redis
|
|
388
391
|
|
389
392
|
def srem(key, value)
|
390
393
|
data_type_check(key, ::Set)
|
391
|
-
deleted = !!(
|
394
|
+
deleted = !!(data[key] && data[key].delete?(value.to_s))
|
392
395
|
remove_key_for_empty_collection(key)
|
393
396
|
deleted
|
394
397
|
end
|
@@ -409,14 +412,14 @@ class Redis
|
|
409
412
|
|
410
413
|
def scard(key)
|
411
414
|
data_type_check(key, ::Set)
|
412
|
-
return 0 unless
|
413
|
-
|
415
|
+
return 0 unless data[key]
|
416
|
+
data[key].size
|
414
417
|
end
|
415
418
|
|
416
419
|
def sinter(*keys)
|
417
420
|
keys.each { |k| data_type_check(k, ::Set) }
|
418
|
-
return ::Set.new if keys.any? { |k|
|
419
|
-
keys = keys.map { |k|
|
421
|
+
return ::Set.new if keys.any? { |k| data[k].nil? }
|
422
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
420
423
|
keys.inject do |set, key|
|
421
424
|
set & key
|
422
425
|
end.to_a
|
@@ -425,12 +428,12 @@ class Redis
|
|
425
428
|
def sinterstore(destination, *keys)
|
426
429
|
data_type_check(destination, ::Set)
|
427
430
|
result = sinter(*keys)
|
428
|
-
|
431
|
+
data[destination] = ::Set.new(result)
|
429
432
|
end
|
430
433
|
|
431
434
|
def sunion(*keys)
|
432
435
|
keys.each { |k| data_type_check(k, ::Set) }
|
433
|
-
keys = keys.map { |k|
|
436
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
434
437
|
keys.inject(::Set.new) do |set, key|
|
435
438
|
set | key
|
436
439
|
end.to_a
|
@@ -439,13 +442,13 @@ class Redis
|
|
439
442
|
def sunionstore(destination, *keys)
|
440
443
|
data_type_check(destination, ::Set)
|
441
444
|
result = sunion(*keys)
|
442
|
-
|
445
|
+
data[destination] = ::Set.new(result)
|
443
446
|
end
|
444
447
|
|
445
448
|
def sdiff(key1, *keys)
|
446
449
|
[key1, *keys].each { |k| data_type_check(k, ::Set) }
|
447
|
-
keys = keys.map { |k|
|
448
|
-
keys.inject(
|
450
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
451
|
+
keys.inject(data[key1]) do |memo, set|
|
449
452
|
memo - set
|
450
453
|
end.to_a
|
451
454
|
end
|
@@ -453,23 +456,23 @@ class Redis
|
|
453
456
|
def sdiffstore(destination, key1, *keys)
|
454
457
|
data_type_check(destination, ::Set)
|
455
458
|
result = sdiff(key1, *keys)
|
456
|
-
|
459
|
+
data[destination] = ::Set.new(result)
|
457
460
|
end
|
458
461
|
|
459
462
|
def srandmember(key)
|
460
463
|
data_type_check(key, ::Set)
|
461
|
-
return nil unless
|
462
|
-
|
464
|
+
return nil unless data[key]
|
465
|
+
data[key].to_a[rand(data[key].size)]
|
463
466
|
end
|
464
467
|
|
465
468
|
def del(*keys)
|
466
469
|
keys = keys.flatten(1)
|
467
470
|
raise Redis::CommandError, "ERR wrong number of arguments for 'del' command" if keys.empty?
|
468
|
-
old_count =
|
471
|
+
old_count = data.keys.size
|
469
472
|
keys.each do |key|
|
470
|
-
|
473
|
+
data.delete(key)
|
471
474
|
end
|
472
|
-
|
475
|
+
old_count - data.keys.size
|
473
476
|
end
|
474
477
|
|
475
478
|
def setnx(key, value)
|
@@ -482,10 +485,10 @@ class Redis
|
|
482
485
|
end
|
483
486
|
|
484
487
|
def rename(key, new_key)
|
485
|
-
return unless
|
486
|
-
|
487
|
-
|
488
|
-
|
488
|
+
return unless data[key]
|
489
|
+
data[new_key] = data[key]
|
490
|
+
data.expires[new_key] = data.expires[key] if data.expires.include?(key)
|
491
|
+
data.delete(key)
|
489
492
|
end
|
490
493
|
|
491
494
|
def renamenx(key, new_key)
|
@@ -498,13 +501,13 @@ class Redis
|
|
498
501
|
end
|
499
502
|
|
500
503
|
def expire(key, ttl)
|
501
|
-
return unless
|
502
|
-
|
504
|
+
return unless data[key]
|
505
|
+
data.expires[key] = Time.now + ttl
|
503
506
|
true
|
504
507
|
end
|
505
508
|
|
506
509
|
def ttl(key)
|
507
|
-
if
|
510
|
+
if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
|
508
511
|
ttl
|
509
512
|
else
|
510
513
|
-1
|
@@ -512,23 +515,23 @@ class Redis
|
|
512
515
|
end
|
513
516
|
|
514
517
|
def expireat(key, timestamp)
|
515
|
-
|
518
|
+
data.expires[key] = Time.at(timestamp)
|
516
519
|
true
|
517
520
|
end
|
518
521
|
|
519
522
|
def persist(key)
|
520
|
-
|
523
|
+
!!data.expires.delete(key)
|
521
524
|
end
|
522
525
|
|
523
526
|
def hset(key, field, value)
|
524
527
|
data_type_check(key, Hash)
|
525
528
|
field = field.to_s
|
526
|
-
if
|
527
|
-
result =
|
528
|
-
|
529
|
+
if data[key]
|
530
|
+
result = !data[key].include?(field)
|
531
|
+
data[key][field] = value.to_s
|
529
532
|
result
|
530
533
|
else
|
531
|
-
|
534
|
+
data[key] = { field => value.to_s }
|
532
535
|
true
|
533
536
|
end
|
534
537
|
end
|
@@ -536,27 +539,28 @@ class Redis
|
|
536
539
|
def hsetnx(key, field, value)
|
537
540
|
data_type_check(key, Hash)
|
538
541
|
field = field.to_s
|
539
|
-
return false if
|
542
|
+
return false if data[key] && data[key][field]
|
540
543
|
hset(key, field, value)
|
541
544
|
end
|
542
545
|
|
543
546
|
def hmset(key, *fields)
|
544
|
-
|
547
|
+
# mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
|
548
|
+
fields = fields[0] if mapped_param?(fields)
|
549
|
+
raise Redis::CommandError, "ERR wrong number of arguments for HMSET" if fields.empty? || fields.size.odd?
|
545
550
|
data_type_check(key, Hash)
|
546
|
-
|
551
|
+
data[key] ||= {}
|
547
552
|
fields.each_slice(2) do |field|
|
548
|
-
|
553
|
+
data[key][field[0].to_s] = field[1].to_s
|
549
554
|
end
|
550
555
|
end
|
551
556
|
|
552
557
|
def hmget(key, *fields)
|
553
558
|
raise Redis::CommandError, "wrong number of arguments for 'hmget' command" if fields.empty?
|
554
559
|
data_type_check(key, Hash)
|
555
|
-
values = []
|
556
560
|
fields.map do |field|
|
557
561
|
field = field.to_s
|
558
|
-
if
|
559
|
-
|
562
|
+
if data[key]
|
563
|
+
data[key][field]
|
560
564
|
else
|
561
565
|
nil
|
562
566
|
end
|
@@ -565,30 +569,30 @@ class Redis
|
|
565
569
|
|
566
570
|
def hlen(key)
|
567
571
|
data_type_check(key, Hash)
|
568
|
-
return 0 unless
|
569
|
-
|
572
|
+
return 0 unless data[key]
|
573
|
+
data[key].size
|
570
574
|
end
|
571
575
|
|
572
576
|
def hvals(key)
|
573
577
|
data_type_check(key, Hash)
|
574
|
-
return [] unless
|
575
|
-
|
578
|
+
return [] unless data[key]
|
579
|
+
data[key].values
|
576
580
|
end
|
577
581
|
|
578
582
|
def hincrby(key, field, increment)
|
579
583
|
data_type_check(key, Hash)
|
580
|
-
if
|
581
|
-
|
584
|
+
if data[key]
|
585
|
+
data[key][field] = (data[key][field.to_s].to_i + increment.to_i).to_s
|
582
586
|
else
|
583
|
-
|
587
|
+
data[key] = { field => increment.to_s }
|
584
588
|
end
|
585
|
-
|
589
|
+
data[key][field].to_i
|
586
590
|
end
|
587
591
|
|
588
592
|
def hexists(key, field)
|
589
593
|
data_type_check(key, Hash)
|
590
|
-
return false unless
|
591
|
-
|
594
|
+
return false unless data[key]
|
595
|
+
data[key].key?(field)
|
592
596
|
end
|
593
597
|
|
594
598
|
def sync ; end
|
@@ -602,44 +606,48 @@ class Redis
|
|
602
606
|
end
|
603
607
|
|
604
608
|
def set(key, value)
|
605
|
-
|
609
|
+
data[key] = value.to_s
|
606
610
|
"OK"
|
607
611
|
end
|
608
612
|
|
609
613
|
def setbit(key, offset, bit)
|
610
|
-
old_val =
|
614
|
+
old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
|
611
615
|
size_increment = [((offset/8)+1)*8-old_val.length, 0].max
|
612
616
|
old_val += Array.new(size_increment).map{"0"}
|
613
617
|
original_val = old_val[offset]
|
614
618
|
old_val[offset] = bit.to_s
|
615
619
|
new_val = ""
|
616
620
|
old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
|
617
|
-
|
621
|
+
data[key] = new_val
|
618
622
|
original_val
|
619
623
|
end
|
620
624
|
|
621
625
|
def setex(key, seconds, value)
|
622
|
-
|
626
|
+
data[key] = value.to_s
|
623
627
|
expire(key, seconds)
|
624
628
|
end
|
625
629
|
|
626
630
|
def setrange(key, offset, value)
|
627
|
-
return unless
|
628
|
-
s =
|
629
|
-
|
631
|
+
return unless data[key]
|
632
|
+
s = data[key][offset,value.size]
|
633
|
+
data[key][s] = value
|
630
634
|
end
|
631
635
|
|
632
636
|
def mset(*pairs)
|
637
|
+
# Handle pairs for mapped_mset command
|
638
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
633
639
|
pairs.each_slice(2) do |pair|
|
634
|
-
|
640
|
+
data[pair[0].to_s] = pair[1].to_s
|
635
641
|
end
|
636
642
|
"OK"
|
637
643
|
end
|
638
644
|
|
639
645
|
def msetnx(*pairs)
|
646
|
+
# Handle pairs for mapped_msetnx command
|
647
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
640
648
|
keys = []
|
641
649
|
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
|
642
|
-
return if keys.any?{|key|
|
650
|
+
return false if keys.any?{|key| data.key?(key) }
|
643
651
|
mset(*pairs)
|
644
652
|
true
|
645
653
|
end
|
@@ -649,27 +657,27 @@ class Redis
|
|
649
657
|
end
|
650
658
|
|
651
659
|
def incr(key)
|
652
|
-
|
653
|
-
|
660
|
+
data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
|
661
|
+
data[key].to_i
|
654
662
|
end
|
655
663
|
|
656
664
|
def incrby(key, by)
|
657
|
-
|
658
|
-
|
665
|
+
data.merge!({ key => (data[key].to_i + by.to_i).to_s || by })
|
666
|
+
data[key].to_i
|
659
667
|
end
|
660
668
|
|
661
669
|
def decr(key)
|
662
|
-
|
663
|
-
|
670
|
+
data.merge!({ key => (data[key].to_i - 1).to_s || "-1"})
|
671
|
+
data[key].to_i
|
664
672
|
end
|
665
673
|
|
666
674
|
def decrby(key, by)
|
667
|
-
|
668
|
-
|
675
|
+
data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
|
676
|
+
data[key].to_i
|
669
677
|
end
|
670
678
|
|
671
679
|
def type(key)
|
672
|
-
case
|
680
|
+
case data[key]
|
673
681
|
when nil then "none"
|
674
682
|
when String then "string"
|
675
683
|
when Hash then "hash"
|
@@ -685,13 +693,11 @@ class Redis
|
|
685
693
|
def slaveof(host, port) ; end
|
686
694
|
|
687
695
|
def exec
|
688
|
-
buffer =
|
689
|
-
@buffer = nil
|
690
|
-
buffer
|
696
|
+
buffer.tap {|x| self.buffer = nil }
|
691
697
|
end
|
692
698
|
|
693
699
|
def multi
|
694
|
-
|
700
|
+
self.buffer = []
|
695
701
|
yield if block_given?
|
696
702
|
"OK"
|
697
703
|
end
|
@@ -718,17 +724,17 @@ class Redis
|
|
718
724
|
end
|
719
725
|
|
720
726
|
data_type_check(key, ZSet)
|
721
|
-
|
727
|
+
data[key] ||= ZSet.new
|
722
728
|
|
723
729
|
if args.size == 2
|
724
730
|
score, value = args
|
725
|
-
exists =
|
726
|
-
|
731
|
+
exists = !data[key].key?(value.to_s)
|
732
|
+
data[key][value.to_s] = score
|
727
733
|
else
|
728
734
|
# Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
|
729
735
|
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
730
|
-
exists = args.map(&:last).map { |el|
|
731
|
-
args.each { |
|
736
|
+
exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
|
737
|
+
args.each { |s, v| data[key][v.to_s] = s }
|
732
738
|
end
|
733
739
|
|
734
740
|
exists
|
@@ -736,73 +742,85 @@ class Redis
|
|
736
742
|
|
737
743
|
def zrem(key, value)
|
738
744
|
data_type_check(key, ZSet)
|
739
|
-
|
740
|
-
|
745
|
+
values = Array(value)
|
746
|
+
return 0 unless data[key]
|
747
|
+
|
748
|
+
response = values.map do |v|
|
749
|
+
data[key].delete(v) if data[key].has_key?(v)
|
750
|
+
end.compact.size
|
751
|
+
|
741
752
|
remove_key_for_empty_collection(key)
|
742
|
-
|
753
|
+
response
|
743
754
|
end
|
744
755
|
|
745
756
|
def zcard(key)
|
746
757
|
data_type_check(key, ZSet)
|
747
|
-
|
758
|
+
data[key] ? data[key].size : 0
|
748
759
|
end
|
749
760
|
|
750
761
|
def zscore(key, value)
|
751
762
|
data_type_check(key, ZSet)
|
752
|
-
|
763
|
+
value = data[key] && data[key][value.to_s]
|
764
|
+
value && value.to_s
|
753
765
|
end
|
754
766
|
|
755
767
|
def zcount(key, min, max)
|
756
768
|
data_type_check(key, ZSet)
|
757
|
-
return 0 unless
|
758
|
-
|
769
|
+
return 0 unless data[key]
|
770
|
+
data[key].select_by_score(min, max).size
|
759
771
|
end
|
760
772
|
|
761
773
|
def zincrby(key, num, value)
|
762
774
|
data_type_check(key, ZSet)
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
775
|
+
data[key] ||= ZSet.new
|
776
|
+
data[key][value.to_s] ||= 0
|
777
|
+
data[key].increment(value.to_s, num)
|
778
|
+
data[key][value.to_s].to_s
|
767
779
|
end
|
768
780
|
|
769
781
|
def zrank(key, value)
|
770
782
|
data_type_check(key, ZSet)
|
771
|
-
|
783
|
+
data[key].keys.sort_by {|k| data[key][k] }.index(value.to_s)
|
772
784
|
end
|
773
785
|
|
774
786
|
def zrevrank(key, value)
|
775
787
|
data_type_check(key, ZSet)
|
776
|
-
|
788
|
+
data[key].keys.sort_by {|k| -data[key][k] }.index(value.to_s)
|
777
789
|
end
|
778
790
|
|
779
791
|
def zrange(key, start, stop, with_scores = nil)
|
780
792
|
data_type_check(key, ZSet)
|
781
|
-
return [] unless
|
793
|
+
return [] unless data[key]
|
782
794
|
|
783
|
-
if
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
795
|
+
# Sort by score, or if scores are equal, key alphanum
|
796
|
+
results = data[key].sort do |(k1, v1), (k2, v2)|
|
797
|
+
if v1 == v2
|
798
|
+
k1 <=> k2
|
799
|
+
else
|
800
|
+
v1 <=> v2
|
801
|
+
end
|
802
|
+
end
|
803
|
+
# Select just the keys unless we want scores
|
804
|
+
results = results.map(&:first) unless with_scores
|
805
|
+
results[start..stop].flatten.map(&:to_s)
|
788
806
|
end
|
789
807
|
|
790
808
|
def zrevrange(key, start, stop, with_scores = nil)
|
791
809
|
data_type_check(key, ZSet)
|
792
|
-
return [] unless
|
810
|
+
return [] unless data[key]
|
793
811
|
|
794
812
|
if with_scores
|
795
|
-
|
813
|
+
data[key].sort_by {|_,v| -v }
|
796
814
|
else
|
797
|
-
|
815
|
+
data[key].keys.sort_by {|k| -data[key][k] }
|
798
816
|
end[start..stop].flatten.map(&:to_s)
|
799
817
|
end
|
800
818
|
|
801
819
|
def zrangebyscore(key, min, max, *opts)
|
802
820
|
data_type_check(key, ZSet)
|
803
|
-
return [] unless
|
821
|
+
return [] unless data[key]
|
804
822
|
|
805
|
-
range =
|
823
|
+
range = data[key].select_by_score(min, max)
|
806
824
|
vals = if opts.include?('WITHSCORES')
|
807
825
|
range.sort_by {|_,v| v }
|
808
826
|
else
|
@@ -817,9 +835,9 @@ class Redis
|
|
817
835
|
|
818
836
|
def zrevrangebyscore(key, max, min, *opts)
|
819
837
|
data_type_check(key, ZSet)
|
820
|
-
return [] unless
|
838
|
+
return [] unless data[key]
|
821
839
|
|
822
|
-
range =
|
840
|
+
range = data[key].select_by_score(min, max)
|
823
841
|
vals = if opts.include?('WITHSCORES')
|
824
842
|
range.sort_by {|_,v| -v }
|
825
843
|
else
|
@@ -834,66 +852,45 @@ class Redis
|
|
834
852
|
|
835
853
|
def zremrangebyscore(key, min, max)
|
836
854
|
data_type_check(key, ZSet)
|
837
|
-
return 0 unless
|
855
|
+
return 0 unless data[key]
|
838
856
|
|
839
|
-
range =
|
840
|
-
range.each {|k,_|
|
857
|
+
range = data[key].select_by_score(min, max)
|
858
|
+
range.each {|k,_| data[key].delete(k) }
|
841
859
|
range.size
|
842
860
|
end
|
843
861
|
|
844
|
-
def zinterstore(out,
|
862
|
+
def zinterstore(out, *args)
|
845
863
|
data_type_check(out, ZSet)
|
864
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
865
|
+
data[out] = SortedSetIntersectStore.new(args_handler, data).call
|
866
|
+
data[out].size
|
867
|
+
end
|
846
868
|
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
when Hash
|
853
|
-
@data[src]
|
854
|
-
else
|
855
|
-
{}
|
856
|
-
end
|
857
|
-
end
|
858
|
-
|
859
|
-
@data[out] = ZSet.new
|
860
|
-
values = hashes.inject([]) {|r, h| r.empty? ? h.keys : r & h.keys }
|
861
|
-
values.each do |value|
|
862
|
-
@data[out][value] = hashes.inject(0) {|n, h| n + h[value].to_i }
|
863
|
-
end
|
864
|
-
|
865
|
-
@data[out].size
|
869
|
+
def zunionstore(out, *args)
|
870
|
+
data_type_check(out, ZSet)
|
871
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
872
|
+
data[out] = SortedSetUnionStore.new(args_handler, data).call
|
873
|
+
data[out].size
|
866
874
|
end
|
867
875
|
|
868
876
|
def zremrangebyrank(key, start, stop)
|
869
|
-
sorted_elements =
|
877
|
+
sorted_elements = data[key].sort_by { |k, v| v }
|
870
878
|
start = sorted_elements.length if start > sorted_elements.length
|
871
879
|
elements_to_delete = sorted_elements[start..stop]
|
872
|
-
elements_to_delete.each { |elem, rank|
|
880
|
+
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
873
881
|
elements_to_delete.size
|
874
882
|
end
|
875
883
|
|
876
884
|
private
|
877
885
|
|
878
|
-
def zrange_select_by_score(key, min, max)
|
879
|
-
if min == '-inf' && max == '+inf'
|
880
|
-
@data[key]
|
881
|
-
elsif max == '+inf'
|
882
|
-
@data[key].reject { |_,v| v < min }
|
883
|
-
elsif min == '-inf'
|
884
|
-
@data[key].reject { |_,v| v > max }
|
885
|
-
else
|
886
|
-
@data[key].reject {|_,v| v < min || v > max }
|
887
|
-
end
|
888
|
-
end
|
889
|
-
|
890
886
|
def remove_key_for_empty_collection(key)
|
891
|
-
del(key) if
|
887
|
+
del(key) if data[key] && data[key].empty?
|
892
888
|
end
|
893
889
|
|
894
890
|
def data_type_check(key, klass)
|
895
|
-
if
|
896
|
-
|
891
|
+
if data[key] && !data[key].is_a?(klass)
|
892
|
+
warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
893
|
+
raise Redis::CommandError.new("ERR Operation against a key holding the wrong kind of value")
|
897
894
|
end
|
898
895
|
end
|
899
896
|
|
@@ -909,6 +906,10 @@ class Redis
|
|
909
906
|
[offset, count]
|
910
907
|
end
|
911
908
|
end
|
909
|
+
|
910
|
+
def mapped_param? param
|
911
|
+
param.size == 1 && param[0].is_a?(Array)
|
912
|
+
end
|
912
913
|
end
|
913
914
|
end
|
914
915
|
end
|