ruby-redis-portertech 0.0.3
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.
- data/LICENSE +9 -0
- data/README.md +80 -0
- data/bin/ruby-redis +12 -0
- data/lib/redis.rb +13 -0
- data/lib/redis/bin.rb +127 -0
- data/lib/redis/client.rb +213 -0
- data/lib/redis/config.rb +49 -0
- data/lib/redis/connection.rb +30 -0
- data/lib/redis/database.rb +133 -0
- data/lib/redis/hashes.rb +70 -0
- data/lib/redis/keys.rb +155 -0
- data/lib/redis/lists.rb +225 -0
- data/lib/redis/logger.rb +69 -0
- data/lib/redis/protocol.rb +107 -0
- data/lib/redis/pubsub.rb +150 -0
- data/lib/redis/reader.rb +175 -0
- data/lib/redis/sender.rb +47 -0
- data/lib/redis/server.rb +60 -0
- data/lib/redis/sets.rb +104 -0
- data/lib/redis/strict.rb +65 -0
- data/lib/redis/strings.rb +142 -0
- data/lib/redis/synchrony.rb +51 -0
- data/lib/redis/version.rb +5 -0
- data/lib/redis/zsets.rb +279 -0
- metadata +120 -0
data/lib/redis/sender.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Redis
|
2
|
+
module Sender
|
3
|
+
|
4
|
+
def send_redis data
|
5
|
+
collect = []
|
6
|
+
if Symbol === data
|
7
|
+
collect << data
|
8
|
+
elsif NilClass === data
|
9
|
+
collect << :'$-1'
|
10
|
+
elsif FalseClass === data
|
11
|
+
collect << :':0'
|
12
|
+
elsif TrueClass === data
|
13
|
+
collect << :':1'
|
14
|
+
elsif Float === data and data.nan?
|
15
|
+
collect << :':0'
|
16
|
+
elsif Float === data and (data.infinite? || 0) > 0
|
17
|
+
collect << :':inf'
|
18
|
+
elsif Float === data and (data.infinite? || 0) < 0
|
19
|
+
collect << :':-inf'
|
20
|
+
elsif Hash === data
|
21
|
+
collect << "*#{data.size * 2}"
|
22
|
+
data.each do |key, value|
|
23
|
+
key = key.to_s
|
24
|
+
value = value.to_s
|
25
|
+
collect << "$#{key.bytesize}"
|
26
|
+
collect << key
|
27
|
+
collect << "$#{value.bytesize}"
|
28
|
+
collect << value
|
29
|
+
end
|
30
|
+
elsif Enumerable === data and !(String === data)
|
31
|
+
collect << "*#{data.size}"
|
32
|
+
data.each do |element|
|
33
|
+
element = element.to_s
|
34
|
+
collect << "$#{element.bytesize}"
|
35
|
+
collect << element
|
36
|
+
end
|
37
|
+
else
|
38
|
+
data = data.to_s
|
39
|
+
collect << "$#{data.bytesize}"
|
40
|
+
collect << data
|
41
|
+
end
|
42
|
+
collect << ''
|
43
|
+
send_data collect.join "\r\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/redis/server.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Redis
|
2
|
+
|
3
|
+
module Server
|
4
|
+
|
5
|
+
def redis_FLUSHDB
|
6
|
+
@database.clear
|
7
|
+
:'+OK'
|
8
|
+
end
|
9
|
+
|
10
|
+
def redis_FLUSHALL
|
11
|
+
@databases.each do |database|
|
12
|
+
database.clear
|
13
|
+
end
|
14
|
+
:'+OK'
|
15
|
+
end
|
16
|
+
|
17
|
+
def redis_DBSIZE
|
18
|
+
@database.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def redis_DEBUG type, key=nil
|
22
|
+
if type.upcase == 'OBJECT'
|
23
|
+
"#{@database[key].class}"
|
24
|
+
value = @database[key]
|
25
|
+
# encoding values are meaningless, they make tcl tests pass
|
26
|
+
# and don't forget they need a trailing space
|
27
|
+
if String === value
|
28
|
+
"Value #{value.class}:#{value.object_id} encoding:raw encoding:int "
|
29
|
+
elsif Numeric === value
|
30
|
+
"Value #{value.class}:#{value.object_id} encoding:int "
|
31
|
+
elsif Array === value
|
32
|
+
"Value #{value.class}:#{value.object_id} encoding:ziplist encoding:linkedlist "
|
33
|
+
elsif Hash === value
|
34
|
+
"Value #{value.class}:#{value.object_id} encoding:zipmap encoding:hashtable "
|
35
|
+
elsif Set === value
|
36
|
+
"Value #{value.class}:#{value.object_id} encoding:intset encoding:hashtable "
|
37
|
+
else
|
38
|
+
"Value #{value.class}:#{value.object_id}"
|
39
|
+
end
|
40
|
+
elsif type.upcase == 'RELOAD'
|
41
|
+
"TODO: what is reload"
|
42
|
+
else
|
43
|
+
raise 'not supported'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def redis_INFO
|
48
|
+
[
|
49
|
+
"redis_version:%s\r\n",
|
50
|
+
"redis_git_sha1:%s\r\n",
|
51
|
+
"redis_git_dirty:%d\r\n",
|
52
|
+
].join % [
|
53
|
+
Redis::VERSION,
|
54
|
+
"Ruby",
|
55
|
+
1,
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/redis/sets.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Redis
|
4
|
+
module Sets
|
5
|
+
|
6
|
+
def redis_SADD key, member
|
7
|
+
record = (@database[key] ||= Set.new)
|
8
|
+
redis_t Set, record
|
9
|
+
return false if record.include? member
|
10
|
+
record.add member
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def redis_SREM key, member
|
15
|
+
record = @database[key] || []
|
16
|
+
return false unless record.include? member
|
17
|
+
record.delete member
|
18
|
+
@database.delete key if record.empty?
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Type checks are only to make tests pass
|
23
|
+
def redis_SMOVE source, destination, member
|
24
|
+
source_record = @database[source] || Set.new
|
25
|
+
dest_record = @database[destination]
|
26
|
+
redis_t Set, source_record
|
27
|
+
redis_t NilClass, Set, dest_record
|
28
|
+
return false unless source_record.include? member
|
29
|
+
(@database[destination] ||= Set.new).add member
|
30
|
+
source_record.delete member
|
31
|
+
@database.delete source if source_record.empty?
|
32
|
+
return true
|
33
|
+
end
|
34
|
+
|
35
|
+
def redis_SCARD key
|
36
|
+
(@database[key] || []).size
|
37
|
+
end
|
38
|
+
|
39
|
+
def redis_SISMEMBER key, member
|
40
|
+
(@database[key] || []).include? member
|
41
|
+
end
|
42
|
+
|
43
|
+
def redis_SMEMBERS key
|
44
|
+
(@database[key] || []).to_a
|
45
|
+
end
|
46
|
+
|
47
|
+
def redis_SINTER *keys
|
48
|
+
keys.each { |key| redis_t NilClass, Array, Set, @database[key] }
|
49
|
+
keys.reduce(nil) do |memo, key|
|
50
|
+
memo ? memo & (@database[key]||[]) : (@database[key]||Set.new)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def redis_SINTERSTORE destination, *keys
|
55
|
+
record = redis_SINTER *keys
|
56
|
+
if record.empty?
|
57
|
+
@database.delete destination
|
58
|
+
else
|
59
|
+
@database[destination] = record
|
60
|
+
end
|
61
|
+
record.size
|
62
|
+
end
|
63
|
+
|
64
|
+
def redis_SUNION *keys
|
65
|
+
keys.each { |key| redis_t NilClass, Array, Set, @database[key] }
|
66
|
+
keys.reduce(Set.new) { |memo, key| memo | (@database[key]||[]) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def redis_SUNIONSTORE destination, *keys
|
70
|
+
record = redis_SUNION *keys
|
71
|
+
if record.empty?
|
72
|
+
@database.delete destination
|
73
|
+
else
|
74
|
+
@database[destination] = record
|
75
|
+
end
|
76
|
+
record.size
|
77
|
+
end
|
78
|
+
|
79
|
+
def redis_SDIFF *keys
|
80
|
+
keys.reduce(nil) { |memo, key| memo ? memo - (@database[key]||[]) : (@database[key]||Set.new) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def redis_SDIFFSTORE destination, *keys
|
84
|
+
(@database[destination] = redis_SDIFF *keys).size
|
85
|
+
end
|
86
|
+
|
87
|
+
def redis_SPOP key
|
88
|
+
set = @database[key]
|
89
|
+
return nil unless set
|
90
|
+
rec = rand set.size
|
91
|
+
result = set.to_a[rec]
|
92
|
+
set.delete result
|
93
|
+
@database.delete key if set.empty?
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def redis_SRANDMEMBER key
|
98
|
+
set = (@database[key] || [])
|
99
|
+
return nil if set.empty?
|
100
|
+
set.to_a[rand set.size]
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
data/lib/redis/strict.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Redis
|
2
|
+
|
3
|
+
# This is faster and allows for experimentation with duck-typing.
|
4
|
+
module NotStrict
|
5
|
+
|
6
|
+
def redis_t *args
|
7
|
+
end
|
8
|
+
|
9
|
+
def redis_f val, msg = nil
|
10
|
+
if Numeric === val
|
11
|
+
val
|
12
|
+
else
|
13
|
+
val_downcase = val.downcase
|
14
|
+
if val_downcase == '-inf'
|
15
|
+
-1.0/0
|
16
|
+
elsif val_downcase =~ /^[+]?inf$/
|
17
|
+
1.0/0
|
18
|
+
else
|
19
|
+
val.to_f
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def redis_i val, msg = nil
|
25
|
+
val.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def redis_pos_i val, msg = 'value is negative'
|
29
|
+
val = redis_i val # may call strict
|
30
|
+
raise msg if val < 0
|
31
|
+
val
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Strict version allows the ruby-redis server to pass redis tests.
|
37
|
+
module Strict
|
38
|
+
include NotStrict
|
39
|
+
|
40
|
+
def redis_t *args
|
41
|
+
val = args.pop
|
42
|
+
msg = 'Operation against a key holding the wrong kind of value'
|
43
|
+
unless Class === args.last
|
44
|
+
msg = val
|
45
|
+
val = args.pop
|
46
|
+
end
|
47
|
+
args.each do |klass|
|
48
|
+
return if klass === val
|
49
|
+
end
|
50
|
+
raise msg
|
51
|
+
end
|
52
|
+
|
53
|
+
def redis_f val, msg = 'value is not a double'
|
54
|
+
raise msg+val.to_s.dump unless Numeric === val or val =~ /^[ +-]?([0-9.e-]*|inf)$/i
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def redis_i val, msg = 'value is not an integer or out of range'
|
59
|
+
raise msg unless Integer === val or val =~ /^[ +-]?[0-9]*$/
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Redis
|
2
|
+
|
3
|
+
# Note: Redis does not have a dedicated integer type.
|
4
|
+
# Integer operations will convert values to integers.
|
5
|
+
|
6
|
+
module Strings
|
7
|
+
|
8
|
+
def redis_GET key
|
9
|
+
value = @database[key]
|
10
|
+
redis_t NilClass, String, Numeric, value
|
11
|
+
value
|
12
|
+
end
|
13
|
+
|
14
|
+
def redis_APPEND key, value
|
15
|
+
new_value = redis_GET(key).to_s + value
|
16
|
+
@database[key] = new_value
|
17
|
+
new_value.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def redis_SETRANGE key, offset, value
|
21
|
+
data = (redis_GET(key)||'').to_s
|
22
|
+
return data.size if value.empty?
|
23
|
+
offset = redis_pos_i offset, 'out of range'
|
24
|
+
raise 'maximum allowed size' if offset + value.size > 512*1024*1024
|
25
|
+
if data.size <= offset
|
26
|
+
data.concat 0.chr * (offset - data.size + value.size)
|
27
|
+
end
|
28
|
+
data[offset,value.size] = value
|
29
|
+
@database[key] = data
|
30
|
+
data.size
|
31
|
+
end
|
32
|
+
|
33
|
+
def redis_GETRANGE key, first, last
|
34
|
+
first = redis_i first
|
35
|
+
last = redis_i last
|
36
|
+
value = redis_GET(key) || ''
|
37
|
+
first = 0 if first < -value.size
|
38
|
+
value[first..last]
|
39
|
+
end
|
40
|
+
|
41
|
+
def redis_GETBIT key, offset
|
42
|
+
data = redis_GET key
|
43
|
+
return 0 unless data
|
44
|
+
data = data.to_s
|
45
|
+
offset = redis_pos_i offset
|
46
|
+
byte = offset / 8
|
47
|
+
bit = 0x80 >> offset % 8
|
48
|
+
return 0 if data.size <= byte
|
49
|
+
original_byte = data[byte].ord
|
50
|
+
original_bit = original_byte & bit
|
51
|
+
original_bit != 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def redis_SETBIT key, offset, value
|
55
|
+
data = (redis_GET(key)||'').to_s
|
56
|
+
offset = redis_pos_i offset, 'out of range'
|
57
|
+
raise 'out of range' if offset >= 4*1024*1024*1024
|
58
|
+
byte = offset / 8
|
59
|
+
bit = 0x80 >> offset % 8
|
60
|
+
if data.size <= byte
|
61
|
+
data += 0.chr * (byte - data.size + 1)
|
62
|
+
end
|
63
|
+
original_byte = data[byte].ord
|
64
|
+
original_bit = original_byte & bit
|
65
|
+
if value == '0'
|
66
|
+
data[byte] = (original_byte & ~bit).chr
|
67
|
+
elsif value == '1'
|
68
|
+
data[byte] = (original_byte | bit).chr
|
69
|
+
else
|
70
|
+
raise 'out of range'
|
71
|
+
end
|
72
|
+
@database[key] = data
|
73
|
+
original_bit != 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def redis_STRLEN key
|
77
|
+
value = redis_GET key
|
78
|
+
value ? value.size : 0
|
79
|
+
end
|
80
|
+
|
81
|
+
def redis_MGET *keys
|
82
|
+
keys.collect do |key|
|
83
|
+
value = @database[key]
|
84
|
+
(String === value) ? value : nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def redis_GETSET key, value
|
89
|
+
old = redis_GET key
|
90
|
+
@database[key] = value
|
91
|
+
old
|
92
|
+
end
|
93
|
+
|
94
|
+
def redis_SET key, value
|
95
|
+
@database[key] = value
|
96
|
+
:'+OK'
|
97
|
+
end
|
98
|
+
|
99
|
+
def redis_SETEX key, seconds, value
|
100
|
+
@database[key] = value
|
101
|
+
@database.expire key, redis_pos_i(seconds, 'invalid expire time in SETEX')
|
102
|
+
:'+OK'
|
103
|
+
end
|
104
|
+
|
105
|
+
def redis_MSET *args
|
106
|
+
Hash[*args].each do |key, value|
|
107
|
+
@database[key] = value
|
108
|
+
end
|
109
|
+
:'+OK'
|
110
|
+
end
|
111
|
+
|
112
|
+
def redis_MSETNX *args
|
113
|
+
hash = Hash[*args]
|
114
|
+
hash.each do |key, value|
|
115
|
+
return false if @database.has_key? key
|
116
|
+
end
|
117
|
+
hash.each do |key, value|
|
118
|
+
@database[key] = value
|
119
|
+
end
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
def redis_SETNX key, value
|
124
|
+
return false if @database.has_key? key
|
125
|
+
@database[key] = value
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
def redis_INCR key
|
130
|
+
@database[key] = redis_i(@database[key] || 0) + 1
|
131
|
+
end
|
132
|
+
|
133
|
+
def redis_INCRBY key, value
|
134
|
+
@database[key] = redis_i(@database[key] || 0) + redis_i(value)
|
135
|
+
end
|
136
|
+
|
137
|
+
def redis_DECRBY key, value
|
138
|
+
@database[key] = redis_i(@database[key] || 0) - redis_i(value)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
require File.expand_path 'client', File.dirname(__FILE__)
|
3
|
+
|
4
|
+
module Redis
|
5
|
+
|
6
|
+
# Compatible with em-synchrony
|
7
|
+
|
8
|
+
def self.synchrony blk=nil, tail=nil, &block
|
9
|
+
blk ||= block
|
10
|
+
context = Proc.new { Fiber.new { blk.call }.resume }
|
11
|
+
EventMachine.run(context, tail)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Synchrony
|
15
|
+
def self.sync(df)
|
16
|
+
f = Fiber.current
|
17
|
+
xback = proc {|r|
|
18
|
+
if f == Fiber.current
|
19
|
+
return r
|
20
|
+
else
|
21
|
+
f.resume r
|
22
|
+
end
|
23
|
+
}
|
24
|
+
df.callback &xback
|
25
|
+
df.errback &xback
|
26
|
+
Fiber.yield
|
27
|
+
end
|
28
|
+
def initialize redis
|
29
|
+
@redis = redis
|
30
|
+
end
|
31
|
+
def method_missing method, *args, &block
|
32
|
+
raise 'synchrony not allowed in multi' if @redis.in_multi? and method != :exec
|
33
|
+
result = @redis.send method, *args, &block
|
34
|
+
if result.respond_to? :callback and result.respond_to? :errback
|
35
|
+
result = Synchrony.sync result
|
36
|
+
raise result if Exception === result
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Client
|
43
|
+
|
44
|
+
def synchrony
|
45
|
+
raise 'you probably want Redis.synchrony instead' if block_given?
|
46
|
+
@synchrony ||= Synchrony.new self
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|